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

3.8 KiB
Raw Blame History

#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("/api/agent/gradio") → nginx proxy → Gradio. JWT forwarded.

Primary submit: app.submit("/chat", {message, conversation_id}) — two-arg API. conversation_id is an additional_inputs key. Examples on UI (for additional_inputs): [["Покажи дашборды", null], ["Статус системы", null]].

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})
  → handler: agent.astream_events(config={"thread_id": conversation_id})
      → HITL fires → checkpoint saved (PostgreSQL)
      → handler: yield {metadata: {type: "confirm_required", thread_id: conversation_id}}
      → Svelte: renders confirmation card (stream still open)

Confirm: submit("/chat", message, [conversation_id, "confirm"])
  → handler: detects "confirm" in additional_inputs[1]
  → loads checkpoint by conversation_id as thread_id
  → invokes graph with Command(resume={"action": "confirm"}, config={"thread_id": conversation_id})

Deny: submit("/chat", message, [conversation_id, "deny"])
  → handler: Command(resume={"action": "deny"}, config=...)

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, ...}

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