Integrar Ollama em uma aplicação Python via API REST
Ollama expõe duas APIs HTTP na porta 11434: uma API nativa (/api/generate, /api/chat) e uma API compatível com OpenAI (/v1/chat/completions). A segunda é o caminho ideal para integrar a API do Ollama ao Python: seu código utiliza exatamente o mesmo SDK que com o GPT-4, mas roda na sua máquina. Este guia aborda padrões concretos — streaming, JSON estruturado, chamada de funções — com exemplos em FastAPI e Flask prontos para colar em um projeto.
Por Mohamed Meguedmi·Atualização 2026-08-27·Testado no Windows, macOS e Linux
#Por que usar a API REST
A CLI ollama run est é prática para testes, mas não foi feita para ser chamada por uma aplicação. A API REST, por outro lado, foi projetada para isso: requisições HTTP padrão, entrada e saída em JSON, streaming via Server-Sent Events. É isso que todas as interfaces (Open WebUI, Cline, LangChain) usam por trás.
Compatibilidade com a OpenAI
O endpoint /v1/chat/completions aceita exatamente o mesmo payload que api.openai.com/v1/chat/completions. Você muda a URL e a chave, e seu código existente continua funcionando.
Sem reinvenção
O SDK oficial openai em Python (ou qualquer cliente HTTP) comunica-se diretamente com Ollama. Não é necessário aprender um cliente específico.
Desacoplamento do runtime
Seu aplicativo Python roda dentro do seu contêiner, Ollama dentro do seu. Quando você passar para vLLM ou LM Studio, você muda apenas a base_url.
Múltiplos clientes simultâneos
Vários scripts Python, um notebook Jupyter e o Open WebUI podem enviar requisições à mesma instância do Ollama. O daemon gerencia a fila sozinho.
i
API nativa versus compatível com OpenAI
Ollama mantém ambas as APIs. A API nativa (/api/chat) expõe parâmetros específicos (num_ctx, num_predict, mirostat), mas é menos portável. A API compatível com OpenAI cobre 95% das necessidades e continua utilizável com qualquer outro fornecedor. Por padrão, comece com essa.
#Pré-requisitos
✓
O kit Copiloto Local
Este guia leva você ao modelo. O kit leva você ao copiloto que programa no seu editor.
O daemon deve escutar em http://localhost:11434. Verifique com curl http://localhost:11434 — você deve ver "Ollama is running".
Python 3.10+
SDKs recentes (openai 1.x) exigem pelo menos Python 3.8, mas 3.10+ para anotações modernas.
Um modelo compatível com chat
ollama pull qwen3.5:9b ou gemma4:12b. Para o function calling, escolha um modelo que seja compatível com esse recurso: Qwen 3.5, Granite 4.2, Mistral Small 24B, Devstral.
VRAM suficiente
Um modelo 9B Q4 (como Qwen 3.5 9B) exige aproximadamente 6 a 7 GB de VRAM, um 12B (Gemma 4 12B) cerca de 8 GB. Sem GPU, ele roda também, mas a uma taxa de 5 a 10 tok/s.
#1. As duas APIs do Ollama
Antes de escrever Python, vamos olhar para os endpoints pelo terminal para entender bem o que está acontecendo. Com curl, falamos diretamente com o daemon, sem nenhuma abstração.
O segundo retorna um payload estritamente idêntico ao da OpenAI: campos choices[0].message.content, id, model, usage. É isso que torna o drop-in possível.
→
A chave API é ignorada mas é obrigatória
O SDK openai exige um parâmetro api_key. O Ollama não verifica nada — passe "ollama" ou qualquer string não vazia. Se você colocar sua chave real da OpenAI por hábito, ela permanece na sua máquina, mas prefira uma string neutra para evitar confusões.
#2. O SDK OpenAI apontado para Ollama
O padrão básico de uma integração com a API do Ollama em Python cabe em cinco linhas: você instala o SDK da OpenAI, instancia-o com a base_url local e chama chat.completions.create como de costume.
Instalação
pip install openai
client.py — chamada básica
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama", # ignoré, mais requis par le SDK
)
reponse = client.chat.completions.create(
model="qwen3.5:9b",
messages=[
{"role": "system", "content": "Tu réponds en français, de façon concise."},
{"role": "user", "content": "Explique en une phrase ce qu'est un LLM."},
],
temperature=0.3,
)
print(reponse.choices[0].message.content)
Execute o script. Se o Ollama estiver rodando e o modelo já estiver baixado, você obterá uma frase. Se você vir uma ConnectionRefusedError, verifique com ollama ps se o daemon está ativo.
model
O nome exato como listado por ollama list (qwen3.5:9b, gemma4:12b, mistral-small, etc.).
messages
Lista de rodadas de conversa. Papéis suportados: system, user, assistant, tool.
temperature
0 para determinístico, 0,7 para criativo. Para extração de dados, mantenha em 0 ou 0,1.
max_tokens
Limite máximo da resposta. Opcional — o Ollama aplica um valor padrão razoável para num_predict.
#3. Streaming token por token com SSE
Para uma boa experiência de uso (chatbot, geração longa), você quer exibir os tokens à medida que são gerados, em vez de esperar até o fim. O Ollama oferece suporte a streaming via Server-Sent Events, e o SDK OpenAI permite fazer isso com um simples loop em Python.
streaming.py
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
flux = client.chat.completions.create(
model="qwen3.5:9b",
messages=[{"role": "user", "content": "Raconte une courte histoire de robot."}],
stream=True,
)
for chunk in flux:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
print()
Cada chunk contém um delta (o trecho de texto adicionado). O último chunk tem delta.content como None e finish_reason preenchido — é o sinal de encerramento.
!
Não esquecer flush=True
Sem flush=True, o Python armazena stdout em buffer por linha e o efeito de streaming desaparece no terminal. Para uma API HTTP, por outro lado, é o servidor web (uvicorn, gunicorn) que realiza o flush — você não precisa cuidar disso.
#4. Modo JSON para saídas estruturadas
Quando você quer fazer o parsing da resposta (extração, classificação, geração de payload), pedir "retorne JSON" no prompt não é suficiente — o modelo costuma inserir texto ao redor. O modo JSON força o decodificador a produzir apenas JSON válido.
json_mode.py
from openai import OpenAI
import json
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
reponse = client.chat.completions.create(
model="qwen3.5:9b",
messages=[
{"role": "system", "content": (
"Tu extrais des informations structurées. "
"Réponds uniquement avec un objet JSON contenant les clés : "
"nom (string), age (int), ville (string)."
)},
{"role": "user", "content": "Marie a 34 ans, elle habite à Lyon."},
],
response_format={"type": "json_object"},
temperature=0,
)
donnees = json.loads(reponse.choices[0].message.content)
print(donnees)
# {'nom': 'Marie', 'age': 34, 'ville': 'Lyon'}
response_format={"type": "json_object"} ativa o modo JSON. No Ollama, isso se traduz em uma restrição no sampler: qualquer token que produziria um JSON inválido é rejeitado. É mais confiável do que escrever no prompt "responda em JSON" e rezar.
→
Mencione "JSON" no prompt
Como na OpenAI, o modo JSON exige pelo menos uma menção à palavra "JSON" na conversa (em uma mensagem system ou user). Sem isso, alguns modelos produzem um objeto vazio. Descreva o esquema esperado no prompt de sistema — é isso que orienta o conteúdo; o modo JSON apenas garante a sintaxe.
#5. Chamada de função (uso de ferramentas)
O function calling permite que o modelo indique que deseja chamar uma função Python em vez de responder diretamente. Nem todos os modelos oferecem suporte a esse recurso — verifique no ollama.com/library se a menção "tools" aparece nas capacidades. Qwen 3.5, Granite 4.2, Mistral Small 24B e Devstral oferecem suporte nativo a esse recurso.
tools.py
from openai import OpenAI
import json
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
# 1. Une fonction Python réelle
def meteo(ville: str) -> dict:
# En vrai, vous appelleriez Open-Meteo ou autre
return {"ville": ville, "temperature_c": 18, "conditions": "nuageux"}
# 2. Sa description au format OpenAI
outils = [{
"type": "function",
"function": {
"name": "meteo",
"description": "Donne la météo actuelle d'une ville française.",
"parameters": {
"type": "object",
"properties": {
"ville": {"type": "string", "description": "Nom de la ville"},
},
"required": ["ville"],
},
},
}]
messages = [{"role": "user", "content": "Quel temps fait-il à Bordeaux ?"}]
# 3. Premier appel : le modèle décide d'appeler la fonction
reponse = client.chat.completions.create(
model="qwen3.5:9b",
messages=messages,
tools=outils,
)
appel = reponse.choices[0].message.tool_calls[0]
args = json.loads(appel.function.arguments)
resultat = meteo(**args)
# 4. Second appel : on renvoie le résultat au modèle pour la réponse finale
messages.append(reponse.choices[0].message)
messages.append({
"role": "tool",
"tool_call_id": appel.id,
"content": json.dumps(resultat),
})
finale = client.chat.completions.create(model="qwen3.5:9b", messages=messages)
print(finale.choices[0].message.content)
O loop tem duas rodadas: a primeira retorna um tool_calls (o modelo diz "chame meteo com ville=Bordeaux"), a segunda retorna a resposta em linguagem natural depois que você executa a função e insere seu resultado. Em produção, você repete o loop enquanto tool_calls estiver preenchido.
!
Nem todos os modelos são iguais
Com um modelo que tem dificuldade com ferramentas (antigos Llama 2, Mistral 7B v0.1), você obterá chamadas mal formatadas ou argumentos alucinados. Se isso acontecer: (1) confirme que o modelo suporta oficialmente ferramentas, (2) reduza a temperatura para 0, (3) simplifique o esquema dos parâmetros.
#6. Expor Ollama via FastAPI
Caso típico: seu frontend chama seu backend em Python, que chama Ollama. O FastAPI gerencia a assincronia corretamente e o streaming chega até o navegador via StreamingResponse.
Dependências
pip install fastapi uvicorn openai
main.py
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from openai import OpenAI
app = FastAPI()
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
class Question(BaseModel):
message: str
model: str = "qwen3.5:9b"
@app.post("/chat")
def chat(q: Question):
reponse = client.chat.completions.create(
model=q.model,
messages=[{"role": "user", "content": q.message}],
)
return {"reponse": reponse.choices[0].message.content}
@app.post("/chat/stream")
def chat_stream(q: Question):
def generateur():
flux = client.chat.completions.create(
model=q.model,
messages=[{"role": "user", "content": q.message}],
stream=True,
)
for chunk in flux:
delta = chunk.choices[0].delta.content
if delta:
yield delta
return StreamingResponse(generateur(), media_type="text/plain")
Iniciar o servidor
uvicorn main:app --reload --port 8000
Testar via CLI
curl -N -X POST http://localhost:8000/chat/stream \
-H "Content-Type: application/json" \
-d '{"message": "Écris un haïku sur Paris."}'
A opção -N (--no-buffer) do curl desativa o buffering do lado do cliente para visualizar o streaming em tempo real. Do lado do frontend JS, você lê o ReadableStream da resposta fetch — da mesma forma que na API OpenAI.
#7. Chatbot Flask com histórico
Para um chatbot completo, é necessário manter o histórico das mensagens entre os turnos da conversa. Aqui está uma versão minimalista em Flask que armazena a conversa na memória (em produção, substituir esse armazenamento por uma sessão ou um banco de dados de verdade).
Quanto maior o histórico, mais tokens você consome por chamada. Para Qwen 3.5, a janela padrão no Ollama é de 2048 tokens — acima desse limite, as mensagens antigas são cortadas silenciosamente. Aumente a janela pela API nativa ou sobrescrevendo a configuração com um Modelfile (num_ctx 8192 ou 32768).
#Para produção
Expor Ollama na rede
Por padrão, o daemon escuta apenas em 127.0.0.1. Para permitir o acesso de outras máquinas, inicie com OLLAMA_HOST=0.0.0.0 — e coloque um proxy reverso com autenticação na frente dele; caso contrário, qualquer pessoa na rede poderá usar seus modelos.
Concorrência e fila de espera
Ollama serializa as requisições por modelo. Para atender vários usuários simultaneamente, inicie várias instâncias ou mude para vLLM, que gerencia o batching dinâmico nativamente.
Timeouts do lado do cliente
Uma requisição a um modelo não carregado pode levar de 10 a 30 segundos (carregamento em VRAM). Configure o timeout do cliente OpenAI com OpenAI(..., timeout=120), em vez de manter o padrão: 10 minutos na biblioteca, mas frequentemente um tempo limite curto no proxy reverso.
Manter o modelo carregado
Por padrão, o Ollama descarrega um modelo da memória após 5 minutos de inatividade. Ao usar a API, passe keep_alive="30m" pela API nativa /api/chat, ou mantenha um ping periódico para evitar uma inicialização a frio na primeira requisição do usuário.
Observabilidade
Registre sempre model, prompt_tokens e completion_tokens (presentes em reponse.usage). Essas são suas métricas de inferência — úteis para identificar um modelo que está ficando mais lento ou um prompt cujo tamanho está aumentando demais.
→
Migrar da API OpenAI
Se você já tem código que se comunica com api.openai.com, a mudança para Ollama cabe em duas linhas: mude base_url="https://api.openai.com/v1" para base_url="http://localhost:11434/v1" e ajuste o nome do modelo. O resto — streaming, modo JSON, ferramentas — funciona da mesma forma. É o grande ponto forte do endpoint compatível com OpenAI.
#Para se aprofundar
Você já tem as peças básicas. Três direções para avançar conforme seu uso:
Criar um agente que decide por conta própria
O guia sobre agentes de IA locais em Python com LangChain leva o function calling até um ciclo completo de execução do agente, com gerenciamento de múltiplas ferramentas e raciocínio em várias etapas.
Adicionar RAG com base nos seus documentos
Para que seu app responda com base em um corpus interno (PDF, anotações, código), conecte uma base vetorial. O guia de introdução ao RAG local estabelece as bases.
Personalizar o comportamento do modelo
Em vez de repetir o prompt de sistema em cada chamada, crie uma variante por meio do Modelfile. O guia de personalização Ollama Modelfile mostra como definir um assistente em francês ou um modo de programação sob um nome de modelo reutilizável.
Este guia ajudou você?
Um comentário, um erro ou uma observação? Avise-nos; isso ajuda a melhorar o guia para todos.