9.0 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)
Story 4 — Multi-Turn Context (P2) — via RunnableWithMessageHistory
Story 5 — Conversation Persistence (P3) — PostgreSQL SSOT, REST CRUD
Story 6 — File Upload & Document Analysis (P2) — multimodal=True, pdfplumber/openpyxl
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 (stream ends, does NOT wait). User confirms → secondsubmit("/chat", msg, [conversation_id, "confirm"])→ handler loads checkpoint by thread_id → invokes graph withCommand(resume={"action":"confirm"}, config={"thread_id":conversation_id})→ graph resumes from checkpoint → new stream begins.
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 does NOT passhistory— onlyconversation_idviaadditional_inputs.
UX & Cancel
- FR-014:
submission.cancel()— native cancel. Partial text preserved. - FR-015:
concurrency_limit=1on handler. Server-side per-user lock prevents concurrent sends.GET /api/agent/conversations/{id}/activeas additional gate for multi-tab. - 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.
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
ss-tools-agentcontainer, nginx proxy/api/agent/gradio - Deprecated:
backend/src/api/routes/assistant/_tool_registry.py→ Tombstone
#endregion AgentChat.Spec