Introdução#
Este artigo explora o modelo de comunicação entre o código cliente e a API Gemini usando o SDK da Vertex AI para Python. Vamos cobrir como as mensagens são estruturadas, como o modelo compreende o contexto de uma conversa e como expandir as capacidades do modelo com function calls (chamadas de função). Embora o foco aqui seja o Gemini, os mesmos conceitos se aplicam a modelos como o Gemma e outros LLMs modernos.
Na Parte 1 desta série, mostrei como criar um agente de IA simples — porém surpreendentemente poderoso — que responde a perguntas de diagnóstico sobre a sua máquina local. Com pouquíssimas linhas de código (e comentários bem detalhados), nosso agente já respondia a pedidos como “quanto de CPU tenho disponível” ou “verifique se há sinais de malware”.
Isso foi possível graças à facilidade do SDK do Python, que abstrai boa parte da complexidade. Por exemplo, usei o recurso de Automatic Function Calling para deixar o agente decidir quando invocar cada função. Esse recurso também me permitiu declarar funções Python normais enquanto o SDK inferia assinaturas e docstrings dinamicamente. No entanto, essa facilidade é exclusiva do SDK Python; desenvolvedores em Go, JavaScript ou Java precisam estruturar essas chamadas de forma manual.
Por isso, neste artigo vamos entender o funcionamento da API Gemini por baixo do capô, preparando você para usar com segurança não apenas o SDK Python, mas qualquer outro SDK disponível (Go, JS, Java). Continuarei usando Python nos exemplos para facilitar a comparação com o post anterior, mas a lógica se aplica a qualquer linguagem.
Abordaremos dois tópicos principais:
- Como funciona o fluxo de conversa entre o cliente e o modelo
- Como implementar function calling de forma manual em baixo nível
Mesmo se você programa exclusivamente em Python, entender o ciclo de vida dessas mensagens é fundamental para dominar recursos avançados do SDK (como a Live API) e construir agentes robustos.
Compreendendo o funcionamento da API#
Agentes funcionam como aplicações cliente-servidor clássicas: de um lado, há um cliente encarregado de montar e enviar as requisições; do outro, um processo remoto que hospeda o runtime do modelo e processa os dados recebidos.
Na Vertex AI, encontramos dois grupos principais de APIs: APIs REST para o modelo tradicional de requisição/resposta (onde o cliente envia uma mensagem e aguarda a conclusão antes de continuar) e a Live API, que processa streaming bidirecional em tempo real via WebSockets. Vamos focar primeiro nas APIs REST.
Podemos gerar conteúdo em diversas modalidades: texto, imagem, áudio e vídeo. Os modelos mais recentes são multimodais nativos, permitindo combinar diferentes tipos de entrada e saída em uma mesma interação. Para manter as coisas simples, vamos começar com texto.
Uma chamada direta e pontual a um modelo se parece com isto:
from google import genai
client = genai.Client(
vertexai=True,
project="meu-projeto-sandbox",
location="us-central1"
)
response = client.models.generate_content(
model="gemini-2.0-flash",
contents="Como você está hoje?"
)
print(response.text)Saída:
Estou funcionando perfeitamente, obrigado por perguntar! Como um modelo de linguagem, não sinto emoções como os humanos, mas estou pronto para ajudar. Como posso ser útil hoje?Primeiro instanciamos o cliente — seja no modo Vertex AI (vertexai=True) ou fornecendo uma chave de API do Gemini Developer.
Em seguida, enviamos o prompt chamando client.models.generate_content, especificando o modelo (gemini-2.0-flash) e a mensagem no argumento contents.
À primeira vista, parece uma simples troca de strings. Mas, por baixo das abstrações do Python, o conteúdo não é uma string simples.
contents é, na verdade, uma lista de estruturas de conteúdo (Content), onde cada estrutura é composta por uma função (role) e uma ou mais partes (parts). A estrutura interna definida na biblioteca de tipos se parece com:
from google.genai import types
contents = [types.Content(
role = "user",
parts = [ types.Part.from_text("Como você está hoje?") ]
)]Quando passamos contents="Como você está hoje?", o SDK do Python converte automaticamente a string para uma estrutura Content contendo uma Part de texto.
Outro ponto crucial: a cada chamada a generate_content, o modelo é executado sem memória prévia. É nossa responsabilidade incluir o histórico de mensagens anteriores nas próximas requisições se quisermos manter uma conversa contínua. Vamos comprovar isso perguntando que dia é hoje duas vezes seguidas:
response = client.models.generate_content(
model="gemini-2.0-flash",
contents="que dia é hoje?"
)
print(response.text)
response = client.models.generate_content(
model="gemini-2.0-flash",
contents="que dia é hoje?"
)
print(response.text)Saída:
$ python3 main.py
Hoje é domingo, 5 de novembro de 2023.
Hoje é sábado, 2 de novembro de 2024.Temos dois problemas aqui: 1) o modelo alucinou, pois não tem acesso ao relógio do sistema, e 2) forneceu duas respostas totalmente diferentes para a mesma pergunta. O problema 1 é resolvido com ferramentas (como uma função datetime ou busca no Google), mas o problema 2 demonstra que o modelo não se lembra do que acabou de responder. O controle do estado da conversa cabe exclusivamente ao código cliente.
Vamos ajustar o código para enviar o histórico da conversa:
response = client.models.generate_content(
model="gemini-2.0-flash",
contents="que dia é hoje?"
)
print(response.text)
# Cada item na lista contents representa um "turno" (turn) da conversa
contents = [
{
"role": "user",
"parts": [{
"text": "que dia é hoje?"
}]
},
{
"role": "model",
"parts": [{
"text": response.text
}]
},
{
"role": "user",
"parts": [{
"text": "que dia é hoje?"
}]
},
]
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=contents
)
print(response.text)Saída:
$ python3 main.py
Hoje é quarta-feira, 15 de novembro de 2023.
Hoje é quarta-feira, 15 de novembro de 2023.Na segunda chamada, enviamos o histórico completo. Observe que o atributo role alterna entre "user" e "model" (os únicos valores aceitos para papel na conversa). É assim que o modelo identifica em qual turno do diálogo está. Se omitíssemos o último turno do usuário, o modelo assumiria que a conversa terminou e não geraria uma nova resposta.
O SDK também fornece helpers como types.UserContent e types.Part.from_text para montar essas estruturas de forma mais elegante.
Para trabalhar com dados multimídia, usamos partes específicas (como chamadas de função, dados binários ou URIs do Cloud Storage):
from google.genai import types
contents = types.Part.from_uri(
file_uri='gs://generativeai-downloads/images/scones.jpg',
mime_type='image/jpeg',
)Ou dados binários inline:
contents = types.Part.from_bytes(
data=minha_foto_binaria,
mime_type='image/jpeg',
)Como gerenciar esse histórico é uma necessidade constante em aplicações conversacionais, o SDK da Vertex AI já traz essa abstração pronta na API chats:
chat = client.chats.create(model='gemini-2.0-flash')
response = chat.send_message('que dia é hoje?')
print(response.text)
response = chat.send_message('que dia é hoje?')
print(response.text)Saída:
$ python3 main.py
Hoje é sábado, 14 de outubro de 2023.
Hoje é sábado, 14 de outubro de 2023.Aqui o objeto chat gerencia o histórico de turnos automaticamente para nós.
Implementando Function Calling de forma manual#
Agora que entendemos como o modelo consome mensagens e gerencia contexto, vamos ver como funciona o ciclo de chamadas de ferramentas sem depender de mágica automática.
Para que o modelo utilize uma função, precisamos declarar a existência dessa função (nome, descrição e parâmetros aceitos). O modelo analisa essas declarações para decidir quando uma chamada é necessária.
Veja a declaração da função get_random_number:
get_random_number_decl = {
"name": "get_random_number",
"description": "Retorna um número aleatório",
}Essa declaração possui três campos principais: name, description e parameters (como esta função não recebe argumentos, o campo foi omitido). O modelo se baseia na descrição da função e dos parâmetros para decidir se deve ou não acioná-la.
No artigo anterior, deixamos o SDK extrair esses metadados da docstring da função. Agora, vamos declarar a função e a ferramenta explicitamente:
def get_random_number():
return 4 # escolhido por um lançamento justo de dados
# garantidamente aleatório (https://xkcd.com/221/)
get_random_number_decl = {
"name": "get_random_number",
"description": "Retorna um número aleatório",
}Você pode conferir outros esquemas de declaração na documentação de function calling da Vertex AI.
Agora registramos a ferramenta na configuração do modelo:
tools = types.Tool(function_declarations=[get_random_number_decl])
config = types.GenerateContentConfig(tools=[tools])
contents = [types.Part.from_text(text="qual é o meu número da sorte hoje?")]
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=contents,
config=config,
)
print(response.candidates[0].content.parts[0])Ao executar o código acima, obtemos:
$ python3 main.py
video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(id=None, args={}, name='get_random_number') function_response=None text=NoneObserve que todos os campos da resposta estão vazios (None), exceto o campo function_call. O modelo não gerou texto final; ele está nos informando: “Por favor, execute a função get_random_number e me devolva o resultado”.
O modelo sabe que a função existe, mas não pode executá-la diretamente no servidor do Google. Ele depende do cliente para rodar o código localmente e fornecer a resposta de volta.
Para concluir o ciclo, executamos a função no cliente e enviamos o resultado de volta ao modelo, acompanhado de todo o histórico anterior:
# 1. Executamos a função solicitada pelo modelo
result = get_random_number()
# 2. Anexamos a resposta do modelo (o pedido de execução da função)...
contents.append(types.ModelContent(parts=response.candidates[0].content.parts))
# 3. ... e anexamos o resultado retornado pela nossa função local
contents.append(types.UserContent(parts=types.Part.from_function_response(name="get_random_number", response={"result": result})))
# 4. Enviamos o histórico atualizado de volta ao modelo
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=contents,
config=config,
)
print(response.text)Saída:
$ python3 main.py
Seu número da sorte para hoje é 4.Conclusões#
Neste artigo, desmistificamos como o cliente de um agente conversa com o modelo em nível de protocolo e eliminamos a “mágica” das ferramentas automáticas.
A automação é excelente para produtividade, mas compreender o fluxo real de mensagens é o que diferencia quem apenas copia exemplos de quem consegue diagnosticar bugs e arquitetar sistemas de agentes confiáveis.
No contexto atual de vibe coding, saber exatamente o que acontece por baixo dos panos permite criar prompts e especificações muito mais precisas, economizando tempo e recursos de computação.
Na próxima parte desta série, Instruções de Sistema e Ferramentas para Agentes, vamos usar essa base para criar uma aplicação de chat completa no terminal com histórico persistente de sessão e injeção dinâmica de schemas de tabelas.
Deixe seus comentários e dúvidas abaixo! Até a próxima o/




