Add follow-up suggestion chips after successful queries#1764
Add follow-up suggestion chips after successful queries#1764ghostiee-11 wants to merge 9 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1764 +/- ##
==========================================
+ Coverage 69.63% 69.73% +0.09%
==========================================
Files 190 190
Lines 31495 31614 +119
==========================================
+ Hits 21933 22046 +113
- Misses 9562 9568 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| prompt = render_template( | ||
| PROMPTS_DIR / "ExplorerUI" / "follow_up_suggestions.jinja2", | ||
| user_query=user_query, | ||
| data_summary=data_summary, | ||
| sql=sql, | ||
| output_type=output_type, | ||
| conversation_history=conversation_history, | ||
| ) | ||
| result = await self.llm.invoke( | ||
| messages=[{"role": "user", "content": "Generate follow-up suggestions."}], | ||
| system=prompt, | ||
| response_model=FollowUpSuggestions, | ||
| ) |
There was a problem hiding this comment.
I think we should use self._invoke_prompt instead, or even self._stream_prompt, so each suggestion appears as it's generated.
There was a problem hiding this comment.
Will refactor to use _invoke_prompt for consistency. Since UI doesn't inherit from Actor (where _invoke_prompt lives), I'll route it through self._coordinator._invoke_prompt. For streaming, structured output with a Pydantic model needs the full response to parse, so I'll start with _invoke_prompt and can explore streaming as a follow-up if needed.
Good call, the FlexBox wrapping does look cramped. I'll switch to a pn.Row with horizontal scrolling so the chips sit in a clean single line and scroll when they overflow. Will also add docs for the follow-up suggestions feature and the settings toggle. |
There was a problem hiding this comment.
Still not happy with how it looks under the message; looks really messy / imbalanced, especially with the horizontal scroll bar. It might be because the icons are above the buttons (maybe reverse them in panel-material-ui).
Instead, maybe we can have a automagic (AI) icon button added to the footer (like pencil edit in your other PR), and then on click it replaces/populates the ChatAreaInput with a suggestion instead?
| "response_model": ThinkingYesNo, | ||
| }, | ||
| "follow_up_suggestions": { | ||
| "template": PROMPTS_DIR / "ExplorerUI" / "follow_up_suggestions.jinja2", |
There was a problem hiding this comment.
Should live in Coordinator imo
| Given data with columns: product, region, revenue, date | ||
| User asked: "Show total revenue by product" | ||
| Good suggestions: | ||
| - (timeline) "Show revenue trend over date by product" | ||
| - (compare_arrows) "Compare revenue across region" | ||
| - (filter_list) "Filter to top 5 products by revenue" | ||
|
|
||
| Bad suggestions (too generic): | ||
| - "Show me more data" | ||
| - "Analyze the results" | ||
| - "Create a visualization" | ||
|
|
||
| Given data with columns: name, age, salary, department | ||
| User asked: "Show average salary by department" | ||
| Good suggestions: | ||
| - (scatter_plot) "Plot age vs salary colored by department" | ||
| - (bar_chart) "Show employee count by department" | ||
| - (filter_list) "Filter departments where avg salary > 50000" |
There was a problem hiding this comment.
Shorten good suggestions I think
| {% if conversation_history %} | ||
|
|
||
| ## Previous Questions in This Exploration (do NOT repeat these) | ||
| {% for msg in conversation_history %}- {{ msg }} |
There was a problem hiding this comment.
not really a "conversation" if we're filtering by user
|
Separately, can we use model_spec="ui" or create a new LLM key (and document)? UI key currently uses the smallest model |
|
Will use |
|
Pushed the latest round of changes:
Still planning to:
Will tackle those in a follow-up PR |
|
I still like this idea so I'm going to hold off on merging. May require changes to panel or pmui ChatMessage to allow adding functionality to button icons (unless already implemented).
|
Thanks a lot Andrew about the review and suggestion, Just working on this, will get back with you quickly |
|
Np rush. It'll probably go into the next release. |
Okay Thankyouu!! |
bd5f010 to
e2f0fb4
Compare
| You are a data exploration assistant. Based on the user's question and the resulting data, suggest 2-3 natural follow-up questions that reference actual columns and values from the dataset. | ||
|
|
||
| ## Rules | ||
| - Each suggestion MUST reference specific column names from the data summary below | ||
| - Suggestions should be diverse: mix breakdowns, comparisons, filters, trends, distributions | ||
| - Keep suggestions short and actionable (under 100 characters) | ||
| - Do NOT repeat any previous queries listed below | ||
| - Do NOT suggest analyses the data cannot support (e.g. no time trend if no date column) | ||
| - Use appropriate icon names: bar_chart, pie_chart, timeline, map, filter_list, compare_arrows, table_chart, trending_up, scatter_plot | ||
|
|
||
| ## Examples | ||
| - (timeline) "Show revenue trend over date by product" | ||
| - (compare_arrows) "Compare revenue across region" | ||
| - (filter_list) "Filter to top 5 products by revenue" | ||
|
|
||
| ## Data Summary | ||
| {{ data_summary }} | ||
|
|
||
| ## SQL Executed | ||
| {{ sql }} | ||
|
|
||
| ## Output Type | ||
| {{ output_type }} | ||
| {% if previous_queries %} | ||
|
|
||
| ## Previous Queries (do NOT repeat these) | ||
| {% for q in previous_queries %}- {{ q }} | ||
| {% endfor %}{% endif %} |
There was a problem hiding this comment.
I think we should use the structure of Actor.jinja2
| icon: str = Field( | ||
| description="Material UI icon name for the button", | ||
| examples=["bar_chart", "timeline", "filter_list"] | ||
| ) | ||
| query: str = Field( | ||
| description="The exact natural language query to send to Lumen AI", | ||
| max_length=100 | ||
| ) | ||
|
|
||
|
|
||
| class FollowUpSuggestions(BaseModel): | ||
| suggestions: list[FollowUpSuggestion] = Field( | ||
| description="2-3 dataset-specific follow-up suggestions", | ||
| min_length=1, | ||
| max_length=3 | ||
| ) |
There was a problem hiding this comment.
I still think it's better to populate the ChatAreaInput.value with one follow up suggestion for efficiency and look.
440b192 to
942c7b1
Compare
Add a lightbulb icon to message footer that populates the chat input with an AI-generated follow-up suggestion on click. Uses the cheapest 'ui' LLM spec for efficient generation. - Add FollowUpSuggestion model in models.py - Add suggest_followup() to Coordinator with llm_spec='ui' - Add follow_up_suggestions.jinja2 extending Actor/main.jinja2 - Add lightbulb icon to footer_actions (fallback to footer_objects) - Add follow_up_suggestions settings toggle - 14 tests (10 UI + 4 coordinator) + docs Depends on panel-extensions/panel-material-ui#605 for footer_actions.
942c7b1 to
582b0e1
Compare
|
Thanks for the drawing Andrew, that made it very clear! Pushed a rework:
|
There was a problem hiding this comment.
- I don't think there should be an auto-generate follow up suggestion; it should just be an icon button that users click when they're out of ideas
- Inconsistent naming: follow_up is sometimes followup
- Lightbulb should always show; no need for a toggle
Toggles should be reserved for anything that auto-generates, e.g. Validation agent or SQL planning.
|
Okay!! will fix all this.. |
- Follow-up suggestion is no longer auto-generated after every query. The lightbulb icon always appears and triggers the LLM call on click. - Remove follow_up_suggestions param and Settings toggle. Toggles are reserved for auto-generating features (Validation, SQL Planning). - Fix naming: suggest_followup -> suggest_follow_up, followup_button -> follow_up_button for consistency. is_followup on Plan is unchanged (separate exploration feature). - Remove _background_tasks (no longer needed). - Update tests and docs accordingly.
| ) | ||
| return result.query | ||
| except Exception: | ||
| return None |
There was a problem hiding this comment.
Maybe it should raise a notification perhaps through pn.state.notifications instead of failing silently.
| size="small", | ||
| icon_size="0.9em", | ||
| margin=(5, 0), | ||
| on_click=lambda _: state.execute(_generate_suggestion), |
There was a problem hiding this comment.
Is it necessary to call state.execute() vs just _generate_suggestion. Also for consistency, I think it should be called _generate_follow_up. And nested functions should be right under function signature, i.e. move
if not plan.out_context.get("pipeline"):
return
if not plan.out_context.get("data"):
return
num_objects = len(self.interface.objects)
below
| def hide_follow_up(_=None): | ||
| if len(self.interface.objects) > num_objects: | ||
| follow_up_button.visible = False |
There was a problem hiding this comment.
Maybe this can be a rx one-liner, e.g. IconButton(visible=self...); see https://medium.com/@pYdeas/rx-marks-the-spot-a-prescription-for-reactive-python-bdf25e5bf727
Alternatively, there might not be a need to hide it. Maybe user wants to keep stemming from the same conversation.
|
Addressed all three inline comments + the top-level review:
|
- Replace silent except with state.notifications.warning in coordinator - Rename _generate_suggestion to _generate_follow_up, move under signature - Keep state.execute() for async on_click (matches replan/rerun pattern) - Remove hide logic so button stays visible for repeated use - Remove bare except Exception: pass, use try/finally instead
| "template": PROMPTS_DIR / "Coordinator" / "tool_relevance.jinja2", | ||
| "response_model": ThinkingYesNo, | ||
| }, | ||
| "follow_up_suggestions": { |
There was a problem hiding this comment.
| "follow_up_suggestions": { | |
| "follow_up": { |
|
|
||
| try: | ||
| result = await self._invoke_prompt( | ||
| "follow_up_suggestions", |
There was a problem hiding this comment.
| "follow_up_suggestions", | |
| "follow_up", |
| with edit_readonly(self._chat_input): | ||
| self._chat_input.value_input = suggestion |
There was a problem hiding this comment.
Do we need edit read only? Can't we just do self._chat_input.value = suggestion
| with edit_readonly(self._chat_input): | |
| self._chat_input.value_input = suggestion | |
| self._chat_input.value = suggestion |
| 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] |
There was a problem hiding this comment.
Let's just assume footer actions is implemented, and then bump panel-material-ui pinned requirement.
- Rename prompt key follow_up_suggestions -> follow_up - Use self._chat_input.value instead of edit_readonly + value_input - Remove try/except fallback for footer_actions, assume it exists - Bump panel-material-ui pin to >=0.9.0 (has footer_actions)
footer_actions is not yet in any released panel-material-ui version. Use hasattr to detect it and fall back to footer_objects for now. Revert pin bump to >=0.8.1.
…i pin Removed hasattr fallback for footer_actions/footer_objects since panel-material-ui >=0.9.0 guarantees footer_actions. Restored the pin that was accidentally lowered to >=0.8.1.
e297d4c to
bd3e033
Compare



While exploring data in Lumen AI, I noticed that after each query I'd stare at the results trying to figure out what to ask next. Users unfamiliar with their dataset's columns have it worse - they don't know what's possible.
This adds 2-3 suggestion chips after each successful query that reference actual column names and values from the result. Clicking one sends it as a new query. A "Follow-Up Suggestions" toggle in Settings lets users turn it off.
Demo
Screen.Recording.2026-03-17.at.7.30.46.PM.mp4
How to reproduce
ExplorerUI().servable(), upload a CSVCloses #1763
AI Disclosure
I identified the UX gap while testing Lumen AI , designed the approach (hooking into
_postprocess_exploration, reusing_add_suggestions_to_footerwith agroup_nameparam), and verified through 9 new tests + manual end-to-end testing. AI helped scaffold the implementation.