-
-
Notifications
You must be signed in to change notification settings - Fork 30
Add follow-up suggestion chips after successful queries #1764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
582b0e1
869fcb0
e84ae85
d91b685
17e88cf
40df87c
3e17d44
bd3e033
454c26d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -25,7 +25,7 @@ | |||||
| from ..config import PROMPTS_DIR, MissingContextError | ||||||
| from ..context import TContext | ||||||
| from ..llm import LlamaCpp, Llm, Message | ||||||
| from ..models import ThinkingYesNo | ||||||
| from ..models import FollowUpSuggestion, ThinkingYesNo | ||||||
| from ..report import ActorTask, Section, TaskGroup | ||||||
| from ..tools import MetadataLookup, Tool, VectorLookupToolUser | ||||||
| from ..utils import ( | ||||||
|
|
@@ -284,6 +284,11 @@ class Coordinator(Viewer, VectorLookupToolUser): | |||||
| "template": PROMPTS_DIR / "Coordinator" / "tool_relevance.jinja2", | ||||||
| "response_model": ThinkingYesNo, | ||||||
| }, | ||||||
| "follow_up_suggestions": { | ||||||
| "template": PROMPTS_DIR / "Coordinator" / "follow_up_suggestions.jinja2", | ||||||
| "response_model": FollowUpSuggestion, | ||||||
| "llm_spec": "ui", | ||||||
| }, | ||||||
| }, | ||||||
| ) | ||||||
|
|
||||||
|
|
@@ -509,6 +514,45 @@ async def _check_tool_relevance(self, tool: Tool, tool_output: str, actor: Actor | |||||
|
|
||||||
| return result.yes | ||||||
|
|
||||||
| async def suggest_follow_up(self, plan: Plan) -> str | None: | ||||||
| """Generate a single follow-up suggestion from a completed plan. | ||||||
|
|
||||||
| Returns a suggestion query string, or None on failure. | ||||||
| """ | ||||||
| pipeline = plan.out_context.get("pipeline") | ||||||
| if pipeline is None: | ||||||
| return None | ||||||
|
|
||||||
| data_summary = plan.out_context.get("data", "") | ||||||
| if not data_summary: | ||||||
| return None | ||||||
|
|
||||||
| sql = plan.out_context.get("sql", "") | ||||||
| previous_queries = [ | ||||||
| m["content"] for m in plan.history | ||||||
| if m.get("role") == "user" | ||||||
| ] | ||||||
| output_type = "chart" if plan.out_context.get("view") else "table" | ||||||
|
|
||||||
| try: | ||||||
| result = await self._invoke_prompt( | ||||||
| "follow_up_suggestions", | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| messages=[{"role": "user", "content": "Generate a follow-up suggestion."}], | ||||||
| context=plan.out_context, | ||||||
| data_summary=data_summary, | ||||||
| sql=sql, | ||||||
| output_type=output_type, | ||||||
| previous_queries=previous_queries, | ||||||
| ) | ||||||
| return result.query | ||||||
| except Exception as e: | ||||||
| if state.notifications: | ||||||
| state.notifications.warning( | ||||||
| f"Could not generate follow-up suggestion: {e}", | ||||||
| duration=3000, | ||||||
| ) | ||||||
| return None | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it should raise a notification perhaps through pn.state.notifications instead of failing silently. |
||||||
|
|
||||||
| def __panel__(self): | ||||||
| return self.interface | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| {% extends 'Actor/main.jinja2' %} | ||
|
|
||
| {% block instructions %} | ||
| Suggest ONE concise follow-up question based on the user's query and the resulting data. | ||
|
|
||
| Rules: | ||
| - MUST reference specific column names from the data summary | ||
| - Keep it short and actionable (under 100 characters) | ||
| - Do NOT repeat any previous queries | ||
| - Do NOT suggest analyses the data cannot support | ||
| {% endblock %} | ||
|
|
||
| {% block examples %} | ||
| Examples: | ||
| - "Show revenue trend over date by product" | ||
| - "Compare revenue across region" | ||
| - "Filter to top 5 products by revenue" | ||
| {% endblock %} | ||
|
|
||
| {% block context %} | ||
| Data Summary: | ||
| {{ data_summary }} | ||
| {% if sql %} | ||
| SQL Executed: {{ sql }} | ||
| {% endif %} | ||
| Output Type: {{ output_type }} | ||
| {% if previous_queries %} | ||
| Previous Queries (do NOT repeat): | ||
| {% for q in previous_queries %}- {{ q }} | ||
| {% endfor %}{% endif %} | ||
| {% endblock %} |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -1596,6 +1596,58 @@ async def _add_analysis_suggestions(self, plan: Plan): | |||||||
| num_objects=len(self.interface.objects), | ||||||||
| ) | ||||||||
|
|
||||||||
| def _add_follow_up_icon(self, plan: Plan): | ||||||||
| """Add follow-up suggestion icon to the last message footer. | ||||||||
|
|
||||||||
| The icon appears immediately. On click, it calls the coordinator | ||||||||
| to generate a suggestion and populates the chat input. | ||||||||
| """ | ||||||||
|
|
||||||||
| async def _generate_follow_up(): | ||||||||
| follow_up_button.disabled = True | ||||||||
| follow_up_button.description = "Generating suggestion..." | ||||||||
| try: | ||||||||
| suggestion = await self._coordinator.suggest_follow_up(plan) | ||||||||
| if suggestion: | ||||||||
| with edit_readonly(self._chat_input): | ||||||||
| self._chat_input.value_input = suggestion | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need edit read only? Can't we just do
Suggested change
|
||||||||
| finally: | ||||||||
| follow_up_button.disabled = False | ||||||||
| follow_up_button.description = "Suggest a follow-up question" | ||||||||
|
|
||||||||
| if not plan.out_context.get("pipeline"): | ||||||||
| return | ||||||||
| if not plan.out_context.get("data"): | ||||||||
| return | ||||||||
|
|
||||||||
| follow_up_button = IconButton( | ||||||||
| icon="lightbulb", | ||||||||
| description="Suggest a follow-up question", | ||||||||
| size="small", | ||||||||
| icon_size="0.9em", | ||||||||
| margin=(5, 0), | ||||||||
| on_click=lambda _: state.execute(_generate_follow_up), | ||||||||
| name="FollowUp", | ||||||||
| disabled=self.interface.param.loading, | ||||||||
| color="default", | ||||||||
| sx={"color": "#FFD700", "padding": "0 0.1em"}, | ||||||||
| ) | ||||||||
|
|
||||||||
| if len(self.interface): | ||||||||
| message = self.interface.objects[-1] | ||||||||
| try: | ||||||||
| existing = message.footer_actions or [] | ||||||||
| except AttributeError: | ||||||||
| existing = message.footer_objects or [] | ||||||||
| existing = [ | ||||||||
| obj for obj in existing | ||||||||
| if getattr(obj, 'name', '') != "FollowUp" | ||||||||
| ] | ||||||||
| try: | ||||||||
| message.footer_actions = existing + [follow_up_button] | ||||||||
| except AttributeError: | ||||||||
| message.footer_objects = existing + [follow_up_button] | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's just assume footer actions is implemented, and then bump panel-material-ui pinned requirement. |
||||||||
|
|
||||||||
| def __panel__(self): | ||||||||
| return self._main | ||||||||
|
|
||||||||
|
|
@@ -2506,6 +2558,7 @@ async def _postprocess_exploration( | |||||||
| await self._sync_sources(SimpleNamespace(new=plan.out_context), global_context=plan.out_context) | ||||||||
| if "pipeline" in plan.out_context: | ||||||||
| await self._add_analysis_suggestions(plan) | ||||||||
| self._add_follow_up_icon(plan) | ||||||||
|
|
||||||||
| if is_new: | ||||||||
| plan.param.watch(partial(self._update_views, exploration), "views") | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.