- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files - Rename git bundle file ss-tools.bundle → superset-tools.bundle - Update .gitignore pattern accordingly - Preserve variable names (hasSsTools etc.) and code identifiers
13 KiB
#region AgentChat.Spec [C:3] [TYPE ADR] [SEMANTICS spec,requirements,agent-chat,gradio,langgraph]
@BRIEF Feature specification — Gradio submit() + LangGraph agent with interrupt()/Command(resume=...). No custom WebSocket, no REST confirmations, no @assistant_tool for new code.
@RATIONALE LangGraph chosen over LangChain AgentExecutor: interrupt() + Command(resume=...) are native HITL features, PostgresSaver is LangGraph-native checkpointer, create_react_agent from langgraph.prebuilt provides standard tool-calling. Gradio submit() for streaming, nginx reverse proxy for browser access.
@REJECTED Replacing Svelte frontend with Gradio UI — violates ADR-0006.
@REJECTED Custom SSE/WebSocket — @gradio/client submit() is the native transport.
@REJECTED Custom bidirectional WebSocket — @gradio/client is request/job-oriented, not raw WS.
@REJECTED REST confirmation endpoints — LangGraph interrupt() eliminates them.
@REJECTED @assistant_tool for new tools — native LangChain @tool is SSOT. Old registry → @DEPRECATED for FR-020.
Feature Branch: 033-gradio-agent-chat
Created: 2026-06-08 | Status: Draft | Last revised: 2026-06-08 (consistency sweep)
Architecture Overview
Browser (SvelteKit) Docker
┌──────────────────────┐ nginx ┌─────────────────────┐
│ @gradio/client │──→ /api/agent/gradio─→│ Gradio Agent :7860 │
│ submit("/chat", │ proxy+JWT │ gr.ChatInterface │
│ {message}, │ │ create_agent(model, │
│ conversation_id) │ │ tools=[@tool...], │
│ for await(event) {} │ │ middleware=[HitL], │
│ submission.cancel() │ │ checkpointer=PG) │
│ │ │ @tool→HTTP→FastAPI │
│ REST /api/assistant/*│──────────────────────→│_____________________│
└──────────────────────┘ │ HTTP
┌─────────▼───────────┐
│ FastAPI :8000 │
│ PostgreSQL │
│ /api/assistant (leg.)│
│ /api/auth/svc-token │
└─────────────────────┘
User Stories
Story 1 — Gradio Agent Engine with Streaming Chat (P1) 🎯
Independent Test: Client.connect("/api/agent/gradio"), submit("/chat", {message}, conversation_id), verify tokens stream via for await (event).
Acceptance:
- Given Gradio running, When Svelte calls
submit(), Then tokens stream in real time viaevent.datachunks. - Given streaming in progress, When user calls
submission.cancel(), Then generation stops, partial text preserved.
Story 2 — Autonomous Tool Selection (P1) 🎯
Independent Test: "покажи дашборды с проблемами валидации" → agent calls search + health tools.
Acceptance:
- Given user query, When agent processes it, Then agent selects and invokes
@toolfunctions frombackend/src/agent/tools.py. - Given tool requires confirmation, When agent calls it, Then HumanInTheLoopMiddleware pauses execution, UI shows confirmation card.
- Given tool fails, When error returned, Then agent explains failure and suggests recovery.
Story 3 — Tool-Call Visibility (P2)
Independent Test: Send multi-tool query, verify each tool appears as inline card with spinner → checkmark/cross transition.
Acceptance:
- Given agent triggers a tool, When
metadata.type="tool_start"received, Then UI shows card with tool name (mono font) and spinner. - Given tool completes, When
metadata.type="tool_end"received, Then spinner becomes green checkmark, result expandable on click. - Given tool fails, When
metadata.type="tool_error"received, Then spinner becomes red cross with error detail expandable on click.
Story 4 — Multi-Turn Context (P2)
Independent Test: Ask "покажи дашборд 42", then "создай для него ветку". Agent resolves "него" → dashboard 42.
Acceptance:
- Given prior messages in conversation, When user uses pronouns/implicit references, Then agent resolves them via
RunnableWithMessageHistorycontext. - Given conversation exceeds 4000 tokens, When new message sent, Then older messages are summarized/truncated before being passed to LLM.
- Given user starts new conversation, When referencing entities from prior conversation, Then agent does NOT have access to prior context (clean isolation).
Story 5 — Conversation Persistence (P3)
Independent Test: Create 3 conversations, close browser tab, reopen — all 3 visible, resume any.
Acceptance:
- Given past conversations exist, When user opens chat panel, Then conversations listed with auto-titles (truncated first message 60 chars), dates, message counts.
- Given past conversation selected, When user sends new message, Then
RunnableWithMessageHistoryloads previous context from PostgreSQL. - Given conversation archived, When user deletes, Then
is_archived=true, removed from active list, visible in archive filter. - Given conversation archived, When associated checkpoints cleared, Then no zombie threads remain in
langgraph-checkpoint-postgrestables.
Story 6 — File Upload & Document Analysis (P2)
Independent Test: Upload PDF report, type "выдели риски". Agent extracts text and responds with structured analysis.
Acceptance:
- Given user attaches PDF, When message sent, Then backend extracts text via pdfplumber, injects as system message context.
- Given user attaches XLSX, When message sent, Then backend parses sheets via openpyxl, presents structured data to agent.
- Given user attaches unsupported format (
.exe,.zip), When upload attempted, Then UI shows validation error with supported format list. - Given file exceeds 10MB, When upload attempted, Then UI rejects with size limit message.
Edge Cases
- Gradio unreachable → reconnect 5×5s
- LLM malformed → retry once, then error
- Message too long → truncate + warn
- Tool >30s → async, result when ready
- Multi-tab → REST gate:
GET /api/agent/conversations/{id}/activerejects second tab - Network lost → detect disconnect, offer retry
Functional Requirements
Transport & Core
- FR-001: Gradio
gr.ChatInterface(type="messages", multimodal=True). Consumable via@gradio/clientsubmit("/chat", payload)withadditional_inputs=conversation_id. - FR-002: Svelte frontend uses
@gradio/client submit()exclusively. Nopredict(), no custom WebSocket. - FR-003: Streaming via LangGraph
agent.astream_events()→ yield →event.datachunks. Frontend iteratesfor await (event of submission).
Agent & Tools
- FR-004: LangGraph
create_react_agent(model, tools, checkpointer=PostgresSaver)fromlanggraph.prebuilt. One-line creation. - FR-005: Native
@toolfunctions inbackend/src/agent/tools.py. Each calls FastAPI REST. Old@assistant_toolregistry →@DEPRECATEDTombstone. - FR-006:
@toolfunctions use PydanticBaseModelforargs_schema. Docstring = description. Single source of metadata. - FR-007: Tools call FastAPI with dual identity: service JWT authenticates the agent, user JWT (forwarded from browser via closure) authorizes the operation. RBAC enforced by FastAPI under user identity.
Confirmation (HumanInTheLoop)
- FR-008: LangGraph
interrupt()in dangerous tool nodes (deploy_dashboard,execute_migration,commit_changes). Handled entirely by LangGraph — zero REST confirmation endpoints. - FR-009: Resume mechanism: graph pauses at
interrupt_beforenode → primarysubmit()stream yieldsmetadata.type="confirm_required"withthread_idand terminates. User confirms → secondsubmit("/chat", msg, additional_inputs=[conversation_id, "confirm"])→ handler loads checkpoint by thread_id. If checkpoint not found: yieldmetadata.type="error" code="CHECKPOINT_EXPIRED". If found: invokeCommand(resume={"action":"confirm"}, config={"thread_id":conversation_id}). HITL resume waits for primary stream cleanup via per-conversation mutex (max 2s).
Persistence
- FR-010: Conversation history in PostgreSQL (
agent_conversations,agent_messages). REST:POST /api/assistant/conversations,GET .../conversations,GET .../history,DELETE .../{id}. - FR-011: Soft-delete:
is_archived=true. Renamed to "Archive" in UX. - FR-012: LangGraph checkpointer uses PostgreSQL (same instance as FastAPI) via
langgraph-checkpoint-postgres. Survives container restarts. Thread ID = conversation_id. - FR-013:
RunnableWithMessageHistoryauto-loads context from PostgreSQL on resume. Svelte passesconversation_idviaadditional_inputsonly. The Gradio handler receiveshistoryfrom Gradio's built-inChatInterface(mandatory parameter) but MUST ignore it — loading context from PostgreSQL bythread_id=conversation_idinstead.
UX & Cancel
- FR-014:
submission.cancel()— native cancel. Partial text preserved. - FR-015: Per-user lock via in-memory dict in handler prevents concurrent sends within same user session.
GET /api/agent/conversations/{id}/activeas additional gate for multi-tab. Gradio handles concurrent users by default — no globalconcurrency_limitneeded. - FR-016: Tool-call visibility via structured JSON metadata in
ChatMessage.metadata, not emoji string parsing.{type: "tool_start", tool: "...", input: {...}}. - FR-017: Confirmation card renders from
ChatMessage.metadata.confirm_required.
Auth
- FR-018: Browser → nginx → Gradio. nginx forwards
Authorizationheader. Gradio handler extracts viarequest: gr.Request.headers. - FR-019: Gradio → FastAPI: service JWT from
POST /api/auth/service-token. Dual identity: service JWT authenticates agent, user JWT authorizes tool calls. - FR-020: Stateless JWT validation in Gradio using shared
JWT_SECRET. No DB required for auth.
File Upload
- FR-021: PDF (pdfplumber), XLSX (openpyxl), JSON, CSV, PNG, JPEG.
multimodal=Truehandles upload. Parser inbackend/src/agent/document_parser.py.
Backward Compat
- FR-022: Existing
/api/assistantREST preserved. Old@assistant_tool→@DEPRECATEDTombstone. - FR-023: Existing Svelte UX patterns preserved: drawer/embedded variants, focus-target sync.
Additional
-
FR-024: Audit logging via
LoggingMiddleware— all interactions toassistant_audit. -
FR-025: RU/EN i18n keys for all new UI strings.
-
FR-026: Resume
submit()MUST wait for primary stream cleanup via per-conversation mutex before loading checkpoint. Primary acquires lock on start, releases on stream end. Resume waits for lock release (max 2s timeout). -
FR-027: Deployment MUST run
PostgresSaver.setup()on agent container startup to create checkpoint tables (checkpoints,checkpoint_writes,checkpoint_migrations). Managed via startup hook. -
FR-028: User messages exceeding 100,000 characters (~25k tokens) are truncated to 100,000 characters with
[...truncated]suffix appended. Truncation occurs at sentence boundary when possible. -
FR-029: When conversation is archived (soft-deleted), agent MUST clear associated checkpoints from
langgraph-checkpoint-postgrestables for thatthread_id.
Success Criteria
- SC-001: Tool selection ≥85% accuracy
- SC-002: First token <1.5s
- SC-003: Dangerous ops 100% confirmation
- SC-004: RBAC bypass 0%
- SC-005: History survives restart
- SC-006: Multi-step ≥30% faster than manual UI
- SC-007: File upload ≤10MB, no degradation
Dependencies
langgraph>=0.2,langchain-core>=0.3,langchain-openai>=0.3,langgraph-checkpoint-postgres(NEW — replaces sqlite)gradio>=5.0,pdfplumber,openpyxl@gradio/clientnpm- Docker: new
superset-tools-agentcontainer, nginx proxy/api/agent/gradio - Deprecated:
backend/src/api/routes/assistant/_tool_registry.py→ Tombstone
#endregion AgentChat.Spec