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

134 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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