Files
ss-tools/specs/033-gradio-agent-chat/contracts/ux/api-ux.md
2026-06-30 19:05:17 +03:00

106 lines
4.8 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.ApiUx [C:3] [TYPE ADR] [SEMANTICS ux,api,agent-chat,final]
@defgroup Ux Final API contract — Gradio `submit()`, LangChain v1 HITL via second submit(), PostgreSQL checkpointer, structured metadata.
## Gradio Transport
**Connection**: `Client.connect("${window.location.origin}/api/agent/gradio")` → Vite/nginx same-origin proxy → Gradio. JWT forwarded.
**Primary submit**: `client.submit("/chat", {text, files}, [conversation_id, null])` — three-arg API. `additional_inputs=[conversation_id, action]` maps to Gradio textboxes. Handler: `handler(message, history, request, conversation_id, action)`.
**Cancel**: `submission.cancel()`.
### Streaming Events
`for await (event of submission)``event.data` is a `ChatMessage` with structured `metadata`:
| `metadata.type` | `metadata` fields | UX Reaction |
|-----------------|-------------------|-------------|
| `stream_token` | `{token: "При"}` | Append token to last assistant msg |
| `tool_start` | `{tool: "search_dashboards", input: {...}}` | Tool card: name + spinner |
| `tool_end` | `{tool: "search_dashboards", output: {...}}` | Spinner→checkmark |
| `tool_error` | `{tool: "search_dashboards", error: "..."}` | Spinner→cross + detail |
| `confirm_required` | `{prompt: "Подтвердить деплой?", thread_id: "..."}` | Confirmation card + timer |
| `confirm_resolved` | `{result: "confirmed"/"denied"}` | Card collapses |
| `error` | `{code: "LLM_UNAVAILABLE", detail: "..."}` | Error card + retry |
| (none) | plain `{content: "..."}` | Normal text |
### HITL Resume Protocol
```
Primary: submit("/chat", {message}, [conversation_id, null])
→ handler: graph.astream_events(config={"thread_id": conversation_id})
→ interrupt_before fires → checkpoint saved (PostgreSQL)
→ handler: yield {metadata: {type: "confirm_required", thread_id: conversation_id}}
→ Svelte: renders confirmation card (primary stream ends)
Confirm: submit("/chat", {message: "confirm", action: "confirm", conversation_id}, [conversation_id, "confirm"])
→ handler: detects action="confirm" → loads checkpoint by thread_id
→ recreates graph with interrupt_before=[]
→ continues checkpoint stream with config={"thread_id": conversation_id}
→ graph resumes from checkpoint → new stream begins
Deny: submit("/chat", {message: "deny", action: "deny", conversation_id}, [conversation_id, "deny"])
→ handler: uses checkpoint thread_id and interrupt_before=[]
→ yields confirm_resolved(result="denied") → agent responds
```
---
## REST Endpoints
| Method | Endpoint | Purpose |
|--------|----------|---------|
| `POST` | `/api/assistant/conversations` | Create → `{id, title, created_at}` |
| `GET` | `/api/assistant/conversations` | Paginated list + search |
| `GET` | `/api/assistant/history` | Paginated messages |
| `POST` | `/api/assistant/conversations/{id}/messages` | Append after stream_end |
| `DELETE` | `/api/assistant/conversations/{id}` | Soft-delete (is_archived=true) |
| `GET` | `/api/agent/conversations/{id}/active` | Multi-tab gate |
| `POST` | `/api/auth/service-token` | Gradio obtains service JWT |
---
## Dual Identity RBAC
```
Gradio handler:
service_jwt = POST /api/auth/service-token (authenticates the agent service)
user_jwt = request.headers["Authorization"] (browser user)
@tool function:
call FastAPI with:
Authorization: Bearer {service_jwt} (agent is authorized to call)
X-User-JWT: {user_jwt} (operation authorized under user)
FastAPI endpoint:
validates service JWT → agent is legitimate
validates X-User-JWT → user has RBAC permission for this operation
audit: {service_actor: "agent", user_actor: user_id, operation, ...}
```
## Tool Schema Context Budget
`backend/src/agent/app.py` MUST select tools with hybrid `get_tools_for_query(query, prefetch_available)` — keyword primary + embedding fallback.
| Intent | Tool subset (approx.) |
|--------|-----------------------|
| Capabilities/help | `show_capabilities` + any matched intents |
| Dashboard query with prefetch | `show_capabilities` + matched intents (search_dashboards suppressed) |
| Dashboard query without prefetch | `show_capabilities` + `search_dashboards` + matched intents |
| Maintenance | `show_capabilities`, `list_maintenance_events`, `start_maintenance`, `end_maintenance` |
| Unknown/general | `show_capabilities`, default set (search, health, env, task) |
Embedding fallback triggered when keyword matching yields <3 tools.
## Error Taxonomy
| Code | Source | UX |
|------|--------|-----|
| `UNAUTHORIZED` | JWT | Redirect /login |
| `FORBIDDEN` | RBAC | metadata.type="error" in stream |
| `LLM_UNAVAILABLE` | LangChain | metadata.type="error" card + retry |
| `GRADIO_UNREACHABLE` | connect | Dot red, retry 5×5s |
| `TOOL_FAILURE` | FastAPI | metadata.type="tool_error" |
#endregion AgentChat.ApiUx