feat(ai): add opt-in chart and dashboard authoring - #43133
Conversation
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #43133 +/- ##
==========================================
- Coverage 66.73% 57.12% -9.62%
==========================================
Files 2876 2941 +65
Lines 164218 171642 +7424
Branches 37890 39170 +1280
==========================================
- Hits 109596 98052 -11544
- Misses 52463 72695 +20232
+ Partials 2159 895 -1264
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@bsovran, keeping this draft while #42805 and #43132 are pending. I checked both failing runs: https://github.com/apache/superset/actions/runs/31779483501 and https://github.com/apache/superset/actions/runs/31779483587. They share one inherited root cause: current master and the #42805 migration produce two Alembic heads (2d6ad72e4af6 and a1c4f7e29b31), so both the single-head check and superset db upgrade fail before the authoring code is exercised. This should be resolved or rebased in #42805, then this branch can be refreshed. Please let me know once the base is ready. |
|
Added Regression coverage reproduces the nested inline |
Adds an AI assistant behind the AI_ASSISTANT feature flag, off by default. A user can ask questions about the dashboard, chart or query in front of them and get an answer grounded in the data they already have access to. Architecture, in layers that can each be replaced by configuration: - Provider seam (AI_LLM_PROVIDER_CLASS): no vendor name, model identifier, base URL or credential appears in core. Ships an OpenAI-compatible and an Anthropic provider behind optional extras, so a private gateway or a self-hosted model is configuration rather than a fork. - Agent runtime (AI_AGENT_RUNTIME_CLASS): a tool-use loop that streams the answer as the model produces it and reports each step as it completes. - Tools over the existing MCP service, gated by per-profile allowlists and a policy chain that keeps generated SQL read-only. - Prompt assembly that fails the build rather than shipping a prompt carrying deployment-specific tables, hostnames or stale tool names. - Storage in Postgres: threads, messages and feedback, with client-supplied idempotency enforced by a unique constraint. Execution is inline by default and can run on the Celery worker (AI_ASSISTANT_EXECUTION_MODE), which decouples a run from any reader and lets a client reconnect to one already in progress. Permissions follow the existing model: the API is @Protect()-ed, the menu is Alpha-only so Gamma and Public cannot reach it, and tools run as the requesting user so a question can never read past that user's grants. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9ec50e7 to
0c830e3
Compare
|
CI note: the current |
|
@sadpandajoe, the previous review feedback is addressed, including the authoring permission boundary and dashboard-layout validation. Could you please re-review this PR when convenient? |
|
CI's red across every backend on this one, |
|
Fixed in f8df855: |
|
CI follow-up: the rerun is blocked before any backend tests execute because this stacked branch carries Alembic head |
| request.assistant_message_uuid, | ||
| content=_terminal_content(result, outcome), | ||
| status=_status_of(outcome), | ||
| extra={ |
There was a problem hiding this comment.
Successful turns discard the reasoning that was streamed to the browser, so reopening a completed conversation shows a different thought process than the live run. Could this persist the bounded result.thoughts here, as the abnormal-run path does?
There was a problem hiding this comment.
Agreed. Successful-turn thought persistence is inherited unchanged from the parent Native AI PR #42805; this authoring PR does not modify the finalization path. The parent should persist the same bounded field on every outcome, then this branch can be rebased. I am leaving the thread open pending that change.
| pip install "apache-superset[ai-anthropic]" # or [ai-openai] | ||
| ``` | ||
|
|
||
| Then run `superset init` so the assistant's permissions are created and assigned |
There was a problem hiding this comment.
Following these enablement steps on an existing deployment leaves the new ai_chat_* tables absent, because superset init does not apply the migration. Could this also require superset db upgrade before the assistant is used?
There was a problem hiding this comment.
Agreed. The reviewed enablement sequence is inherited unchanged from #42805. This authoring PR adds tool-specific documentation but does not own the base migration runbook. The parent docs should add superset db upgrade once, after which this branch will be rebased. I am leaving this open until then.
|
|
||
| ## Retention | ||
|
|
||
| Conversations are kept for `AI_ASSISTANT_MESSAGE_RETENTION_DAYS` (default 30). |
There was a problem hiding this comment.
The retention setting is not enforced by this branch: the only added Celery task is ai.run_turn, so there is no pruning task or scheduler hook for an operator to schedule. Could this provide the pruning mechanism or document that the setting is currently inert?
There was a problem hiding this comment.
Agreed. The retention contract and missing pruning task are inherited unchanged from #42805, where the root issue is already tracked: #42805 (comment). This child PR will be rebased after the parent resolves it; I am leaving the thread open meanwhile.
| | { status: 'loaded'; formData: QueryFormData } | ||
| | { status: 'error'; message: string }; | ||
|
|
||
| const exploreUrlFor = (formDataKey: string): string => |
There was a problem hiding this comment.
This absolute Explore URL drops the application root. Deployments served under a prefix therefore send the new “View in Explore” link to /explore/… instead of the mounted route. Could this use the existing application-root-aware URL helper?
There was a problem hiding this comment.
Agreed. The reviewed Explore-link construction is inherited unchanged from the parent Native AI PR #42805 and is not introduced by the authoring adapters. The application-root-aware fix belongs in the parent UI implementation, then this branch can be rebased. I am leaving the thread open pending that fix.
| </ActionButton> | ||
| {/* A conversation with messages is confirmed before deletion; an | ||
| empty one is discarded without a prompt. */} | ||
| {tab.messages.length > 0 ? ( |
There was a problem hiding this comment.
Thread-list tabs are deliberately created with messages: [] until first selection, so deleting an unopened conversation bypasses this confirmation even when it has persisted messages. Could the list retain/use the server message count for this decision?
There was a problem hiding this comment.
Agreed. Unopened-thread deletion behavior is inherited unchanged from #42805 and is already tracked on the parent: #42805 (comment). This authoring PR should not duplicate the parent state-model fix, so the thread remains open until rebase.
|
|
||
| const PillLabel = styled.span` | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; |
There was a problem hiding this comment.
Answer deltas update liveAnswer, but it is absent from this effect’s dependencies. After tool activity stops, a long streamed answer can grow below the viewport without the transcript following it. Could this depend on liveAnswer too?
There was a problem hiding this comment.
Agreed. The auto-scroll effect is inherited unchanged from the parent Native AI PR #42805; the authoring commits do not modify this panel behavior. The dependency fix should land once in the parent UI, then be rebased here. I am leaving this thread open pending that update.
| ); | ||
|
|
||
| /** Resolved when the user answers a checkpoint; see `streamRun`. */ | ||
| const checkpointGateRef = useRef<{ resolve: () => void } | null>(null); |
There was a problem hiding this comment.
Runs are tracked per tab, but this single checkpoint resolver is shared by every stream. If two tabs reach checkpoints, continuing one can resolve the other run and leave the selected run blocked. Could the gate be keyed by tab/run like activeRunsByTab?
There was a problem hiding this comment.
Agreed. The single checkpoint resolver is inherited unchanged from the parent Native AI PR #42805 and is not part of the authoring tool change. The gate needs to be keyed in the parent run-state implementation, then rebased here. I am leaving the thread open until that is resolved.
| except (AIChatMessageInvalidError, AIChatThreadInvalidError) as ex: | ||
| return self.response_422(message=str(ex)) | ||
|
|
||
| run_id = new_run_id() |
There was a problem hiding this comment.
A replay with the same request ID reuses the existing message rows, but this still creates a new run ID, overwrites their run context, and starts inference again. A lost POST response can therefore produce two paid runs racing on one assistant message. Could this return the original run when the append command reports created == false?
There was a problem hiding this comment.
Agreed. Duplicate request handling is inherited unchanged from #42805 and is already tracked at #42805 (comment). This authoring PR does not alter request claiming, so I am leaving the thread open pending the parent fix and rebase.
| # A failure to publish must not kill the run that is producing useful | ||
| # work; the reader will time out and the answer is still persisted. | ||
| try: | ||
| self._cache.xadd(self._stream(run_id), payload, "*", 10_000) |
There was a problem hiding this comment.
This creates Redis streams without a TTL; expiration is only installed by a reader calling close(). If no stream reader connects, every abandoned run retains up to 10,000 events indefinitely. Could publishing set or refresh the stream expiry?
There was a problem hiding this comment.
Agreed. Producer-side stream expiry is inherited unchanged from #42805 and is already tracked at #42805 (comment). It is outside the authoring adapter change; this thread remains open until the parent fix is rebased.
| return self.response_404() | ||
|
|
||
| turn = None | ||
| if current_app.config.get("AI_ASSISTANT_EXECUTION_MODE") != "worker": |
There was a problem hiding this comment.
Inline-mode reconnects construct and execute a new turn for any matching message without claiming the pending row or checking its status. Two stream GETs—or a retry after completion—can therefore run the same request twice and repeat authoring side effects. Could this make the run claim idempotent before starting it?
There was a problem hiding this comment.
Agreed. Inline run claiming and terminal replay are inherited unchanged from #42805. The duplicate-side-effect issue is already tracked at #42805 (comment). I am leaving this open and will rebase the child after the parent fix.
| generator cannot do both; it has the same shape either way, so callers do | ||
| not branch on which path ran. | ||
| """ | ||
| completion = CompletionRequest( |
There was a problem hiding this comment.
The requested exact model is recorded on the run but this completion request only carries model_alias, so the provider cannot receive the pin. A request for model B can run the alias default A while telemetry reports B. Could this pass the resolved exact model through the runtime?
There was a problem hiding this comment.
Agreed. Exact-model propagation is inherited unchanged from #42805 and is tracked at #42805 (comment). The authoring commits do not change completion request construction, so the thread remains open pending the parent fix and rebase.
| tools=tuple(request.tools.definitions()) if request.tools else (), | ||
| ) | ||
|
|
||
| if not self.provider.supports_streaming: |
There was a problem hiding this comment.
The providers disable SDK retries in favor of superset.ai.llm.retry, but these calls invoke the provider directly and never apply that middleware. A transient 429/5xx therefore fails the turn on its first attempt despite the configured retry policy. Could the runtime wrap both completion paths with the retry policy?
There was a problem hiding this comment.
Agreed. Runtime retry integration is inherited unchanged from #42805 and is already tracked at #42805 (comment). This authoring PR does not touch provider invocation; I am leaving the thread open until the parent implements and validates the retry semantics.
There was a problem hiding this comment.
🟡 Changes recommended
There are at least two concrete correctness issues in the current diff (suggestions parsing vs tests, and a race where AI actions can be lost before the panel attaches its event listener).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds an opt-in AI assistant extension to Superset, including backend APIs, storage, prompts, tool plumbing, and frontend registration, with guardrails for RBAC and safe UX defaults (off-by-default, chromeless render suppression, read-only profiles by default).
Changes:
- Adds AI assistant backend building blocks (types, commands, schemas, suggestions, runtime interfaces, retry/circuit breaker utilities) and wires the AI REST API behind the
AI_ASSISTANTfeature flag. - Improves dashboard authoring safety by validating supplied
position_jsonlayouts and falling back to an auto-generated layout with a warning when invalid. - Registers the assistant in the frontend chat host (flag-gated), adds UI components/hooks/tests for assistant UX, and documents/configures optional provider extras.
File summaries
| File | Description |
|---|---|
| tests/unit_tests/mcp_service/dashboard/tool/test_dashboard_generation.py | Updates layout override test shape and adds coverage for invalid layout fallback warnings. |
| tests/unit_tests/ai/test_tools_result_shape.py | Adds unit tests for SQL tool result normalization and row limit validation. |
| tests/unit_tests/ai/test_tools_display.py | Adds tests ensuring LLM prompt-framing delimiters are stripped from UI display only. |
| tests/unit_tests/ai/test_suggestions.py | Adds tests for suggested prompt parsing and “never raise” behavior. |
| tests/unit_tests/ai/init.py | Adds package init (license header). |
| tests/integration_tests/superset_test_config.py | Enables AI flag for integration tests and pins to an offline scripted provider. |
| tests/integration_tests/ai/init.py | Adds package init (license header). |
| superset/security/manager.py | Restricts the AI assistant menu to Alpha by default (avoids Gamma/Public by inheritance). |
| superset/migrations/versions/2026-08-04_00-00_a1c4f7e29b31_add_ai_chat_tables.py | Adds DB tables for AI threads/messages/feedback with indexes and rollback. |
| superset/mcp_service/dashboard/tool/generate_dashboard.py | Validates position_json layouts; invalid inputs fall back to packed grid + warning. |
| superset/mcp_service/dashboard/schemas.py | Updates dashboard generation schema docs to reflect fallback-on-invalid behavior. |
| superset/mcp_service/auth.py | Adds an in-process user override context for MCP calls and adjusts auth priority order. |
| superset/initialization/init.py | Registers AI REST API only when AI_ASSISTANT flag is enabled. |
| superset/commands/ai/thread.py | Adds commands for AI thread create/update/delete with validation and ownership scoping. |
| superset/commands/ai/submit_feedback.py | Adds command to upsert thumbs up/down feedback scoped to thread ownership. |
| superset/commands/ai/exceptions.py | Introduces AI command exception types with “missing, not forbidden” semantics. |
| superset/commands/ai/append_message.py | Adds idempotent message append command with role/status validation. |
| superset/commands/ai/init.py | Exposes AI command entry points. |
| superset/ai/types.py | Defines shared enums and typed dicts for runs, messages, SSE event vocabulary. |
| superset/ai/tools/init.py | Exposes tool registry entry points and bundles for runtime consumption. |
| superset/ai/tasks.py | Adds Celery task wrapper for worker-mode assistant execution. |
| superset/ai/suggestions.py | Implements opt-in model-generated opening suggestions (never-raise fallback). |
| superset/ai/schemas.py | Adds Marshmallow request/response schemas for AI API endpoints. |
| superset/ai/runtime/base.py | Defines runtime contract (events + results) and tool dispatcher protocols. |
| superset/ai/runtime/init.py | Adds runtime package init and module docstring. |
| superset/ai/prompts/core/superset_product.py | Adds core “Superset behavior” prompt section for grounding and correct guidance. |
| superset/ai/prompts/core/sql_conventions.py | Adds portable SQL craft prompt section (no dialect-specific syntax). |
| superset/ai/prompts/core/safety.py | Adds safety prompt section emphasizing injection resistance and scope limits. |
| superset/ai/prompts/core/persona.py | Adds persona + grounding prompt section and response-shaping guidance. |
| superset/ai/prompts/core/init.py | Assembles the set of core prompt sections including a time anchor. |
| superset/ai/prompts/init.py | Exposes prompt assembly helpers and validation utilities. |
| superset/ai/mcp/init.py | Documents external MCP server extension seam and trust boundary notes. |
| superset/ai/llm/retry.py | Adds retry/backoff and context-local circuit breaker for provider calls. |
| superset/ai/llm/init.py | Adds LLM package init. |
| superset/ai/knowledge.py | Adds knowledge-provider interface for deployment-specific domains and prompt sections. |
| superset/ai/factories.py | Adds runtime/provider/profile/tool registry resolution from configuration. |
| superset/ai/events.py | Defines SSE event framing and canonical encoding helpers. |
| superset/ai/init.py | Adds AI package init with “no vendor imports at module scope” contract. |
| superset-frontend/src/setup/setupApp.ts | Imports AI feature registration as a setup side effect (flag-gated module). |
| superset-frontend/src/features/ai/index.ts | Registers assistant as a chat provider; suppresses chromeless renders; exposes register/unregister. |
| superset-frontend/src/features/ai/index.test.tsx | Adds tests for registration gating, chromeless suppression, and host mounting behavior. |
| superset-frontend/src/features/ai/hooks/useChatBot.test.ts | Adds tests for transcript merge logic and page-context construction behavior. |
| superset-frontend/src/features/ai/hooks/useAIAction.ts | Adds DOM-event-based action triggering API for other app areas to invoke the assistant. |
| superset-frontend/src/features/ai/hooks/useAIAction.test.ts | Adds tests validating event dispatch and prompt builders. |
| superset-frontend/src/features/ai/hooks/quickPrompts.ts | Adds client-side derived opening prompt suggestions (free fallback). |
| superset-frontend/src/features/ai/hooks/chatThreadsApi.ts | Adds thread/message CRUD API helpers and mapping to chat tab/message shapes. |
| superset-frontend/src/features/ai/components/ChatTabsMenu.test.tsx | Adds tests for conversation list UI behaviors (rename/delete/relative timestamps). |
| superset-frontend/src/features/ai/components/chatMarkdown.test.tsx | Adds tests for fenced-block handling, SQL actions, and chart-embed fences. |
| superset-frontend/src/features/ai/components/ChatAgentSelect.tsx | Adds agent profile dropdown (preserves server order; shows descriptions). |
| superset-frontend/src/features/ai/components/ChatAgentSelect.test.tsx | Adds tests ensuring order stability and selection behavior. |
| superset-frontend/src/features/ai/AiAssistantTrigger.tsx | Adds trigger button component tied to chat open/close state. |
| superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts | Adds AiAssistant feature flag constant. |
| superset-frontend/packages/superset-ui-core/src/components/Icons/AntdEnhanced.tsx | Adds additional icons used by assistant UI (like/dislike/send/message). |
| pyproject.toml | Adds optional extras for AI providers and external MCP client. |
| docs/static/feature-flags.json | Documents the AI_ASSISTANT flag and its safety/enablement semantics. |
| docker/pythonpath_dev/superset_config.py | Adds local-dev configuration for AI assistant and worker-mode settings. |
| docker/.env-local.example | Documents optional AI-related env vars for local development. |
Review details
- Files reviewed: 65/116 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| export const triggerAIAction = (payload: AIActionPayload): void => { | ||
| if (!payload.prompt.trim()) { | ||
| return; | ||
| } | ||
| chat.open(); | ||
| window.dispatchEvent( | ||
| new CustomEvent<AIActionPayload>(AI_ACTION_EVENT, { detail: payload }), | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Confirmed: chat.open() updates the external store synchronously, but React mounts the panel and installs its useEffect listener later, so a closed panel can miss this event. This code is inherited unchanged from the owning base PR #42805 (including at its current head), not introduced by the #43133 authoring layer. I am leaving this thread open and keeping the root fix on #42805 rather than duplicating it across stacked PRs.
|
@rusackas, the authoring-specific human feedback is addressed. The remaining newly reported action-event race is inherited from #42805 and is tracked there rather than duplicated in this stack. Could you confirm whether this PR should remain parked until the base is updated or whether the focused authoring diff can be reviewed now? |
feat(ai): add opt-in chart and dashboard authoring
SUMMARY
Add opt-in native AI tools for virtual datasets, charts, and dashboards. Shipped profiles remain read-only; configured builder profiles reuse the registered MCP implementations through a real FastMCP context and normal RBAC. Prefer FastMCP
structured_contentover its internaldatawrapper so authoring responses remain plain payloads.Depends on #42805 and #43132. Related SIP: #42806.
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
Not applicable; backend behavior only.
TESTING INSTRUCTIONS
pytest -q tests/unit_tests/ai/test_authoring_tools.py tests/unit_tests/ai/test_tools.py tests/unit_tests/ai/test_mcp_tools.py tests/unit_tests/ai/test_policy.pyResult: 222 passed.
Ruff check, Ruff format check, and
git diff --checkpass.ADDITIONAL INFORMATION