Konhecia
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ço | Papel |
|---|---|
| FastAPI | API assíncrona, autenticação por API Key, streaming via SSE |
| Qdrant | Base vetorial — chunks dos documentos e seus embeddings |
| PostgreSQL | Histórico de conversas e mensagens (SQLAlchemy async + Alembic) |
| Ollama | qwen2.5:7b (geração) + nomic-embed-text (embeddings), local |
| ollama-init | Container 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:
- Recusa no código — se nenhum chunk recuperado atinge o limiar mínimo de similaridade, a recusa é devolvida sem sequer chamar o LLM.
- 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 PDFPOST /api/v1/chat— pergunta com resposta em streaming (SSE), com sessãoGET /api/v1/chat/history/{id}— histórico completo da conversaGET /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:
- Docker Compose: subindo a stack
- FastAPI: DI, Pydantic e SSE
- SQLAlchemy assíncrono e Alembic
- Ollama: LLM e embeddings locais
- Qdrant: embeddings e busca vetorial
- LangChain sem mágica: chain RAG com LCEL
- Retrospectiva: 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.