Continuação: Python e APIs
Na semana passada (Python Mínimo), montamos a base: variáveis, listas, dicionários e funções. E vimos a estrutura mais importante desta aula:
Mensagens de LLM = lista de dicionários com
roleecontent.
Hoje o Python conversa de verdade com o modelo — e tudo roda na sua própria máquina.
O conceito universal: HTTP request/response
Toda API de LLM funciona com o mesmo padrão, chame ela do nome que chamar:
Você manda um JSON via HTTP e recebe um JSON de volta. Muda a URL, muda um detalhe do formato, mas o conceito é universal. Entender HTTP = entender todas as APIs de LLM.
Pense num garçom: você entrega o pedido (request) e recebe o prato (response). O garçom não cozinha — ele transporta.
O que você precisa ter
| Item | Ollama (local) | Ollama no Colab |
|---|---|---|
| API key | ❌ Não precisa | ❌ Não precisa |
| Conta em serviço | ❌ Nenhuma | Só a conta Google pro Colab |
| Internet | ⚠️ Só pra baixar o modelo | Sempre (Colab é cloud) |
| GPU | Opcional (roda em CPU) | T4 do Colab free |
| Python 3.10+ | ✅ Sim | ✅ Sim (vem pronto) |
Biblioteca requests |
✅ Sim | ✅ Sim |
Zero API key, zero conta de provedor, zero custo. O modelo é baixado uma vez e roda na máquina — o oposto do cloud, onde os dados saem da sua rede.
Setup: Ollama
O Ollama é um servidor que roda modelos de linguagem na sua máquina. Em quatro passos:
# 1. Instalar (Linux/macOS) curl -fsSL https://ollama.com/install.sh | sh # 2. Baixar o modelo do curso (~2-3 GB, roda até em notebook simples) ollama pull gemma4:e2b-it-qat # 3. Conferir o que está instalado ollama list # 4. Conversar via terminal (teste rápido, sem código!) ollama run gemma4:e2b-it-qat
Depois disso, o Ollama fica servindo na porta 11434. Qualquer programa pode mandar requisições HTTP pra http://localhost:11434 e conversar com o modelo.
No Colab, o notebook desta aula (📓 abrir o Colab) faz o setup por você: T4 GPU, instalação e download já estão nas células. A primeira inferência demora ~2-3 min (warm up — o modelo carrega na GPU) e depois as respostas ficam rápidas.
A API nativa do Ollama: /api/chat
Aqui está o coração da aula: requests puro, sem SDK nenhum.
import requests OLLAMA_URL = "http://localhost:11434/api/chat" MODEL = "gemma4:e2b-it-qat" def chat(messages, stream=False): """Envia mensagens pro Ollama e retorna a resposta.""" payload = { "model": MODEL, "messages": messages, "stream": stream, "keep_alive": -1, # mantém o modelo carregado } response = requests.post(OLLAMA_URL, json=payload, timeout=300) if response.status_code != 200: raise Exception(f"Erro {response.status_code}: {response.text}") return response # Teste r = chat([ {"role": "system", "content": "Você é um assistente útil. Responda em português."}, {"role": "user", "content": "Qual é a capital de Minas Gerais?"} ]) resultado = r.json() print(resultado["message"]["content"])
A resposta chega em JSON — o campo message.content traz o texto do modelo:
{
"model": "gemma4:e2b-it-qat",
"message": {
"role": "assistant",
"content": "A capital de Minas Gerais é Belo Horizonte."
},
"eval_count": 14
}
Repare como a semana passada já preparou tudo: o payload é uma lista de dicts, e a resposta também é um dict. Python puro contra um modelo rodando na sua máquina.
Parâmetros de configuração
O modelo obedece a instruções, mas o estilo da resposta você controla com parâmetros:
payload["options"] = { "temperature": 0.7, # 0.0 determinístico → 1.0 criativo "num_ctx": 8192, # janela de contexto (tokens) }
| Faixa | Comportamento | Uso |
|---|---|---|
| 0.0-0.3 | Muito determinístico | Código, fatos, análises |
| 0.4-0.7 | Equilibrado | Conversa geral |
| 0.8-1.0 | Criativo | Escrita criativa, brainstorming |
Chat com memória (multi-turno)
Cada chamada envia o histórico completo. O modelo não "lembra" — ele recebe toda a conversa de novo:
historico = [ {"role": "system", "content": "Você é um assistente útil. Responda em português. Seja conciso."} ] historico.append({"role": "user", "content": "Meu nome é Lucas e eu amo IA."}) r1 = chat(historico) historico.append({"role": "assistant", "content": r1.json()["message"]["content"]}) print(f"🤖: {r1.json()['message']['content']}") historico.append({"role": "user", "content": "Qual é o meu nome?"}) r2 = chat(historico) historico.append({"role": "assistant", "content": r2.json()["message"]["content"]}) print(f"🤖: {r2.json()['message']['content']}") # Deve lembrar do nome!
É como repetir toda a conversa toda vez que você fala. O "contexto" do modelo é essa lista — e é ela que vamos manipular nas próximas aulas pra construir agentes.
Streaming (resposta em tempo real)
Com stream=True, o servidor manda pedaços da resposta conforme gera — a mesma experiência do chat "digitando":
response = chat([ {"role": "user", "content": "Explique o que é uma API em 3 frases."} ], stream=True) for line in response.iter_lines(): if line: chunk = json.loads(line) if "message" in chunk and "content" in chunk["message"]: print(chunk["message"]["content"], end="", flush=True) print()
O padrão universal: /v1/chat/completions
O Ollama também expõe um endpoint OpenAI-compatible: /v1/chat/completions. O formato nasceu na API da OpenAI e virou padrão de mercado — praticamente todo servidor de LLM fala esse dialeto.
OPENAI_URL = "http://localhost:11434/v1/chat/completions" payload = { "model": "gemma4:e2b-it-qat", "messages": [ {"role": "system", "content": "Você é um assistente útil. Responda em português."}, {"role": "user", "content": "O que é Python em uma frase?"} ], "temperature": 0.7, "max_tokens": 64 } response = requests.post(OPENAI_URL, json=payload, timeout=300) data = response.json() print(data["choices"][0]["message"]["content"])
Ponto-chave: o formato é idêntico ao da OpenAI. A única diferença aqui é a URL (
localhostem vez deapi.openai.com). Isso é poderoso: o mesmo código funciona com qualquer servidor que fale esse dialeto.
Outros servidores: a família llama.cpp
Todo modelo de linguagem (mesmo os do Ollama) roda, lá no fundo, no llama.cpp — o motor de inferência open source que virou a infraestrutura do ecossistema local. O ollama é uma camada amigável em cima dele.
Quando você quer controle fino, pode subir o llama-server direto — apontando pra um arquivo GGUF no disco. Este exemplo roda o LFM2.5-2.6B (um modelo agêntico da Liquid AI, treinado pra usar ferramentas), com contexto de 8K tokens:
llama-server \ --port 8081 \ --model ~/.llama-models/lfm2.5-2.6b/LFM2.5-2.6B-QAD-Q4_0.gguf \ --fit on --fit-target 512 \ --ctx-size 8192 \ --jinja \ --temp 0.1 --top-p 0.9 --top-k 50 --min-p 0.0 \ --repeat-penalty 1.1 \ --cache-type-k q4_0 --cache-type-v q4_0 \ --flash-attn auto
O que cada flag faz:
| Flag | Papel |
|---|---|
--model |
O GGUF do disco (LFM2.5 quantizado Q40) |
--fit on / --fit-target |
Encaixa o modelo na VRAM disponível (deixa 512 MB de folga) |
--ctx-size |
Janela de contexto: 8192 tokens |
--jinja |
Usa o chat template embutido no GGUF |
--temp, --top-p, --top-k, --min-p, --repeat-penalty |
Amostragem (recomendação da Liquid AI: temp bem baixa, 0.1) |
--cache-type-k/v |
KV cache quantizado — economiza VRAM |
--flash-attn |
Flash Attention — contexto grande com menos memória |
O servidor sobe em http://localhost:8081 — e aí é só apontar o mesmo código de antes:
# O MESMO payload, trocando URL e modelo. Só isso. response = requests.post( "http://localhost:8081/v1/chat/completions", json=payload, # model: "lfm2.5-2.6b" (o nome é livre!) timeout=120 ) print(response.json()["choices"][0]["message"]["content"])
⚠️ maxtokens folgado: esse modelo raciocina antes de responder — o rascunho de pensamento entra no campo
reasoning_contente consome o orçamento de tokens. Commax_tokenscurto,contentchega vazio (~200+ tokens dá folga pra uma resposta tranquila). Comportamento do modelo, não do servidor — o Ollama não separa os campos, o llama-server sim.
Um código, dois servidores na mesma máquina. Ollama e llama-server falam o mesmo dialeto — e ferramentas como o llama-swap (na porta 12434) colocam um gerenciador na frente de vários servidores pra trocar de modelo a quente.
Tratamento de Erros
Erros são iguais com qualquer API — o padrão é try/except com retry e backoff:
import time def chamar_api(url, payload, timeout=30, tentativas=3): """Faz request com retry automático.""" for tentativa in range(tentativas): try: response = requests.post(url, json=payload, timeout=timeout) response.raise_for_status() # levanta exceção para erros HTTP return response.json() except requests.exceptions.ConnectionError: print(f"⚠️ Servidor fora do ar. Tentativa {tentativa + 1}/{tentativas}...") time.sleep(2) except requests.exceptions.Timeout: print(f"⚠️ Timeout. Tentativa {tentativa + 1}/{tentativas}...") time.sleep(2) except requests.exceptions.HTTPError as e: if e.response.status_code == 429: print("⚠️ Rate limit. Aguardando...") time.sleep(5) else: raise Exception(f"❌ HTTP {e.response.status_code}: {e.response.text}") raise Exception("❌ Número máximo de tentativas excedido")
Códigos de erro comuns:
| Código | Significado | O que fazer |
|---|---|---|
| ConnectionError | Servidor não existe | Conferir se ollama serve~/~llama-server está rodando |
| 404 | Modelo não encontrado | Conferir o nome listado em /api/tags |
| 401 | Não autorizado | Servidor exigindo auth (raríssimo em local) |
| 429 | Rate limit | Aguardar e tentar novamente |
| 500 | Erro no servidor | Conferir payload; tentar de novo |
No mundo local, o erro mais comum não é 401 nem 429 — é ConnectionError: você esqueceu de subir o servidor antes de fazer a requisição.
Exercícios Práticos
Exercício 1: Uma função, qualquer servidor
Crie uma função perguntar() que conversa com qualquer servidor OpenAI-compatible — só mudando a URL:
def perguntar(mensagem, url, model, system="Você é um assistente útil."): """Faz uma pergunta a qualquer API de LLM, via requests.post().""" # Implemente aqui pass
📝 Gabarito
def perguntar(mensagem, url, model, system="Você é um assistente útil.", temperature=0.7): payload = { "model": model, "messages": [ {"role": "system", "content": system}, {"role": "user", "content": mensagem} ], "temperature": temperature } response = requests.post(url, json=payload, timeout=120) response.raise_for_status() data = response.json() return data["choices"][0]["message"]["content"] # Ollama local print(perguntar( "Qual é a capital de Minas Gerais?", url="http://localhost:11434/v1/chat/completions", model="gemma4:e2b-it-qat" ))
Exercício 2: Chat com memória
Implemente um chat com histórico usando dicionários (sem classes):
historico = {"mensagens": [ {"role": "system", "content": "Você é um assistente útil. Responda em português."} ]} def conversar(mensagem): """Adiciona a mensagem ao histórico, chama a API, retorna a resposta.""" # Implemente pass def ver_historico(): """Retorna o histórico sem o system prompt.""" # Implemente pass def limpar_historico(): """Limpa o histórico mantendo o system prompt.""" # Implemente pass
📝 Gabarito
def conversar(mensagem): historico["mensagens"].append({"role": "user", "content": mensagem}) resposta = perguntar( mensagem, url="http://localhost:11434/v1/chat/completions", model="gemma4:e2b-it-qat", system=historico["mensagens"][0]["content"] ) historico["mensagens"].append({"role": "assistant", "content": resposta}) return resposta def ver_historico(): return historico["mensagens"][1:] def limpar_historico(): sistema = historico["mensagens"][0] historico["mensagens"] = [sistema] # Uso print(conversar("Meu nome é Lucas e eu amo IA.")) print(conversar("Qual é o meu nome?")) # Deve lembrar! print(ver_historico())
Exercício 3: Dois servidores, mesma requisição
Rode a mesma perguntar() do Exercício 1 contra dois servidores: o Ollama (localhost:11434) e um llama-server subido com o comando da apostila (localhost:8081, com o LFM2.5). O que muda de uma chamada pra outra?
📝 Gabarito
# Só a URL e o nome do modelo mudam. O payload é IDÊNTICO: for url, model in [ ("http://localhost:11434/v1/chat/completions", "gemma4:e2b-it-qat"), ("http://localhost:8081/v1/chat/completions", "lfm2.5-2.6b"), ]: resposta = perguntar( "O que é uma API em uma frase?", url=url, model=model ) print(f"{model}: {resposta}\n")
Compare as duas respostas: modelos diferentes, padrão HTTP idêntico.
Exercício 4: Tratamento de erros
Envolva a conversar() do Exercício 2 com a chamar_api() da seção anterior, para tratar:
- Servidor offline (
ConnectionError) - Timeout (
Timeout) - Erro HTTP genérico (
raise_for_status)
Para ir mais longe
- LangChain: framework que abstrai as diferenças entre servidores — veremos na Semana 06!
- llama.cpp (github.com/ggml-org/llama.cpp): o motor que roda tudo — a documentação das flags é riquíssima.
- llama.app (https://llama.app): o instalador oficial do llama.cpp desktop, pela própria equipe ggml — Mac e Windows, sem configuração; roda modelos e fala o mesmo dialeto
/v1. - Liquid AI LFM2.5: o modelo agêntico de 2.6B da demo — família completa em huggingface.co/LiquidAI.
- Biblioteca do Ollama:
ollama.com/library— centenas de modelos, umpullde distância.
Dica: comece com o Ollama (setup em um comando). Explore outros servidores e modelos quando o básico estiver confortável — o dialeto HTTP é sempre o mesmo.
Para lembrar
"Python é a ferramenta. O LLM é o cérebro. A API é a ponte entre eles."
- O padrão universal: JSON via HTTP — request e response
- Ollama →
localhost:11434(nativo e/v1OpenAI-compatible) - llama-server →
localhost:8081— mesmo payload, só muda a URL - Chat com memória → a lista de dicts É o contexto
- Próxima aula (16/10) → a estrutura de um agente de verdade!