Ollama: Exercícios Abrangentes da Fase 2
Os exercícios abrangentes da Fase 2 são o salto de "saber usar" para "dominar" — personalização de Modelfile, aceleração GPU, migração de API, tudo coberto.
📋 Pré-requisitos: Você precisa dominar o seguinte primeiro
- Aula 1: Conceitos de IA Local e Visão Geral do Ambiente
- Aula 2: Instalação do Ollama e Configuração do Ambiente
- Aula 3: Interação Básica via CLI
- Aula 4: Gerenciamento de Modelos
- Aula 5: Fundamentos da API REST
- Aula 6: Exercícios Abrangentes da Fase 1
- Aula 7: Integração do Python SDK
- Aula 8: Modelos Personalizados com Modelfile
- Aula 9: Configuração de GPU e CUDA
- Aula 10: Modelos Multimodais
- Aula 11: API Compatível com OpenAI
1. O Que Você Vai Aprender
- Prática combinada de Modelfile + Python SDK
- Benchmarking de velocidade de Inferência CPU vs GPU
- Migração completa de aplicação OpenAI para Ollama
- Implementação de microsserviço de descrição de imagens Multimodal
- Planejamento da arquitetura do SupportBot V2
2. Uma História Real de Uma Fundadora de SaaS
(1) O Problema: Funcionalidades do Protótipo São Muito Limitadas
O SupportBot V1 da Alice só consegue fazer conversas simples de texto, carecendo de personalização de papel, aceleração GPU, análise de imagens e compatibilidade de API. Ela precisa integrar todas as capacidades aprendidas na Fase 2 para construir uma versão V2 mais poderosa.
(2) A Solução: Integração Completa das Capacidades da Fase 2
# SupportBot V2 aproveita todas as habilidades da Fase 2:
# - Modelfile personalizado para especialização de papel
# - Aceleração GPU para velocidade
# - API compatível com OpenAI para fácil integração
# - Multimodal para análise de imagens
3. Exercício 1: Prática Combinada de Modelfile + Python SDK
(1) Crie um Modelo Personalizado e Chame-o com o SDK
flowchart TD
A[Escrever Modelfile] --> B[ollama create]
B --> C[Importar Python SDK]
C --> D[Construir Aplicação]
| Passo | Ação | Ponto-Chave |
|---|---|---|
| 1 | Escrever Modelfile do SupportBot | SYSTEM + PARAMETER + MESSAGE |
| 2 | ollama create |
Construir Modelo personalizado |
| 3 | Python chamar ollama.chat(model='supportbot') |
Sem necessidade de passar System Prompt |
| 4 | Comparar saída com/sem Modelfile | Melhoria na consistência do papel |
▶ Exemplo 1: Fluxo de Trabalho Completo Modelfile + SDK
# supportbot.Modelfile
FROM qwen2.5:7b
SYSTEM You are SupportBot for GlobalShop e-commerce. Be polite, concise (2-3 sentences). For order queries ask for order number. If unsure, say 'Let me connect you with a human agent.'
PARAMETER temperature 0.4
PARAMETER num_ctx 4096
PARAMETER num_predict 256
MESSAGE user What is your return policy?
MESSAGE assistant Our return policy allows returns within 30 days in original condition. Free return shipping included.
import ollama
# Create model (run once)
# ollama.create(model='supportbot', from_='./supportbot.Modelfile')
# Use the custom model - no system prompt needed!
def test_supportbot():
questions = [
"I want to return order #12345",
"Do you ship to Germany?",
"What's the price of the iPhone 15?"
]
for q in questions:
response = ollama.chat(
model='supportbot',
messages=[{'role': 'user', 'content': q}],
stream=False
)
print(f"Q: {q}")
print(f"A: {response['message']['content']}\n")
test_supportbot()
4. Exercício 2: Benchmarking CPU vs GPU
(1) Dimensões do Benchmark
| Métrica | Esperado CPU | Esperado GPU | Diferença |
|---|---|---|---|
| Latência do Primeiro Token (TTFT) | 2-5s | 0.2-0.5s | 5-10x |
| Velocidade de Geração (tok/s) | 5-10 | 30-60 | 5-10x |
| Latência Total (200 tokens) | 20-40s | 3-7s | 5-10x |
| Uso de Memória | RAM | VRAM | Tipos diferentes |
▶ Exemplo 2: Script de Comparação CPU vs GPU
import ollama
import time
from statistics import mean
def benchmark(model: str, prompt: str, runs: int = 5) -> dict:
"""Benchmark model inference speed."""
latencies = []
token_speeds = []
# Warm up (first call includes model loading)
ollama.chat(model=model, messages=[{'role': 'user', 'content': 'warm up'}],
stream=False)
for _ in range(runs):
start = time.time()
response = ollama.chat(
model=model,
messages=[{'role': 'user', 'content': prompt}],
stream=False
)
elapsed = time.time() - start
# Estimate tokens (rough: ~4 chars per token for English)
content = response['message']['content']
token_count = len(content) // 4
latencies.append(elapsed)
token_speeds.append(token_count / elapsed if elapsed > 0 else 0)
return {
"model": model,
"avg_latency_s": round(mean(latencies), 2),
"avg_tokens_per_s": round(mean(token_speeds), 1),
"runs": runs
}
# Run benchmark
print("=== CPU vs GPU Benchmark ===")
result = benchmark("qwen2.5", "Explain machine learning in 100 words")
print(f"Model: {result['model']}")
print(f"Avg latency: {result['avg_latency_s']}s")
print(f"Avg speed: {result['avg_tokens_per_s']} tokens/s")
# Compare different models
for model in ["llama3.2:3b", "qwen2.5", "mistral"]:
result = benchmark(model, "Explain AI in 50 words", runs=3)
print(f"{model}: {result['avg_tokens_per_s']} tok/s, {result['avg_latency_s']}s")
Saída:
=== CPU vs GPU Benchmark ===
5. Exercício 3: Migração de Aplicação OpenAI
(1) Fluxo de Trabalho de Migração
flowchart TD
A[App OpenAI Original] --> B{Identificar chamadas de API}
B --> C[Mudar base_url para Ollama]
C --> D[Mudar nome do Modelo]
D --> E[Testar cada funcionalidade]
E --> F{Todas funcionalidades funcionam?}
F -->|Sim| G[Migração Completa]
F -->|Não| H[Corrigir incompatibilidades]
H --> E
| Passo de Verificação | Ação | Verificação |
|---|---|---|
| 1 | Mudar base_url | curl localhost:11434/v1/models retorna lista de Modelos |
| 2 | Mudar nome do Modelo | qwen2.5 responde normalmente |
| 3 | Testar saída streaming | stream=True funciona corretamente |
| 4 | Testar modo JSON | response_format funciona corretamente |
| 5 | Testar Embeddings | nomic-embed-text funciona corretamente |
| 6 | Testar Function Calling | Se não suportado, mudar para alternativa em modo JSON |
▶ Exemplo 3: Prática Completa de Migração
from openai import OpenAI
import json
# Before migration
# client = OpenAI(api_key="sk-xxx")
# model = "gpt-4"
# After migration - only these 3 lines changed
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
model = "qwen2.5"
embed_model = "nomic-embed-text"
# Test 1: Chat
print("=== Test 1: Chat ===")
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Hello, who are you?"}],
temperature=0.3
)
print(response.choices[0].message.content)
# Test 2: Streaming
print("\n=== Test 2: Streaming ===")
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Count from 1 to 5"}],
stream=True
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
print()
# Test 3: JSON mode
print("\n=== Test 3: JSON Mode ===")
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "List 3 colors as JSON array"}],
response_format={"type": "json_object"},
temperature=0.1
)
print(response.choices[0].message.content)
# Test 4: Embeddings
print("\n=== Test 4: Embeddings ===")
response = client.embeddings.create(model=embed_model, input="Hello world")
print(f"Embedding dimension: {len(response.data[0].embedding)}")
print("\n=== All tests passed! ===")
Saída:
=== Test 1: Chat ===
\n=== Test 2: Streaming ===
\n=== Test 3: JSON Mode ===
\n=== Test 4: Embeddings ===
\n=== All tests passed! ===
6. Desafio Multimodal: Microsserviço de Descrição de Imagens
(1) Arquitetura do Microsserviço
flowchart TD
A[Requisição HTTP<br/>imagem + prompt] --> B[Endpoint FastAPI]
B --> C[Codificar Imagem em Base64]
C --> D[Modelo Ollama llava]
D --> E[Descrição da Imagem]
E --> F[Resposta JSON]
▶ Exemplo 4: Microsserviço de Descrição de Imagens com FastAPI
from fastapi import FastAPI, UploadFile, File, Form
from fastapi.responses import JSONResponse
import ollama
import base64
app = FastAPI(title="Image Description Service")
@app.post("/describe")
async def describe_image(
file: UploadFile = File(...),
prompt: str = Form("Describe this image in detail")
):
"""Describe an uploaded image using llava."""
image_data = base64.b64encode(await file.read()).decode("utf-8")
response = ollama.chat(
model="llava",
messages=[{
"role": "user",
"content": prompt,
"images": [image_data]
}],
stream=False,
options={"temperature": 0.3}
)
return JSONResponse({
"description": response["message"]["content"],
"model": "llava",
"filename": file.filename
})
@app.post("/extract-text")
async def extract_text(file: UploadFile = File(...)):
"""OCR-like text extraction from image."""
image_data = base64.b64encode(await file.read()).decode("utf-8")
response = ollama.chat(
model="llava",
messages=[{
"role": "user",
"content": "Extract all visible text from this image verbatim.",
"images": [image_data]
}],
stream=False,
options={"temperature": 0.1}
)
return JSONResponse({
"text": response["message"]["content"],
"model": "llava"
})
# Run: uvicorn service:app --host 0.0.0.0 --port 8000
Saída:
# Function defined successfully
▶ Exemplo 5: Plano de Arquitetura do SupportBot V2
# SupportBot V2 Architecture Blueprint
SUPPORTBOT_V2_SPEC = {
"name": "SupportBot V2",
"components": {
"custom_model": {
"tool": "Modelfile",
"purpose": "Role-specialized customer service model",
"config": "qwen2.5:7b + custom SYSTEM + PARAMETER"
},
"api_gateway": {
"tool": "OpenAI compatible API",
"purpose": "Easy integration with existing tools",
"config": "/v1/chat/completions endpoint"
},
"image_service": {
"tool": "llava multimodal",
"purpose": "Damage report analysis, product photo description",
"config": "FastAPI + llava + Base64 encoding"
},
"embedding_service": {
"tool": "nomic-embed-text",
"purpose": "Product knowledge base (RAG ready for Phase 3)",
"config": "/v1/embeddings endpoint"
},
"gpu_acceleration": {
"tool": "CUDA / Metal",
"purpose": "Fast inference for real-time responses",
"config": "Auto-detected by Ollama"
}
},
"cost_comparison": {
"before": "GPT-4 API: ~2,000 USD/month",
"after": "Local Ollama: ~100 USD/month (hardware + electricity)",
"saving": "1,900 USD/month (95% reduction)"
}
}
print("SupportBot V2 Architecture:")
for name, spec in SUPPORTBOT_V2_SPEC["components"].items():
print(f" {name}: {spec['tool']} - {spec['purpose']}")
Saída:
SupportBot V2 Architecture:
7. Exemplo Abrangente: Verificação Completa de Habilidades da Fase 2
# ============================================
# Comprehensive: Phase 2 full skills verification
# Modelfile + SDK + GPU + OpenAI compat + Multimodal
# ============================================
import ollama
from openai import OpenAI
import base64
import time
import json
from pathlib import Path
def phase2_verify():
results = {}
# Test 1: Custom Modelfile model
print("=== Test 1: Custom Modelfile Model ===")
try:
resp = ollama.chat(
model="supportbot",
messages=[{"role": "user", "content": "I want a refund for order #12345"}],
stream=False
)
results["modelfile"] = "PASS"
print(f" Response: {resp['message']['content'][:80]}...")
except Exception as e:
results["modelfile"] = f"FAIL: {e}"
# Test 2: GPU acceleration
print("\n=== Test 2: GPU Acceleration ===")
start = time.time()
resp = ollama.chat(
model="qwen2.5",
messages=[{"role": "user", "content": "Hello"}],
stream=False
)
elapsed = time.time() - start
results["gpu_speed"] = f"{elapsed:.2f}s"
print(f" Response time: {elapsed:.2f}s ({'GPU' if elapsed < 3 else 'CPU'})")
# Test 3: OpenAI compatible API
print("\n=== Test 3: OpenAI Compatible API ===")
try:
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
resp = client.chat.completions.create(
model="qwen2.5",
messages=[{"role": "user", "content": "Say OK"}],
temperature=0.1
)
results["openai_compat"] = "PASS"
print(f" Response: {resp.choices[0].message.content}")
except Exception as e:
results["openai_compat"] = f"FAIL: {e}"
# Test 4: Embeddings
print("\n=== Test 4: Embeddings ===")
try:
resp = client.embeddings.create(model="nomic-embed-text", input="test")
dim = len(resp.data[0].embedding)
results["embeddings"] = f"PASS ({dim}d)"
print(f" Embedding dimension: {dim}")
except Exception as e:
results["embeddings"] = f"FAIL: {e}"
# Test 5: Streaming
print("\n=== Test 5: Streaming ===")
try:
stream = client.chat.completions.create(
model="qwen2.5",
messages=[{"role": "user", "content": "Count 1 to 3"}],
stream=True
)
chunks = 0
for chunk in stream:
if chunk.choices[0].delta.content:
chunks += 1
results["streaming"] = f"PASS ({chunks} chunks)"
print(f" Received {chunks} chunks")
except Exception as e:
results["streaming"] = f"FAIL: {e}"
# Summary
print("\n" + "=" * 40)
print("Phase 2 Verification Results:")
for test, status in results.items():
print(f" {test}: {status}")
passed = sum(1 for v in results.values() if "PASS" in str(v))
print(f"\n Total: {passed}/{len(results)} passed")
if passed >= 4:
print(" ✅ Ready for Phase 3!")
else:
print(" ⚠️ Review failed tests.")
if __name__ == "__main__":
phase2_verify()
❓ Perguntas Frequentes
P: Meu Modelo criado com Modelfile não é encontrado pelo Python SDK, o que devo fazer? R: Certifique-se de que
ollama createfoi bem-sucedido (sem erros). Useollama listpara confirmar que o Modelo existe. O nome do Modelo no Python SDK deve corresponder exatamente ao especificado emollama create.
P: Resultados do benchmark variam muito, o que devo fazer? R: A primeira chamada inclui o tempo de carregamento do Modelo (início a frio). Faça 2 chamadas de aquecimento antes de medir. Não execute outros programas durante o teste. Tire a média de 5+ execuções.
P: Algumas funcionalidades quebram após a migração OpenAI, o que devo fazer? R: Solucione um por um — teste o Chat básico primeiro, depois Streaming, depois modo JSON. Verifique cada funcionalidade independentemente para identificar a incompatibilidade específica.
P: O Modelo llava ainda não foi baixado, o que devo fazer? R: O Exercício 4 requer
ollama pull llavaprimeiro. Se o espaço em disco for insuficiente, você pode pular o Exercício 4 — o RAG da Fase 3 não requer Multimodal.
P: Qual é a diferença central entre o SupportBot V2 e o V1? R: V1 é um wrapper em nível de script Python; V2 integra personalização de papel via Modelfile, gateway de API compatível com OpenAI e análise de imagens Multimodal — é uma arquitetura de microsserviço.
P: Que conhecimento prévio é necessário para a Fase 3? R: Noções básicas de LangChain (Prompt Template, Chain, Agent), conceitos de RAG (carregamento de documentos, recuperação Vetorial) e fundamentos de Docker. As Aulas 13-18 cobrirão cada um em detalhe.
📖 Resumo
- A combinação Modelfile + Python SDK permite uso de Modelos personalizados sem configuração no código
- Benchmarking requer aquecimento + múltiplas médias; a diferença CPU vs GPU é de 5-10x
- Migração OpenAI requer mudar apenas base_url/api_key/model — três locais
- Microsserviço Multimodal usa FastAPI + codificação Base64 + llava
- SupportBot V2 integra Modelfile + OpenAI compatível + Multimodal + Embedding
- Verificação completa de habilidades da Fase 2: passe em 5 testes para prosseguir para a Fase 3
📝 Exercícios
- Básico (Dificuldade ⭐): Crie um Modelfile do SupportBot, chame-o com o Python SDK e teste 3 perguntas diferentes, registrando a consistência do papel.
- Intermediário (Dificuldade ⭐⭐): Execute benchmarks CPU vs GPU, compare a velocidade em 3 Modelos diferentes, e produza uma tabela de relatório de desempenho.
- Avançado (Dificuldade ⭐⭐⭐): Complete a implementação completa do SupportBot V2 — personalização de papel via Modelfile + API compatível com OpenAI + análise de imagens Multimodal (opcional), e escreva um documento de migração.