█████╗ ███████╗███████╗███████╗███████╗███████╗ ██████╗ ██████╗ █████╗ ██╗
██╔══██╗██╔════╝██╔════╝██╔════╝██╔════╝██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗██║
███████║███████╗███████╗█████╗ ███████╗███████╗██║ ██║██████╔╝ ███████║██║
██╔══██║╚════██║╚════██║██╔══╝ ╚════██║╚════██║██║ ██║██╔══██╗ ██╔══██║██║
██║ ██║███████║███████║███████╗███████║███████║╚██████╔╝██║ ██║ ██║ ██║██║
╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝
Assistente pessoal de finanças e agenda construído com LangChain + LangGraph.
O sistema usa uma arquitetura multi-agente onde cada agente tem uma responsabilidade bem definida:
classificar a intenção, processar o domínio correto e formatar a resposta final para o usuário.
O Assessor.AI atua como um parceiro pessoal que responde perguntas e executa ações em dois domínios:
Finanças pessoais
- Registra, consulta e atualiza transações (gastos, receitas, transferências)
- Calcula saldo total e saldo por dia
- Classifica transações por categoria (comida, transporte, lazer, saúde, etc.)
- Gera diagnósticos e recomendações financeiras com base nos dados reais do banco
Agenda e compromissos
- Cria, consulta e atualiza eventos
- Consulta eventos do dia
- Gerencia localização, horários e observações de cada evento
Para tudo fora desses dois escopos (small talk, saudações, perguntas fora de área), o próprio roteador responde diretamente ao usuário.
flowchart LR
U(["Usuário"])
GE["Guardrail Entrada"]
R["Router"]
F["Financeiro"]
A["Agenda"]
FAQ["FAQ"]
O["Orquestrador"]
GS["Guardrail Saída"]
E(["Fim"])
U --> GE
GE -->|"bloqueado"| E
GE -->|"aprovado"| R
R -->|"ROUTE=financeiro"| F
R -->|"ROUTE=agenda"| A
R -->|"ROUTE=faq"| FAQ
R -->|"fora de escopo"| E
F --> O
A --> O
O --> GS
FAQ --> GS
GS --> E
assessor-ai/
├── main.py # Dispatcher — `python main.py tui|api`
├── pyproject.toml # Dependências do projeto
│
├── src/assessor_ai/
│ ├── api/ # Camada HTTP (FastAPI)
│ │ ├── app.py # App FastAPI — routers, handlers de erro, middleware, rotas A2A
│ │ ├── lifespan.py # Compila grafo + checkpointer no startup; dispose no shutdown
│ │ ├── exception_handlers.py # Erros de domínio -> status HTTP + ErrorResponse{detail, code}
│ │ ├── auth.py # get_current_user via X-API-Key; verify_signup_secret
│ │ ├── gen_key.py # generate_api_key
│ │ └── routes/ # chats.py, health.py, keys.py, users.py
│ │
│ ├── services/ # Casos de uso — não conhecem HTTP
│ │ ├── chat_service.py # create_chat, send_message, get_history, validar_ownership, encerrar_sessao
│ │ ├── runner.py # Invoca fluxo_agentes (graph/builder.py) via ainvoke, propaga tags/metadata pro LangSmith
│ │ └── exceptions.py # ChatNaoEncontrado, ChatDeOutroUsuario, LimiteDeMensagensExcedido, FalhaNoAgente
│ │
│ ├── repositories/
│ │ └── chat_repository.py # Fachada sobre graph/tools/chats, graph/tools/usuarios e infra/cache
│ │
│ ├── schemas/ # Contratos de dados
│ │ ├── models.py # ChatMessage, Role — contrato interno, independente de Mongo/tool
│ │ ├── chat.py # MessageCreate, ChatSummary, ChatMessageResponse (HTTP)
│ │ ├── errors.py # ErrorCode e ErrorResponse
│ │ ├── health.py # HealthStatus e HealthCheckResponse
│ │ ├── key.py # APIKey e contrato de criação de chave
│ │ └── user.py # UserID e contratos de usuário
│ │
│ ├── tui/ # Interface Textual
│ │ ├── app.py # AssessorTUI — tela de chat
│ │ ├── display.py # Bubble (Rich Panel), MessageRow, Pensando (LoadingIndicator)
│ │ └── app.tcss # Stylesheet do Textual
│ │
│ ├── a2a/ # Protocolo A2A (JSON-RPC), montado no mesmo app FastAPI
│ │ ├── main.py # montar_rotas(app) — agent card + endpoint JSON-RPC em /a2a
│ │ └── agents/
│ │ ├── card.py # AgentCard (nome, versão, interface, skills)
│ │ ├── capabilites.py # AgentSkill(s) expostas no card
│ │ └── interface.py # AssessorAgentExecutor — ponte pro services/chat_service.py
│ │
│ ├── config.py # Env vars via pydantic-settings; credenciais em SecretStr
│ ├── models.py # PROVIDER_MAP, BUILDERS, Model Enum
│ ├── logging.py # ColorFormatter, get_logger e o decorator log_tool
│ ├── privacy.py # Regex de PII + anonimizar_entrada
│ ├── identifiers.py # UserID, ChatID, APIKey, APIKeyHash e geradores de IDs
│ ├── graph/
│ │ ├── agents/ # Agentes compilados, nodes e prompts do LangGraph
│ │ │ ├── __init__.py # router_app, financeiro_app, agenda_app, faq_app, orquestrador_app
│ │ │ ├── nodes/
│ │ │ │ ├── names.py # NodeName e constantes dos nodes
│ │ │ │ ├── router.py # no_roteador
│ │ │ │ ├── financeiro.py # no_financeiro
│ │ │ │ ├── agenda.py # no_agenda
│ │ │ │ ├── faq.py # no_faq
│ │ │ │ ├── orquestrador.py # no_orquestrador
│ │ │ │ └── guardrail/ # Nodes e schemas dos guardrails
│ │ │ └── prompts/ # Prompts .md, loader e contratos de seções
│ │ │ ├── loader.py # load_prompt/load_sections e contexto do turno
│ │ │ └── *.md # Prompts dos agentes e templates dos guardrails
│ │ ├── state.py # Estado, EstadoUpdate e Route
│ │ ├── llm.py # build_llm e instâncias de LLM
│ │ └── builder.py # Construção e compilação do grafo LangGraph
│ │
│ ├── infra/ # Conexões compartilhadas entre camadas e features
│ │ ├── postgres.py # PostgresConn, Base, @transacional e current_user_id
│ │ ├── mongo.py # MongoConn + MongoRepo
│ │ ├── redis.py # RedisConn
│ │ ├── qdrant.py # QdrantConn + modelo de embedding
│ │ └── cache.py # Cache do perfil no Redis
│ │
│ └── graph/tools/ # Features usadas pelos agentes e repositórios internos
│ ├── financeiro/
│ │ ├── models.py # Transaction, Category, TransactionType, PaymentType
│ │ ├── schemas.py # Schemas Pydantic dos argumentos das tools
│ │ └── repo.py # FinanceiroRepo — 5 tools: add/query/update_transaction, total/daily_balance
│ ├── agenda/
│ │ ├── models.py # Event
│ │ ├── schemas.py # Schemas Pydantic dos argumentos das tools
│ │ └── repo.py # AgendaRepo — 4 tools: add_event, query_events, query_daily_events, update_event
│ ├── faq/
│ │ ├── schemas.py # FaqRetrieverArgs, SearchResponse
│ │ ├── repo.py # FaqRepo — tool faq_retriever (busca semântica no Qdrant)
│ │ └── ingest.py # Script (`python -m ...faq.ingest`) que indexa o PDF de FAQ
│ ├── chats/ # Interno (não é tool do LLM)
│ │ ├── schemas.py # ChatDocument, Role, Mensagem
│ │ ├── helpers.py # gerar_resumo, gerar_perfil
│ │ └── repo.py # ChatsRepo — criar, buscar, atualizar_mensagens, encerrar_sessao
│ ├── usuarios/ # Interno — única feature que cruza os três bancos
│ │ ├── models.py # User (linha de FK no Postgres)
│ │ ├── schemas.py # UserDocument + chaves/TTL da API key
│ │ └── repo.py # UsuariosRepo — cadastro/perfil (Mongo), garantir_usuario (Mongo+PG), API key (Redis)
│ └── response.py # ResponseStatus, ToolResponse e classe Response
│
├── alembic/ # Migrations versionadas do schema PostgreSQL
│
└── data/
└── documents/ # PDFs para RAG
└── FAQ_assessor_v1.1.pdf
Usuário
│
▼
[Guardrail Entrada] ──── bloqueado ───► encerra (sem persistir no histórico)
│ detecta prompt injection e acesso a dados internos (determinístico)
│ classifica a mensagem via LLM (APROVADO | OFENSIVO | PERIGOSO | ILICITO | ...)
│ anonimiza PII antes de passar adiante
│
▼
[Router] ──── small talk / fora de escopo ───► responde diretamente ao usuário
│
│ ROUTE=financeiro|agenda|faq
▼
[Especialista] (Financeiro, Agenda ou FAQ)
│ consulta/escreve no banco via tools
│ popula resposta_especialista no estado
▼
[Orquestrador] (apenas Financeiro e Agenda)
│ recebe o JSON do especialista + histórico da conversa
│ formata a resposta em linguagem natural
▼
[Guardrail Saída]
│ redige PII remanescente
│ revisa compliance (CVM/ANBIMA): remove garantias de rentabilidade e recomendações de ativos sem disclaimer
▼
Usuário
| Agente | Modelo | Responsabilidade |
|---|---|---|
| Guardrail Entrada | gemini-2.5-flash (temp 0.0) |
Bloqueia mensagens indevidas e anonimiza PII |
| Router | openai/gpt-oss-120b (temp 0.0) |
Classifica a intenção e emite ROUTE=financeiro|agenda|faq, ou responde diretamente |
| Financeiro | gemini-2.5-flash + fallback openai/gpt-oss-120b |
Interpreta a pergunta financeira e chama as tools do banco |
| Agenda | gemini-2.5-flash + fallback openai/gpt-oss-120b |
Interpreta perguntas de agenda e chama as tools de eventos |
| FAQ | openai/gpt-oss-120b (temp 0.0) |
Consulta o PDF via RAG e responde dúvidas sobre o sistema |
| Orquestrador | openai/gpt-oss-120b (temp 0.0) |
Formata a resposta do especialista em linguagem natural |
| Guardrail Saída | openai/gpt-oss-120b (temp 0.0) |
Revisa compliance e redige PII na resposta final |
O guardrail de entrada executa verificações em ordem de custo crescente:
- Detecção determinística — regex para prompt injection e keywords de acesso a dados internos
- Anonimização de PII — substitui CPF, CNPJ, número de conta, cartão, e-mail e telefone por tokens antes de passar ao LLM
- Classificação LLM — categoriza a mensagem em
APROVADO,OFENSIVO,PERIGOSO,ILICITO,POLITICOouINDICACAO_INVEST
Mensagens bloqueadas não são persistidas no histórico.
O guardrail de saída nunca bloqueia — apenas revisa:
- Redação de PII — remove dados pessoais remanescentes da resposta (CPF, CNPJ, número de conta e cartão)
- Compliance CVM/ANBIMA — corrige afirmações que garantam rentabilidade futura ou recomendem ativos sem disclaimer de risco
| Tool | Descrição |
|---|---|
add_transaction |
Insere uma transação (amount, tipo, categoria, método de pagamento) |
query_transactions |
Consulta transações com filtros por data, tipo e texto |
update_transaction |
Atualiza transação por ID ou por busca de texto + data |
total_balance |
Retorna saldo total (INCOME − EXPENSES) |
daily_balance |
Retorna saldo de um dia específico |
Tipos de transação: INCOME (1), EXPENSES (2), TRANSFER (3).
Categorias: comida, besteira, estudo, férias, transporte, moradia, saúde, lazer, contas, investimento, presente, outros.
| Tool | Descrição |
|---|---|
add_event |
Insere um evento (título, horário, local, observações) |
query_events |
Consulta eventos com filtros por período e título |
query_daily_events |
Retorna todos os eventos de um dia específico |
update_event |
Atualiza evento por ID ou por busca de texto + data |
| Tool | Descrição |
|---|---|
faq_retriever |
Busca semântica no PDF de FAQ via Qdrant + Gemini Embeddings (graph/tools/faq/) |
Indexação: python -m assessor_ai.graph.tools.faq.ingest (script separado da tool, roda sob demanda).
| Camada | Tecnologia | Responsabilidade |
|---|---|---|
| Transações e eventos | PostgreSQL | Dados financeiros e de agenda do usuário |
| Histórico de conversa | MongoDB | Mensagens por sessão (últimas 5 por consulta) |
| Checkpointing de grafo | LangGraph AsyncPostgresSaver | Estado interno do grafo entre turnos, persistido no PostgreSQL |
| Cache de perfil, rate limit, API keys | Redis | Cache do perfil_usuario (TTL 1h), limite de mensagens por user_id na janela de 60s, hash de API keys da API |
O MongoDB armazena users (cadastro e perfil comportamental) e chats (histórico de mensagens por sessão). O histórico de mensagens é limitado via projeção $slice: -5 para evitar contextos longos demais.
O campo perfil_usuario — gerado a partir do histórico acumulado e armazenado em users — é carregado no estado do grafo antes de cada invocação, servindo como contexto cross-session do usuário. É lido do Redis primeiro (infra/cache.py); só cai no Mongo em cache miss, e o cache é invalidado ao encerrar a sessão (quando o perfil pode ter sido atualizado a partir do resumo).
O RAG do FAQ roda sobre o Qdrant (graph/tools/faq/) — substituiu o índice FAISS local.
GEMINI_API_KEY=...
GROQ_API_KEY=...
POSTGRES_URL=postgresql://usuario:senha@host:5432/banco
MONGO_URL=mongodb://usuario:senha@host:27017/
MONGO_COLLECTION_NAME=assessor
REDIS_URL=redis://host:6379/0
QDRANT_URL=http://host:6333
QDRANT_COLLECTION_NAME=faq
SIGNUP_SECRET=...
LANGSMITH_TRACING=false
LANGSMITH_API_KEY=...
LANGSMITH_PROJECT=assessor-ai
API_KEY_AUTH_ENABLED=trueVer .env.example para a referência completa (inclui QDRANT_API_KEY opcional, usada só em instâncias cloud do Qdrant). SIGNUP_SECRET é obrigatório — sem ele Settings() falha ao importar; é o valor exigido no header X-Signup-Secret do POST /v1/keys. LANGSMITH_* é opcional — só ativa tracing/observabilidade do grafo se LANGSMITH_TRACING=true (ver seção Observabilidade abaixo). API_KEY_AUTH_ENABLED é opcional (default true) — com false, /v1/chats para de exigir X-API-Key e passa a reaproveitar/criar um usuário padrão a cada request (chat_service.obter_usuario_padrao, mesmo bootstrap do terminal/TUI); é um desligamento temporário pro estágio atual do projeto, não uma remoção — a chave de rota, api/auth.py:get_current_user, continua existindo e testada (ver TODO.md).
A2A_BASE_URL=http://localhost:8000
Ver [.env.example](.env.example) para a referência completa (inclui `QDRANT_API_KEY` opcional, usada só em instâncias cloud do Qdrant). `SIGNUP_SECRET` é obrigatório — sem ele `Settings()` falha ao importar; é o valor exigido no header `X-Signup-Secret` do `POST /v1/keys`. `LANGSMITH_*` é opcional — só ativa tracing/observabilidade do grafo se `LANGSMITH_TRACING=true` (ver seção [Observabilidade](#observabilidade) abaixo). `A2A_BASE_URL` é opcional (default já cobre execução local) — só muda a URL declarada no `AgentCard` do A2A (ver seção [A2A](#a2a) acima).
### Instalação
```bash
uv venv
uv sync
python main.py tui # interface Textual
python main.py api # sobe a API FastAPI via uvicorn em 0.0.0.0:8000Postgres, Mongo, Redis e Qdrant são serviços em nuvem — não há infra local pra subir. Na TUI,
digite /exit (ou Ctrl+C) pra encerrar a sessão.
Também dá pra rodar via justfile: just venv (cria .venv), just run [modo] (default tui)
e just dev [modo] (mesma coisa, injetando env vars via infisical run --).
python main.py api também expõe o protocolo A2A (agent-to-agent),
montado no mesmo app FastAPI:
-
GET /.well-known/agent-card.json—AgentCardcom nome, versão e as skills expostas (capabilites.py):moneysaving— registra e consulta transações, saldo total/diário e gastos por categoria e períodoagenda— cria, consulta e atualiza compromissos do calendáriofaq— perguntas sobre o que o assistente faz e como usá-lo
As skills são só metadado de discovery;
POST /a2aroteia toda mensagem pelo grafo completo (o router decide o domínio), independente de qual skill o cliente achou no card. -
POST /a2a— endpoint JSON-RPC (métodoSendMessage) que processa a mensagem viaAssessorAgentExecutor, a mesma camadaservices/chat_service.pyusada por terminal/TUI/API. Cadacontext_iddo protocolo vira uma sessão/usuário do Assessor — na primeira mensagem, ou semcontext_id.
Primeira versão, sem tarefas assíncronas (streaming/push_notifications desligados no
AgentCard) nem autenticação — a rota está aberta de propósito, porque a auth por API key
atrapalha o caso de uso A2A entre agentes (ver TODO.md).
Schema do PostgreSQL versionado em alembic/versions/ (POSTGRES_URL configurado):
uv run alembic upgrade headO acesso a dados usa SQLAlchemy ORM (graph/tools/{financeiro,agenda,usuarios}/models.py), então --autogenerate funciona
normalmente a partir daqui:
uv run alembic revision --autogenerate -m "..."Sempre revise o diff gerado antes de aplicar — e rode --autogenerate sem alterações pendentes de
vez em quando pra garantir que os models continuam batendo exatamente com o schema real (diff vazio).
Tracing dos agentes via LangSmith — opcional, desligado por padrão
(LANGSMITH_TRACING=false). Quando ligado, graph.ainvoke() (services/runner.py:executar) é
rastreado automaticamente pelo LangChain/LangGraph, incluindo cada nó (guardrail, router,
financeiro, agenda, faq, orquestrador) e cada chamada de LLM — sem precisar instrumentar nada à
mão. runner.py também passa tags=["chat"] e metadata={"user_id", "session_id"} no config
do invoke(), propagados automaticamente pra todo run filho, permitindo filtrar/auditar traces por
usuário ou sessão no painel do LangSmith.
Pontos de I/O que o LangChain não rastreia sozinho (repositories/chat_repository.py:buscar_perfil,
buscar_historico, salvar_mensagens) usam @traceable manual, com process_inputs/
process_outputs redigindo PII (reaproveitando anonimizar_entrada do guardrail) antes de subir
pro LangSmith Cloud.
Limitação conhecida: essa redação cobre só os pontos com @traceable manual — o run raiz do
LangGraph e o próprio nó de guardrail de entrada (auto-rastreados) ainda logam a mensagem crua do
usuário como input, já que a anonimização só acontece no output desse nó. Ver TODO.md pra mais
contexto antes de ligar tracing em produção com dado real.
- LangChain — framework de agentes e tools
- LangGraph — orquestração stateful e checkpointing
- LangSmith — tracing/observabilidade opcional do grafo (ver Observabilidade)
- FastAPI — API HTTP (
api/), com slowapi pro rate limit por IP - a2a-sdk — protocolo A2A (
a2a/), agent card + JSON-RPC montados no mesmo app FastAPI - Textual — TUI (
tui/) - SQLAlchemy — ORM sobre o PostgreSQL (
graph/tools/{financeiro,agenda,usuarios}/models.py), compsycopg2como driver - Alembic — migrations versionadas do schema PostgreSQL
- pymongo — driver MongoDB para histórico de conversa
- redis-py — cache de perfil, rate limit de mensagens e API keys (
infra/redis.py) - qdrant-client — busca vetorial para RAG do FAQ (
graph/tools/faq/) - Rich + pyfiglet — interface de terminal e arte ASCII da TUI
- Pydantic — validação de schemas das tools
langchain-anthropic,langchain-google-genai,langchain-groq— integrações com providers