134 lines
9.0 KiB
Markdown
134 lines
9.0 KiB
Markdown
#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
|