5.1 LangChain i LangGraph en Profunditat¶
Versió de Referència
LangChain 0.3.x i LangGraph 0.2.x. LangChain va fer un refactoring important de 0.1 a 0.2 i 0.3 — molts tutorials antics (pre-2024) usen l'API antiga. Consulta sempre la documentació oficial.
🦜 LangChain 0.3: L'Ecosistema¶
LangChain no és una sola llibreria sinó un ecosistema de paquets:
langchain-core → Interfaces base (LLM, Tool, Memory...)
langchain → Implementació de chains i agents
langchain-openai → Integració amb OpenAI
langchain-anthropic → Integració amb Anthropic/Claude
langchain-community → 200+ integracions de tercers
langchain-experimental → Eines experimentals (Python REPL, etc.)
langgraph → Agents basats en grafs d'estat
langsmith → Observabilitat i tracing
# Instal·lació completa per al curs
pip install langchain==0.3.7 \
langchain-openai==0.2.6 \
langchain-anthropic==0.3.0 \
langchain-community==0.3.7 \
langgraph==0.2.45 \
langsmith \
chromadb \
duckduckgo-search
🔗 LCEL: LangChain Expression Language¶
LangChain 0.3 usa LCEL per composar components via l'operador | (pipe):
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# ── Chain bàsica: Prompt | LLM ─────────────────────────────────
prompt = ChatPromptTemplate.from_template(
"Explica '{concepte}' de forma clara per a alumnes de CFGS."
)
chain_basica = prompt | llm # ← Composició via pipe!
result = chain_basica.invoke({"concepte": "subnetting IPv4"})
print(result.content)
# ── Chain amb parser d'output ──────────────────────────────────
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from pydantic import BaseModel, Field
class ExplicacioConcepte(BaseModel):
concepte: str = Field(description="Nom del concepte")
definicio: str = Field(description="Definició en 2 frases")
exemple: str = Field(description="Exemple pràctic")
dificultat: str = Field(description="baixa/mitja/alta")
parser = JsonOutputParser(pydantic_object=ExplicacioConcepte)
prompt_json = ChatPromptTemplate.from_messages([
("system", "Respon SEMPRE en format JSON vàlid."),
("human", "Explica el concepte: {concepte}\n{format_instructions}"),
]).partial(format_instructions=parser.get_format_instructions())
chain_json = prompt_json | llm | parser
result = chain_json.invoke({"concepte": "VLAN"})
print(result)
# → {'concepte': 'VLAN', 'definicio': '...', 'exemple': '...', 'dificultat': 'mitja'}
# ── Chain Seqüencial (encadenament de steps) ───────────────────
prompt_resum = ChatPromptTemplate.from_template(
"Resumeix en 3 punts: {text}"
)
prompt_traduccio = ChatPromptTemplate.from_template(
"Tradueix al català: {text}"
)
chain_seq = (
{"text": prompt | llm | StrOutputParser()}
| prompt_resum | llm | StrOutputParser()
)
🕸️ LangGraph: Agents basats en Grafs d'Estat¶
LangGraph permet crear agents amb lògica de control complexa: branching, cicles, human-in-the-loop, i col·laboració multi-agent.
Conceptes Fonamentals¶
NODES → Funcions que processen l'estat i el modifiquen
EDGES → Connexions entre nodes (condicionals o directes)
ESTAT → Un diccionari TypedDict que flueix per tot el graf
CICLES → Permeten iteració (el que fan els agents ReAct)
Agent ReAct amb LangGraph¶
from typing import TypedDict, Annotated, Sequence
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_openai import ChatOpenAI
from langchain_core.messages import BaseMessage, HumanMessage
import operator
# ── 1. Definir l'Estat del Graf ────────────────────────────────
class AgentState(TypedDict):
# La llista de missatges s'acumula amb operator.add
messages: Annotated[Sequence[BaseMessage], operator.add]
# ── 2. Definir els Nodes ───────────────────────────────────────
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [dns_lookup, get_weather, calculadora] # Eines definides prèviament
llm_with_tools = llm.bind_tools(tools)
def call_model(state: AgentState) -> AgentState:
"""Node que crida el LLM."""
messages = state["messages"]
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
tool_node = ToolNode(tools) # Node pre-construït per executar eines
# ── 3. Construir el Graf ───────────────────────────────────────
workflow = StateGraph(AgentState)
# Afegir nodes
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)
# Punt d'entrada
workflow.set_entry_point("agent")
# Edge condicional: si l'agent vol usar eines → "tools", sinó → END
workflow.add_conditional_edges(
"agent",
tools_condition, # Funció pre-construïda que detecta tool calls
{
"tools": "tools", # Si hi ha tool calls → anar a "tools"
END: END # Si no → finalitzar
}
)
# Després d'executar eines, tornar a l'agent
workflow.add_edge("tools", "agent")
# Compilar el graf
app = workflow.compile()
# ── 4. Executar ────────────────────────────────────────────────
result = app.invoke({
"messages": [HumanMessage(content="Quina IP té www.google.com i quin temps fa a la seva seu?")]
})
for msg in result["messages"]:
print(f"{msg.__class__.__name__}: {msg.content[:100]}")
Human-in-the-Loop amb LangGraph¶
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, END
import uuid
# Checkpointer per a persistència i human-in-the-loop
checkpointer = MemorySaver()
# ── Agent amb aprovació humana per a accions crítiques ─────────
def check_needs_approval(state: AgentState) -> str:
"""Decideix si cal aprovació humana per a l'acció."""
last_msg = state["messages"][-1]
# Si l'agent vol usar eines destructives → demanar aprovació
if hasattr(last_msg, "tool_calls"):
for tc in last_msg.tool_calls:
if tc["name"] in {"esborrar_fitxer", "modificar_config", "reiniciar_servei"}:
return "human_approval" # → Aturar i esperar humà
return tools_condition(state) # → Comportament normal
workflow_hitl = StateGraph(AgentState)
workflow_hitl.add_node("agent", call_model)
workflow_hitl.add_node("tools", tool_node)
workflow_hitl.add_node("human_approval", lambda state: state) # Node de pausa
workflow_hitl.set_entry_point("agent")
workflow_hitl.add_conditional_edges("agent", check_needs_approval)
workflow_hitl.add_edge("tools", "agent")
workflow_hitl.add_edge("human_approval", "tools") # Continua si l'humà aprova
# Compilar amb checkpointer per poder reprendre
app_hitl = workflow_hitl.compile(
checkpointer=checkpointer,
interrupt_before=["human_approval"] # Aturar ABANS d'aprovar
)
# Primera execució (s'atura a human_approval)
thread_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}
for event in app_hitl.stream(
{"messages": [HumanMessage("Reinicia el servidor Apache")]},
config, stream_mode="values"
):
print(event)
# Inspecció de l'estat (per a l'aprovació humana)
snapshot = app_hitl.get_state(config)
print("Pending:", snapshot.next) # → ('human_approval',)
# Reprendre l'execució (l'humà ha aprovat)
for event in app_hitl.stream(None, config, stream_mode="values"):
print(event)
🔭 LangSmith: Observabilitat i Debugging¶
LangSmith és la plataforma d'observabilitat per a LangChain. Permet: - Veure les traces completes de cada execució - Debugar errors en el raonament de l'agent - Avaluar la qualitat de les respostes - Monitorar el cost i la latència
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_ENDPOINT"] = "https://api.smith.langchain.com"
os.environ["LANGCHAIN_API_KEY"] = "lsv2_..." # Obtenir a smith.langchain.com
os.environ["LANGCHAIN_PROJECT"] = "curs-agents-ia"
# A partir d'aquí, TOTES les execucions de LangChain s'envien a LangSmith
# sense cap canvi al codi!
result = executor.invoke({"input": "Quin temps fa a Barcelona?"})
# → La traça completa (prompt, eines, respostes) es pot veure a LangSmith UI
✅ Activitats¶
Exercici 5.1.1 — LCEL Pipeline
Construeix una pipeline LCEL que: (1) prengui un topic, (2) generi 3 preguntes d'examen, (3) generi les respostes model, (4) retorni un JSON estructurat.
Exercici 5.1.2 — Agent LangGraph amb Cicles
Implementa un agent LangGraph per a diagnòstic de xarxa que: (1) faci ping a un host, (2) si falla, consulti DNS, (3) si DNS falla, consulti si el host existeix a Wikipedia. Dibuixa el graf primer, després implementa'l.
Exercici 5.1.3 — Human-in-the-Loop
Modifica l'agent de l'exercici anterior per demanar aprovació humana ABANS d'executar qualsevol diagnòstic sobre IPs de xarxa privada (192.168.x.x, 10.x.x.x). Usa interrupt_before.