langchain
89079ad4 - feat(langchain): new decorator pattern for dynamically generated middleware (#33053)

Commit
155 days ago
feat(langchain): new decorator pattern for dynamically generated middleware (#33053) # Main Changes 1. Adding decorator utilities for dynamically defining middleware with single hook functions (see an example below for dynamic system prompt) 2. Adding better conditional edge drawing with jump configuration attached to middleware. Can be registered w/ the decorator new decorator! ## Decorator Utilities ```py from langchain.agents.middleware_agent import create_agent, AgentState, ModelRequest from langchain.agents.middleware.types import modify_model_request from langchain_core.messages import HumanMessage from langgraph.checkpoint.memory import InMemorySaver @modify_model_request def modify_system_prompt(request: ModelRequest, state: AgentState) -> ModelRequest: request.system_prompt = ( "You are a helpful assistant." f"Please record the number of previous messages in your response: {len(state['messages'])}" ) return request agent = create_agent( model="openai:gpt-4o-mini", middleware=[modify_system_prompt] ).compile(checkpointer=InMemorySaver()) ``` ## Visualization and Routing improvements We now require that middlewares define the valid jumps for each hook. If using the new decorator syntax, this can be done with: ```py @before_model(jump_to=["__end__"]) @after_model(jump_to=["tools", "__end__"]) ``` If using the subclassing syntax, you can use these two class vars: ```py class MyMiddlewareAgentMiddleware): before_model_jump_to = ["__end__"] after_model_jump_to = ["tools", "__end__"] ``` Open for debate if we want to bundle these in a single jump map / config for a middleware. Easy to migrate later if we decide to add more hooks. We will need to **really clearly document** that these must be explicitly set in order to enable conditional edges. Notice for the below case, `Middleware2` does actually enable jumps. <table> <thead> <tr> <th>Before (broken), adding conditional edges unconditionally</th> <th>After (fixed), adding conditional edges sparingly</th> </tr> </thead> <tbody> <tr> <td> <img width="619" height="508" alt="Screenshot 2025-09-23 at 10 23 23 AM" src="https://github.com/user-attachments/assets/bba2d098-a839-4335-8e8c-b50dd8090959" /> </td> <td> <img width="469" height="490" alt="Screenshot 2025-09-23 at 10 23 13 AM" src="https://github.com/user-attachments/assets/717abf0b-fc73-4d5f-9313-b81247d8fe26" /> </td> </tr> </tbody> </table> <details> <summary>Snippet for the above</summary> ```py from typing import Any from langchain.agents.tool_node import InjectedState from langgraph.runtime import Runtime from langchain.agents.middleware.types import AgentMiddleware, AgentState from langchain.agents.middleware_agent import create_agent from langchain_core.tools import tool from typing import Annotated from langchain_core.messages import HumanMessage from typing_extensions import NotRequired @tool def simple_tool(input: str) -> str: """A simple tool.""" return "successful tool call" class Middleware1(AgentMiddleware): """Custom middleware that adds a simple tool.""" tools = [simple_tool] def before_model(self, state: AgentState, runtime: Runtime) -> None: return None def after_model(self, state: AgentState, runtime: Runtime) -> None: return None class Middleware2(AgentMiddleware): before_model_jump_to = ["tools", "__end__"] def before_model(self, state: AgentState, runtime: Runtime) -> None: return None def after_model(self, state: AgentState, runtime: Runtime) -> None: return None class Middleware3(AgentMiddleware): def before_model(self, state: AgentState, runtime: Runtime) -> None: return None def after_model(self, state: AgentState, runtime: Runtime) -> None: return None builder = create_agent( model="openai:gpt-4o-mini", middleware=[Middleware1(), Middleware2(), Middleware3()], system_prompt="You are a helpful assistant.", ) agent = builder.compile() ``` </details> ## More Examples ### Guardrails `after_model` <img width="379" height="335" alt="Screenshot 2025-09-23 at 10 40 09 AM" src="https://github.com/user-attachments/assets/45bac7dd-398e-45d1-ae58-6ecfa27dfc87" /> <details> <summary>Code</summary> ```py from langchain.agents.middleware_agent import create_agent, AgentState, ModelRequest from langchain.agents.middleware.types import after_model from langchain_core.messages import HumanMessage, AIMessage from langgraph.checkpoint.memory import InMemorySaver from typing import cast, Any @after_model(jump_to=["model", "__end__"]) def after_model_hook(state: AgentState) -> dict[str, Any]: """Check the last AI message for safety violations.""" last_message_content = cast(AIMessage, state["messages"][-1]).content.lower() print(last_message_content) unsafe_keywords = ["pineapple"] if any(keyword in last_message_content for keyword in unsafe_keywords): # Jump back to model to regenerate response return {"jump_to": "model", "messages": [HumanMessage("Please regenerate your response, and don't talk about pineapples. You can talk about apples instead.")]} return {"jump_to": "__end__"} # Create agent with guardrails middleware agent = create_agent( model="openai:gpt-4o-mini", middleware=[after_model_hook], system_prompt="Keep your responses to one sentence please!" ).compile() # Test with potentially unsafe input result = agent.invoke( {"messages": [HumanMessage("Tell me something about pineapples")]}, ) for msg in result["messages"]: print(msg.pretty_print()) """ ================================ Human Message ================================= Tell me something about pineapples None ================================== Ai Message ================================== Pineapples are tropical fruits known for their sweet, tangy flavor and distinctive spiky exterior. None ================================ Human Message ================================= Please regenerate your response, and don't talk about pineapples. You can talk about apples instead. None ================================== Ai Message ================================== Apples are popular fruits that come in various varieties, known for their crisp texture and sweetness, and are often used in cooking and baking. None """ ``` </details>
Author
Parents
Loading