0xEtern4lW0lf
Agente de RAG Corporativo

Konhecia

3 min de leitura
PythonFastAPILangChainQdrantPostgreSQLOllamaDocker

Visão geral

O Konhecia é um agente de suporte que responde perguntas sobre documentação interna usando RAG (Retrieval-Augmented Generation), rodando inteiramente na própria infraestrutura: nenhuma chamada a API externa de LLM, nenhuma chave paga, nenhum trecho de documento corporativo saindo do perímetro.

Documentos em PDF são ingeridos numa base vetorial, e cada pergunta recupera os trechos mais relevantes antes da geração. A resposta é estritamente fundamentada no material recuperado — quando a informação não está lá, o agente recusa explicitamente em vez de inventar.

Arquitetura

pergunta → embedding → busca vetorial (Qdrant) → limiar de relevância
        → prompt (persona + contexto + histórico) → LLM local → streaming SSE

Cinco serviços orquestrados por Docker Compose:

ServiçoPapel
FastAPIAPI assíncrona, autenticação por API Key, streaming via SSE
QdrantBase vetorial — chunks dos documentos e seus embeddings
PostgreSQLHistórico de conversas e mensagens (SQLAlchemy async + Alembic)
Ollamaqwen2.5:7b (geração) + nomic-embed-text (embeddings), local
ollama-initContainer one-shot que baixa os modelos no primeiro boot

Anti-alucinação em duas camadas

O problema central de um agente corporativo não é responder — é não inventar. A defesa tem duas camadas independentes que convergem para a mesma frase de recusa:

  1. Recusa no código — se nenhum chunk recuperado atinge o limiar mínimo de similaridade, a recusa é devolvida sem sequer chamar o LLM.
  2. Recusa instruída — mesmo com chunks acima do limiar, o prompt instrui o modelo a recusar quando o contexto não responde de fato, e proíbe explicitamente linguagem especulativa (“pode-se inferir”, “provavelmente”).

Em nenhuma das duas a resposta expõe fontes — devolver citação numa resposta recusada seria dar aval de procedência a uma não-resposta.

API

Três endpoints, todos protegidos por X-API-Key (com comparação em tempo constante):

  • POST /api/v1/documents/upload — ingestão idempotente de PDF
  • POST /api/v1/chat — pergunta com resposta em streaming (SSE), com sessão
  • GET /api/v1/chat/history/{id} — histórico completo da conversa
  • GET /api/v1/health — status individual de Postgres, Qdrant e Ollama
event: token
data: {"text": " resetar"}

event: done
data: {"sources": [{"source": "manual.pdf", "page": 1, "score": 0.7161}], "grounded": true}

Testes

19 testes automatizados contra infraestrutura real — Postgres, Qdrant e Ollama do próprio Compose, sem mocks —, incluindo dois roundtrips completos de ponta a ponta contra o LLM. Isolamento por collection Qdrant efêmera por teste e limpeza em cascata no relacional.

Configuração

Todo o comportamento é parametrizável por .env: tamanho e sobreposição de chunk, top_k, limiar de relevância, janela de histórico, temperatura, além do nome, tom de voz e frase de recusa do agente. O domínio de suporte não é hardcoded — o mesmo código serve TI ou RH trocando variáveis.

Escrevi sobre cada peça

Uma série de posts cobre os fundamentos de cada tecnologia da stack, e uma retrospectiva conta o que deu certo e o que deu errado:

Limitações conhecidas

Projeto local/demonstrativo: uma única API Key compartilhada, sem usuários nem multi-tenant. Apenas PDF é suportado na ingestão. Sem GPU exposta ao Docker, a geração roda em CPU — mais lenta, porém correta.