Skip to content

🔍 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:

  1. SessionService (seu FirestoreSessionService)
  2. Responsável por: Persistir dados da sessão
  3. O que faz: Salva mensagens no Firestore
  4. O que NÃO faz: Automaticamente carregar histórico

  5. Runner + Agent

  6. Responsável por: Executar o agente
  7. O que faz: Processar a mensagem atual
  8. 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:

  1. Verificar se mensagens estão sendo salvas no Firestore

    gcloud firestore-databases list
    gcloud firestore document get agente_busca_produtos_sessions/{session_id}
    

  2. Verificar logs do Agent

    gcloud run logs read agente-busca-produtos-slack --follow | grep -i "session\|history\|message"
    

  3. 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