Ollama: API Compatível com OpenAI
A API compatível com OpenAI é a ponte para migração — mude duas linhas de código, passe de nuvem para local.
base_url para http://localhost:11434/v1, definir api_key para qualquer string não vazia (por exemplo, "ollama"), e mudar o nome do Modelo para um Modelo local (por exemplo, "qwen2.5"). Você pode então reutilizar todo o código existente da biblioteca openai. Isso significa que suas aplicações ChatGPT existentes, projetos LangChain e fluxos AutoGen podem mudar para local com zero alterações de código.
📋 Pré-requisitos: Você precisa dominar o seguinte primeiro
- Aula 5: Fundamentos da API REST
1. O Que Você Vai Aprender
- Mapeamento dos endpoints
/v1/chat/completionse/v1/embeddings - Troca da biblioteca openai do Python para Ollama
- Diferenças de compatibilidade e limitações de recursos
- Migração na prática: convertendo uma aplicação ChatGPT para backend Ollama
- Estudo de caso da Alice: economizando 2.000 USD/mês
2. Uma História Real de Uma Fundadora de SaaS
/api/chat diretamente como alternativa.
api_key="ollama" é uma convenção placeholder para o endpoint compatível do Ollama — o Ollama não valida o conteúdo da API Key. Isso significa que api_key pode ser qualquer string (por exemplo, "sk-1234", "dummy"), com funcionalidade idêntica. Autenticação real deve ser implementada na camada de proxy reverso.
(1) O Problema: Custo Mensal do GPT-4 de 2.000 USD
O SupportBot da Alice usa a API do GPT-4, processando 5 milhões de tokens por mês com uma conta de 2.000 USD. A empresa exige redução de custos, mas a migração requer reescrever muito código — todas as chamadas estão vinculadas à biblioteca openai do Python.
(2) A Solução: Mude para Local com Duas Linhas de Código
A API compatível com OpenAI permite que Alice mude apenas base_url e api_key, completando a migração em 5 minutos:
from openai import OpenAI
# Before: OpenAI cloud
# client = OpenAI(api_key="sk-xxx")
# After: Ollama local (only 2 lines changed!)
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
3. Mapeamento de Endpoints Compatíveis
api_key="ollama" é uma convenção placeholder para a API compatível do Ollama — o Ollama não valida a API Key. Isso significa que qualquer pessoa que saiba o endereço do seu Ollama pode chamá-lo. Em produção, sempre adicione autenticação real por Key via proxy reverso como Nginx.
(1) Referência de Endpoints da API
| Endpoint OpenAI | Endpoint Compatível do Ollama | Status |
|---|---|---|
/v1/chat/completions |
✅ Totalmente compatível | Uso principal |
/v1/completions |
✅ Compatível | Completions legadas |
/v1/embeddings |
✅ Compatível | Embedding Vetorial |
/v1/models |
✅ Compatível | Listagem de Modelos |
/v1/images/generations |
❌ Não suportado | Geração de imagens |
/v1/audio/transcriptions |
❌ Não suportado | Transcrição de áudio |
flowchart LR
A[OpenAI SDK] -->|troca base_url| B[Ollama /v1/...]
B --> C[/v1/chat/completions]
B --> D[/v1/completions]
B --> E[/v1/embeddings]
B --> F[/v1/models]
B --> G[/v1/images ❌]
(2) Diferenças de Compatibilidade Detalhadas
| Recurso | OpenAI | Ollama Compatível | Diferença |
|---|---|---|---|
| Streaming | Formato SSE | ✅ Compatível SSE | Consistente |
| Function Calling | Suporte completo | ⚠️ Suporte parcial | Alguns Modelos suportam |
| JSON Mode | response_format | ✅ format=json | Consistente |
| Embeddings | text-embedding-3 | ✅ Usa nomic-embed-text | Modelo diferente |
| Vision | gpt-4o vision | ✅ Usa llava | Modelo diferente |
| Fine-tuning | Suportado | ❌ | Use Modelfile em vez |
| Rate Limiting | RPM/TPM | ❌ | Requer implementação externa |
▶ Exemplo 1: Troca Básica da Biblioteca openai
from openai import OpenAI
# Point to local Ollama server
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama" # Any non-empty string works
)
# Chat completion (identical to OpenAI API usage)
response = client.chat.completions.create(
model="qwen2.5",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain RAG in 2 sentences"}
],
temperature=0.3
)
print(response.choices[0].message.content)
Saída:
# Execution successful
4. Migração de Recursos Centrais
(1) Referência de Migração de Recursos
| Recurso | Código OpenAI | Mudança Ollama | Esforço |
|---|---|---|---|
| Chat | model="gpt-4" |
model="qwen2.5" |
Mudar nome do Modelo |
| Streaming | stream=True |
Sem mudança necessária | Zero mudanças |
| Embeddings | model="text-embedding-3-small" |
model="nomic-embed-text" |
Mudar nome do Modelo |
| JSON Mode | response_format={"type": "json_object"} |
Mesmo | Sem mudança necessária |
| System Prompt | messages=[{"role":"system"...}] |
Mesmo | Zero mudanças |
| Function Calling | tools=[...] |
⚠️ Suporte parcial do Modelo | Precisa de teste |
▶ Exemplo 2: Migração de Saída Streaming
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama"
)
# Streaming works identically
stream = client.chat.completions.create(
model="qwen2.5",
messages=[{"role": "user", "content": "Write a short poem about AI"}],
stream=True,
temperature=0.7
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
Saída:
# Execution successful
▶ Exemplo 3: Migração de Embeddings
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama"
)
# Embeddings: just change the model name
response = client.embeddings.create(
model="nomic-embed-text", # Was: text-embedding-3-small
input="What is the return policy for electronics?"
)
print(f"Embedding dimension: {len(response.data[0].embedding)}")
# 768 dimensions for nomic-embed-text
Saída:
# Execution successful
▶ Exemplo 4: Migração do Modo JSON
from openai import OpenAI
import json
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama"
)
# JSON mode: identical API
response = client.chat.completions.create(
model="qwen2.5",
messages=[
{"role": "system", "content": "You are a product catalog API."},
{"role": "user", "content": "List 3 laptops under $500"}
],
response_format={"type": "json_object"},
temperature=0.3
)
data = json.loads(response.choices[0].message.content)
print(json.dumps(data, indent=2))
Saída:
# Execution successful
5. Migração na Prática e Limitações de Compatibilidade
/api/chat diretamente como alternativa.
(1) Checklist de Migração
| Item de Verificação | Descrição | Risco |
|---|---|---|
| Nome do Modelo | gpt-4 → qwen2.5/llama3.1 | Precisa de avaliação de qualidade |
| Function Calling | Testar se funciona corretamente | Pode falhar parcialmente |
| Contexto máximo | gpt-4: 128K → varia por Modelo local | Notar num_ctx |
| Limite de tokens de saída | Parâmetro max_tokens | Precisa de teste |
| Rate limiting | OpenAI RPM → local sem limite | Deve construir você mesmo |
| Concorrência | OpenAI alta concorrência → local limitado | Precisa de escalabilidade |
(2) Alternativas para Recursos Incompatíveis
| Recurso OpenAI | Alternativa Ollama | Implementação |
|---|---|---|
| Fine-tuning | Modelfile | Detalhado na Aula 8 |
| Function Calling | Prompt + parsing JSON | Implementação manual |
| Geração de Imagens | llava (apenas compreensão) | Geração não suportada |
| Batch API | Scripts Shell/Python | Auto-orquestração |
| Assistants API | Agente LangChain | Detalhado na Aula 13 |
▶ Exemplo 5: Alternativa ao Function Calling
from openai import OpenAI
import json
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama"
)
# Instead of Function Calling, use JSON mode + prompt
tools = {
"get_order_status": {"order_id": "string"},
"process_refund": {"order_id": "string", "amount": "number"},
"search_products": {"query": "string", "category": "string"}
}
response = client.chat.completions.create(
model="qwen2.5",
messages=[
{"role": "system", "content": f"""You are a customer service bot.
Available tools: {json.dumps(tools)}
If you need to call a tool, respond with JSON: {{"tool": "name", "args": {{...}}}}
Otherwise, respond normally."""},
{"role": "user", "content": "Where is my order #12345?"}
],
response_format={"type": "json_object"},
temperature=0.2
)
result = json.loads(response.choices[0].message.content)
if "tool" in result:
print(f"Call tool: {result['tool']} with args: {result['args']}")
# Call: get_order_status with {"order_id": "12345"}
else:
print("Direct response:", result)
Saída:
Direct response:
6. Exemplo Abrangente: Migração do SupportBot do GPT-4
# ============================================
# Comprehensive: SupportBot migration
# From GPT-4 to local Ollama with OpenAI SDK
# ============================================
from openai import OpenAI
import json
from dataclasses import dataclass
from typing import Optional
@dataclass
class SupportBotMigrator:
"""SupportBot with easy OpenAI/Ollama switching."""
# Change these 2 lines to switch between cloud and local
base_url: str = "http://localhost:11434/v1"
api_key: str = "ollama"
model: str = "qwen2.5"
temperature: float = 0.4
def __post_init__(self):
self.client = OpenAI(
base_url=self.base_url,
api_key=self.api_key
)
self.system_prompt = (
"You are SupportBot, an e-commerce customer service agent. "
"Be polite, concise, and helpful. "
"For order queries, ask for order number. "
"If unsure, say 'Let me connect you with a human agent.'"
)
self.messages = [
{"role": "system", "content": self.system_prompt}
]
def chat(self, user_input: str) -> str:
self.messages.append({"role": "user", "content": user_input})
try:
response = self.client.chat.completions.create(
model=self.model,
messages=self.messages[-10:], # sliding window
temperature=self.temperature
)
reply = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": reply})
return reply
except Exception as e:
self.messages.pop()
return f"Error: {e}"
def classify_intent(self, user_input: str) -> dict:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": """Classify the customer intent.
Return JSON: {"intent": "order_status|refund|product_query|shipping|other", "confidence": 0.0-1.0}"""},
{"role": "user", "content": user_input}
],
response_format={"type": "json_object"},
temperature=0.1
)
return json.loads(response.choices[0].message.content)
def generate_embedding(self, text: str) -> list[float]:
response = self.client.embeddings.create(
model="nomic-embed-text",
input=text
)
return response.data[0].embedding
# Usage - compare cloud vs local
if __name__ == "__main__":
# Local Ollama (current config)
bot = SupportBotMigrator()
# Switch to OpenAI cloud (uncomment to use)
# bot = SupportBotMigrator(
# base_url="https://api.openai.com/v1",
# api_key="sk-your-key",
# model="gpt-4"
# )
queries = [
"Where is my order #88765?",
"I want a refund for damaged goods",
"Does this laptop have HDMI port?"
]
for q in queries:
intent = bot.classify_intent(q)
reply = bot.chat(q)
print(f"Q: {q}")
print(f"Intent: {intent}")
print(f"A: {reply[:100]}...")
print()
❓ Perguntas Frequentes
P: O que devo preencher em api_key? R: O Ollama não valida a API Key — qualquer string não vazia funciona (por exemplo, "ollama"). Mas você deve fornecer uma, caso contrário a biblioteca openai lançará um erro.
P: Function Calling pode ser usado? R: Parcialmente suportado por alguns Modelos (por exemplo, qwen2.5, llama3.1), mas menos estável que o GPT-4. Recomendamos usar modo JSON + prompt para simular Function Calling para mais controle.
P: E se a qualidade degradar após a migração? R: Use uma estratégia híbrida — perguntas simples usam o Modelo 8B local, perguntas complexas recorrem ao GPT-4. Um Modelo 8B cobrindo 80% dos cenários já pode reduzir significativamente os custos.
P: Dimensões diferentes de Embedding afetam o RAG? R: Sim. nomic-embed-text produz 768 dimensões; OpenAI text-embedding-3-small produz 1536 dimensões. Migrar um sistema RAG requer reconstruir o índice Vetorial.
P: Como uso OpenAI e Ollama simultaneamente? R: Instancie dois clientes — um apontando para OpenAI, outro para Ollama. Roteeie requisições para diferentes clientes com base em complexidade ou orçamento.
P: Há diferença de desempenho entre os endpoints /v1 e /api do Ollama? R: Não. O endpoint /v1 é uma camada de compatibilidade sobre o endpoint /api, chamando o mesmo motor de Inferência subjacente. O desempenho é idêntico.
📖 Resumo
- O Ollama fornece /v1/chat/completions e outros endpoints compatíveis com OpenAI, cobrindo Chat/Embeddings/Models
- Migração requer mudar apenas base_url e api_key — duas linhas de código
- Function Calling é parcialmente suportado; modo JSON é recomendado como alternativa
- Modelos de Embedding diferem; migrar RAG requer reconstruir o índice Vetorial
- Estratégia híbrida: 80% local + 20% nuvem para máximo custo-benefício
- Alice economiza 2.000 USD/mês, completando a migração em 5 minutos
📝 Exercícios
- Básico (Dificuldade ⭐): Conecte ao Ollama usando a biblioteca openai do Python, complete uma chamada chat.completions e verifique a compatibilidade.
- Intermediário (Dificuldade ⭐⭐): Migre um script OpenAI existente (com saída streaming e modo JSON) para Ollama, documentando as mudanças necessárias.
- Avançado (Dificuldade ⭐⭐⭐): Implemente um roteador de duplo backend que automaticamente roteia para Ollama local ou GPT-4 em nuvem com base na complexidade da pergunta, e rastreie economia de custos.