Ficha 02 · LLMs, APIs, RAG
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.