Como criar agentes de IA persistentes com o ADK e o CloudSQL

1. Introdução

Nesta sessão prática, você vai além dos chatbots básicos e sem estado para criar um concierge de um café inteligente, um agente de IA desenvolvido pelo Gemini que atua como um barista simpático. Ele recebe pedidos de café rastreados no estado da sessão, lembra preferências alimentares de longo prazo no estado do escopo do usuário e persiste tudo em um banco de dados PostgreSQL do Cloud SQL. No final, o agente se lembra de que você tem intolerância à lactose, mesmo depois de reiniciar o aplicativo e iniciar uma conversa totalmente nova.

Confira a arquitetura do sistema que vamos criar

a98bbd65ddedd29c.jpeg

Pré-requisitos

  • Uma conta do Google Cloud com uma conta de faturamento de teste
  • Noções básicas sobre o Python.
  • Não é necessário ter experiência com ADK, agentes de IA ou Cloud SQL

O que você vai aprender

  • Criar um agente de IA usando o Kit de Desenvolvimento de Agente (ADK) do Google com ferramentas personalizadas
  • Definir ferramentas que leem e gravam o estado da sessão via ToolContext
  • Distinguir entre estado no escopo da sessão e estado no escopo do usuário (prefixo user:)
  • Provisionar uma instância do Cloud SQL PostgreSQL e se conectar a ela pelo Cloud Shell
  • Migrar do armazenamento local (que é o padrão ao usar o comando adk web) para DatabaseSessionService para armazenamento permanente com banco de dados dedicado
  • Verifique se a memória do agente persiste entre reinicializações do aplicativo e sessões de conversa separadas.

O que é necessário

  • Um computador em funcionamento e uma conexão de Internet confiável.
  • Um navegador, como o Chrome, para acessar o console do Google Cloud
  • Uma mente curiosa e vontade de aprender.

2. Configuração de seu ambiente

Esta etapa prepara o ambiente do Cloud Shell e configura seu projeto do Google Cloud

Abra o Cloud Shell

Abra o Cloud Shell no navegador. O Cloud Shell oferece um ambiente pré-configurado com todas as ferramentas necessárias para este codelab. Clique em Autorizar quando solicitado a

Sua interface deve ser parecida com esta

86307fac5da2f077.png

Essa será nossa interface principal, com o IDE na parte de cima e o terminal na parte de baixo.

Configurar seu diretório de trabalho

Crie o diretório de trabalho. Todo o código que você escrever neste codelab vai ficar aqui, separado do repositório de referência:

# Create your working directory
mkdir -p ~/build-agent-adk-cloudsql

# Change cloudshell workspace and working directory into previously created dir
cloudshell workspace ~/build-agent-adk-cloudsql && cd ~/build-agent-adk-cloudsql

Para gerar o terminal, encontre Visualizar -> Terminal.

ccc3214812750f1c.png

Configurar o projeto do Google Cloud e as variáveis de ambiente iniciais

Faça o download do script de configuração do projeto para o diretório de trabalho:

curl -sL https://raw.githubusercontent.com/alphinside/cloud-trial-project-setup/main/setup_verify_trial_project.sh -o setup_verify_trial_project.sh

Execute o script. Ele verifica sua conta de faturamento de teste, cria um projeto (ou valida um existente), salva o ID do projeto em um arquivo .env no diretório atual e define o projeto ativo no terminal.

bash setup_verify_trial_project.sh && source .env

Ao executar esse comando, você vai receber uma sugestão de nome para o ID do projeto. Pressione Enter para continuar.

54b615cd15f2a535.png

Depois de esperar um pouco, se você vir esta saída no console, poderá passar para a próxima etapa e576b4c13d595156.png

O script executado faz as seguintes etapas:

  1. Verifique se você tem uma conta de faturamento de teste ativa
  2. Verifique se há um projeto em .env (se houver).
  3. Crie um novo projeto ou reutilize o atual
  4. Vincular a conta de faturamento de teste ao projeto
  5. Salve o ID do projeto em .env
  6. Definir o projeto como o projeto ativo do gcloud

Verifique se o projeto está definido corretamente conferindo o texto amarelo ao lado do diretório de trabalho no prompt do terminal do Cloud Shell. Ele vai mostrar o ID do projeto.

9e11ee21cd23405f.png

