🔍 Problema: Perda de Contexto - Soluções Oficiais do ADK¶
📋 Problema Identificado¶
Firebase está salvando as mensagens ✅
MAS o agente não recupera o histórico da sessão
RESULTADO: Agente perde contexto a cada nova mensagem
🎯 Por Que Acontece?¶
O ADK tem 2 componentes separados:
- SessionService (seu
FirestoreSessionService) - Responsável por: Persistir dados da sessão
- O que faz: Salva mensagens no Firestore
-
O que NÃO faz: Automaticamente carregar histórico
-
Runner + Agent
- Responsável por: Executar o agente
- O que faz: Processar a mensagem atual
- O que NÃO faz: Buscar histórico do SessionService automaticamente
O gap: Eles não se comunicam automaticamente!
✅ Soluções Oficiais (3 Abordagens)¶
SOLUÇÃO 1: Usar Message History em Session (RECOMENDADO)¶
O ADK espera que o Session tenha um atributo messages com o histórico. O Runner passa isso ao agente automaticamente.
Implementação:
class FirestoreSessionService(BaseSessionService):
async def get_session(
self,
*,
app_name: str,
user_id: str,
session_id: str,
config: Optional[any] = None
) -> Optional[Session]:
"""Recupera session COM histórico de mensagens"""
try:
doc_ref = self.collection.document(session_id)
doc = doc_ref.get()
if not doc.exists:
# Cria nova sessão
return await self.create_session(
app_name=app_name,
user_id=user_id,
session_id=session_id
)
session_data = doc.to_dict()
# ⭐ CRUCIAL: Carregar mensagens do Firestore
messages = session_data.get("messages", [])
# ⭐ Converter para Content objects (ADK espera isso)
content_messages = []
for msg in messages:
content = types.Content(
role=msg.get("role", "user"),
parts=[types.Part(text=msg["parts"][0]["text"])]
)
content_messages.append(content)
# ⭐ Criar Session COM messages
session = Session(
id=session_data.get("id", session_id),
app_name=session_data.get("app_name", app_name),
user_id=session_data.get("user_id", user_id),
messages=content_messages # ← AQUI! Passa histórico
)
logger.info(f"✅ Sessão recuperada com {len(content_messages)} mensagens")
return session
except Exception as e:
logger.error(f"❌ Erro ao recuperar sessão: {e}")
return await self.create_session(...)
Como funciona:
Request 1:
User: "Olá"
→ Session criada com 0 mensagens
→ Agent responde
→ Mensagem salva em Firestore
Request 2:
→ Session recuperada DO FIRESTORE
→ Mensagens carregadas: ["User: Olá", "Agent: Oi!"]
→ Agent vê histórico completo! ✅
→ Agent pode fazer follow-up
→ Contexto preservado! ✅
SOLUÇÃO 2: Usar StateManager do ADK (AVANÇADO)¶
O ADK oferece um padrão de state que gerencia contexto automáticamente.
from google.adk.sessions import StateManager
class FirestoreSessionService(BaseSessionService):
async def get_session(self, ...) -> Optional[Session]:
session_data = doc.to_dict()
# ⭐ Recuperar state anterior
state = session_data.get("state", {})
conversation_history = state.get("conversation_history", [])
# ⭐ Criar Session com state
session = Session(
id=session_id,
app_name=app_name,
user_id=user_id,
messages=content_messages,
state={
"conversation_history": conversation_history,
"context": state.get("context", {}),
"user_preferences": state.get("user_preferences", {})
}
)
return session
async def update_session(self, app_name, user_id, session_id, new_message):
# ... atualizar também o state
update_data = {
"messages": messages,
"state": {
"conversation_history": [msg for msg in messages],
"context": extract_context(messages),
"user_preferences": {}
}
}
doc_ref.update(update_data)
Benefício: O state persiste entre sessões e é passado automaticamente ao agente.
SOLUÇÃO 3: Implementar History Handler Customizado (MAIS CONTROLE)¶
Se o agente não está lendo o histórico sozinho, forçar ele a fazer isso:
# No agent.py
from google.genai import types
async def include_conversation_history(session: Session) -> str:
"""
Formata histórico da sessão para incluir no contexto do agente
"""
history = ""
if hasattr(session, 'messages') and session.messages:
history = "\n## Histórico da Conversa\n"
for msg in session.messages:
role = "Usuário" if msg.role == "user" else "Assistente"
text = msg.parts[0].text if msg.parts else "[sem conteúdo]"
history += f"- {role}: {text}\n"
return history
# No instructions do agent
INSTRUCTIONS = """
Você é um assistente de busca de produtos.
{CONVERSATION_HISTORY}
Análise o histórico acima para entender o contexto da conversa.
Se o usuário referenciar algo dito anteriormente, use esse contexto.
"""
# Ao chamar runner.run_async, passar histórico formatado
history_text = await include_conversation_history(session)
instructions_with_history = INSTRUCTIONS.format(
CONVERSATION_HISTORY=history_text
)
🏆 RECOMENDAÇÃO: Qual Usar?¶
Para seu caso (Slack Bot):¶
Use SOLUÇÃO 1 + SOLUÇÃO 3 combinadas:
# 1. Carregar mensagens no get_session (SOLUÇÃO 1)
# → ADK passa histórico ao agent
# 2. Incluir histórico formatado nos instructions (SOLUÇÃO 3)
# → Agent tem contexto explícito mesmo que ADK não passe
Motivo: - ✅ Garante que agente vê histórico sempre - ✅ Sem dependência de implementação interna do ADK - ✅ Explícito e fácil de debugar - ✅ Funciona com qualquer SessionService
🔧 Implementação Completa (Solução 1 + 3)¶
Passo 1: Atualizar FirestoreSessionService¶
# session/session_service.py
async def get_session(self, *, app_name: str, user_id: str, session_id: str, config=None):
"""Recupera session COM histórico de mensagens"""
doc = self.collection.document(session_id).get()
if not doc.exists:
return await self.create_session(app_name=app_name, user_id=user_id, session_id=session_id)
session_data = doc.to_dict()
# ⭐ Carregar mensagens
messages = session_data.get("messages", [])
content_messages = []
for msg in messages:
content = types.Content(
role=msg.get("role", "user"),
parts=[types.Part(text=msg.get("parts", [{}])[0].get("text", ""))]
)
content_messages.append(content)
# ⭐ Session COM histórico
session = Session(
id=session_data.get("id", session_id),
app_name=session_data.get("app_name", app_name),
user_id=session_data.get("user_id", user_id),
messages=content_messages
)
logger.info(f"✅ Sessão {session_id}: {len(content_messages)} mensagens carregadas")
return session
Passo 2: Atualizar agent.py Instructions¶
# agent.py
def get_system_instructions(session=None) -> str:
"""Gera instructions com histórico da sessão"""
history = ""
if session and hasattr(session, 'messages') and session.messages:
history = "\n## Contexto da Conversa Anterior:\n"
for msg in session.messages[-5:]: # Últimas 5 mensagens
role = "Usuário" if msg.role == "user" else "Assistente"
text = msg.parts[0].text if msg.parts else ""
history += f"\n{role}: {text}"
return f"""
Você é um assistente de busca de produtos na plataforma ifriend.
{history}
Use o contexto acima para entender conversas anteriores e fazer follow-ups relevantes.
Se o usuário referenciar algo mencionado antes, use essa informação.
[... resto do instructions ...]
"""
Passo 3: Usar no slack_bot.py¶
# slack_bot.py - em handle_message()
# Recuperar session COM histórico
session = await runner.session_service.get_session(
app_name=runner.app_name,
user_id=slack_user_id,
session_id=session_id
)
# Log do histórico
if session and hasattr(session, 'messages'):
logger.info(f"📜 Histórico carregado: {len(session.messages)} mensagens")
for i, msg in enumerate(session.messages[-3:]): # Últimas 3
logger.info(f" {i+1}. {msg.role}: {msg.parts[0].text[:50]}...")
# Executar agent (ADK passa session automaticamente)
async for event in runner.run_async(
user_id=slack_user_id,
session_id=session_id,
new_message=content
):
# ... processar evento
✅ Validação¶
Testar se histórico está sendo carregado:
# 1. Enviar primeira mensagem
User: "Qual é o preço do produto X?"
Bot: "O preço é R$ 100"
# 2. Checar logs - deve conter
"📜 Histórico carregado: 2 mensagens"
" 1. user: Qual é o preço do produto X?"
" 2. assistant: O preço é R$ 100"
# 3. Enviar segunda mensagem
User: "E se eu quiser 5 unidades?"
Bot: "Você quer 5 unidades do produto X? O preço total seria R$ 500"
# ← Contexto mantido! ✅
📊 Comparação de Soluções¶
| Solução | Automático | Confiável | Explícito | Custo |
|---|---|---|---|---|
| 1: Message History | ✅ (se ADK suporta) | ⚠️ (depende ADK) | ❌ | Baixo |
| 2: State Manager | ✅ | ✅ | ⚠️ | Médio |
| 3: History Handler | ❌ (manual) | ✅ | ✅ | Alto |
| 1 + 3 (Recomendado) | ✅ | ✅ | ✅ | Médio |
🚀 Implementação Rápida (5 min)¶
Se quer resolver AGORA:
# Em slack_bot.py, antes de runner.run_async()
# Recuperar histórico
doc = firestore.Client().collection("agente_busca_produtos_sessions").document(session_id).get()
history_text = ""
if doc.exists:
messages = doc.to_dict().get("messages", [])
for msg in messages[-5:]: # Últimas 5
role = "Usuário" if msg.get("role") == "user" else "Bot"
text = msg.get("parts", [{}])[0].get("text", "")
history_text += f"\n{role}: {text}"
# Preparar mensagem COM contexto
full_context = f"""
Histórico anterior:
{history_text}
Mensagem atual: {user_query}
"""
content = types.Content(role='user', parts=[types.Part(text=full_context)])
# Agora rodar
async for event in runner.run_async(...):
# ... resto do código
Isso força o agent a ver o histórico mesmo que ADK não passe automaticamente.
📞 Suporte¶
Se ainda não funcionar:
-
Verificar se mensagens estão sendo salvas no Firestore
gcloud firestore-databases list gcloud firestore document get agente_busca_produtos_sessions/{session_id} -
Verificar logs do Agent
gcloud run logs read agente-busca-produtos-slack --follow | grep -i "session\|history\|message" -
Debug manual
# Adicionar ao agent print(f"Session messages: {[m.parts[0].text for m in session.messages]}")
Status: Soluções Documentadas
Recomendação: Implementar Solução 1 + 3