Partilhar: WhatsApp
aulify
UC UC00623 · T. Desenv. Software, T. Multimédia

Ficha 02 · LLMs, APIs, RAG

OpenAI, Anthropic, prompt engineering
Versão · Aluno
Tempo · 45 minutos
Aluno(a)
Turma
Data

Exercício 1 · Setup OpenAI API

Cria conta OpenAI, obtém API key (em platform.openai.com), e faz primeira chamada GPT-4o-mini que pergunta "Capital de Portugal?".

Resposta:
pip install openai
export OPENAI_API_KEY="sk-..."   # ou no .env
from openai import OpenAI

client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "user", "content": "Qual é a capital de Portugal?"}
    ]
)

print(response.choices[0].message.content)
# "A capital de Portugal é Lisboa."

print(f"Tokens: {response.usage.total_tokens}")
print(f"Custo: ${response.usage.total_tokens * 0.15 / 1_000_000:.6f}")

Conta começa com $5 free credit; depois pay-as-you-go.

Exercício 2 · System prompt + persona

Cria função pergunta_a_chef(texto) que usa um system prompt para responder como um chef cozinheiro profissional, dando dicas culinárias.

Resposta:
from openai import OpenAI

client = OpenAI()

SYSTEM_PROMPT = """
És um chef cozinheiro profissional com 20 anos de experiência
em cozinha portuguesa e mediterrânica. Respondes com:
- Tom cordial mas directo
- Dicas práticas baseadas em experiência
- Substituições para ingredientes difíceis
- Tempos e temperaturas precisos
- Sempre português europeu
"""

def pergunta_a_chef(texto: str) -> str:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": texto}
        ]
    )
    return response.choices[0].message.content

# Test
print(pergunta_a_chef("Como faço um bacalhau à brás perfeito?"))
print(pergunta_a_chef("Que vinho vai com risotto de cogumelos?"))

Exercício 3 · Few-shot classifier

Usa few-shot prompting para classificar sentimento (POSITIVO/NEGATIVO/NEUTRO) de 5 frases de teste. Sem ML, só prompt.

Resposta:
from openai import OpenAI

client = OpenAI()

PROMPT_TEMPLATE = """Classifica o sentimento da frase como POSITIVO, NEGATIVO ou NEUTRO.

Exemplos:
Frase: "Adorei o filme, recomendo!" → POSITIVO
Frase: "Foi um desperdício de tempo." → NEGATIVO
Frase: "Achei normal, nada de especial." → NEUTRO
Frase: "Maravilhoso, voltarei!" → POSITIVO
Frase: "Péssimo serviço, nunca mais." → NEGATIVO
Frase: "Foi ok pelo preço." → NEUTRO

Frase: "{frase}" →"""

def classificar(frase: str) -> str:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "user", "content": PROMPT_TEMPLATE.format(frase=frase)}
        ],
        max_tokens=10,
        temperature=0,
    )
    return response.choices[0].message.content.strip()

testes = [
    "Os miúdos adoraram!",
    "Esperava muito mais por este preço.",
    "Foi uma experiência transformadora.",
    "Cheguei a horas, voltei embora.",
    "Tinha lugar livre.",
]

for t in testes:
    print(f"{classificar(t):<10} | {t}")

Exercício 4 · Output JSON estruturado

Cria função que extrai dados de um texto e retorna JSON com nome, idade, email. Usa response_format={"type": "json_object"}.

Resposta:
from openai import OpenAI
import json

client = OpenAI()

def extrair_pessoa(texto: str) -> dict:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "Extrai dados pessoais. Responde APENAS com JSON válido com keys: nome, idade, email. Se algo não estiver no texto, usa null."},
            {"role": "user", "content": texto}
        ],
        response_format={"type": "json_object"},
        temperature=0,
    )
    return json.loads(response.choices[0].message.content)

textos = [
    "João Silva tem 32 anos e o email dele é joao@example.com.",
    "Sou a Ana, 25 anos.",
    "Contacta-me em rui@dev.pt - sou o Rui.",
]

for t in textos:
    print(extrair_pessoa(t))

# {'nome': 'João Silva', 'idade': 32, 'email': 'joao@example.com'}
# {'nome': 'Ana', 'idade': 25, 'email': None}
# {'nome': 'Rui', 'idade': None, 'email': 'rui@dev.pt'}

Exercício 5 · Anthropic Claude API

Faz o equivalente do exercício 1 mas com Claude. Compara qualidade de resposta para uma pergunta complexa (ex: "Explica o que é big-O e por que importa").

Resposta:
pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-..."
import anthropic
from openai import OpenAI

anthropic_client = anthropic.Anthropic()
openai_client = OpenAI()

pergunta = "Explica o que é Big-O e por que importa para um developer."