Ativar as APIs necessárias

Ative as APIs do Google Cloud necessárias para este codelab:

gcloud services enable \
  aiplatform.googleapis.com \
  sqladmin.googleapis.com \
  compute.googleapis.com
  • API Vertex AI (aiplatform.googleapis.com): seu agente usa modelos do Gemini pela Vertex AI.
  • API Cloud SQL Admin (sqladmin.googleapis.com): você provisiona e gerencia uma instância do PostgreSQL para armazenamento permanente.
  • API Compute Engine (compute.googleapis.com): necessária para criar instâncias do Cloud SQL.

Configurar a região dos produtos do Gemini e do Cloud

Antes de continuar, vamos configurar a localização/região necessária para o produto com que interagimos. Adicione a seguinte configuração ao arquivo .env:

# This is for our Gemini endpoint
echo "GOOGLE_CLOUD_LOCATION=global" >> .env

# This is for our other Cloud products
echo "REGION=us-central1" >> .env

source .env

Vamos para a próxima etapa

3. Configurar o Cloud SQL

Esta etapa provisiona uma instância do Cloud SQL PostgreSQL e muda seu agente do armazenamento na memória para o armazenamento com suporte de banco de dados. A criação da instância leva alguns minutos. Por isso, vamos começar esse processo primeiro e continuar nossa discussão sobre o próximo tópico enquanto esperamos a conclusão.

Iniciar a criação da instância

Adicione a senha do banco de dados ao arquivo .env e recarregue-o. Vamos usar cafe-agent-pwd-2025 como senha.

echo "DB_PASSWORD=cafe-agent-pwd-2025" >> .env
source .env

Execute este comando para criar uma instância do Cloud SQL PostgreSQL. A conclusão leva alguns minutos. Deixe o processo em execução e continue para a próxima seção.

gcloud sql instances create cafe-concierge-db \
  --database-version=POSTGRES_17 \
  --edition=ENTERPRISE \
  --region=${REGION} \
  --availability-type=ZONAL \
  --project=${GOOGLE_CLOUD_PROJECT} \
  --tier=db-f1-micro \
  --root-password=${DB_PASSWORD} \
  --quiet &

Algumas observações sobre o comando acima:

  • db-f1-micro é o menor (e mais barato) nível do Cloud SQL, suficiente para este codelab.
  • --root-password define a senha do usuário postgres padrão.
  • O sufixo & no comando executa o comando em segundo plano para que você possa continuar trabalhando.

O processo será executado em segundo plano, mas a saída do console será mostrada ocasionalmente no terminal atual. Abra uma nova guia do terminal no Cloud Shell (clique no ícone +) para ficar mais focado.

b01e3fbd89f17332.png

Navegue até o diretório de trabalho novamente e ative o projeto usando o script de configuração anterior.

cd ~/build-agent-adk-cloudsql
bash setup_verify_trial_project.sh && source .env

Em seguida, vamos para a próxima seção

4. Criar o agente de concierge do café

Esta etapa cria a estrutura do projeto para o agente do ADK e define um Cafe Concierge básico com uma ferramenta de menu.

Inicializar o projeto Python

Este codelab usa o uv, um gerenciador de pacotes Python rápido que lida com ambientes virtuais e dependências em uma única ferramenta. Ele vem pré-instalado no Cloud Shell.

Inicialize um projeto Python e adicione o ADK como uma dependência:

uv init
uv add google-adk==1.25.0 asyncpg

O uv init cria um pyproject.toml e um ambiente virtual. O comando uv add instala a dependência e a registra em pyproject.toml.

Inicializar a estrutura do projeto do agente

O ADK espera um layout de pasta específico: um diretório com o nome do seu agente que contenha __init__.py, agent.py e também .env dentro do diretório do agente.

O ADK tem um comando integrado para ajudar você a estabelecer isso rapidamente. Execute o comando a seguir:

uv run adk create cafe_concierge \
    --model gemini-2.5-flash \
    --project ${GOOGLE_CLOUD_PROJECT} \
    --region ${GOOGLE_CLOUD_LOCATION}

Esse comando vai criar uma estrutura de agente com gemini-2.5-flash como o cérebro. Seu diretório vai ficar assim:

build-agent-adk-cloudsql/
├── cafe_concierge/
│   ├── __init__.py
│   ├── agent.py
│   └── .env
├── pyproject.toml
├── .env      
├── .venv/
└── ...

