LangChain hooks de middleware -- @before_model y @after_model
before_model y after_model son los dos hooks de middleware más utilizados. Se ejecutan antes y después de cada llamada al modelo, y son adecuados para filtrado de contenido, preprocesamiento de mensajes, revisión de respuestas, etc.
@before_model: intercepción antes de la llamada al modelo
@before_model se ejecuta antes de cada llamada al modelo. Aquí puedes modificar mensajes, inyectar condiciones de contexto u omitir directamente la llamada al modelo.
Escenario 1: Preprocesamiento de mensajes — Limitar la longitud de la conversación
Ejemplo
load_dotenv()
from langchain.agents import create_agent
from langchain.agents.middleware import before_model
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
@before_model
def limit_context(state, runtime):
"""Limita la longitud del historial de mensajes para evitar que el contexto sea demasiado largo"""
messages = state.get("messages", [])
# Conservar el system message + los últimos 6 mensajes
MAX_MESSAGES = 6
if len(messages) > MAX_MESSAGES:
# Recortar a los mensajes más recientes
trimmed = messages[-MAX_MESSAGES:]
# Asegurar que el primer mensaje sea del usuario
if trimmed and trimmed[0].type != "human":
trimmed = trimmed[1:]
return {"messages": trimmed}
return None
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
middleware=[limit_context],
system_prompt="Eres el asistente de EJEMPLO.",
)
# Simular una conversación de varios turnos
result = agent.invoke({
"messages": [
HumanMessage(content="Primera ronda"),
HumanMessage(content="Segunda ronda"),
HumanMessage(content="Tercera ronda"),
HumanMessage(content="Cuarta ronda"),
HumanMessage(content="Quinta ronda"),
HumanMessage(content="Sexta ronda"),
HumanMessage(content="Séptima ronda"),
]
})
print(f"Número de mensajes: {len(result['messages'])}")
print(f"Respuesta: {result['messages'][-1].content}")
Resultado de la ejecución:
Número de mensajes: 8 Respuesta: ¡Hola! Parece que estás probando una conversación de varios turnos. ¿En qué puedo ayudarte?
Escenario 2: Filtrado de contenido — Bloquear palabras sensibles
Ejemplo
SENSITIVE_WORDS = ["contraseña", "número de tarjeta bancaria", "número de documento de identidad"]
@before_model
def content_filter(state, runtime):
"""Comprueba si el mensaje del usuario contiene palabras sensibles; si las contiene, interrumpe"""
messages = state.get("messages", [])
if not messages:
return None
last_msg = messages[-1]
content = str(last_msg.content) if hasattr(last_msg, 'content') else ""
for word in SENSITIVE_WORDS:
if word in content:
print(f"[Interceptado] Palabra sensible detectada: {word}")
# jump_to="end" para finalizar directamente y evitar que el modelo responda
return {
"jump_to": "end",
"messages": [
HumanMessage(content=f"Lo siento, por seguridad, no puedo procesar solicitudes que contengan «{word}».")
]
}
return None
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
middleware=[content_filter],
system_prompt="Eres el asistente de EJEMPLO.",
)
# Pregunta normal
result = agent.invoke({
"messages": [HumanMessage(content="¿Cómo empezar con Python?")]
})
print(f"Pregunta normal: {result['messages'][-1].content[:80]}")
# Pregunta sensible
result = agent.invoke({
"messages": [HumanMessage(content="¿Cuál es mi número de tarjeta bancaria? ¿Puedes ayudarme a consultarlo?")]
})
print(f"\nPregunta sensible: {result['messages'][-1].content}")
Resultado de la ejecución:
[拦截] 检测到敏感词: 银行卡号 Pregunta normal: Python 入门可以从安装 Python 环境开始... Pregunta sensible: 抱歉,为了安全,不能处理包含「银行卡号」的请求。
@after_model — Procesamiento posterior a la llamada del modelo
@after_model se ejecuta después de la respuesta del modelo; es adecuado para revisar la salida del modelo, extraer información clave, añadir instrucciones posteriores, etc.
Escenario 3: Revisión del contenido de la respuesta
Ejemplo
FORBIDDEN_TOPICS = ["política", "violencia", "pornografía"]
@after_model
def response_audit(state, runtime):
"""Revisa la respuesta del modelo; si involucra temas prohibidos, la reemplaza"""
messages = state.get("messages", [])
if not messages:
return None
last_msg = messages[-1]
content = str(last_msg.content) if hasattr(last_msg, 'content') else ""
for topic in FORBIDDEN_TOPICS:
if topic in content:
runtime.stream_writer({
"type": "warning",
"message": f"Se ha detectado que la respuesta contiene contenido relacionado con «{topic}»; ha sido reemplazada."
})
# Devolver un mensaje de reemplazo (mediante el reductor add_messages)
from langchain.messages import AIMessage
return {
"messages": [
AIMessage(content="Lo siento, no puedo responder esta pregunta."
"Por favor, pregunta sobre temas relacionados con el aprendizaje de programación.")
]
}
return None
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
middleware=[response_audit],
system_prompt="Eres el asistente de EJEMPLO.",
)
result = agent.invoke({
"messages": [HumanMessage(content="¿Qué recursos de aprendizaje hay para Python?")]
})
print(f"Respuesta normal: {result['messages'][-1].content}")
Escenario 4: Añadir automáticamente información de aviso
Ejemplo
from langchain.messages import AIMessage
@after_model
def append_disclaimer(state, runtime):
"""Añadir automáticamente un descargo de responsabilidad después de cada respuesta del modelo"""
messages = state.get("messages", [])
if not messages:
return None
last_msg = messages[-1]
# Añadir solo en la respuesta final de la IA (sin tool_calls)
if (last_msg.type == "ai"
and last_msg.content
and not (hasattr(last_msg, 'tool_calls') and last_msg.tool_calls)):
# Reemplazar el último mensaje de la IA añadiendo el descargo de responsabilidad
return {
"messages": [
AIMessage(
content=(
last_msg.content
+ "\n\n---\n*El contenido anterior es generado por el asistente de IA de EJEMPLO, solo como referencia.*
)
)
]
}
return None
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
middleware=[append_disclaimer],
system_prompt="Eres el asistente de EJEMPLO.",
)
result = agent.invoke({
"messages": [HumanMessage(content="¿Es fácil aprender Python?")]
})
print(result["messages"][-1].content)
Resultado de la ejecución:
Python 是一门非常适合初学者的编程语言,它的语法简洁、贴近自然语言... --- *El contenido anterior fue generado por el asistente de IA de Ejemplo y es solo de referencia.*
can_jump_to — Control de salto de flujo
En before_model y after_model, puedes controlar el flujo del agente mediante el parámetro can_jump_to y el estado jump_to:
Ejemplo
@before_model(can_jump_to=["end"]) # Declarar los destinos a los que se puede saltar
def conditional_exit(state, runtime):
"""Finalizar el agente directamente bajo ciertas condiciones"""
messages = state.get("messages", [])
if not messages:
return None
# Si el usuario dice "adiós", finalizar directamente
last_content = str(messages[-1].content)
if last_content.strip() in ["adiós", "chao", "bye"]:
return {
"jump_to": "end", # Finalizar el agente directamente
"messages": [{"role": "assistant", "content": "¡Adiós! Espero poder servirle en la próxima ocasión."}]
}
return None
| Valor de can_jump_to | Significado | Escenario aplicable |
|---|---|---|
| ["end"] | Puede saltar al final | Salida condicional, intercepción de seguridad |
| ["model"] | Puede saltar de vuelta al modelo | Requiere que el modelo reprocese |
| ["tools"] | Puede saltar al nodo de herramienta | Omitir el modelo y ejecutar la herramienta directamente |
| ["model", "end"] | Puede saltar al modelo o al final | Múltiples ramas condicionales |
Otras extensionesSi no se declara un destino en can_jump_to, jump_to se ignorará. Este es un mecanismo de seguridad para evitar que el middleware salte accidentalmente a nodos no válidos.