open33 API

TutoriaisAtualizado em 8 min de leitura

Migrar da OpenAI para o Claude: o que muda no código

Se o seu app usa a API da OpenAI, trocar para o Claude Opus 5 pede poucas mudanças: outro SDK, o prompt de sistema num campo próprio, o max_tokens obrigatório e a resposta dividida em blocos.

Este guia mostra cada mudança com o código de antes e o de depois, em Python e em TypeScript. Os exemplos usam o SDK oficial da Anthropic, que fala o formato nativo da API do Claude e dá acesso a todos os recursos dela.

O que muda, em uma tabela

Na OpenAI No Claude
pip install openai pip install anthropic
OPENAI_API_KEY ANTHROPIC_API_KEY e ANTHROPIC_BASE_URL
chat.completions.create() messages.create()
Mensagem com "role": "system" Parâmetro system
Limite de saída opcional max_tokens obrigatório
choices[0].message.content Blocos em content: use os de type == "text"
finish_reason stop_reason
usage.prompt_tokens e completion_tokens usage.input_tokens e output_tokens
stop stop_sequences
temperature, top_p Não existem no Claude Opus 5: tire do código
Ferramentas com function.parameters Ferramentas com input_schema
Resultado de ferramenta com "role": "tool" Bloco tool_result numa mensagem user
response_format com JSON Schema output_config.format com JSON Schema

1. Instale o SDK e configure a chave

pip install anthropic

Para TypeScript e Node.js, npm install @anthropic-ai/sdk.

O SDK lê a URL base e a chave das variáveis de ambiente:

export ANTHROPIC_BASE_URL="https://api.open33.tech"
export ANTHROPIC_API_KEY="SUA_CHAVE"

No Windows, use $env:ANTHROPIC_BASE_URL = "..." no PowerShell. Os dois SDKs, o da OpenAI e o da Anthropic, podem ficar instalados juntos, o que permite migrar uma função de cada vez.

2. A chamada básica

Antes, com a OpenAI:

from openai import OpenAI

client = OpenAI()

resposta = client.chat.completions.create(
    model="gpt-4o",
    temperature=0.3,
    messages=[
        {"role": "system", "content": "Você é o suporte da loja. Responda em português, em até 3 frases."},
        {"role": "user", "content": "Qual o prazo para trocar um produto?"},
    ],
)
print(resposta.choices[0].message.content)

Depois, com o Claude:

import anthropic

client = anthropic.Anthropic()  # lê ANTHROPIC_BASE_URL e ANTHROPIC_API_KEY

resposta = client.messages.create(
    model="claude-opus-5",
    max_tokens=16000,
    system="Você é o suporte da loja. Responda em português, em até 3 frases.",
    messages=[
        {"role": "user", "content": "Qual o prazo para trocar um produto?"},
    ],
)
texto = "".join(b.text for b in resposta.content if b.type == "text")
print(texto)

O que mudou:

Se o seu código usa a API Responses da OpenAI (client.responses.create), a troca é parecida: instructions vira system, input vira messages e output_text vira a junção dos blocos de texto.

3. O mesmo em TypeScript

Antes:

import OpenAI from "openai";

const client = new OpenAI();

const resposta = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [
    { role: "system", content: "Você é o suporte da loja. Responda em português." },
    { role: "user", content: "Qual o prazo para trocar um produto?" },
  ],
});
console.log(resposta.choices[0].message.content);

Depois:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic(); // lê ANTHROPIC_BASE_URL e ANTHROPIC_API_KEY

const resposta = await client.messages.create({
  model: "claude-opus-5",
  max_tokens: 16000,
  system: "Você é o suporte da loja. Responda em português.",
  messages: [{ role: "user", content: "Qual o prazo para trocar um produto?" }],
});
const texto = resposta.content
  .filter((b): b is Anthropic.TextBlock => b.type === "text")
  .map((b) => b.text)
  .join("");
console.log(texto);

4. Históricos salvos no formato da OpenAI

Se você guarda conversas no banco no formato da OpenAI, com o sistema dentro da lista, não precisa converter tudo de uma vez. Esta função separa o sistema e devolve o resto no formato do Claude:

def para_o_claude(mensagens_openai: list[dict]) -> dict:
    """Converte uma conversa de texto no formato da OpenAI para os parâmetros do Claude."""
    sistema = "\n\n".join(m["content"] for m in mensagens_openai if m["role"] == "system")
    conversa = [
        {"role": m["role"], "content": m["content"]}
        for m in mensagens_openai
        if m["role"] in ("user", "assistant")
    ]
    parametros = {"messages": conversa}
    if sistema:
        parametros["system"] = sistema
    return parametros


historico = [
    {"role": "system", "content": "Você é o suporte da loja."},
    {"role": "user", "content": "Oi, comprei um tênis ontem."},
    {"role": "assistant", "content": "Oi! Como posso ajudar com o seu tênis?"},
    {"role": "user", "content": "Posso trocar o tamanho?"},
]

resposta = client.messages.create(model="claude-opus-5", max_tokens=16000, **para_o_claude(historico))

A função cobre conversas de texto. Se o histórico tem chamadas de ferramenta (tool_calls e "role": "tool"), converta essas mensagens para blocos tool_use e tool_result, no formato da seção 6.

A API do Claude, como a da OpenAI, não guarda a conversa: você manda o histórico inteiro a cada chamada. A primeira mensagem da lista precisa ser do usuário.

5. Streaming

Antes:

