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.

💡 Dica: Estes exercícios abrangentes visam consolidar as habilidades centrais da Fase 2 (personalização de Modelfile, integração do Python SDK, aceleração GPU, API compatível com OpenAI, Modelos Multimodais). Recomendamos completar os exercícios 1→2→3→4 em ordem, pois cada exercício baseia-se no conhecimento e resultado dos anteriores. Após completar todos os exercícios, você terá um plano completo de arquitetura do SupportBot V2, estabelecendo uma base sólida para a Implantação Docker e integração RAG da Fase 3.

📋 Pré-requisitos: Você precisa dominar o seguinte primeiro

1. O Que Você Vai Aprender


2. Uma História Real de Uma Fundadora de SaaS

ℹ️ Info: O resultado dos exercícios abrangentes da Fase 2 é um plano de arquitetura do SupportBot V2 — isso não é apenas prática, mas também entrada para a Fase 3 subsequente (Docker/Quantização/orquestração multi-Modelo) e Fase 4 (segurança/monitoramento/Implantação em produção). Invista tempo no bom planejamento agora, e você colherá os benefícios depois.

⚠️ Aviso: Após migrar para a API compatível com OpenAI, você deve fazer testes de regressão — o comportamento do Modelo local difere do GPT-4, com possíveis desvios de formato de saída, adesão variável ao System Prompt e diferenças na compreensão de contexto de diálogo multi-turno.

(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

PYTHON
# 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

💡 Dica: Modelfile + Python SDK é a melhor combinação — o Modelfile solidifica papéis e parâmetros, enquanto o SDK gerencia lógica de negócio e conversação. Não hardcode System Prompts no código; deixe o Modelfile servir como o "arquivo de configuração," e deixe o código apenas chamá-lo.

(1) Crie um Modelo Personalizado e Chame-o com o SDK

100%
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

TEXT
# 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.
PYTHON
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

⚠️ Nota: Ao fazer benchmarking, certifique-se de fazer 2-3 chamadas de aquecimento primeiro (descartar resultados) antes de iniciar a medição formal. A primeira requisição inclui o tempo de carregamento do Modelo a frio (5-30 segundos), o que inflará significativamente a média. Além disso, não execute outras tarefas intensivas em GPU durante o teste para garantir uma comparação justa.

(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

PYTHON
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:

TEXT
=== CPU vs GPU Benchmark ===

5. Exercício 3: Migração de Aplicação OpenAI

(1) Fluxo de Trabalho de Migração

100%
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

PYTHON
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:

TEXT
=== 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

100%
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

PYTHON
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:

TEXT
# Function defined successfully

▶ Exemplo 5: Plano de Arquitetura do SupportBot V2

PYTHON
# 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:

TEXT
SupportBot V2 Architecture:

7. Exemplo Abrangente: Verificação Completa de Habilidades da Fase 2

PYTHON
# ============================================
# 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 create foi bem-sucedido (sem erros). Use ollama list para confirmar que o Modelo existe. O nome do Modelo no Python SDK deve corresponder exatamente ao especificado em ollama 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 llava primeiro. 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


📝 Exercícios

  1. 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.
  2. Intermediário (Dificuldade ⭐⭐): Execute benchmarks CPU vs GPU, compare a velocidade em 3 Modelos diferentes, e produza uma tabela de relatório de desempenho.
  3. 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.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%