Lab 043: Agentes Multimodais com GPT-4o Vision¶
O que Você Vai Aprender¶
- Enviar imagens para o GPT-4o usando a API de visão da OpenAI (métodos base64 e URL)
- Construir um agente que pode analisar fotos de produtos e responder perguntas sobre eles
- Combinar visão + chamada de ferramentas: o modelo vê uma imagem e chama ferramentas com base no que observa
- Lidar com entradas de múltiplas imagens para comparação de produtos
- Aplicar visão em cenários reais: identificação de produtos, avaliação de danos, estimativa de tamanho
Introdução¶
O GPT-4o é nativamente multimodal — ele processa texto e imagens em um único modelo, não como um pipeline de modelos separados. Isso permite agentes que podem "ver" o que o usuário está olhando e responder com contexto.
Casos de uso do OutdoorGear para visão: - O cliente envia uma foto de um produto → o agente identifica e recupera as especificações - O cliente mostra um item danificado → o agente avalia o dano e inicia a devolução - O cliente pergunta "essa barraca cabe nesse carro?" com duas fotos → o agente estima - O cliente compartilha uma foto de trilha → o agente recomenda equipamentos para aquele terreno e clima
Pré-requisitos¶
O GPT-4o com visão está disponível no nível gratuito do GitHub Models — não é necessária assinatura do Azure.
Parte 1: Visão Básica — Analisar uma Foto de Produto¶
Passo 1: Enviar uma URL de imagem para o GPT-4o¶
# vision_basics.py
import os
from openai import OpenAI
client = OpenAI(
base_url="https://models.inference.ai.azure.com",
api_key=os.environ["GITHUB_TOKEN"],
)
# We'll use a public photo of a tent for demo purposes
# In production, customers upload their own images
TENT_IMAGE_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8e/Tent_at_sunset.jpg/320px-Tent_at_sunset.jpg"
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": TENT_IMAGE_URL},
},
{
"type": "text",
"text": "This is an OutdoorGear customer photo. "
"Describe the tent in this image: type, approximate size, condition, "
"and whether it appears to be a 3-season or 4-season design.",
},
],
}
],
max_tokens=300,
)
print("=== Vision Analysis ===")
print(response.choices[0].message.content)
Passo 2: Enviar uma imagem local (base64)¶
Quando os clientes enviam imagens, você recebe bytes do arquivo — envie-os codificados em base64:
# vision_local_image.py
import base64
import os
from openai import OpenAI
def encode_image(image_path: str) -> str:
"""Encode a local image file to base64 string."""
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def analyze_product_image(image_path: str, question: str) -> str:
"""Ask GPT-4o a question about a local image file."""
client = OpenAI(
base_url="https://models.inference.ai.azure.com",
api_key=os.environ["GITHUB_TOKEN"],
)
b64_image = encode_image(image_path)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "You are an OutdoorGear product expert. "
"Analyze customer-submitted product photos and provide helpful, accurate assessments.",
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{b64_image}",
"detail": "low", # "low" saves tokens; "high" for detailed analysis
},
},
{"type": "text", "text": question},
],
},
],
max_tokens=400,
)
return response.choices[0].message.content
# Usage (provide your own image):
# result = analyze_product_image("my_tent.jpg", "Is this tent suitable for winter camping?")
# print(result)
Parte 2: Visão + Chamada de Ferramentas¶
O verdadeiro poder dos agentes multimodais: o modelo vê uma imagem, decide quais ferramentas chamar e toma ação.
Passo 3: Agente de identificação de produtos¶
# vision_agent.py
import os
import json
from openai import OpenAI
client = OpenAI(
base_url="https://models.inference.ai.azure.com",
api_key=os.environ["GITHUB_TOKEN"],
)
# Tools the agent can call after seeing an image
TOOLS = [
{
"type": "function",
"function": {
"name": "lookup_product",
"description": "Look up product details by name or description. "
"Use after identifying a product in an image.",
"parameters": {
"type": "object",
"properties": {
"product_name": {
"type": "string",
"description": "Product name or description to look up (e.g. 'TrailBlazer Tent 2P')",
}
},
"required": ["product_name"],
},
},
},
{
"type": "function",
"function": {
"name": "initiate_return",
"description": "Initiate a product return/warranty claim. "
"Use when an image shows damage or defects.",
"parameters": {
"type": "object",
"properties": {
"product_id": {"type": "string"},
"damage_type": {"type": "string", "description": "Description of the observed damage"},
"severity": {"type": "string", "enum": ["minor", "moderate", "severe"]},
},
"required": ["product_id", "damage_type", "severity"],
},
},
},
]
PRODUCTS_DB = {
"trailblazer tent": {"id": "P001", "name": "TrailBlazer Tent 2P", "price": 249.99, "warranty": "lifetime"},
"summit dome": {"id": "P002", "name": "Summit Dome 4P", "price": 549.99, "warranty": "lifetime"},
"trailblazer solo": {"id": "P003", "name": "TrailBlazer Solo", "price": 299.99, "warranty": "lifetime"},
"arcticdown": {"id": "P004", "name": "ArcticDown Bag", "price": 389.99, "warranty": "5 years"},
}
def execute_tool(name: str, args: dict) -> str:
if name == "lookup_product":
query = args["product_name"].lower()
for key, product in PRODUCTS_DB.items():
if key in query or query in key:
return json.dumps(product)
return json.dumps({"error": f"Product '{args['product_name']}' not found in catalog"})
elif name == "initiate_return":
return json.dumps({
"return_id": "RTN-2025-00042",
"status": "initiated",
"product_id": args["product_id"],
"damage_type": args["damage_type"],
"severity": args["severity"],
"next_steps": "Ship the item to our returns center. Label included via email.",
})
return json.dumps({"error": "Unknown tool"})
def vision_agent(image_url: str, user_message: str) -> str:
"""Run a multimodal agent that sees an image and calls tools."""
messages = [
{
"role": "system",
"content": "You are an OutdoorGear customer service agent. "
"When a customer shares a product image: identify the product, "
"then use tools to look it up or handle warranty claims.",
},
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": image_url}},
{"type": "text", "text": user_message},
],
},
]
# Agent loop: keep running until no more tool calls
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOLS,
max_tokens=500,
)
assistant_msg = response.choices[0].message
messages.append(assistant_msg)
if not assistant_msg.tool_calls:
return assistant_msg.content # Done!
# Execute tool calls
for tool_call in assistant_msg.tool_calls:
result = execute_tool(
tool_call.function.name,
json.loads(tool_call.function.arguments)
)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
# Demo: Customer submits a photo and asks for product info
TENT_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8e/Tent_at_sunset.jpg/320px-Tent_at_sunset.jpg"
print("=== Vision + Tool Calling Agent ===")
answer = vision_agent(
image_url=TENT_URL,
user_message="This is my tent from OutdoorGear. Can you tell me its warranty status?",
)
print(answer)
Parte 3: Comparação de Múltiplas Imagens¶
O GPT-4o pode analisar múltiplas imagens em uma única requisição:
# multi_image.py
import os
from openai import OpenAI
client = OpenAI(
base_url="https://models.inference.ai.azure.com",
api_key=os.environ["GITHUB_TOKEN"],
)
def compare_images(image_urls: list[str], comparison_question: str) -> str:
"""Compare multiple images in a single GPT-4o call."""
content = []
for i, url in enumerate(image_urls, 1):
content.append({"type": "text", "text": f"Image {i}:"})
content.append({"type": "image_url", "image_url": {"url": url}})
content.append({"type": "text", "text": comparison_question})
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are an OutdoorGear product expert."},
{"role": "user", "content": content},
],
max_tokens=500,
)
return response.choices[0].message.content
# Demo: Compare two tents for suitability
TENT_1 = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8e/Tent_at_sunset.jpg/320px-Tent_at_sunset.jpg"
TENT_2 = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/Bivouac_tent.jpg/320px-Bivouac_tent.jpg"
result = compare_images(
[TENT_1, TENT_2],
"Comparing these two tents: which appears more suitable for winter camping, "
"and which is lighter/smaller for backpacking? Explain your reasoning.",
)
print("=== Multi-Image Comparison ===")
print(result)
Parte 4: Melhores Práticas de Visão¶
Otimização de tokens¶
# Vision input token costs:
# "low" detail: ~85 tokens per image → use for product identification
# "high" detail: ~1000+ tokens per image → use for damage assessment, fine details
# Always specify detail level:
{
"type": "image_url",
"image_url": {
"url": url,
"detail": "low" # or "high" — choose based on task
}
}
Diretrizes de tamanho de imagem¶
| Caso de uso | Detalhe | Tamanho máximo da imagem |
|---|---|---|
| Identificação de produto | low |
Qualquer (redimensionado automaticamente) |
| Avaliação de danos | high |
2048×2048px ideal |
| Extração de texto (rótulos) | high |
Alta resolução necessária |
| Perguntas e respostas gerais | low |
Qualquer |
Segurança e moderação¶
def safe_vision_request(image_url: str, user_question: str) -> str:
"""Wraps the vision request with a system prompt that limits scope."""
# Always constrain vision agents to their domain
# Prevents misuse (e.g., asking about medical conditions in photos)
system = (
"You are an OutdoorGear product assistant. "
"You ONLY analyze outdoor equipment and gear in images. "
"If the image does not contain outdoor gear, respond: "
"'I can only help with OutdoorGear products. This image doesn't appear to show outdoor equipment.'"
)
# ... rest of request
🧠 Verificação de Conhecimento¶
1. Qual é a diferença entre detail: 'low' e detail: 'high' nas requisições de visão?
detail: 'low' redimensiona a imagem para 512×512 pixels e usa ~85 tokens — rápido e barato, adequado para identificação geral de produtos e compreensão de cenas. detail: 'high' divide a imagem em blocos de 512×512 e processa cada um com detalhe completo, usando ~1000+ tokens — necessário para ler texto pequeno (rótulos, números de série), detectar danos finos ou analisar detalhes intrincados. Sempre use low a menos que a tarefa exija explicitamente detalhe fino.
2. Por que combinar visão com chamada de ferramentas é mais poderoso do que apenas visão?
Agentes apenas com visão podem descrever o que veem, mas não podem tomar ação. Combinar visão com chamada de ferramentas significa: o agente vê uma mochila danificada → chama lookup_product() para identificá-la → chama initiate_return() para iniciar o processo de garantia — tudo em um único turno de conversa. O agente se torna um participante ativo em vez de apenas um narrador.
3. Qual é uma prática-chave de segurança ao implantar agentes multimodais?
Restrição de escopo via prompt de sistema: diga explicitamente ao modelo quais tipos de imagens ele deve e não deve processar. Sem isso, os usuários podem enviar imagens não relacionadas (médicas, pessoais, NSFW) e extrair respostas. Um prompt de sistema com escopo definido como "Apenas analise imagens de equipamentos outdoor — recuse qualquer outra coisa" reduz significativamente o uso indevido. Combine com APIs de moderação de conteúdo (Azure Content Safety) para implantações em produção.
Resumo¶
| Conceito | Implementação |
|---|---|
| Entrada de imagem por URL | "type": "image_url", "image_url": {"url": "..."} |
| Entrada de imagem em Base64 | "url": "data:image/jpeg;base64,<encoded>" |
| Controle de tokens | "detail": "low" (~85 tokens) ou "high" (1000+) |
| Visão + ferramentas | Mesmo loop de chamada de ferramentas dos agentes de texto |
| Múltiplas imagens | Múltiplos blocos image_url em um único array content |
Próximos Passos¶
- Adicionar streaming às respostas de visão: → Lab 019 — Streaming Responses
- Controle de custos para agentes com uso intenso de visão: → Lab 038 — AI Cost Optimization
- Avaliar a qualidade de agentes multimodais: → Lab 035 — Agent Evaluation