Alternar tema

Desenvolvimento de aplicações multimodais com IA: da escolha do modelo à implantação

Easton editorial illustration: multi-tenant AI service platform

Você talvez já use GPT-4 ou Claude para escrever código e revisar textos. Mas, quando o requisito passa a ser “analisar os dados desta captura de tela” ou “entender o conteúdo do vídeo enviado pelo usuário”, um modelo puramente textual deixa de ser suficiente. É justamente esse problema que a IA multimodal resolve: ela permite que o modelo leia texto e também “enxergue” imagens e vídeos.

No último ano, a IA multimodal avançou muito mais rápido do que se esperava. GPT-4o, Claude Vision e Gemini 1.5 Pro chegaram ao mercado, ampliando continuamente os limites dessa tecnologia. Para quem desenvolve, porém, a questão realmente importante não é “até onde a IA multimodal consegue chegar”, mas “como usá-la, qual modelo escolher e como controlar o custo”. Este artigo aborda cada uma dessas questões com foco na prática.


1. Conceitos fundamentais da IA multimodal

1.1 O que é IA multimodal

Em termos simples, uma IA multimodal é um modelo capaz de processar vários tipos de dados ao mesmo tempo. Modelos de texto tradicionais recebem apenas texto; modelos multimodais recebem texto, imagens, áudio ou vídeo e produzem o resultado solicitado.

Veja um exemplo: você envia a foto de um produto e pergunta “onde está a etiqueta de preço e qual é o valor?”. O modelo entende o conteúdo visual, localiza a área da etiqueta, lê os números e, por fim, responde. Em uma solução tradicional, seria preciso combinar três modelos — detecção de objetos, OCR e compreensão de texto. Agora, uma única chamada multimodal resolve tudo.

1.2 Evolução da arquitetura: de peças encaixadas à integração nativa

As primeiras soluções multimodais funcionavam, em sua maioria, como um conjunto de peças encaixadas: um codificador visual, como CLIP ou ViT, transformava a imagem em vetores, que depois eram enviados ao modelo de linguagem. O GPT-4V segue essa lógica, acrescentando um adaptador visual ao GPT-4.

O problema é que essa capacidade visual “acrescentada depois” costuma parecer desconectada. Ao interpretar uma imagem, o modelo essencialmente usa a lógica do modelo de linguagem para “adivinhar” o conteúdo visual e pode falhar em tarefas que exigem raciocínio visual aprofundado.

Os modelos multimodais nativos resolvem esse problema. GPT-4o e Gemini foram projetados desde o início para trabalhar com várias modalidades e processam texto, imagem e áudio de forma unificada nas camadas internas. A diferença é direta: modelos nativos têm desempenho claramente melhor em tarefas de raciocínio visual, como “comparar as diferenças entre duas imagens” ou “deduzir uma conclusão a partir de um gráfico”.

1.3 Tendências tecnológicas em 2025–2026

O ano de 2025 ficou conhecido como o “ano dos agentes”, e os recursos multimodais deixaram de ser um diferencial para se tornar um item padrão. Algumas tendências se destacam:

Avanço nos contextos longos. O Gemini 1.5 Pro aceita contextos com mais de 1 milhão de tokens e consegue processar, de uma só vez, mais de uma hora de vídeo. Antes, vídeos longos exigiam análise quadro a quadro e resumos por segmento; agora, o modelo pode “assistir a tudo” antes de responder.

Queda contínua dos custos. Modelos de código aberto avançam rapidamente. Em algumas tarefas, modelos chineses como Qwen2-VL e GLM-4V já se aproximam das opções proprietárias. Para cenários sensíveis a custo, a implantação privada se tornou uma alternativa viável.

Popularização dos agentes multimodais. O modelo não se limita mais a “olhar uma imagem e descrevê-la”: ele consegue executar ações com base no conteúdo visual. Um pedido como “olhe esta captura de tela e clique no botão de login” exige um ciclo completo de compreensão visual, uso de ferramentas e planejamento da tarefa.