stream = client.chat.completions.create(model="gpt-4o", messages=mensagens, stream=True)
for pedaco in stream:
    print(pedaco.choices[0].delta.content or "", end="", flush=True)

Depois:

with client.messages.stream(
    model="claude-opus-5",
    max_tokens=16000,
    system="Você é o suporte da loja.",
    messages=mensagens,
) as stream:
    for texto in stream.text_stream:
        print(texto, end="", flush=True)

O text_stream entrega só o texto, sem os blocos de raciocínio. O guia de streaming na API do Claude mostra como mandar o texto para o navegador.

6. Ferramentas (function calling)

No Claude, function calling se chama tool use. A definição da ferramenta perde um nível e troca parameters por input_schema:

Antes:

ferramentas_openai = [{
    "type": "function",
    "function": {
        "name": "consultar_pedido",
        "description": "Consulta o status de um pedido pelo número.",
        "parameters": {
            "type": "object",
            "properties": {"numero": {"type": "string"}},
            "required": ["numero"],
        },
    },
}]

Depois:

ferramentas = [{
    "name": "consultar_pedido",
    "description": "Consulta o status de um pedido pelo número.",
    "input_schema": {
        "type": "object",
        "properties": {"numero": {"type": "string"}},
        "required": ["numero"],
    },
}]

Se você tem muitas ferramentas, converta automaticamente:

def converter_ferramentas(ferramentas_openai: list[dict]) -> list[dict]:
    return [
        {
            "name": f["function"]["name"],
            "description": f["function"].get("description", ""),
            "input_schema": f["function"].get("parameters", {"type": "object", "properties": {}}),
        }
        for f in ferramentas_openai
    ]

O loop também muda. No Claude, ele fica assim:

import json

def consultar_pedido(numero: str) -> dict:
    return {"numero": numero, "status": "em transporte", "previsao": "2026-10-02"}

FUNCOES = {"consultar_pedido": consultar_pedido}

mensagens = [{"role": "user", "content": "Cadê o meu pedido 10231?"}]

while True:
    resposta = client.messages.create(
        model="claude-opus-5",
        max_tokens=16000,
        tools=ferramentas,
        messages=mensagens,
    )
    if resposta.stop_reason != "tool_use":
        break

    mensagens.append({"role": "assistant", "content": resposta.content})
    resultados = []
    for bloco in resposta.content:
        if bloco.type == "tool_use":
            saida = FUNCOES[bloco.name](**bloco.input)
            resultados.append({
                "type": "tool_result",
                "tool_use_id": bloco.id,
                "content": json.dumps(saida, ensure_ascii=False),
            })
    mensagens.append({"role": "user", "content": resultados})

print("".join(b.text for b in resposta.content if b.type == "text"))

As diferenças que mais causam erro:

O exemplo completo, com tratamento de erros, está no guia de tool use na API do Claude.

7. Respostas em JSON

Antes:

response_format={
    "type": "json_schema",
    "json_schema": {"name": "pedido", "schema": esquema, "strict": True},
}

Depois:

output_config={"format": {"type": "json_schema", "schema": esquema}}

Com o output_config, o bloco de texto da resposta traz um JSON que segue o esquema. Todo objeto do esquema precisa de "additionalProperties": false. Se você usava o modo JSON simples ({"type": "json_object"}), escreva um esquema: é mais seguro. O passo a passo, com Pydantic e Zod, está no guia de respostas em JSON na API do Claude.

8. Erros e novas tentativas

Os nomes das exceções são os mesmos nos dois SDKs, só muda o pacote: openai.RateLimitError vira anthropic.RateLimitError, e o mesmo vale para AuthenticationError, BadRequestError, APIConnectionError e APIStatusError.

Os dois SDKs já tentam de novo sozinhos, duas vezes, em erros de conexão, 429 e 5xx. A API do Claude tem um erro a mais, o 529, quando a API está sobrecarregada. O guia de erros da API do Claude explica cada código.

Checklist da migração

  1. Trocar o SDK e as variáveis da chave.
  2. Mover o prompt de sistema para o parâmetro system.
  3. Definir o max_tokens com folga.
  4. Tirar temperature e top_p, e os parâmetros que a API do Claude não tem, como frequency_penalty, presence_penalty, n e seed.
  5. Ler a resposta pelos blocos de texto.
  6. Converter as ferramentas e o loop de chamadas.
  7. Trocar o response_format pelo output_config.
  8. Testar com conversas reais e ajustar os prompts que mudarem de comportamento.

Perguntas frequentes

Quanto trabalho dá migrar da OpenAI para o Claude?

Numa integração simples, as mudanças cabem em poucas linhas: o SDK, o system, o max_tokens e a leitura da resposta. Ferramentas e JSON pedem mais ajustes, mas a lógica é a mesma.

Preciso reescrever os meus prompts?

Na maioria dos casos, não. Os prompts funcionam como estão, mas vale testar com conversas reais e ajustar o que mudar de comportamento, como o tamanho das respostas.

Por que dá erro 400 com temperature?

O Claude Opus 5 não aceita temperature, top_p nem top_k. Tire esses parâmetros e controle o estilo da resposta pelo prompt.

O Claude tem function calling?

Tem, com o nome de tool use. A ferramenta usa input_schema no lugar de parameters, os argumentos chegam como dicionário e os resultados voltam em blocos tool_result.

Posso usar a OpenAI e o Claude no mesmo projeto?

Pode. Os dois SDKs convivem no mesmo projeto, cada um com a sua chave. Isso permite migrar aos poucos, uma função de cada vez, e comparar as respostas.

Continue lendo