# Claude
claude_resp = anthropic_client.messages.create(
    model="claude-opus-4-5",
    max_tokens=500,
    messages=[{"role": "user", "content": pergunta}]
)
print("=== CLAUDE ===")
print(claude_resp.content[0].text)

# GPT-4o-mini
gpt_resp = openai_client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": pergunta}]
)
print("\n=== GPT-4o-mini ===")
print(gpt_resp.choices[0].message.content)

Comparar: Claude tende a ser mais detalhado e técnico. GPT-4o-mini mais conciso. Cada projecto pode preferir um.

Exercício 6 · Streaming

Faz chamada streaming a Claude e imprime tokens à medida que chegam (efeito ChatGPT-style).

Resposta:
import anthropic

client = anthropic.Anthropic()

with client.messages.stream(
    model="claude-opus-4-5",
    max_tokens=500,
    messages=[
        {"role": "user", "content": "Conta-me uma história curta sobre um robot que aprende a programar."}
    ]
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
print()

Vai aparecer texto a fluir. Em frontend web, expor via Server-Sent Events ou WebSocket.

Exercício 7 · RAG simples com Chroma

Cria mini-RAG sobre 3 documentos curtos. Indexa em Chroma (local), faz uma pergunta, e o LLM responde só com base nos docs.

Resposta:
pip install chromadb openai
import chromadb
from openai import OpenAI

client = OpenAI()
chroma = chromadb.Client()
collection = chroma.create_collection("notas")

docs = [
    "Python foi criado por Guido van Rossum em 1991. É interpretado, dinâmico e dominante em ciência de dados.",
    "JavaScript nasceu em 1995, criado por Brendan Eich em 10 dias. Domina o browser e cresceu para backend com Node.js.",
    "Rust apareceu em 2010, da Mozilla. Foca em performance e segurança de memória sem garbage collector.",
]

# Embeddings
embs = [
    client.embeddings.create(model="text-embedding-3-small", input=d).data[0].embedding
    for d in docs
]

collection.add(documents=docs, embeddings=embs, ids=[f"d{i}" for i in range(len(docs))])

def perguntar(query: str) -> str:
    q_emb = client.embeddings.create(model="text-embedding-3-small", input=query).data[0].embedding
    results = collection.query(query_embeddings=[q_emb], n_results=2)
    contexto = "\n".join(results["documents"][0])

    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": f"Responde com base APENAS neste contexto. Se não souberes, diz 'não sei'.\n\nContexto:\n{contexto}"},
            {"role": "user", "content": query}
        ]
    )
    return resp.choices[0].message.content

print(perguntar("Quando foi criado o Python?"))
print(perguntar("Quem fez o JavaScript?"))
print(perguntar("Qual é a capital de França?"))   # "não sei"

Exercício 8 · Mini-agent com tools

Cria agent simples com 2 tools: - get_weather(city): devolve string fake (ex: "20°C"). - calculate(a, b, op): faz operação aritmética.

Demonstra que o LLM escolhe a tool certa.

Resposta:
import anthropic

client = anthropic.Anthropic()

# Tools "falsas"
def get_weather(city: str) -> str:
    return f"Em {city} está 20°C com sol."

def calculate(a: float, b: float, op: str) -> float:
    if op == "add": return a + b
    if op == "sub": return a - b
    if op == "mul": return a * b
    if op == "div": return a / b

tools_schema = [
    {
        "name": "get_weather",
        "description": "Obter informação meteorológica de uma cidade",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"]
        }
    },
    {
        "name": "calculate",
        "description": "Faz uma operação aritmética entre dois números",
        "input_schema": {
            "type": "object",
            "properties": {
                "a": {"type": "number"},
                "b": {"type": "number"},
                "op": {"type": "string", "enum": ["add", "sub", "mul", "div"]}
            },
            "required": ["a", "b", "op"]
        }
    }
]

def agent(query: str):
    messages = [{"role": "user", "content": query}]

    while True:
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=500,
            tools=tools_schema,
            messages=messages
        )

        if response.stop_reason == "tool_use":
            # Executar tool
            tool_use = next(b for b in response.content if b.type == "tool_use")
            print(f"  → Calling {tool_use.name}({tool_use.input})")

            if tool_use.name == "get_weather":
                result = get_weather(**tool_use.input)
            elif tool_use.name == "calculate":
                result = calculate(**tool_use.input)

            messages.append({"role": "assistant", "content": response.content})
            messages.append({
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": tool_use.id,
                    "content": str(result)
                }]
            })
        else:
            # Final answer
            return next(b.text for b in response.content if hasattr(b, "text"))

print(agent("Como está o tempo em Lisboa?"))
print(agent("Quanto é 15 * 23?"))
print(agent("Qual é o tempo em Porto e quanto é 50 + 25?"))

LLM decide quando chamar cada tool e combina resultados.