2. Comparação e escolha dos principais modelos multimodais

Ao escolher um modelo, não considere apenas a posição em benchmarks. No desenvolvimento real, a estabilidade da API, o custo, a facilidade de integração e os requisitos de conformidade podem ser decisivos.

2.1 OpenAI: GPT-4V e GPT-4o

O GPT-4V foi a primeira solução multimodal da OpenAI e deu “olhos” ao GPT-4 por meio de um adaptador visual. O GPT-4o veio depois como uma versão multimodal nativa, com capacidade geral superior.

Quando escolher o GPT-4o?

  • Quando houver raciocínio visual, como deduzir conclusões a partir de uma imagem ou comparar diferenças
  • Em conversas multimodais com várias rodadas, nas quais a imagem mencionada antes continua em discussão
  • Quando a prioridade for a maior precisão possível

Quando escolher o GPT-4V?

  • Em tarefas simples de descrição ou classificação de imagens
  • Quando a latência for importante, pois o GPT-4V às vezes responde mais rápido
  • Para manter compatibilidade com sistemas antigos

As chamadas dos dois modelos são praticamente iguais:

from openai import OpenAI

client = OpenAI()

# Opção 1: usar a URL de uma imagem
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "O que aparece nesta imagem?"},
            {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}
        ]
    }]
)

# Opção 2: usar codificação Base64
import base64

with open("image.png", "rb") as f:
    image_data = base64.b64encode(f.read()).decode("utf-8")

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Analise esta imagem"},
            {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_data}"}}
        ]
    }]
)

print(response.choices[0].message.content)

2.2 Anthropic: Claude Vision

O Claude Vision se destaca na análise de documentos e na extração de detalhes. Quando é necessário extrair informações estruturadas de PDFs, gráficos ou capturas de tela, Claude é uma boa opção.

Cenários em que o Claude Vision se destaca:

  • Análise de documentos, incluindo PDF, materiais digitalizados e tabelas complexas
  • Extração de detalhes, com uma leitura mais “cuidadosa” que a de outros modelos
  • Processamento de documentos longos, com contexto de 200K

A chamada é um pouco diferente: Claude trata a imagem como um content block independente.

from anthropic import Anthropic
import base64

client = Anthropic()

# Ler a imagem e convertê-la para Base64
with open("document.png", "rb") as f:
    image_data = base64.b64encode(f.read()).decode("utf-8")

response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "image",
                "source": {
                    "type": "base64",
                    "media_type": "image/png",
                    "data": image_data
                }
            },
            {"type": "text", "text": "Extraia todos os dados das tabelas do documento e retorne-os em formato JSON"}
        ]
    }]
)

print(response.content[0].text)

2.3 Google: família Gemini

O grande diferencial do Gemini é o contexto longo. O Gemini 1.5 Pro aceita mais de 1 milhão de tokens e consegue processar vídeos muito extensos ou analisar vários documentos. Quando o cenário envolve grande volume de conteúdo visual, vale a pena experimentar o Gemini.

Cenários adequados:

  • Análise de vídeos longos, com mais de 10 minutos
  • Processamento em lote de vários documentos
  • Tarefas que precisam estabelecer relações entre conteúdos visuais

2.4 Opções de código aberto: Qwen2-VL e GLM-4V

Em cenários sensíveis a custo ou aos dados, ou quando é necessária uma implantação privada, modelos de código aberto são uma escolha prática.

Qwen2-VL: modelo aberto da Alibaba, otimizado para chinês e compatível com imagens em resolução 4K. Apresenta desempenho estável em aplicações empresariais e seu custo de chamada fica em torno de 1/10 do cobrado por modelos proprietários.

GLM-4V: modelo aberto da Zhipu, favorável à conformidade regulatória na China. Sua arquitetura MoE reduz o custo de inferência.

2.5 Matriz de decisão

