Files
ss-tools/specs/033-gradio-agent-chat/spec.md
2026-06-09 09:43:34 +03:00

9.0 KiB
Raw Blame History

#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:

  1. Given Gradio running, When Svelte calls submit(), Then tokens stream in real time via event.data chunks.
  2. 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:

  1. Given user query, When agent processes it, Then agent selects and invokes @tool functions from backend/src/agent/tools.py.
  2. Given tool requires confirmation, When agent calls it, Then HumanInTheLoopMiddleware pauses execution, UI shows confirmation card.
  3. 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}/active rejects second tab
  • Network lost → detect disconnect, offer retry

Functional Requirements

Transport & Core

  • FR-001: Gradio gr.ChatInterface(type="messages", multimodal=True). Consumable via @gradio/client submit("/chat", payload) with additional_inputs=conversation_id.
  • FR-002: Svelte frontend uses @gradio/client submit() exclusively. No predict(), no custom WebSocket.
  • FR-003: Streaming via LangGraph agent.astream_events() → yield → event.data chunks. Frontend iterates for await (event of submission).

Agent & Tools

  • FR-004: LangGraph create_react_agent(model, tools, checkpointer=PostgresSaver) from langgraph.prebuilt. One-line creation.
  • FR-005: Native @tool functions in backend/src/agent/tools.py. Each calls FastAPI REST. Old @assistant_tool registry → @DEPRECATED Tombstone.
  • FR-006: @tool functions use Pydantic BaseModel for args_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_before node → primary submit() stream yields metadata.type="confirm_required" with thread_id and terminates (stream ends, does NOT wait). User confirms → second submit("/chat", msg, [conversation_id, "confirm"]) → handler loads checkpoint by thread_id → invokes graph with Command(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: RunnableWithMessageHistory auto-loads context from PostgreSQL on resume. Svelte does NOT pass history — only conversation_id via additional_inputs.

UX & Cancel

  • FR-014: submission.cancel() — native cancel. Partial text preserved.
  • FR-015: concurrency_limit=1 on handler. Server-side per-user lock prevents concurrent sends. GET /api/agent/conversations/{id}/active as 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 Authorization header. Gradio handler extracts via request: 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=True handles upload. Parser in backend/src/agent/document_parser.py.

Backward Compat

  • FR-022: Existing /api/assistant REST preserved. Old @assistant_tool@DEPRECATED Tombstone.
  • FR-023: Existing Svelte UX patterns preserved: drawer/embedded variants, focus-target sync.

Additional

  • FR-024: Audit logging via LoggingMiddleware — all interactions to assistant_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/client npm
  • Docker: new ss-tools-agent container, nginx proxy /api/agent/gradio
  • Deprecated: backend/src/api/routes/assistant/_tool_registry.py → Tombstone

#endregion AgentChat.Spec