Pular para o conteúdo

Diagrama de contêineres C4

A visão de contêineres decompõe o sistema do agente em unidades implantáveis. Um app FastAPI fica à frente da superfície pública (/health, /chat, /chat/resume e o /graph/topology somente leitura) mais a casca de single-page app em /; não há endpoint /metrics. /chat e /chat/resume usam negociação de conteúdo: uma requisição Accept: text/event-stream recebe um fluxo de server-sent-events com eventos de execução por nó, qualquer outro Accept recebe o JSON estável ChatResponse. O runtime do agente LangGraph é dono do pipeline conversacional: o grafo de seis nós intake -> guardrail_pre -> [retrieve_context] -> generate_response -> guardrail_post -> closing, com um nó opcional review_response de human-in-the-loop (HITL) inserido entre generate_response e guardrail_post quando o HITL está habilitado. Os módulos de guardrail rodam dentro dos nós do grafo (guardrail_pre e guardrail_post), não como uma camada orquestrada separada. O armazenamento RAG é Chroma embarcado, fundamentado em uma KB sintética de 38 cartões. O arcabouço de avaliação roda fora do processo, construindo o mesmo grafo. A instrumentação OpenTelemetry conecta cada nó aos backends de observabilidade.

O grafo é compilado uma vez quando o app FastAPI inicia e é reutilizado entre requisições. A camada de persistência é injetável por checkpointer: um checkpointer em memória por padrão, ou um checkpointer durável apoiado em Postgres quando uma string de conexão de banco de dados é configurada (o caminho durável para retomadas de HITL que atravessam um reinício de processo). Consulte ADR-0001 para a justificativa.

C4Container
  title Container View - Healthcare Agent + Eval Harness

  Person(user, "User", "Synthetic patient persona")
  Person_Ext(operator, "Operator", "Channel gateway (messaging Business API / carrier VAS)")

  System_Boundary(sys, "Healthcare Agent + Eval Harness") {
    Container(api, "FastAPI App", "Python 3.12, FastAPI", "Exposes /health, /chat, /chat/resume, the read-only /graph/topology, the SPA shell at /, and the unlisted /debug/state. /chat and /chat/resume are content-negotiated: text/event-stream gets an SSE stream of per-node events, any other Accept gets the v1.0.0 ChatResponse JSON. Compiles the agent graph once in lifespan; stateless across requests apart from the checkpointer.")
    Container(agent_rt, "LangGraph Agent Runtime", "Python 3.12, LangGraph 1.x", "Six-node StateGraph: intake -> guardrail_pre -> [retrieve_context] -> generate_response -> guardrail_post -> closing. retrieve_context is the conditional RAG node. An optional review_response interrupt() HITL node is inserted when HITL_ENABLED=1.")
    Container(llm_client, "LLMClient Adapters", "Python 3.12, OpenAI-compatible SDKs", "Thin Protocol with adapters for OpenAI, Anthropic, Groq. Selected by LLM_PROVIDER; fallback-wrapped (OpenAI gpt-4o-mini primary -> Anthropic claude-haiku-4-5 fallback).")
    Container(guard, "Guardrail Modules", "Python 3.12", "First-class modules - input_validation, pii, scope, escalation, citations, persona, review - invoked from inside the guardrail_pre / guardrail_post / review_response graph nodes.")
    ContainerDb(rag, "RAG Store", "Chroma embedded", "38 synthetic KB cards with source_url, accessed_at, license. Persisted to a configurable on-disk directory (CHROMA_PERSIST_DIR).")
    Container(embed_sel, "Embedding Selector", "Python 3.12, voyageai / sentence-transformers", "voyage-3.5 when a key is present; BAAI/bge-small-en-v1.5 fallback baked into the image.")
    Container(otel, "OTel Instrumentation", "opentelemetry-python + OpenInference", "Wraps every node, every LLM call, every retrieval. OTLP exporter.")
    ContainerDb(saver, "Persistence Layer", "checkpointer-injectable", "MemorySaver by default; an AsyncPostgresSaver when POSTGRES_DSN is set, for HITL resumes that survive a process restart.")
    Container(eval_harness, "Eval Harness", "Python 3.12, pytest + DeepEval", "Loads JSONL goldens, builds the agent graph once, runs each case, scores with 5 deterministic plus 3 judge-backed scorers, emits markdown + JSON. CI gate trips on threshold breach.")
  }

  System_Ext(llm, "LLM Provider(s)", "OpenAI / Anthropic (Groq judge-fallback)")
  System_Ext(embed_ext, "Embedding Provider", "Voyage AI voyage-3.5")
  System_Ext(langfuse, "Langfuse Cloud Hobby", "Live-demo trace backend")
  System_Ext(phoenix, "Phoenix (self-hosted)", "Eval-time trace backend")

  Rel(user, operator, "Sends turns over messaging channel")
  Rel(operator, api, "Forwards POST /chat over HTTPS")
  Rel(api, agent_rt, "Runs the compiled graph via ainvoke (JSON) or astream (SSE)")
  Rel(agent_rt, guard, "Runs guardrail checks inside graph nodes")
  Rel(agent_rt, llm_client, "Generates with")
  Rel(llm_client, llm, "HTTPS / OpenAI-compatible REST")
  Rel(agent_rt, rag, "Retrieves grounded citations from")
  Rel(rag, embed_sel, "Embeds queries with")
  Rel(embed_sel, embed_ext, "HTTPS when voyage selected")
  Rel(agent_rt, saver, "Checkpoints state to")
  Rel(otel, langfuse, "Exports spans (live demo, when configured) via OTLP")
  Rel(otel, phoenix, "Exports spans (eval-time, when configured) via OTLP")
  Rel(eval_harness, agent_rt, "Builds and drives the graph in-process")
  Rel(eval_harness, otel, "Emits spans during eval runs")

  UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")

O armazenamento RAG usa recuperação híbrida: correspondência léxica BM25 mais vetores densos (BAAI BGE) mais um reordenamento por cross-encoder, fundidos via fusão recíproca de ranques (RRF), sobre cartões da KB fragmentados semanticamente com recuperação de documento-pai. O arcabouço de avaliação pontua cada caso com cinco pontuadores deterministas sempre ativos mais três pontuadores apoiados por juiz; o modelo juiz é o Anthropic claude-haiku-4-5. Uma violação de limiar aciona a barreira de CI.