Qual escolher? Comece pelos requisitos reais:

CenárioModelo recomendadoMotivo
Protótipo rápido ou MVPGPT-4oAPI madura, boa documentação e depuração simples
Análise de documentos e extração de dadosClaude VisionBoa capacidade de lidar com detalhes e reconhecer tabelas
Análise de vídeos longosGemini 1.5 ProContexto muito longo e raciocínio multimodal
Sensibilidade a custo e alta concorrênciaQwen2-VLCódigo aberto, maior controle e baixo custo por chamada
Dados sensíveis e implantação privadaGLM-4VImplantação local, sem enviar dados para fora do ambiente
Conteúdo em chinês e orçamento limitadoQwen2-VLOtimizado para chinês e com boa relação custo-benefício

3. Prática de compreensão e processamento de imagens

3.1 Fundamentos da chamada de API

O ponto central de uma API multimodal é construir corretamente o formato da mensagem. Seja com OpenAI ou Anthropic, a lógica é a mesma: enviar imagem e texto como partes diferentes da mensagem.

Também é preciso observar o tamanho da imagem. Os tokens são calculados com base nos pixels, portanto imagens maiores custam mais. A estratégia de redimensionamento automático do GPT-4o ajusta a imagem para uma resolução adequada, mas, para controlar o custo com precisão, é melhor tratá-la antes do envio.

3.2 Descrição de imagens e perguntas e respostas

O caso mais básico consiste em pedir ao modelo que descreva uma imagem ou responda a perguntas sobre ela. Abaixo está uma implementação completa para perguntas e respostas sobre imagens:

from openai import OpenAI
import base64
from pathlib import Path

class ImageAnalyzer:
    def __init__(self, model="gpt-4o"):
        self.client = OpenAI()
        self.model = model

    def analyze(self, image_path: str, question: str) -> str:
        """Analisa uma imagem e responde a uma pergunta"""
        # Ler a imagem
        with open(image_path, "rb") as f:
            image_data = base64.b64encode(f.read()).decode("utf-8")

        # Identificar o tipo da imagem
        suffix = Path(image_path).suffix.lower()
        media_type = {
            ".jpg": "image/jpeg",
            ".jpeg": "image/jpeg",
            ".png": "image/png",
            ".gif": "image/gif",
            ".webp": "image/webp"
        }.get(suffix, "image/jpeg")

        # Construir a requisição
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[{
                "role": "user",
                "content": [
                    {"type": "text", "text": question},
                    {"type": "image_url", "image_url": {
                        "url": f"data:{media_type};base64,{image_data}"
                    }}
                ]
            }],
            max_tokens=1000
        )

        return response.choices[0].message.content

# Exemplo de uso
analyzer = ImageAnalyzer()
result = analyzer.analyze("product.jpg", "Qual é a marca deste produto e quanto ele custa?")
print(result)

3.3 Análise de documentos (PDF e gráficos)

Ao processar um PDF, primeiro converta cada página em uma imagem e depois analise uma página por vez. Este é um analisador prático de documentos:

import fitz  # PyMuPDF
from PIL import Image
import io
import base64
from openai import OpenAI

def pdf_to_images(pdf_path: str, dpi: int = 150) -> list:
    """Converte um PDF em uma lista de imagens"""
    doc = fitz.open(pdf_path)
    images = []

    for page_num in range(len(doc)):
        page = doc[page_num]
        # Renderizar a página como imagem
        mat = fitz.Matrix(dpi / 72, dpi / 72)
        pix = page.get_pixmap(matrix=mat)

        # Converter para PIL Image
        img_data = pix.tobytes("png")
        img = Image.open(io.BytesIO(img_data))
        images.append(img)

    doc.close()
    return images

