4.8 KiB
#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