Escrever o agente

Abra cafe_concierge/agent.py no editor do Cloud Shell

cloudshell edit cafe_concierge/agent.py

e substitua o arquivo com o seguinte código:

# cafe_concierge/agent.py
from google.adk.agents import LlmAgent
from google.adk.tools import ToolContext

CAFE_MENU = {
    "espresso": {
        "price": 3.50,
        "description": "Rich and bold single shot",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "latte": {
        "price": 5.00,
        "description": "Espresso with steamed milk",
        "tags": ["gluten-free"],
    },
    "oat milk latte": {
        "price": 5.50,
        "description": "Espresso with steamed oat milk",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "cappuccino": {
        "price": 4.50,
        "description": "Espresso with equal parts steamed milk and foam",
        "tags": ["gluten-free"],
    },
    "cold brew": {
        "price": 4.00,
        "description": "Slow-steeped for 12 hours, served over ice",
        "tags": ["vegan", "dairy-free", "gluten-free"],
    },
    "matcha latte": {
        "price": 5.50,
        "description": "Ceremonial grade matcha with steamed milk",
        "tags": ["gluten-free"],
    },
    "croissant": {
        "price": 3.00,
        "description": "Buttery, flaky French pastry",
        "tags": [],
    },
    "banana bread": {
        "price": 3.50,
        "description": "Homemade with walnuts",
        "tags": ["vegan"],
    },
}


def get_menu() -> dict:
    """Returns the full cafe menu with prices, descriptions, and dietary tags.

    Use this tool when the customer asks what's available, wants to see
    the menu, or asks about specific items.
    """
    return CAFE_MENU


root_agent = LlmAgent(
    name="cafe_concierge",
    model="gemini-2.5-flash",
    instruction="""You are a friendly and knowledgeable barista at "The Cloud Cafe".

Your job:
- Help customers browse the menu and answer questions about items.
- Take coffee and food orders.
- Remember and respect dietary preferences.

Be conversational, warm, and concise. If a customer mentions a dietary
restriction, acknowledge it and suggest suitable options from the menu.
""",
    tools=[get_menu],
)

Isso define um agente básico com uma ferramenta: get_menu(). O agente pode responder a perguntas sobre o cardápio, mas ainda não consegue rastrear pedidos ou lembrar preferências.

Verificar se o agente está em execução

Inicie a interface do desenvolvedor do ADK no seu diretório de trabalho:

cd ~/build-agent-adk-cloudsql
uv run adk web

Abra o URL mostrado no terminal (normalmente http://localhost:8000) usando o recurso de visualização na Web do Cloud Shell. Selecione cafe_concierge no menu suspenso do agente no canto superior esquerdo.

Digite o texto a seguir na barra de chat e verifique se o agente responde com itens de menu e preços.

What's on the menu?

376ee6b189657e7a.png

Interrompa a interface de desenvolvimento com Ctrl+C antes de continuar.

5. Adicionar gerenciamento de pedidos com estado

O agente pode mostrar o menu, mas não pode receber pedidos nem lembrar preferências. Esta etapa adiciona quatro ferramentas que usam o sistema de estado do ADK para rastrear pedidos em uma conversa e armazenar preferências alimentares em várias conversas.

Entender eventos e estado da sessão

Cada conversa do ADK fica dentro de um objeto Session. Uma sessão rastreia duas coisas distintas: eventos e estado. Entender a diferença é fundamental para criar agentes que se lembrem das coisas certas da maneira certa.

Eventos são o registro cronológico de tudo o que acontece em uma conversa. Cada mensagem do usuário, cada resposta do agente, cada chamada de ferramenta e seu valor de retorno são registrados como um Event e anexados à lista events da sessão. Os eventos são imutáveis: depois de registrados, eles nunca mudam. Pense nos eventos como a transcrição completa de uma conversa.

O estado é um bloco de notas de chave-valor que o agente lê e grava durante uma conversa. Ao contrário dos eventos, o estado é mutável: os valores mudam à medida que a conversa evolui. O estado é onde o agente armazena os dados estruturados necessários para agir: o pedido atual, as preferências do cliente, um total em andamento. Pense no estado como post-its que o agente mantém ao lado da transcrição.

Veja como eles se relacionam:

cd9871699451867d.png

As ferramentas leem e gravam o estado usando ToolContext, um objeto que o ADK injeta automaticamente em qualquer função de ferramenta que o declare como um parâmetro. Você não cria esse arquivo. Com o tool_context.state, uma ferramenta pode ler e gravar o bloco de notas de estado da sessão. O ADK inspeciona a assinatura da função: parâmetros com o tipo ToolContext são injetados, e todos os outros parâmetros são preenchidos pelo LLM com base na conversa.

Quando uma ferramenta grava em tool_context.state, o ADK registra essa mudança como um state_delta dentro do evento. Em seguida, o SessionService aplica o delta ao estado atual da sessão. Isso significa que as mudanças de estado são sempre rastreáveis até o evento que as causou. Isso também é válido para outras formas de contexto, como callback_context.

Entender os prefixos de estado

As chaves de estado usam prefixos para controlar o escopo:

Prefixo

Escopo

Sobrevive à reinicialização? (com DB)

(nenhum)

Apenas a sessão atual

Sim

user:

Todas as sessões deste usuário

Sim

app:

Todas as sessões, todos os usuários

Sim

temp:

Somente a invocação atual

Não

Neste codelab, você vai usar dois desses prefixos: chaves sem prefixo para dados no escopo da sessão (o pedido atual, relevante apenas para esta conversa) e chaves user: para dados no escopo do usuário (preferências alimentares, relevantes em todas as conversas desse usuário).

Adicionar as ferramentas com estado

Abra cafe_concierge/agent.py no editor do Cloud Shell.

cloudshell edit cafe_concierge/agent.py

Em seguida, adicione as quatro funções acima da definição de root_agent:

# cafe_concierge/agent.py (add below get_menu, above root_agent)

def place_order(tool_context: ToolContext, items: list[str]) -> dict:
    """Places an order for the specified menu items.

    Use this tool when the customer confirms they want to order something.

    Args:
        tool_context: Provided automatically by ADK.
        items: A list of menu item names the customer wants to order.
    """
    valid_items = []
    invalid_items = []
    total = 0.0

    for item in items:
        item_lower = item.lower()
        if item_lower in CAFE_MENU:
            valid_items.append(item_lower)
            total += CAFE_MENU[item_lower]["price"]
        else:
            invalid_items.append(item)

    if not valid_items:
        return {"error": f"None of these items are on our menu: {invalid_items}"}

    order = {"items": valid_items, "total": round(total, 2)}
    tool_context.state["current_order"] = order

    result = {"order": order}
    if invalid_items:
        result["warning"] = f"These items are not on our menu: {invalid_items}"
    return result


def get_order_summary(tool_context: ToolContext) -> dict:
    """Returns the current order summary for this session.

    Use this tool when the customer asks about their current order,
    wants to review what they ordered, or asks for the total.

    Args:
        tool_context: Provided automatically by ADK.
    """
    order = tool_context.state.get("current_order")
    if order:
        return {"order": order}
    return {"message": "No order has been placed yet in this session."}


def set_dietary_preference(tool_context: ToolContext, preference: str) -> dict:
    """Saves a dietary preference that persists across all conversations.

    Use this tool when the customer mentions a dietary restriction or
    preference (e.g., "I'm vegan", "I'm lactose intolerant",
    "I have a nut allergy").

    Args:
        tool_context: Provided automatically by ADK.
        preference: The dietary preference to save (e.g., "vegan",
            "lactose intolerant", "nut allergy").
    """
    existing = tool_context.state.get("user:dietary_preferences", [])
    if not isinstance(existing, list):
        existing = []

    preference_lower = preference.lower().strip()
    if preference_lower not in existing:
        existing.append(preference_lower)

    tool_context.state["user:dietary_preferences"] = existing
    return {
        "saved": preference_lower,
        "all_preferences": existing,
    }


def get_dietary_preferences(tool_context: ToolContext) -> dict:
    """Retrieves the customer's saved dietary preferences.

    Use this tool when you need to check the customer's dietary
    restrictions before making recommendations.

    Args:
        tool_context: Provided automatically by ADK.
    """
    preferences = tool_context.state.get("user:dietary_preferences", [])
    if preferences:
        return {"preferences": preferences}
    return {"message": "No dietary preferences saved yet."}

Duas observações importantes:

  1. place_order e get_order_summary usam chaves sem prefixo (current_order). Esse estado está vinculado à sessão atual. Uma nova conversa começa com um pedido vazio.
  2. set_dietary_preference e get_dietary_preferences usam o prefixo user: (user:dietary_preferences). Esse estado é compartilhado em todas as sessões do mesmo usuário.

Atualizar o agente com novas ferramentas e instruções

Substitua a definição root_agent atual na parte de baixo do arquivo por:

# cafe_concierge/agent.py (replace the existing root_agent)

root_agent = LlmAgent(
    name="cafe_concierge",
    model="gemini-2.5-flash",
    instruction="""You are a friendly and knowledgeable barista at "The Cloud Cafe".

Your job:
- Help customers browse the menu and answer questions about items.
- Take coffee and food orders.
- Remember and respect dietary preferences.

The customer's saved dietary preferences are: {user:dietary_preferences?}

IMPORTANT RULES:
- When a customer mentions a dietary restriction, ALWAYS save it using the
  set_dietary_preference tool before doing anything else.
- Before recommending items, check the customer's dietary preferences. If they
  have preferences saved, only recommend items compatible with those
  restrictions. Check the menu item tags to determine compatibility.
- When placing an order, confirm the items and total with the customer.

Be conversational, warm, and concise.
""",
    tools=[
        get_menu,
        place_order,
        get_order_summary,
        set_dietary_preference,
        get_dietary_preferences,
    ],
)

A instrução usa o modelo de injeção de estado {user:dietary_preferences?} para injetar as preferências salvas do cliente diretamente no comando.

Verificar o arquivo completo

Seu cafe_concierge/agent.py agora deve conter:

  • O dicionário CAFE_MENU
  • Cinco funções de ferramenta: get_menu, place_order, get_order_summary, set_dietary_preference, get_dietary_preferences
  • A definição de root_agent com todas as cinco ferramentas

6. Testar o agente com a interface de desenvolvimento do ADK

Esta etapa executa o agente e exercita todos os recursos com estado: ordenação, rastreamento de preferências e memória entre sessões (no mesmo processo). Você também vai inspecionar os painéis "Eventos" e "Estado" para ver como o ADK rastreia a conversa internamente.

Iniciar a interface de desenvolvimento

cd ~/build-agent-adk-cloudsql
uv run adk web

Abra a visualização da Web na porta 8000 e selecione cafe_concierge no menu suspenso.

Conversa 1: fazer um pedido e definir preferências

Tente estes comandos em sequência:

What's on the menu?
I'm lactose intolerant
What would you recommend?
I'll have an oat milk latte and a banana bread
What's my order?

Inspecionar eventos de sessão

Todos os Eventos serão capturados e exibidos na interface da Web. Na caixa de chat, você vai notar que não há apenas seu comando e resposta, mas também tool_call e tool_response.

9051b46978c8017b.png

Uma lista de eventos vai aparecer em ordem. Cada evento tem um autor (quem o produziu) e um tipo (que tipo de interação ele representa):

Author

Tipo

O que ele representa

user

message

Uma mensagem que você digitou no chat

cafe_concierge

message

A resposta de texto do agente

cafe_concierge

tool_call

O agente decidiu chamar uma ferramenta (mostra o nome da função + argumentos)

cafe_concierge

tool_response

O valor de retorno de uma chamada de função

Clique em um dos eventos tool_call, por exemplo, a chamada set_dietary_preference. Você verá:

  • Nome da função: set_dietary_preference
  • Arguments: {"preference": "lactose intolerant"}

Agora clique no evento tool_response correspondente logo abaixo. Você vai ver o valor de retorno:

  • Resposta: {"saved": "lactose intolerant", "all_preferences": ["lactose intolerant"]}

b528f4efd6a9f337.png

Procure o campo state_delta no evento tool_response. Isso mostra exatamente qual estado mudou como resultado da chamada de função:

state_delta: {"user:dietary_preferences": ["lactose intolerant"]}

Cada mudança de estado pode ser rastreada até um evento específico. É assim que o ADK garante que o bloco de notas de estado permaneça sincronizado com o histórico de conversas.

Inspecionar o estado da sessão

Clique na guia Estado. Ao contrário do registro de eventos (que mostra o histórico completo), a guia "Estado" mostra um snapshot do que o agente sabe agora: o valor atual de cada chave de estado.

5e06fb54f3f0d8d6.png

Você vai encontrar duas entradas:

  • current_order{"items": ["oat milk latte", "banana bread"], "total": 9.0}
  • user:dietary_preferences["lactose intolerant"]

Observe a diferença nos nomes das chaves:

  • current_order não tem prefixo. Ele é do escopo da sessão. Ele existe apenas nessa conversa e desaparece quando a sessão termina.
  • user:dietary_preferences tem o prefixo user: e é no escopo do usuário. Ele é compartilhado em todas as sessões desse usuário.

Essa distinção é invisível no código (ambos usam tool_context.state), mas controla o alcance dos dados. Você vai ver isso no próximo teste.

Conversa 2: verificar o estado do usuário em várias sessões

Clique no botão Nova sessão na interface do desenvolvedor para iniciar uma nova conversa. Isso cria uma nova sessão para o mesmo usuário.

57408cfae5f041ac.png

Teste este comando:

What do you recommend for me?

Verifique a guia Estado na nova sessão. A tecla user:dietary_preferences é transferida, mas current_order desaparece, porque esse estado estava vinculado à sessão anterior.

764eb3885251307d.png

7. Observar a limitação do armazenamento local

O agente se lembra das preferências entre as sessões, mas apenas enquanto o armazenamento local existir. Esta etapa demonstra a limitação fundamental do armazenamento local.

Inicie o agente novamente

Você parou a interface de desenvolvimento no final da etapa anterior. Agora vamos remover o armazenamento local e iniciá-lo novamente para simular um ambiente sem servidor, que não tem estado:

cd ~/build-agent-adk-cloudsql
rm -f cafe_concierge/.adk/session.db
uv run adk web

Agora, abra a Visualização da Web na porta 8000 e selecione cafe_concierge.

Teste de recordação de preferências

Tipo:

Do you remember my dietary preferences?

O agente não se lembra. As preferências alimentares, o histórico de pedidos — tudo sumiu.

82a5e05434cafe83.png

Tudo foi apagado quando excluímos o armazenamento local, o que geralmente acontecia quando usávamos um ambiente sem servidor. O session.db armazena todo o estado na memória do processo. A remoção apaga tudo.

A solução: especifique DatabaseSessionService, que neste tutorial vai armazenar todos os dados da sessão em um banco de dados PostgreSQL no Cloud SQL. O código e as ferramentas do agente permanecem exatamente os mesmos. A única coisa que muda é o back-end de armazenamento.

Interrompa a interface de desenvolvimento com Ctrl+C antes de continuar.

8. Revisar a configuração do banco de dados

Neste momento, a criação da instância de banco de dados já deve ter sido concluída. Para verificar, execute o seguinte comando:

gcloud sql instances describe cafe-concierge-db --format="value(state)"

Você vai ver a seguinte saída. Marque como concluído.

RUNNABLE

Criar o banco de dados

Crie um banco de dados dedicado para os dados da sessão do agente:

gcloud sql databases create agent_db --instance=cafe-concierge-db

Iniciar o proxy de autenticação do Cloud SQL

O proxy de autenticação do Cloud SQL fornece uma conexão segura e autenticada do Cloud Shell à sua instância do Cloud SQL sem precisar colocar endereços IP na lista de permissões. Ele já vem pré-instalado no Cloud Shell.

cloud-sql-proxy ${GOOGLE_CLOUD_PROJECT}:${REGION}:cafe-concierge-db --port 5432 &

O sufixo & no comando faz com que o proxy seja executado em segundo plano. Você vai ver uma saída confirmando que o proxy está pronto, como mostrado abaixo.

[your-project-id:your-region:cafe-concierge-db] Listening on 127.0.0.1:5432
The proxy has started successfully and is ready for new connections!

Verificar a conexão

Teste se é possível se conectar ao banco de dados pelo proxy:

psql "host=127.0.0.1 port=5432 dbname=agent_db user=postgres password=$DB_PASSWORD" -c "SELECT 'Connection ok' AS status;"

Você verá:

      status
---------------------
 Connection ok
(1 row)

9. Verificar a memória persistente em várias sessões

Esta etapa prova que a memória do seu agente sobrevive à redefinição quando garantimos que cafe_concierge/.adk/session_db (o banco de dados local) seja removido e abranja sessões de conversa.

Iniciar o agente

Verifique se o proxy de autenticação do Cloud SQL ainda está em execução (confira com os jobs). Se não estiver, reinicie:

if ss -tlnp | grep -q ':5432 '; then
  echo "Cloud SQL Auth Proxy is already running."
else
  cloud-sql-proxy ${GOOGLE_CLOUD_PROJECT}:${REGION}:cafe-concierge-db --port 5432 &
fi

Em seguida, vamos iniciar a interface do desenvolvedor do ADK especificando o banco de dados como o serviço de sessão.

uv run adk web --session_service_uri postgresql+asyncpg://postgres:${DB_PASSWORD}@127.0.0.1:5432/agent_db

Abra a Visualização da Web na porta 8000 e selecione cafe_concierge.

Teste 1: fazer um pedido e definir preferências

Na primeira sessão, siga estas instruções:

Show me the menu
I'm vegan
What can I eat?
I'll have a cold brew and banana bread

Teste 2: sobreviver a uma reinicialização

Pare a interface do usuário de desenvolvimento com Ctrl+C e verifique se o session.db local foi removido.

rm -f cafe_concierge/.adk/session.db

Em seguida, execute novamente o servidor da interface de desenvolvimento.

uv run adk web --session_service_uri postgresql+asyncpg://postgres:${DB_PASSWORD}@127.0.0.1:5432/agent_db

Abra a visualização da Web na porta 8000, selecione cafe_concierge e inicie uma nova sessão. Em seguida, pergunte

What are my dietary preferences?

O agente responde com suas preferências salvas: vegana. Os dados sobreviveram à reinicialização porque agora estão armazenados no PostgreSQL, não no armazenamento local. O mesmo acontece se criarmos uma nova sessão, já que o estado user: é transferido para cada nova sessão desse usuário.

9c139bf89becb748.png

Inspecionar o banco de dados diretamente

Abra uma nova guia do terminal no Cloud Shell e consulte o banco de dados para ver os dados armazenados:

psql "host=127.0.0.1 port=5432 dbname=agent_db user=postgres password=$DB_PASSWORD" -c "\dt"

Você vai encontrar tabelas que o ADK criou automaticamente para armazenar sessões, eventos e estados, como neste exemplo:

                List of relations
 Schema |         Name          | Type  |  Owner   
--------+-----------------------+-------+----------
 public | adk_internal_metadata | table | postgres
 public | app_states            | table | postgres
 public | events                | table | postgres
 public | sessions              | table | postgres
 public | user_states           | table | postgres
(5 rows)

Resumo do comportamento do estado

Chave de estado

Prefixo

Escopo

Compartilhados entre sessões?

current_order

(nenhum)

Sessão

Não

user:dietary_preferences

user:

Usuário

Sim

10. Parabéns / Limpeza

Parabéns! Você criou um agente de IA persistente e com estado usando o ADK e o Cloud SQL.

O que você aprendeu

  • Como criar um agente do ADK com ferramentas personalizadas que leem e gravam o estado da sessão
  • A diferença entre o estado no escopo da sessão (sem prefixo) e o estado no escopo do usuário (prefixo user:)
  • Por que o adk local session.db padrão é adequado apenas para desenvolvimento: todos os dados são perdidos na remoção (e são fáceis de remover, sem backup), não sendo adequados para implantação sem servidor, que é sem estado.
  • Como provisionar uma instância do Cloud SQL PostgreSQL e se conectar a ela com o proxy de autenticação do Cloud SQL
  • Como se conectar ao DatabaseSessionService com PostgreSQL no CloudSQL com uma mudança mínima de código: mesmas ferramentas, mesmo agente, back-end diferente
  • Como o estado no escopo do usuário persiste em sessões de conversa separadas

Limpeza

Para evitar cobranças na sua conta do Google Cloud, limpe os recursos criados neste codelab.

A maneira mais fácil de fazer a limpeza é excluir o projeto. Isso remove todos os recursos associados ao projeto.

gcloud projects delete ${GOOGLE_CLOUD_PROJECT}

Opção 2: excluir recursos individuais

Se você quiser manter o projeto, mas remover apenas os recursos criados neste codelab:

gcloud sql instances delete cafe-concierge-db --quiet