def extract_table_from_page(image: Image.Image, client: OpenAI) -> dict:
    """Extrai dados de tabelas da imagem de uma página"""
    # Converter para base64
    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    image_data = base64.b64encode(buffer.getvalue()).decode("utf-8")

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": """
                Extraia os dados das tabelas da imagem e retorne-os em formato JSON.
                Se houver várias tabelas, use um array.
                Exemplo de formato: {"tables": [{"headers": [...], "rows": [...]}]}
                """},
                {"type": "image_url", "image_url": {
                    "url": f"data:image/png;base64,{image_data}"
                }}
            ]
        }],
        response_format={"type": "json_object"}
    )

    import json
    return json.loads(response.choices[0].message.content)

# Fluxo completo
images = pdf_to_images("report.pdf")
for i, img in enumerate(images):
    print(f"Processando a página {i+1}...")
    tables = extract_table_from_page(img, OpenAI())
    print(f"Foram extraídas {len(tables.get('tables', []))} tabelas")

3.4 Processamento de imagens em lote

O controle de concorrência é importante ao processar muitas imagens. As APIs impõem limites de requisições, e disparar chamadas em paralelo sem controle provoca bloqueios:

import asyncio
from openai import AsyncOpenAI
import aiofiles
import base64

class BatchImageProcessor:
    def __init__(self, model="gpt-4o", max_concurrent=5):
        self.client = AsyncOpenAI()
        self.model = model
        self.semaphore = asyncio.Semaphore(max_concurrent)

    async def process_single(self, image_path: str, prompt: str) -> dict:
        """Processa uma única imagem"""
        async with self.semaphore:
            try:
                async with aiofiles.open(image_path, "rb") as f:
                    image_bytes = await f.read()
                image_data = base64.b64encode(image_bytes).decode("utf-8")

                response = await self.client.chat.completions.create(
                    model=self.model,
                    messages=[{
                        "role": "user",
                        "content": [
                            {"type": "text", "text": prompt},
                            {"type": "image_url", "image_url": {
                                "url": f"data:image/jpeg;base64,{image_data}"
                            }}
                        ]
                    }]
                )

                return {
                    "path": image_path,
                    "result": response.choices[0].message.content,
                    "success": True
                }
            except Exception as e:
                return {
                    "path": image_path,
                    "error": str(e),
                    "success": False
                }

    async def process_batch(self, image_paths: list, prompt: str) -> list:
        """Processa um lote de imagens"""
        tasks = [self.process_single(p, prompt) for p in image_paths]
        return await asyncio.gather(*tasks)

# Exemplo de uso
async def main():
    processor = BatchImageProcessor(max_concurrent=3)
    results = await processor.process_batch(
        ["img1.jpg", "img2.jpg", "img3.jpg"],
        "Descreva o conteúdo desta imagem em até 50 palavras"
    )
    for r in results:
        print(f"{r['path']}: {r.get('result', r.get('error'))}")

asyncio.run(main())

4. Prática de compreensão de vídeos

O ponto central do processamento de vídeo é “reduzir a dimensionalidade”: transformar as imagens contínuas da linha do tempo em quadros-chave discretos e depois analisar cada um. O desafio é equilibrar a integridade das informações com o custo de processamento.

4.1 Extração e processamento de quadros do vídeo

import cv2
import base64
from pathlib import Path

class VideoProcessor:
    def __init__(self, video_path: str):
        self.video_path = video_path
        self.cap = cv2.VideoCapture(video_path)
        self.fps = self.cap.get(cv2.CAP_PROP_FPS)
        self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.duration = self.total_frames / self.fps

    def extract_frames(self, strategy="interval", **kwargs):
        """Extrai quadros do vídeo

        Args:
            strategy: estratégia de extração
                - interval: extrair um quadro a cada N segundos
                - scene: extrair quando a cena mudar
                - uniform: extrair N quadros uniformemente
        """
        frames = []

        if strategy == "interval":
            interval_sec = kwargs.get("interval", 1.0)
            interval_frames = int(interval_sec * self.fps)

            frame_idx = 0
            while self.cap.isOpened():
                ret, frame = self.cap.read()
                if not ret:
                    break
                if frame_idx % interval_frames == 0:
                    frames.append((frame_idx / self.fps, frame))
                frame_idx += 1

        elif strategy == "uniform":
            num_frames = kwargs.get("num_frames", 10)
            interval = max(1, self.total_frames // num_frames)

            for i in range(num_frames):
                self.cap.set(cv2.CAP_PROP_POS_FRAMES, i * interval)
                ret, frame = self.cap.read()
                if ret:
                    frames.append((i * interval / self.fps, frame))

        self.cap.release()
        return frames

    def frame_to_base64(self, frame) -> str:
        """Converte um quadro para base64"""
        _, buffer = cv2.imencode('.jpg', frame)
        return base64.b64encode(buffer).decode('utf-8')

# Exemplo de uso
processor = VideoProcessor("demo.mp4")
print(f"Duração do vídeo: {processor.duration:.1f} segundos")

# Extrair um quadro a cada 2 segundos
frames = processor.extract_frames(strategy="interval", interval=2.0)
print(f"Foram extraídos {len(frames)} quadros")

4.2 Estratégias para compreender vídeos longos

O custo cresce rapidamente ao processar vídeos longos. Algumas estratégias práticas ajudam:

Processamento em camadas: primeiro examine rapidamente o vídeo em baixa resolução e com baixa taxa de quadros para identificar os trechos importantes. Depois, faça uma análise detalhada apenas desses trechos.

Detecção de cenas: processe apenas os quadros em que a cena muda e ignore imagens repetidas. O OpenCV oferece ferramentas para detectar cenas.

Resumo primeiro: peça ao modelo que resuma cada segmento e, no final, consolide todos os resumos em uma conclusão.

4.3 Exemplo prático: geração de resumo de vídeo

from openai import OpenAI

def generate_video_summary(frames: list, client: OpenAI) -> str:
    """Gera um resumo do vídeo com base nos quadros-chave"""
    # Dividir os quadros em lotes de no máximo 5
    batch_size = 5
    segment_summaries = []

    for i in range(0, len(frames), batch_size):
        batch = frames[i:i+batch_size]

        # Construir o conteúdo da mensagem
        content = [{"type": "text", "text": "Descreva de forma clara e concisa o que acontece nestas imagens"}]
        for timestamp, frame in batch:
            frame_base64 = VideoProcessor("").frame_to_base64(frame)
            content.append({
                "type": "image_url",
                "image_url": {"url": f"data:image/jpeg;base64,{frame_base64}"}
            })

        # Chamar a API
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": content}]
        )

        segment_summaries.append(response.choices[0].message.content)

    # Consolidar os resumos de todos os segmentos
    final_prompt = f"""
    Estes são os resumos dos segmentos do vídeo:
    {chr(10).join(f'{i+1}. {s}' for i, s in enumerate(segment_summaries))}

    Consolide as informações acima em um resumo completo do vídeo, incluindo:
    1. Conteúdo principal
    2. Eventos ou informações importantes
    3. Tema geral
    """

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": final_prompt}]
    )

    return response.choices[0].message.content

5. Otimização de custos e desempenho

O custo das chamadas multimodais vem principalmente dos tokens visuais. Uma imagem de 1024×1024 consome cerca de 765 tokens; sem o tratamento adequado, uma única requisição pode custar dezenas de yuans.

5.1 Cálculo dos tokens visuais

Regras de cálculo de tokens do GPT-4o:

Dimensões da imagemModo de baixa resoluçãoModo de alta resolução
512×51285 tokens255 tokens
1024×1024170 tokens765 tokens
2048×2048255 tokens2550 tokens

O modo de baixa resolução é adequado quando os detalhes não são necessários, como na classificação do tipo de imagem ou em uma descrição geral. Para ler texto ou identificar detalhes, é preciso usar o modo de alta resolução.

5.2 Compressão e pré-processamento de imagens

Pré-processar a imagem antes do envio é uma maneira eficaz de controlar custos:

from PIL import Image
from pathlib import Path

def optimize_image(image_path: str, max_size: int = 1024, quality: int = 85) -> str:
    """Otimiza as dimensões e a qualidade da imagem"""
    img = Image.open(image_path)

    # Ajustar as dimensões
    if max(img.size) > max_size:
        ratio = max_size / max(img.size)
        new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
        img = img.resize(new_size, Image.Resampling.LANCZOS)

    # Recortar a área importante, caso a posição seja conhecida
    # img = img.crop((left, top, right, bottom))

    # Salvar a imagem otimizada
    optimized_path = f"optimized_{Path(image_path).name}"
    img.save(optimized_path, "JPEG", quality=quality)

    return optimized_path

# Exemplo de uso
optimized = optimize_image("screenshot.png", max_size=1024)
# A imagem original pode ter 2 MB, enquanto a versão otimizada pode ter apenas 200 KB

5.3 Estratégias de cache e processamento em lote

Cache de resultados: é possível armazenar o resultado das consultas feitas com a mesma imagem. Use o hash da imagem como key:

import hashlib

def get_image_hash(image_path: str) -> str:
    """Calcula o hash da imagem"""
    with open(image_path, "rb") as f:
        return hashlib.md5(f.read()).hexdigest()

# Lógica do cache
cache = {}
image_hash = get_image_hash("product.jpg")

if image_hash in cache:
    result = cache[image_hash]
else:
    result = analyzer.analyze("product.jpg", "Descreva este produto")
    cache[image_hash] = result

Agrupamento em lote: quando houver várias imagens relacionadas, tente enviá-las em uma única requisição:

# Não recomendado: várias requisições
for img in images:
    result = analyze_image(img, "Descreva a imagem")

# Recomendado: uma única requisição
all_images_content = [{"type": "text", "text": "Descreva estas imagens"}]
for img in images:
    all_images_content.append({
        "type": "image_url",
        "image_url": {"url": img_url}
    })

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": all_images_content}]
)

5.4 Estratégia de combinação de modelos

Nem toda tarefa exige o modelo mais potente. É possível escolher um modelo por camada:

def smart_analyze(image_path: str, task_type: str):
    """Escolhe o modelo conforme o tipo de tarefa"""
    if task_type in ["classify", "detect"]:
        # Usar um modelo pequeno para classificação e detecção simples
        model = "gpt-4o-mini"
    elif task_type in ["ocr", "extract"]:
        # Usar um modelo intermediário para OCR e extração de dados
        model = "gpt-4o"
    else:
        # Usar um modelo potente para raciocínio complexo
        model = "gpt-4o"

    # ... lógica da chamada

6. Boas práticas para implantação em produção

Para passar de uma demonstração ao ambiente de produção, é preciso considerar vários outros aspectos de engenharia.

6.1 Tratamento de erros e mecanismo de novas tentativas

Uma chamada de API pode falhar a qualquer momento por timeout de rede, limite de requisições ou erro do servidor. É indispensável implementar um tratamento de erros robusto:

import time
from openai import APIError, RateLimitError, APIConnectionError

def robust_api_call(func, max_retries=3, backoff_factor=2):
    """Chamada de API com mecanismo de novas tentativas"""
    for attempt in range(max_retries):
        try:
            return func()
        except RateLimitError:
            if attempt < max_retries - 1:
                wait_time = backoff_factor ** attempt
                print(f"Limite de requisições atingido. Nova tentativa em {wait_time} segundos...")
                time.sleep(wait_time)
            else:
                raise
        except APIConnectionError as e:
            print(f"Erro de conexão de rede: {e}")
            if attempt < max_retries - 1:
                time.sleep(1)
            else:
                raise
        except APIError as e:
            print(f"Erro da API: {e}")
            raise

6.2 Controle de concorrência e limite de requisições

Os limites das APIs multimodais costumam ser mais restritos que os de APIs de texto. Implemente um limitador do tipo token bucket:

import asyncio
import time

class RateLimiter:
    def __init__(self, requests_per_minute: int):
        self.interval = 60.0 / requests_per_minute
        self.last_request = 0
        self.lock = asyncio.Lock()

    async def acquire(self):
        async with self.lock:
            now = time.time()
            wait_time = self.last_request + self.interval - now
            if wait_time > 0:
                await asyncio.sleep(wait_time)
            self.last_request = time.time()

# Exemplo de uso
limiter = RateLimiter(requests_per_minute=100)

async def process_with_limit(image_path):
    await limiter.acquire()
    return await async_analyze(image_path)

6.3 Monitoramento e logs

Registre as principais informações de cada chamada para facilitar a investigação de problemas:

import logging
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def log_api_call(model: str, input_tokens: int, output_tokens: int, latency: float):
    logger.info(
        f"Chamada de API - modelo: {model}, "
        f"tokens de entrada: {input_tokens}, tokens de saída: {output_tokens}, "
        f"latência: {latency:.2f}s"
    )

# Registrar após a chamada
start_time = time.time()
response = client.chat.completions.create(...)
latency = time.time() - start_time

log_api_call(
    model="gpt-4o",
    input_tokens=response.usage.prompt_tokens,
    output_tokens=response.usage.completion_tokens,
    latency=latency
)

6.4 Exemplo de código completo

A seguir, reunimos o conteúdo anterior em uma classe utilitária pronta para uso:

from openai import OpenAI
from pathlib import Path
import base64
import logging
import time
from typing import Optional, List, Dict

logger = logging.getLogger(__name__)

class MultimodalAnalyzer:
    """Classe utilitária para análise multimodal"""

    def __init__(
        self,
        model: str = "gpt-4o",
        max_retries: int = 3,
        requests_per_minute: int = 100
    ):
        self.client = OpenAI()
        self.model = model
        self.max_retries = max_retries
        self.min_interval = 60.0 / requests_per_minute
        self.last_request_time = 0

    def _wait_for_rate_limit(self):
        """Aplica o limite de requisições"""
        now = time.time()
        wait_time = self.last_request_time + self.min_interval - now
        if wait_time > 0:
            time.sleep(wait_time)
        self.last_request_time = time.time()

    def _read_image(self, image_path: str) -> str:
        """Lê uma imagem e a converte para base64"""
        with open(image_path, "rb") as f:
            return base64.b64encode(f.read()).decode("utf-8")

    def _call_with_retry(self, messages: list) -> dict:
        """Faz uma chamada de API com novas tentativas"""
        for attempt in range(self.max_retries):
            try:
                self._wait_for_rate_limit()
                start_time = time.time()

                response = self.client.chat.completions.create(
                    model=self.model,
                    messages=messages,
                    max_tokens=1000
                )

                latency = time.time() - start_time
                logger.info(
                    f"Chamada de API concluída - tokens: {response.usage.total_tokens}, "
                    f"latência: {latency:.2f}s"
                )

                return {
                    "content": response.choices[0].message.content,
                    "tokens": {
                        "prompt": response.usage.prompt_tokens,
                        "completion": response.usage.completion_tokens
                    }
                }

            except Exception as e:
                logger.error(f"Falha na chamada da API (tentativa {attempt + 1}/{self.max_retries}): {e}")
                if attempt == self.max_retries - 1:
                    raise
                time.sleep(2 ** attempt)

    def analyze_image(
        self,
        image_path: str,
        prompt: str,
        detail: str = "auto"
    ) -> dict:
        """Analisa uma única imagem"""
        image_data = self._read_image(image_path)

        messages = [{
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image_data}",
                        "detail": detail
                    }
                }
            ]
        }]

        return self._call_with_retry(messages)

    def analyze_multiple_images(
        self,
        image_paths: List[str],
        prompt: str
    ) -> dict:
        """Analisa várias imagens"""
        content = [{"type": "text", "text": prompt}]

        for path in image_paths:
            image_data = self._read_image(path)
            content.append({
                "type": "image_url",
                "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
            })

        return self._call_with_retry([{"role": "user", "content": content}])

    def extract_text_from_image(self, image_path: str) -> str:
        """Extrai texto de uma imagem (OCR)"""
        result = self.analyze_image(
            image_path,
            "Extraia todo o texto da imagem e preserve o formato original"
        )
        return result["content"]

    def describe_image(self, image_path: str) -> str:
        """Gera uma descrição da imagem"""
        result = self.analyze_image(
            image_path,
            "Descreva o conteúdo desta imagem em um parágrafo"
        )
        return result["content"]


# Exemplo de uso
if __name__ == "__main__":
    analyzer = MultimodalAnalyzer()

    # Analisar uma única imagem
    result = analyzer.analyze_image(
        "product.jpg",
        "Qual é a marca deste produto e quanto ele custa?"
    )
    print(result["content"])

    # Extrair texto com OCR
    text = analyzer.extract_text_from_image("document.png")
    print(text)

Conclusão

A IA multimodal está deixando de ser um “brinquedo curioso” para se tornar uma ferramenta prática. Ao escolher um modelo, não considere apenas o benchmark: relacione a escolha ao cenário concreto. Para analisar vídeos longos, use Gemini; para documentos, Claude; para protótipos rápidos, GPT-4o; e, quando o custo for decisivo, avalie opções de código aberto.

Durante o desenvolvimento, controlar custos é fundamental. Pré-processar imagens, escolher a resolução certa e implementar cache são medidas capazes de reduzir bastante as despesas. No ambiente de produção, tratamento de erros, limite de requisições e logs de monitoramento são indispensáveis.

Os limites da IA multimodal continuam avançando. Algumas áreas merecem atenção em 2025: a popularização de agentes multimodais, contextos cada vez mais longos e a evolução contínua dos modelos abertos. Dominar essas habilidades fundamentais ajuda você a acompanhar rapidamente as próximas mudanças tecnológicas.


Referências

FAQ

Devo escolher o GPT-4o ou o GPT-4V?
Escolha o GPT-4o quando precisar de raciocínio visual ou conversas multimodais em várias rodadas. Para descrição simples de imagens, baixa latência ou compatibilidade com sistemas antigos, escolha o GPT-4V.
Como controlar o custo de uma API multimodal?
Há três estratégias principais:

• Pré-processar as imagens: comprimir as dimensões e reduzir a resolução antes do envio
• Escolher a resolução adequada: usar o modo de baixa resolução quando os detalhes não forem necessários
• Implementar cache: armazenar os resultados de consultas feitas com a mesma imagem
Quais técnicas ajudam a processar vídeos longos?
Use processamento em camadas: primeiro examine o vídeo com baixa taxa de quadros para identificar os trechos importantes. Aplique detecção de cenas para processar apenas mudanças visuais e resuma cada segmento antes de consolidar tudo. O Gemini 1.5 Pro aceita contextos muito longos e pode processar vídeos extensos de uma vez.
É possível usar modelos multimodais de código aberto?
Sim. O Qwen2-VL é otimizado para chinês, enquanto o GLM-4V favorece a conformidade regulatória na China. Em cenários sensíveis a custo ou que exigem implantação privada, modelos abertos são uma opção prática, com custo de chamada em torno de 1/10 do cobrado por modelos proprietários.
O que é preciso preparar para o ambiente de produção?
Há três pontos centrais: novas tentativas em caso de erro, para lidar com timeout de rede e limite de requisições; controle de concorrência, com um limitador do tipo token bucket para evitar bloqueios; e logs de monitoramento, registrando os tokens e a latência de cada chamada.

19 min de leitura · Publicado em: 24 mar 2026 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog