Pràctica 1 — El Primer Agent amb LangChain¶
📋 Fitxa de la Pràctica
🎯 Objectius¶
Al finalitzar aquesta pràctica, l'alumne/a serà capaç de:
- Configurar un entorn Python per a LangChain
- Implementar un agent bàsic amb un LLM i una eina de cerca
- Interpretar el raonament intern d'un agent (verbose)
- Afegir eines personalitzades amb el decorador
@tool - Gestionar errors i límits de l'agent
🔧 Configuració de l'Entorn¶
✅ Prerequisits
- Python 3.11 o superior instal·lat
- Una clau d'API d'OpenAI (o Anthropic com a alternativa)
- VS Code o PyCharm instal·lat
- Connexió a internet
Pas 1: Crear l'entorn virtual¶
# Crear un directori per al projecte
mkdir practica1-primer-agent
cd practica1-primer-agent
# Crear l'entorn virtual
python -m venv .venv
# Activar l'entorn (Linux/macOS)
source .venv/bin/activate
# Activar l'entorn (Windows)
.venv\Scripts\activate
# Verificar que estem a l'entorn correcte
which python # Linux/macOS → hauria de mostrar la ruta de .venv
where python # Windows
Pas 2: Instal·lar les dependències¶
# Instal·lar les llibreries necessàries
pip install langchain==0.3.7 \
langchain-openai==0.2.6 \
langchain-community==0.3.7 \
duckduckgo-search==6.3.7 \
python-dotenv==1.0.1
# Verificar la instal·lació
python -c "import langchain; print(f'LangChain {langchain.__version__}')"
Gestió de versions
Les versions especificades estan testades i funcionen correctament juntes. Si uses versions més noves, pot haver canvis en l'API. Consulta el CHANGELOG de LangChain.
Pas 3: Configurar les claus d'API¶
# Crear el fitxer de variables d'entorn (NO pujar mai a git!)
touch .env
echo "OPENAI_API_KEY=sk-proj-xxxxxxxxxxxx" >> .env
# Crear el .gitignore per protegir les claus
echo ".env" >> .gitignore
echo ".venv/" >> .gitignore
echo "__pycache__/" >> .gitignore
SEGURETAT CRÍTICA
MAI posis les teves claus d'API directament al codi. Usa sempre variables d'entorn. Una clau d'API exposada a GitHub pot generar factures de milers d'euros en pocs minuts si un bot maliciós la troba.
💻 Part 1: Agent Bàsic (60 minuts)¶
Crea el fitxer agent_basic.py:
"""
Pràctica 1 — Part 1: Agent Bàsic amb LangChain
Curs d'Agents d'IA — CFGS ASIX/DAW
"""
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain_community.tools import DuckDuckGoSearchResults
from langchain import hub
# Cargar variables d'entorn
load_dotenv()
# ─── CONFIGURACIÓ ────────────────────────────────────────────────
# Verificar que tenim la clau d'API
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError(
"No s'ha trobat OPENAI_API_KEY. "
"Assegura't de crear el fitxer .env amb la clau."
)
# ─── 1. MODEL DE LLENGUATGE ──────────────────────────────────────
llm = ChatOpenAI(
model="gpt-4o-mini", # Model econòmic per a proves
temperature=0, # 0 = determinista (millor per a agents)
max_tokens=1500, # Limitar tokens de resposta
api_key=api_key
)
print("✅ Model carregat correctament")
# ─── 2. EINES ────────────────────────────────────────────────────
search_tool = DuckDuckGoSearchResults(
name="cerca_web",
description=(
"Cerca informació actual a internet. "
"Usa aquesta eina per a preguntes sobre events recents, "
"novetats tecnològiques, o qualsevol informació que pugui "
"haver canviat recentment. "
"Entrada: string amb la cerca en català o castellà."
),
num_results=3 # Nombre de resultats a retornar
)
tools = [search_tool]
print(f"✅ {len(tools)} eines configurades")
# ─── 3. PROMPT DE REACT ──────────────────────────────────────────
# El prompt de ReAct instrueix el model a raonar pas a pas
# i a usar eines quan cal
prompt = hub.pull("hwchase17/react")
print("✅ Prompt ReAct carregat")
# ─── 4. CREAR L'AGENT ────────────────────────────────────────────
agent = create_react_agent(
llm=llm,
tools=tools,
prompt=prompt
)
# ─── 5. EXECUTOR ─────────────────────────────────────────────────
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # ← Mostra el raonament intern (IMPRESCINDIBLE per aprendre!)
max_iterations=6, # Màxim d'iteracions per evitar bucles infinits
max_execution_time=30, # Timeout en segons
handle_parsing_errors=True, # Gestionar errors de format automàticament
return_intermediate_steps=True # Retornar tots els passos intermedis
)
print("✅ Agent creat i llest!")
print("=" * 60)
# ─── 6. EXECUTAR TASQUES ─────────────────────────────────────────
def fer_consulta(pregunta: str) -> dict:
"""Executa una consulta a l'agent i retorna el resultat complet."""
print(f"\n🤔 PREGUNTA: {pregunta}\n")
print("-" * 60)
try:
result = executor.invoke({"input": pregunta})
print("\n" + "=" * 60)
print(f"✅ RESPOSTA FINAL:\n{result['output']}")
# Mostrar resum dels passos
steps = result.get("intermediate_steps", [])
print(f"\n📊 Estadístiques:")
print(f" - Iteracions: {len(steps)}")
print(f" - Eines usades: {[s[0].tool for s in steps]}")
return result
except Exception as e:
print(f"❌ Error: {e}")
return {"error": str(e)}
# Preguntes de prova
if __name__ == "__main__":
preguntes = [
"Quina és la versió actual de Python i quan es va publicar?",
"Quins són els 3 LLM open-source més populars ara mateix?",
]
for pregunta in preguntes:
fer_consulta(pregunta)
input("\n[Prem ENTER per a la següent pregunta...]")
Executar i Observar¶
📝 Punt de Reflexió 1.1
Observa la sortida amb verbose=True i respon al teu quadern de pràctiques:
- Quants "Thought → Action → Observation" ha necessitat per a cada pregunta?
- El model ha justificat correctament PER QUÈ usava l'eina de cerca?
- Hi ha algun moment en que el model hagi fet una inferència incorrecta?
💻 Part 2: Agent amb Eines Personalitzades (90 minuts)¶
Ara crearem eines pròpies usant el decorador @tool:
"""
Pràctica 1 — Part 2: Eines Personalitzades
"""
import math
import json
from datetime import datetime
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
from dotenv import load_dotenv
load_dotenv()
# ─── EINES PERSONALITZADES AMB @tool ─────────────────────────────
@tool
def calculadora(expressio: str) -> str:
"""
Realitza càlculs matemàtics.
Accepta expressions com: '25 * 4 + 10', 'sqrt(144)', '2**10', 'pi * 5**2'
NO usar per a preguntes factuals, NOMÉS per a càlculs numèrics.
Args:
expressio: L'expressió matemàtica a calcular
Returns:
El resultat del càlcul com a string
"""
# Context segur: només operadors matemàtics
context_segur = {
"sqrt": math.sqrt,
"pi": math.pi,
"e": math.e,
"sin": math.sin,
"cos": math.cos,
"log": math.log,
"log2": math.log2,
"log10": math.log10,
"abs": abs,
"round": round,
"__builtins__": {} # Deshabilitar builtins per seguretat
}
try:
resultat = eval(expressio, context_segur)
return f"El resultat de '{expressio}' és: {resultat:.6g}"
except ZeroDivisionError:
return "Error: divisió per zero"
except Exception as e:
return f"Error en el càlcul de '{expressio}': {str(e)}"
@tool
def data_hora_actual(zona_horaria: str = "Europe/Madrid") -> str:
"""
Retorna la data i hora actuals del sistema.
Usa aquesta eina quan et preguntin per la data actual, l'hora,
el dia de la setmana, o quan hagis de calcular quants dies han
passat des d'una data.
Args:
zona_horaria: Zona horària (per defecte 'Europe/Madrid')
Returns:
Data i hora actuals formatejades
"""
now = datetime.now()
dies_setmana = ["Dilluns", "Dimarts", "Dimecres", "Dijous",
"Divendres", "Dissabte", "Diumenge"]
mesos = ["Gener", "Febrer", "Març", "Abril", "Maig", "Juny",
"Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"]
return (
f"Data i hora actuals ({zona_horaria}):\n"
f" - {dies_setmana[now.weekday()]}, "
f"{now.day} de {mesos[now.month-1]} de {now.year}\n"
f" - Hora: {now.strftime('%H:%M:%S')}\n"
f" - Dia de l'any: {now.timetuple().tm_yday}\n"
f" - Setmana de l'any: {now.isocalendar()[1]}"
)
@tool
def conversor_unitats(valor: float, de: str, a: str) -> str:
"""
Converteix entre unitats de mesura comunes.
Unitats suportades:
- Temperatura: celsius, fahrenheit, kelvin
- Distància: km, milles, metres, peus
- Pes: kg, lliures, grams
Args:
valor: El valor numèric a convertir
de: Unitat d'origen (p.ex. 'celsius')
a: Unitat de destí (p.ex. 'fahrenheit')
Returns:
El valor convertit amb la unitat
"""
conversions = {
# Temperatura
("celsius", "fahrenheit"): lambda x: x * 9/5 + 32,
("fahrenheit", "celsius"): lambda x: (x - 32) * 5/9,
("celsius", "kelvin"): lambda x: x + 273.15,
("kelvin", "celsius"): lambda x: x - 273.15,
# Distància
("km", "milles"): lambda x: x * 0.621371,
("milles", "km"): lambda x: x * 1.60934,
("metres", "peus"): lambda x: x * 3.28084,
("peus", "metres"): lambda x: x * 0.3048,
# Pes
("kg", "lliures"): lambda x: x * 2.20462,
("lliures", "kg"): lambda x: x * 0.453592,
("kg", "grams"): lambda x: x * 1000,
("grams", "kg"): lambda x: x / 1000,
}
key = (de.lower(), a.lower())
if key in conversions:
resultat = conversions[key](valor)
return f"{valor} {de} = {resultat:.4f} {a}"
else:
return (f"Conversió de '{de}' a '{a}' no suportada. "
f"Unitats disponibles: celsius, fahrenheit, kelvin, "
f"km, milles, metres, peus, kg, lliures, grams")
# ─── CREAR L'AGENT AMB LES NOSTRES EINES ─────────────────────────
from langchain_community.tools import DuckDuckGoSearchResults
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [
calculadora,
data_hora_actual,
conversor_unitats,
DuckDuckGoSearchResults(name="cerca_web", num_results=3),
]
prompt = hub.pull("hwchase17/react")
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent, tools=tools,
verbose=True, max_iterations=8,
handle_parsing_errors=True
)
# ─── TASQUES DE PROVA ─────────────────────────────────────────────
if __name__ == "__main__":
tasques_prova = [
# Usa la calculadora
"Quant és la superfície d'un cercle amb radi 7.5 metres?",
# Usa data + calculadora
"Quants dies han passat desde l'1 de gener d'aquest any fins avui?",
# Usa el conversor
"Quants graus Fahrenheit són 100 graus Celsius? I 37 graus?",
# Combina cerca + calculadora
"Quin és el PIB d'Espanya i quin percentatge és respecte al PIB mundial?",
]
for i, tasca in enumerate(tasques_prova, 1):
print(f"\n{'='*60}")
print(f"📝 TASCA {i}/{len(tasques_prova)}: {tasca}")
print('='*60)
result = executor.invoke({"input": tasca})
print(f"\n✅ RESULTAT: {result['output']}")
if i < len(tasques_prova):
input("\n[ENTER per continuar...]\n")
💻 Part 3: Entrega i Documentació (30 minuts)¶
Estructura d'Entrega¶
practica1-primer-agent/
├── .env ← NO incloure al ZIP d'entrega!
├── .gitignore
├── requirements.txt
├── agent_basic.py ← Part 1
├── agent_eines.py ← Part 2
├── nova_eina.py ← EXERCICI FINAL (veure baix)
├── captures/
│ ├── execucio_part1.png ← Captura de pantalla de l'execució
│ ├── execucio_part2.png
│ └── execucio_nova.png
└── informe.md ← Reflexions i respostes als punts de reflexió
Generar requirements.txt¶
🏆 Exercici Final: Eina Pròpia (puntuació addicional)¶
Repte Final
Crea una eina personalitzada que NO sigui una de les que hem vist a classe. Algunes idees:
- 🌤️ Eina meteorològica (usar wttr.in API, que és gratuïta i no requereix clau)
- 📊 Eina de generació de gràfics (matplotlib → retornar una imatge en base64)
- 📝 Eina de lectura de fitxers locals (llegir CSV o TXT)
- 🔐 Eina de generació de contrasenyes segures
- 🎨 Eina de conversió de colors (hex ↔ RGB ↔ HSL)
Requisits mínims de la nova eina: 1. Nom descriptiu i docstring completa 2. Gestió d'errors adequada 3. Prova l'eina dins d'un agent amb almenys 3 tasques
# Exemple d'estructura per a l'eina de temps
import requests
from langchain.tools import tool
@tool
def temps_actual(ciutat: str) -> str:
"""
Obté el temps meteorològic actual d'una ciutat.
Usa aquesta eina quan et preguntin sobre el temps, temperatura,
o condicions meteorològiques d'una ubicació.
Args:
ciutat: Nom de la ciutat (p.ex. 'Barcelona', 'Madrid', 'London')
Returns:
Informació meteorològica actual de la ciutat
"""
try:
# wttr.in és una API de temps gratuïta i sense clau
url = f"https://wttr.in/{ciutat}?format=j1&lang=ca"
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
current = data["current_condition"][0]
return (
f"Temps a {ciutat}:\n"
f" - Temperatura: {current['temp_C']}°C "
f"(sensació: {current['FeelsLikeC']}°C)\n"
f" - Descripció: {current['weatherDesc'][0]['value']}\n"
f" - Humitat: {current['humidity']}%\n"
f" - Vent: {current['windspeedKmph']} km/h"
)
except requests.exceptions.RequestException as e:
return f"Error obtenint el temps: {str(e)}"
except (KeyError, IndexError) as e:
return f"Ciutat '{ciutat}' no trobada o format de resposta inesperat."
📊 Criteris d'Avaluació¶
| Criteri | Pes | Indicadors |
|---|---|---|
| L'agent s'executa sense errors | 20% | Cap excepció no controlada |
| Les eines retornen resultats correctes | 25% | Càlculs, dates i conversions precises |
| L'agent usa les eines adequades | 20% | Tria l'eina correcta per a cada tasca |
| Qualitat del codi (comentaris, estructura) | 15% | PEP 8, docstrings, gestió d'errors |
| Informe de reflexió | 10% | Resposta als punts de reflexió |
| Eina pròpia (exercici final) | 10% | Funcional, documentada, provada |
Format d'Entrega
Entregar un ZIP (sense la carpeta .venv i sense el fitxer .env)
a la plataforma Moodle del curs, amb el nom:
P1_NomCognom_AgentsIA.zip