Skip to content

Agentic AI Architecture

Conversational workflow

handle_chat_turn coordinates a bounded form-filling conversation. Pass one classifies category/subcategory; pass two extracts fields; pass three either asks for missing fields or finalizes a summary and grievance. Session state is stored through the Redis-backed chat session layer.

flowchart TD
  classDef default fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
  classDef highlight fill:#065f46,stroke:#34d399,stroke-width:2px,color:#f0fdf4
  Message["Citizen message"]:::highlight --> Session["Load Redis chat session"]
  Session --> Classify["Classify category"]
  Classify --> Extract["Extract mandatory fields"]
  Extract --> Missing{"Fields missing?"}
  Missing -->|Yes| Followup["Stream follow-up over SSE"]
  Followup --> Message
  Missing -->|No| Finalize["Finalize and create grievance"]
  Finalize --> Assign["Assign officer and SLA"]
  RAG["RAG query"] --> Filter["Role allowlist"]
  Filter --> Vector["pgvector similarity search"]
  Vector --> Ground["LLM answer with sources"]
  linkStyle default stroke:#64748b,stroke-width:2px

Model boundaries

LiteLLM supplies an OpenAI-compatible chat model for classification and generation. Gemini provides embeddings and may support GCP-native model paths. The backend keeps prompts and response parsing in the chat service rather than exposing model calls to the browser.

Guardrails

Structured schemas, category validation, session ownership checks, mandatory-field state, RAG context-only instructions, source extraction, and bounded role filters reduce accidental leakage and malformed case creation. Unknown: exact model response validation coverage across all provider configurations.

Chat session state

The ChatSession object contains a session ID, user ID, message history, attachments, detected category/subcategory, confirmation state, collected fields, missing fields, and a lifecycle status. The router always resolves it with the authenticated user, which prevents a user from reading another user’s session by guessing an ID.

The state machine is deliberately form-oriented:

stateDiagram-v2
  [*] --> collecting: /chat/start
  collecting --> collecting: category, extraction, follow-up
  collecting --> complete: all mandatory fields
  collecting --> abandoned: DELETE /chat/{session_id}
  complete --> [*]: grievance persisted
  abandoned --> [*]

RAG generation contract

RagService.query_rag first maps roles to allowed document roles:

  • user → user, both
  • officer → user, officer, both
  • admin/super_admin → user, officer, admin, both

Only retrieved chunks are placed in the context prompt. The prompt instructs the model to answer from context, say when context is insufficient, and end with a source list. The service parses that source line and intersects it with actual retrieved source names before returning sources to the client.

Failure and retry behavior

The chat router converts stream exceptions into a generic SSE error event rather than exposing the raw exception. Upload parsing distinguishes bad input from server failure. RAG failures become HTTP 500 responses. A production hardening path should add provider timeouts, bounded retries, idempotency for finalization, and explicit cancellation handling when the browser disconnects from SSE.