5. Sessão, Memória e Autenticação¶
Visão geral dos backends¶
O projeto usa um Factory Pattern para criar session e memory services a partir de env vars:
flowchart TD
ENV["SESSION_BACKEND=redis<br/>MEMORY_BACKEND=cloudsql"]
ENV --> SF["get_session_service(backend)"]
ENV --> MF["get_memory_service(backend)"]
SF --> |"redis"| REDIS_S["RedisSessionService<br/><i>~1-5ms latência</i>"]
SF --> |"cloudsql"| CSQL_S["DatabaseSessionService<br/><i>~100-800ms latência</i>"]
SF --> |"inmemory"| MEM_S["InMemorySessionService<br/><i>~0ms (dev only)</i>"]
MF --> |"cloudsql"| CSQL_M["CloudSQLMemoryService<br/><i>MySQL persistente</i>"]
MF --> |"redis"| REDIS_M["RedisMemoryService<br/><i>Cache rápido</i>"]
MF --> |"openmemory"| OM["OpenMemoryService<br/><i>Vetorial (pgvector)</i>"]
Factory definida em ifriend_agent/config/backends.py.
Sessions (estado da conversa)¶
A session mantém o estado atual da conversa de um usuário: histórico de mensagens, contexto JWT, metadata.
Backends disponíveis¶
| Backend | Classe | Latência | Quando usar |
|---|---|---|---|
| Redis | RedisSessionService |
~1-5ms | Produção (recomendado) |
| CloudSQL | ADK DatabaseSessionService |
~100-800ms | Produção (persistência SQL) |
| InMemory | ADK InMemorySessionService |
~0ms | Dev local / testes |
O que é salvo na session¶
flowchart TD
SESSION["Session"]
SESSION --> STATE["state (dict)"]
SESSION --> EVENTS["events (lista)"]
STATE --> JWT["jwt_context<br/>{user_id, email, roles,<br/>partner_code, token, token_exp}"]
STATE --> DATE["current_date<br/>'2026-03-11 10:30:00'"]
STATE --> META["message_metadata<br/>{platform, channel_id, thread_id}"]
STATE --> MEMS["memories<br/>[memórias carregadas]"]
EVENTS --> E1["UserContent: 'Quero um tour em Roma'"]
EVENTS --> E2["FunctionCall: busca_produtos(...)"]
EVENTS --> E3["FunctionResponse: {resultados...}"]
EVENTS --> E4["AgentContent: 'Encontrei 3 opções...'"]
Ciclo de vida da session¶
sequenceDiagram
participant CP as ConversationProcessor
participant SS as SessionService
CP->>SS: get_session(app_name, user_id)
alt Session existe
SS-->>CP: Session existente (com state e eventos)
else Session não existe
CP->>SS: create_session(app_name, user_id, state={})
SS-->>CP: Nova Session
end
Note over CP: ADK Runner processa mensagem
Note over CP: State é atualizado (jwt, date, etc.)
CP->>SS: Session é salva automaticamente pelo Runner
Memory (memória de longo prazo)¶
A memória persiste entre sessões — o agente "lembra" de conversas anteriores do usuário.
Backends disponíveis¶
| Backend | Classe | Uso |
|---|---|---|
| CloudSQL | CloudSQLMemoryService |
Persistência MySQL (produção) |
| Redis | RedisMemoryService |
Cache rápido com TTL |
| OpenMemory | OpenMemoryService |
Memória vetorial com pgvector |
Fluxo da memória¶
flowchart LR
subgraph "Automático — PreloadMemoryTool"
LOAD["PreloadMemoryTool<br/><i>FULLTEXT search com query do usuário<br/>Executa antes de cada chamada LLM</i>"]
end
subgraph "Processamento"
AGENT["Root Agent + Sub-Agentes<br/><i>Usa memórias como contexto<br/>via PAST_CONVERSATIONS</i>"]
end
subgraph "After Agent (callback)"
SAVE["save_session_to_memory_callback<br/><i>Sumariza conversa via LLM<br/>UPSERT no CloudSQL</i>"]
end
LOAD --> AGENT --> SAVE
DB[(CloudSQL / MySQL<br/>FULLTEXT index)] --> LOAD
SAVE --> DB
Autenticação JWT¶
O projeto trabalha com dois tipos de JWT:
flowchart TD
subgraph "JWT Sistêmico 🔑"
SYS_DESC["Credenciais da API iFriend<br/>(API_EMAIL + API_PASSWORD)"]
SYS_USE["Usado para: operações admin,<br/>emitir reservas no sistema"]
SYS_MGR["Gerenciado por: AuthTokenManager<br/>(ifriend_agent/tools/booking/auth.py)"]
end
subgraph "JWT de Usuário 👤"
USR_DESC["Token do frontend/plataforma<br/>(passado pelo usuário logado)"]
USR_USE["Usado para: personalizar preços,<br/>identificar agência/operadora"]
USR_MGR["Gerenciado por: JWTContextManager<br/>(ifriend_agent/session/jwt_context.py)"]
end
Como o JWT chega ao agente¶
flowchart TD
subgraph "WebChat / SSE"
WC["Header: Authorization: Bearer {token}<br/>ou body.jwt_token"]
end
subgraph "WhatsApp / Slack"
WA["JWT Lookup via Redis"]
WA_LINK["Passo 1: POST /whatsapp/link {phone, jwt}<br/>ou /ifriend-auth no Slack"]
WA_GET["Passo 2: Mensagem chega → busca JWT<br/>no Redis pelo user_id"]
WA_LINK --> WA_GET
end
WC --> PARSE["Adapter.parse_webhook_request()<br/>extrai jwt_token"]
WA_GET --> PARSE
PARSE --> PROC["ConversationProcessor<br/>decodifica JWT"]
PROC --> STATE["session.state['jwt_context']"]
STATE --> TOOLS["Tools acessam via<br/>tool_context.state.get('jwt_context')"]
JWT Lookup (WhatsApp/Slack)¶
Como WhatsApp e Slack não suportam headers personalizados, usamos um serviço de lookup no Redis:
sequenceDiagram
actor Frontend as Frontend/App
participant API as FastAPI
participant Redis as Redis
Note over Frontend,Redis: Passo 1: Vincular JWT ao user_id
Frontend->>API: POST /whatsapp/link {phone: "+5511...", jwt: "eyJ..."}
API->>Redis: SET jwt_lookup:+5511... = "eyJ..." (TTL: 30 dias)
API-->>Frontend: OK
Note over Frontend,Redis: Passo 2: Mensagem chega
actor User as Usuário WhatsApp
User->>API: Mensagem via webhook
API->>Redis: GET jwt_lookup:+5511...
Redis-->>API: "eyJ..." (token)
API->>API: Decodifica JWT → jwt_context
Note over API: Segue fluxo normal com jwt_context no state
Componentes de autenticação¶
| Componente | Arquivo | Responsabilidade |
|---|---|---|
JWTContextManager |
ifriend_agent/session/jwt_context.py |
Decodifica/valida tokens (HS256 e RS256) |
JWTLookupService |
ifriend_agent/session/jwt_lookup_service.py |
Store/get JWT no Redis (WhatsApp/Slack) |
SessionContextHelper |
ifriend_agent/session/session_context.py |
Helper para gerenciar JWT em sessões ADK |
AuthTokenManager |
ifriend_agent/tools/booking/auth.py |
JWT sistêmico (login API_EMAIL/API_PASSWORD) |
UserContext |
Dataclass | Struct: user_id, email, name, roles, token |
inject_jwt_before_agent |
ifriend_agent/callbacks/jwt_context_callback.py |
Callback que garante JWT no state |
Enriquecimento de contexto¶
O JWT do usuário permite personalizar a experiência:
flowchart LR
JWT["jwt_context<br/>{partner_code: 'AGENCIA_XYZ'}"]
JWT --> HEADERS["enrich_headers.py<br/>Adiciona X-Context-Partner<br/>nos requests à API"]
JWT --> PRICES["enrich_products.py<br/>Calcula preços com<br/>markup da agência"]
HEADERS --> API["API iFriend<br/>(responde com dados personalizados)"]
PRICES --> USER["Usuário vê preço<br/>com markup aplicado"]
Variáveis de ambiente JWT¶
| Env Var | Descrição |
|---|---|
JWT_SECRET_KEY |
Chave para HS256 |
JWT_ALGORITHM |
HS256 ou RS256 |
JWT_PUBLIC_KEY |
Chave pública para RS256 |
JWT_PASSPHRASE |
Passphrase da chave (se aplicável) |
JWT_ISSUER |
Issuer esperado no token |
JWT_AUDIENCE |
Audience esperada |
JWT_VERIFY_EXPIRATION |
true/false — verificar expiração |
JWT_LOOKUP_TTL_DAYS |
TTL do lookup no Redis (padrão: 30) |
Resumo visual: session + memory + JWT¶
flowchart TD
MSG["Mensagem do usuário"] --> ADAPTER["Adapter<br/>extrai jwt_token"]
ADAPTER --> PROC["ConversationProcessor"]
PROC --> |"decodifica"| JWT["JWTContextManager"]
JWT --> |"jwt_context"| STATE["session.state"]
PROC --> |"get/create"| SS["SessionService<br/>(Redis)"]
SS --> STATE
STATE --> AGENT["Root Agent<br/>(Orquestrador)"]
AGENT --> |"PreloadMemoryTool<br/>FULLTEXT search"| MM["MemoryService<br/>(CloudSQL)"]
MM --> |"memórias injetadas<br/>como PAST_CONVERSATIONS"| AGENT
AGENT --> |"transfer_to_agent"| SUB["Sub-Agentes<br/>Especializados"]
SUB --> AGENT
AGENT --> AFTER["After Callback<br/>save_session_to_memory"]
AFTER --> |"resumo LLM + UPSERT"| MM
Anterior: ← Adaptadores de Mensageria · Próximo: Setup Local e Desenvolvimento →