Ensure functions have docstrings for documentation
async def abefore_agent(
1"""Types for middleware and agents."""23from __future__ import annotations45from collections.abc import Awaitable, Callable, Sequence6from dataclasses import dataclass, field, replace7from inspect import iscoroutinefunction8from typing import (9 TYPE_CHECKING,10 Annotated,11 Any,12 Generic,13 Literal,14 Protocol,15 cast,16 overload,17)1819if TYPE_CHECKING:20 from collections.abc import Awaitable2122# Needed as top level import for Pydantic schema generation on AgentState23import warnings24from typing import TypeAlias2526from langchain_core.messages import (27 AIMessage,28 AnyMessage,29 BaseMessage,30 SystemMessage,31 ToolMessage,32)33from langgraph.channels.ephemeral_value import EphemeralValue34from langgraph.graph.message import add_messages35from langgraph.prebuilt.tool_node import ToolCallRequest, ToolCallWrapper36from langgraph.typing import ContextT37from typing_extensions import NotRequired, Required, TypedDict, TypeVar, Unpack3839if TYPE_CHECKING:40 from langchain_core.language_models.chat_models import BaseChatModel41 from langchain_core.tools import BaseTool42 from langgraph.runtime import Runtime43 from langgraph.stream._mux import TransformerFactory44 from langgraph.types import Command4546 from langchain.agents.structured_output import ResponseFormat4748__all__ = [49 "AgentMiddleware",50 "AgentState",51 "ContextT",52 "ExtendedModelResponse",53 "ModelCallResult",54 "ModelRequest",55 "ModelResponse",56 "OmitFromSchema",57 "ResponseT",58 "StateT_co",59 "ToolCallRequest",60 "ToolCallWrapper",61 "after_agent",62 "after_model",63 "before_agent",64 "before_model",65 "dynamic_prompt",66 "hook_config",67 "wrap_tool_call",68]6970JumpTo = Literal["tools", "model", "end"]71"""Destination to jump to when a middleware node returns."""7273ResponseT = TypeVar("ResponseT", default=Any)747576class _ModelRequestOverrides(TypedDict, total=False):77 """Possible overrides for `ModelRequest.override()` method."""7879 model: BaseChatModel80 system_message: SystemMessage | None81 messages: list[AnyMessage]82 tool_choice: Any | None83 tools: list[BaseTool | dict[str, Any]]84 response_format: ResponseFormat[Any] | None85 model_settings: dict[str, Any]86 state: AgentState[Any]878889@dataclass(init=False)90class ModelRequest(Generic[ContextT]):91 """Model request information for the agent.9293 Type Parameters:94 ContextT: The type of the runtime context.9596 Defaults to `None` if not specified.97 """9899 model: BaseChatModel100 messages: list[AnyMessage] # excluding system message101 system_message: SystemMessage | None102 tool_choice: Any | None103 tools: list[BaseTool | dict[str, Any]]104 response_format: ResponseFormat[Any] | None105 state: AgentState[Any]106 runtime: Runtime[ContextT]107 model_settings: dict[str, Any] = field(default_factory=dict)108109 def __init__(110 self,111 *,112 model: BaseChatModel,113 messages: list[AnyMessage],114 system_message: SystemMessage | None = None,115 system_prompt: str | None = None,116 tool_choice: Any | None = None,117 tools: list[BaseTool | dict[str, Any]] | None = None,118 response_format: ResponseFormat[Any] | None = None,119 state: AgentState[Any] | None = None,120 runtime: Runtime[ContextT] | None = None,121 model_settings: dict[str, Any] | None = None,122 ) -> None:123 """Initialize `ModelRequest` with backward compatibility for `system_prompt`.124125 Args:126 model: The chat model to use.127 messages: List of messages (excluding system prompt).128 tool_choice: Tool choice configuration.129 tools: List of available tools.130 response_format: Response format specification.131 state: Agent state.132 runtime: Runtime context.133 model_settings: Additional model settings.134 system_message: System message instance (preferred).135 system_prompt: System prompt string (deprecated, converted to `SystemMessage`).136137 Raises:138 ValueError: If both `system_prompt` and `system_message` are provided.139 """140 # Handle system_prompt/system_message conversion and validation141 if system_prompt is not None and system_message is not None:142 msg = "Cannot specify both system_prompt and system_message"143 raise ValueError(msg)144145 if system_prompt is not None:146 system_message = SystemMessage(content=system_prompt)147148 with warnings.catch_warnings():149 warnings.simplefilter("ignore", category=DeprecationWarning)150 self.model = model151 self.messages = messages152 self.system_message = system_message153 self.tool_choice = tool_choice154 self.tools = tools if tools is not None else []155 self.response_format = response_format156 self.state = state if state is not None else {"messages": []}157 self.runtime = runtime # type: ignore[assignment]158 self.model_settings = model_settings if model_settings is not None else {}159160 @property161 def system_prompt(self) -> str | None:162 """Get system prompt text from system_message.163164 Returns:165 The content of the system message if present, otherwise `None`.166 """167 if self.system_message is None:168 return None169 return self.system_message.text170171 def __setattr__(self, name: str, value: Any) -> None:172 """Set an attribute with a deprecation warning.173174 Direct attribute assignment on `ModelRequest` is deprecated. Use the175 `override()` method instead to create a new request with modified attributes.176177 Args:178 name: Attribute name.179 value: Attribute value.180 """181 # Special handling for system_prompt - convert to system_message182 if name == "system_prompt":183 warnings.warn(184 "Direct attribute assignment to ModelRequest.system_prompt is deprecated. "185 "Use request.override(system_message=SystemMessage(...)) instead to create "186 "a new request with the modified system message.",187 DeprecationWarning,188 stacklevel=2,189 )190 if value is None:191 object.__setattr__(self, "system_message", None)192 else:193 object.__setattr__(self, "system_message", SystemMessage(content=value))194 return195196 warnings.warn(197 f"Direct attribute assignment to ModelRequest.{name} is deprecated. "198 f"Use request.override({name}=...) instead to create a new request "199 f"with the modified attribute.",200 DeprecationWarning,201 stacklevel=2,202 )203 object.__setattr__(self, name, value)204205 def override(self, **overrides: Unpack[_ModelRequestOverrides]) -> ModelRequest[ContextT]:206 """Replace the request with a new request with the given overrides.207208 Returns a new `ModelRequest` instance with the specified attributes replaced.209210 This follows an immutable pattern, leaving the original request unchanged.211212 Args:213 **overrides: Keyword arguments for attributes to override.214215 Supported keys:216217 - `model`: `BaseChatModel` instance218 - `system_prompt`: deprecated, use `system_message` instead219 - `system_message`: `SystemMessage` instance220 - `messages`: `list` of messages221 - `tool_choice`: Tool choice configuration222 - `tools`: `list` of available tools223 - `response_format`: Response format specification224 - `model_settings`: Additional model settings225 - `state`: Agent state dictionary226227 Returns:228 New `ModelRequest` instance with specified overrides applied.229230 Examples:231 !!! example "Create a new request with different model"232233 ```python234 new_request = request.override(model=different_model)235 ```236237 !!! example "Override system message (preferred)"238239 ```python240 from langchain_core.messages import SystemMessage241242 new_request = request.override(243 system_message=SystemMessage(content="New instructions")244 )245 ```246247 !!! example "Override multiple attributes"248249 ```python250 new_request = request.override(251 model=ChatOpenAI(model="gpt-5.5"),252 system_message=SystemMessage(content="New instructions"),253 )254 ```255256 Raises:257 ValueError: If both `system_prompt` and `system_message` are provided.258 """259 # Handle system_prompt/system_message conversion260 if "system_prompt" in overrides and "system_message" in overrides:261 msg = "Cannot specify both system_prompt and system_message"262 raise ValueError(msg)263264 if "system_prompt" in overrides:265 system_prompt = cast("str | None", overrides.pop("system_prompt")) # type: ignore[typeddict-item]266 if system_prompt is None:267 overrides["system_message"] = None268 else:269 overrides["system_message"] = SystemMessage(content=system_prompt)270271 return replace(self, **overrides)272273274@dataclass275class ModelResponse(Generic[ResponseT]):276 """Response from model execution including messages and optional structured output.277278 The result will usually contain a single `AIMessage`, but may include an additional279 `ToolMessage` if the model used a tool for structured output.280281 Type Parameters:282 ResponseT: The type of the structured response. Defaults to `Any` if not specified.283 """284285 result: list[BaseMessage]286 """List of messages from model execution."""287288 structured_response: ResponseT | None = None289 """Parsed structured output if `response_format` was specified, `None` otherwise."""290291292@dataclass293class ExtendedModelResponse(Generic[ResponseT]):294 """Model response with an optional 'Command' from 'wrap_model_call' middleware.295296 Use this to return a 'Command' alongside the model response from a297 'wrap_model_call' handler. The command is applied as an additional state298 update after the model node completes, using the graph's reducers (e.g.299 'add_messages' for the 'messages' key).300301 Because each 'Command' is applied through the reducer, messages in the302 command are **added alongside** the model response messages rather than303 replacing them. For non-reducer state fields, later commands overwrite304 earlier ones (outermost middleware wins over inner).305306 Type Parameters:307 ResponseT: The type of the structured response. Defaults to 'Any' if not specified.308 """309310 model_response: ModelResponse[ResponseT]311 """The underlying model response."""312313 command: Command[Any] | None = None314 """Optional command to apply as an additional state update."""315316317ModelCallResult: TypeAlias = (318 "ModelResponse[ResponseT] | AIMessage | ExtendedModelResponse[ResponseT]"319)320"""`TypeAlias` for model call handler return value.321322Middleware can return either:323324- `ModelResponse`: Full response with messages and optional structured output325- `AIMessage`: Simplified return for simple use cases326- `ExtendedModelResponse`: Response with an optional `Command` for additional state updates327 `goto`, `resume`, and `graph` are not yet supported on these commands.328 A `NotImplementedError` will be raised if you try to use them.329"""330331332@dataclass333class OmitFromSchema:334 """Annotation used to mark state attributes as omitted from input or output schemas."""335336 input: bool = True337 """Whether to omit the attribute from the input schema."""338339 output: bool = True340 """Whether to omit the attribute from the output schema."""341342343OmitFromInput = OmitFromSchema(input=True, output=False)344"""Annotation used to mark state attributes as omitted from input schema."""345346OmitFromOutput = OmitFromSchema(input=False, output=True)347"""Annotation used to mark state attributes as omitted from output schema."""348349PrivateStateAttr = OmitFromSchema(input=True, output=True)350"""Annotation used to mark state attributes as purely internal for a given middleware."""351352353class AgentState(TypedDict, Generic[ResponseT]):354 """State schema for the agent."""355356 messages: Required[Annotated[list[AnyMessage], add_messages]]357 jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]358 structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]359360361class _InputAgentState(TypedDict): # noqa: PYI049362 """Input state schema for the agent."""363364 messages: Required[Annotated[list[AnyMessage | dict[str, Any]], add_messages]]365366367class _OutputAgentState(TypedDict, Generic[ResponseT]): # noqa: PYI049368 """Output state schema for the agent."""369370 messages: Required[Annotated[list[AnyMessage], add_messages]]371 structured_response: NotRequired[ResponseT]372373374StateT = TypeVar("StateT", bound=AgentState[Any], default=AgentState[Any])375StateT_co = TypeVar("StateT_co", bound=AgentState[Any], default=AgentState[Any], covariant=True)376StateT_contra = TypeVar("StateT_contra", bound=AgentState[Any], contravariant=True)377378379class _DefaultAgentState(AgentState[Any]):380 """AgentMiddleware default state."""381382383class AgentMiddleware(Generic[StateT, ContextT, ResponseT]):384 """Base middleware class for an agent.385386 Subclass this and implement any of the defined methods to customize agent behavior387 between steps in the main agent loop.388389 Type Parameters:390 StateT: The type of the agent state. Defaults to `AgentState[Any]`.391 ContextT: The type of the runtime context. Defaults to `None`.392 ResponseT: The type of the structured response. Defaults to `Any`.393 """394395 state_schema: type[StateT] = cast("type[StateT]", _DefaultAgentState)396 """The schema for state passed to the middleware nodes."""397398 tools: Sequence[BaseTool]399 """Additional tools registered by the middleware."""400401 transformers: Sequence[TransformerFactory] = ()402 """Stream transformer factories registered by the middleware.403404 Each entry is a scope-aware factory invoked as `factory(scope)` so every405 invocation receives a fresh instance. Factories are merged with the406 `transformers` argument of [`create_agent`][langchain.agents.create_agent]407 at graph compile time, after the `ToolCallTransformer` and before any408 user-supplied entries.409 """410411 @property412 def name(self) -> str:413 """The name of the middleware instance.414415 Defaults to the class name, but can be overridden for custom naming.416 """417 return self.__class__.__name__418419 def before_agent(self, state: StateT, runtime: Runtime[ContextT]) -> dict[str, Any] | None:420 """Logic to run before the agent execution starts.421422 Args:423 state: The current agent state.424 runtime: The runtime context.425426 Returns:427 Agent state updates to apply before agent execution.428 """429430 async def abefore_agent(431 self, state: StateT, runtime: Runtime[ContextT]432 ) -> dict[str, Any] | None:433 """Async logic to run before the agent execution starts.434435 Args:436 state: The current agent state.437 runtime: The runtime context.438439 Returns:440 Agent state updates to apply before agent execution.441 """442443 def before_model(self, state: StateT, runtime: Runtime[ContextT]) -> dict[str, Any] | None:444 """Logic to run before the model is called.445446 Args:447 state: The current agent state.448 runtime: The runtime context.449450 Returns:451 Agent state updates to apply before model call.452 """453454 async def abefore_model(455 self, state: StateT, runtime: Runtime[ContextT]456 ) -> dict[str, Any] | None:457 """Async logic to run before the model is called.458459 Args:460 state: The agent state.461 runtime: The runtime context.462463 Returns:464 Agent state updates to apply before model call.465 """466467 def after_model(self, state: StateT, runtime: Runtime[ContextT]) -> dict[str, Any] | None:468 """Logic to run after the model is called.469470 Args:471 state: The current agent state.472 runtime: The runtime context.473474 Returns:475 Agent state updates to apply after model call.476 """477478 async def aafter_model(479 self, state: StateT, runtime: Runtime[ContextT]480 ) -> dict[str, Any] | None:481 """Async logic to run after the model is called.482483 Args:484 state: The current agent state.485 runtime: The runtime context.486487 Returns:488 Agent state updates to apply after model call.489 """490491 def wrap_model_call(492 self,493 request: ModelRequest[ContextT],494 handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],495 ) -> ModelResponse[ResponseT] | AIMessage | ExtendedModelResponse[ResponseT]:496 """Intercept and control model execution via handler callback.497498 Async version is `awrap_model_call`499500 The handler callback executes the model request and returns a `ModelResponse`.501 Middleware can call the handler multiple times for retry logic, skip calling502 it to short-circuit, or modify the request/response. Multiple middleware503 compose with first in list as outermost layer.504505 Args:506 request: Model request to execute (includes state and runtime).507 handler: Callback that executes the model request and returns508 `ModelResponse`.509510 Call this to execute the model.511512 Can be called multiple times for retry logic.513514 Can skip calling it to short-circuit.515516 Returns:517 The model call result.518519 Examples:520 !!! example "Retry on error"521522 ```python523 def wrap_model_call(self, request, handler):524 for attempt in range(3):525 try:526 return handler(request)527 except Exception:528 if attempt == 2:529 raise530 ```531532 !!! example "Rewrite response"533534 ```python535 def wrap_model_call(self, request, handler):536 response = handler(request)537 ai_msg = response.result[0]538 return ModelResponse(539 result=[AIMessage(content=f"[{ai_msg.content}]")],540 structured_response=response.structured_response,541 )542 ```543544 !!! example "Error to fallback"545546 ```python547 def wrap_model_call(self, request, handler):548 try:549 return handler(request)550 except Exception:551 return ModelResponse(result=[AIMessage(content="Service unavailable")])552 ```553554 !!! example "Cache/short-circuit"555556 ```python557 def wrap_model_call(self, request, handler):558 if cached := get_cache(request):559 return cached # Short-circuit with cached result560 response = handler(request)561 save_cache(request, response)562 return response563 ```564565 !!! example "Simple `AIMessage` return (converted automatically)"566567 ```python568 def wrap_model_call(self, request, handler):569 response = handler(request)570 # Can return AIMessage directly for simple cases571 return AIMessage(content="Simplified response")572 ```573 """574 msg = (575 "Synchronous implementation of wrap_model_call is not available. "576 "You are likely encountering this error because you defined only the async version "577 "(awrap_model_call) and invoked your agent in a synchronous context "578 "(e.g., using `stream()` or `invoke()`). "579 "To resolve this, either: "580 "(1) subclass AgentMiddleware and implement the synchronous wrap_model_call method, "581 "(2) use the @wrap_model_call decorator on a standalone sync function, or "582 "(3) invoke your agent asynchronously using `astream()` or `ainvoke()`."583 )584 raise NotImplementedError(msg)585586 async def awrap_model_call(587 self,588 request: ModelRequest[ContextT],589 handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],590 ) -> ModelResponse[ResponseT] | AIMessage | ExtendedModelResponse[ResponseT]:591 """Intercept and control async model execution via handler callback.592593 The handler callback executes the model request and returns a `ModelResponse`.594595 Middleware can call the handler multiple times for retry logic, skip calling596 it to short-circuit, or modify the request/response. Multiple middleware597 compose with first in list as outermost layer.598599 Args:600 request: Model request to execute (includes state and runtime).601 handler: Async callback that executes the model request and returns602 `ModelResponse`.603604 Call this to execute the model.605606 Can be called multiple times for retry logic.607608 Can skip calling it to short-circuit.609610 Returns:611 The model call result.612613 Examples:614 !!! example "Retry on error"615616 ```python617 async def awrap_model_call(self, request, handler):618 for attempt in range(3):619 try:620 return await handler(request)621 except Exception:622 if attempt == 2:623 raise624 ```625 """626 msg = (627 "Asynchronous implementation of awrap_model_call is not available. "628 "You are likely encountering this error because you defined only the sync version "629 "(wrap_model_call) and invoked your agent in an asynchronous context "630 "(e.g., using `astream()` or `ainvoke()`). "631 "To resolve this, either: "632 "(1) subclass AgentMiddleware and implement the asynchronous awrap_model_call method, "633 "(2) use the @wrap_model_call decorator on a standalone async function, or "634 "(3) invoke your agent synchronously using `stream()` or `invoke()`."635 )636 raise NotImplementedError(msg)637638 def after_agent(self, state: StateT, runtime: Runtime[ContextT]) -> dict[str, Any] | None:639 """Logic to run after the agent execution completes.640641 Args:642 state: The current agent state.643 runtime: The runtime context.644645 Returns:646 Agent state updates to apply after agent execution.647 """648649 async def aafter_agent(650 self, state: StateT, runtime: Runtime[ContextT]651 ) -> dict[str, Any] | None:652 """Async logic to run after the agent execution completes.653654 Args:655 state: The current agent state.656 runtime: The runtime context.657658 Returns:659 Agent state updates to apply after agent execution.660 """661662 def wrap_tool_call(663 self,664 request: ToolCallRequest,665 handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],666 ) -> ToolMessage | Command[Any]:667 """Intercept tool execution for retries, monitoring, or modification.668669 Async version is `awrap_tool_call`670671 Multiple middleware compose automatically (first defined = outermost).672673 Exceptions propagate unless `handle_tool_errors` is configured on `ToolNode`.674675 Args:676 request: Tool call request with call `dict`, `BaseTool`, state, and runtime.677678 Access state via `request.state` and runtime via `request.runtime`.679 handler: `Callable` to execute the tool (can be called multiple times).680681 Returns:682 `ToolMessage` or `Command` (the final result).683684 The handler `Callable` can be invoked multiple times for retry logic.685686 Each call to handler is independent and stateless.687688 Examples:689 !!! example "Modify request before execution"690691 ```python692 def wrap_tool_call(self, request, handler):693 modified_call = {694 **request.tool_call,695 "args": {696 **request.tool_call["args"],697 "value": request.tool_call["args"]["value"] * 2,698 },699 }700 request = request.override(tool_call=modified_call)701 return handler(request)702 ```703704 !!! example "Retry on error (call handler multiple times)"705706 ```python707 def wrap_tool_call(self, request, handler):708 for attempt in range(3):709 try:710 result = handler(request)711 if is_valid(result):712 return result713 except Exception:714 if attempt == 2:715 raise716 return result717 ```718719 !!! example "Conditional retry based on response"720721 ```python722 def wrap_tool_call(self, request, handler):723 for attempt in range(3):724 result = handler(request)725 if isinstance(result, ToolMessage) and result.status != "error":726 return result727 if attempt < 2:728 continue729 return result730 ```731 """732 msg = (733 "Synchronous implementation of wrap_tool_call is not available. "734 "You are likely encountering this error because you defined only the async version "735 "(awrap_tool_call) and invoked your agent in a synchronous context "736 "(e.g., using `stream()` or `invoke()`). "737 "To resolve this, either: "738 "(1) subclass AgentMiddleware and implement the synchronous wrap_tool_call method, "739 "(2) use the @wrap_tool_call decorator on a standalone sync function, or "740 "(3) invoke your agent asynchronously using `astream()` or `ainvoke()`."741 )742 raise NotImplementedError(msg)743744 async def awrap_tool_call(745 self,746 request: ToolCallRequest,747 handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],748 ) -> ToolMessage | Command[Any]:749 """Intercept and control async tool execution via handler callback.750751 The handler callback executes the tool call and returns a `ToolMessage` or752 `Command`. Middleware can call the handler multiple times for retry logic, skip753 calling it to short-circuit, or modify the request/response. Multiple middleware754 compose with first in list as outermost layer.755756 Args:757 request: Tool call request with call `dict`, `BaseTool`, state, and runtime.758759 Access state via `request.state` and runtime via `request.runtime`.760 handler: Async callable to execute the tool and returns `ToolMessage` or761 `Command`.762763 Call this to execute the tool.764765 Can be called multiple times for retry logic.766767 Can skip calling it to short-circuit.768769 Returns:770 `ToolMessage` or `Command` (the final result).771772 The handler `Callable` can be invoked multiple times for retry logic.773774 Each call to handler is independent and stateless.775776 Examples:777 !!! example "Async retry on error"778779 ```python780 async def awrap_tool_call(self, request, handler):781 for attempt in range(3):782 try:783 result = await handler(request)784 if is_valid(result):785 return result786 except Exception:787 if attempt == 2:788 raise789 return result790 ```791792 ```python793 async def awrap_tool_call(self, request, handler):794 if cached := await get_cache_async(request):795 return ToolMessage(content=cached, tool_call_id=request.tool_call["id"])796 result = await handler(request)797 await save_cache_async(request, result)798 return result799 ```800 """801 msg = (802 "Asynchronous implementation of awrap_tool_call is not available. "803 "You are likely encountering this error because you defined only the sync version "804 "(wrap_tool_call) and invoked your agent in an asynchronous context "805 "(e.g., using `astream()` or `ainvoke()`). "806 "To resolve this, either: "807 "(1) subclass AgentMiddleware and implement the asynchronous awrap_tool_call method, "808 "(2) use the @wrap_tool_call decorator on a standalone async function, or "809 "(3) invoke your agent synchronously using `stream()` or `invoke()`."810 )811 raise NotImplementedError(msg)812813814class _CallableWithStateAndRuntime(Protocol[StateT_contra, ContextT]):815 """Callable with `AgentState` and `Runtime` as arguments."""816817 def __call__(818 self, state: StateT_contra, runtime: Runtime[ContextT]819 ) -> dict[str, Any] | Command[Any] | None | Awaitable[dict[str, Any] | Command[Any] | None]:820 """Perform some logic with the state and runtime."""821 ...822823824class _CallableReturningSystemMessage(Protocol[StateT_contra, ContextT]): # type: ignore[misc]825 """Callable that returns a prompt string or SystemMessage given `ModelRequest`."""826827 def __call__(828 self, request: ModelRequest[ContextT]829 ) -> str | SystemMessage | Awaitable[str | SystemMessage]:830 """Generate a system prompt string or SystemMessage based on the request."""831 ...832833834class _CallableReturningModelResponse(Protocol[StateT_contra, ContextT, ResponseT]): # type: ignore[misc]835 """Callable for model call interception with handler callback.836837 Receives handler callback to execute model and returns `ModelResponse` or838 `AIMessage`.839 """840841 def __call__(842 self,843 request: ModelRequest[ContextT],844 handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],845 ) -> ModelResponse[ResponseT] | AIMessage:846 """Intercept model execution via handler callback."""847 ...848849850class _CallableReturningToolResponse(Protocol):851 """Callable for tool call interception with handler callback.852853 Receives handler callback to execute tool and returns final `ToolMessage` or854 `Command`.855 """856857 def __call__(858 self,859 request: ToolCallRequest,860 handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],861 ) -> ToolMessage | Command[Any]:862 """Intercept tool execution via handler callback."""863 ...864865866CallableT = TypeVar("CallableT", bound=Callable[..., Any])867868869def hook_config(870 *,871 can_jump_to: list[JumpTo] | None = None,872) -> Callable[[CallableT], CallableT]:873 """Decorator to configure hook behavior in middleware methods.874875 Use this decorator on `before_model` or `after_model` methods in middleware classes876 to configure their behavior. Currently supports specifying which destinations they877 can jump to, which establishes conditional edges in the agent graph.878879 Args:880 can_jump_to: Optional list of valid jump destinations.881882 Can be:883884 - `'tools'`: Jump to the tools node885 - `'model'`: Jump back to the model node886 - `'end'`: Jump to the end of the graph887888 Returns:889 Decorator function that marks the method with configuration metadata.890891 Examples:892 !!! example "Using decorator on a class method"893894 ```python895 class MyMiddleware(AgentMiddleware):896 @hook_config(can_jump_to=["end", "model"])897 def before_model(self, state: AgentState) -> dict[str, Any] | None:898 if some_condition(state):899 return {"jump_to": "end"}900 return None901 ```902903 Alternative: Use the `can_jump_to` parameter in `before_model`/`after_model`904 decorators:905906 ```python907 @before_model(can_jump_to=["end"])908 def conditional_middleware(state: AgentState) -> dict[str, Any] | None:909 if should_exit(state):910 return {"jump_to": "end"}911 return None912 ```913 """914915 def decorator(func: CallableT) -> CallableT:916 if can_jump_to is not None:917 func.__can_jump_to__ = can_jump_to # type: ignore[attr-defined]918 return func919920 return decorator921922923@overload924def before_model(925 func: _CallableWithStateAndRuntime[StateT, ContextT],926) -> AgentMiddleware[StateT, ContextT]: ...927928929@overload930def before_model(931 func: None = None,932 *,933 state_schema: type[StateT] | None = None,934 tools: list[BaseTool] | None = None,935 can_jump_to: list[JumpTo] | None = None,936 name: str | None = None,937) -> Callable[938 [_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]939]: ...940941942def before_model(943 func: _CallableWithStateAndRuntime[StateT, ContextT] | None = None,944 *,945 state_schema: type[StateT] | None = None,946 tools: list[BaseTool] | None = None,947 can_jump_to: list[JumpTo] | None = None,948 name: str | None = None,949) -> (950 Callable[[_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]]951 | AgentMiddleware[StateT, ContextT]952):953 """Decorator used to dynamically create a middleware with the `before_model` hook.954955 Args:956 func: The function to be decorated.957958 Must accept: `state: StateT, runtime: Runtime[ContextT]` - State and runtime959 context960 state_schema: Optional custom state schema type.961962 If not provided, uses the default `AgentState` schema.963 tools: Optional list of additional tools to register with this middleware.964 can_jump_to: Optional list of valid jump destinations for conditional edges.965966 Valid values are: `'tools'`, `'model'`, `'end'`967 name: Optional name for the generated middleware class.968969 If not provided, uses the decorated function's name.970971 Returns:972 Either an `AgentMiddleware` instance (if func is provided directly) or a973 decorator function that can be applied to a function it is wrapping.974975 The decorated function should return:976977 - `dict[str, Any]` - State updates to merge into the agent state978 - `Command` - A command to control flow (e.g., jump to different node)979 - `None` - No state updates or flow control980981 Examples:982 !!! example "Basic usage"983984 ```python985 @before_model986 def log_before_model(state: AgentState, runtime: Runtime) -> None:987 print(f"About to call model with {len(state['messages'])} messages")988 ```989990 !!! example "With conditional jumping"991992 ```python993 @before_model(can_jump_to=["end"])994 def conditional_before_model(995 state: AgentState, runtime: Runtime996 ) -> dict[str, Any] | None:997 if some_condition(state):998 return {"jump_to": "end"}999 return None1000 ```10011002 !!! example "With custom state schema"10031004 ```python1005 @before_model(state_schema=MyCustomState)1006 def custom_before_model(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:1007 return {"custom_field": "updated_value"}1008 ```10091010 !!! example "Streaming custom events before model call"10111012 Use `runtime.stream_writer` to emit custom events before each model invocation.1013 Events are received when streaming with `stream_mode="custom"`.10141015 ```python1016 @before_model1017 async def notify_model_call(state: AgentState, runtime: Runtime) -> None:1018 '''Notify user before model is called.'''1019 runtime.stream_writer(1020 {1021 "type": "status",1022 "message": "Thinking...",1023 }1024 )1025 ```1026 """10271028 def decorator(1029 func: _CallableWithStateAndRuntime[StateT, ContextT],1030 ) -> AgentMiddleware[StateT, ContextT]:1031 is_async = iscoroutinefunction(func)10321033 func_can_jump_to = (1034 can_jump_to if can_jump_to is not None else getattr(func, "__can_jump_to__", [])1035 )10361037 if is_async:10381039 async def async_wrapped(1040 _self: AgentMiddleware[StateT, ContextT],1041 state: StateT,1042 runtime: Runtime[ContextT],1043 ) -> dict[str, Any] | Command[Any] | None:1044 return await func(state, runtime) # type: ignore[misc]10451046 # Preserve can_jump_to metadata on the wrapped function1047 if func_can_jump_to:1048 async_wrapped.__can_jump_to__ = func_can_jump_to # type: ignore[attr-defined]10491050 middleware_name = name or cast(1051 "str", getattr(func, "__name__", "BeforeModelMiddleware")1052 )10531054 return type(1055 middleware_name,1056 (AgentMiddleware,),1057 {1058 "state_schema": state_schema or AgentState,1059 "tools": tools or [],1060 "abefore_model": async_wrapped,1061 },1062 )()10631064 def wrapped(1065 _self: AgentMiddleware[StateT, ContextT],1066 state: StateT,1067 runtime: Runtime[ContextT],1068 ) -> dict[str, Any] | Command[Any] | None:1069 return func(state, runtime) # type: ignore[return-value]10701071 # Preserve can_jump_to metadata on the wrapped function1072 if func_can_jump_to:1073 wrapped.__can_jump_to__ = func_can_jump_to # type: ignore[attr-defined]10741075 # Use function name as default if no name provided1076 middleware_name = name or cast("str", getattr(func, "__name__", "BeforeModelMiddleware"))10771078 return type(1079 middleware_name,1080 (AgentMiddleware,),1081 {1082 "state_schema": state_schema or AgentState,1083 "tools": tools or [],1084 "before_model": wrapped,1085 },1086 )()10871088 if func is not None:1089 return decorator(func)1090 return decorator109110921093@overload1094def after_model(1095 func: _CallableWithStateAndRuntime[StateT, ContextT],1096) -> AgentMiddleware[StateT, ContextT]: ...109710981099@overload1100def after_model(1101 func: None = None,1102 *,1103 state_schema: type[StateT] | None = None,1104 tools: list[BaseTool] | None = None,1105 can_jump_to: list[JumpTo] | None = None,1106 name: str | None = None,1107) -> Callable[1108 [_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]1109]: ...111011111112def after_model(1113 func: _CallableWithStateAndRuntime[StateT, ContextT] | None = None,1114 *,1115 state_schema: type[StateT] | None = None,1116 tools: list[BaseTool] | None = None,1117 can_jump_to: list[JumpTo] | None = None,1118 name: str | None = None,1119) -> (1120 Callable[[_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]]1121 | AgentMiddleware[StateT, ContextT]1122):1123 """Decorator used to dynamically create a middleware with the `after_model` hook.11241125 Args:1126 func: The function to be decorated.11271128 Must accept: `state: StateT, runtime: Runtime[ContextT]` - State and runtime1129 context1130 state_schema: Optional custom state schema type.11311132 If not provided, uses the default `AgentState` schema.1133 tools: Optional list of additional tools to register with this middleware.1134 can_jump_to: Optional list of valid jump destinations for conditional edges.11351136 Valid values are: `'tools'`, `'model'`, `'end'`1137 name: Optional name for the generated middleware class.11381139 If not provided, uses the decorated function's name.11401141 Returns:1142 Either an `AgentMiddleware` instance (if func is provided) or a decorator1143 function that can be applied to a function.11441145 The decorated function should return:11461147 - `dict[str, Any]` - State updates to merge into the agent state1148 - `Command` - A command to control flow (e.g., jump to different node)1149 - `None` - No state updates or flow control11501151 Examples:1152 !!! example "Basic usage for logging model responses"11531154 ```python1155 @after_model1156 def log_latest_message(state: AgentState, runtime: Runtime) -> None:1157 print(state["messages"][-1].content)1158 ```11591160 !!! example "With custom state schema"11611162 ```python1163 @after_model(state_schema=MyCustomState, name="MyAfterModelMiddleware")1164 def custom_after_model(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:1165 return {"custom_field": "updated_after_model"}1166 ```11671168 !!! example "Streaming custom events after model call"11691170 Use `runtime.stream_writer` to emit custom events after model responds.1171 Events are received when streaming with `stream_mode="custom"`.11721173 ```python1174 @after_model1175 async def notify_model_response(state: AgentState, runtime: Runtime) -> None:1176 '''Notify user after model has responded.'''1177 last_message = state["messages"][-1]1178 has_tool_calls = hasattr(last_message, "tool_calls") and last_message.tool_calls1179 runtime.stream_writer(1180 {1181 "type": "status",1182 "message": "Using tools..." if has_tool_calls else "Response ready!",1183 }1184 )1185 ```1186 """11871188 def decorator(1189 func: _CallableWithStateAndRuntime[StateT, ContextT],1190 ) -> AgentMiddleware[StateT, ContextT]:1191 is_async = iscoroutinefunction(func)1192 # Extract can_jump_to from decorator parameter or from function metadata1193 func_can_jump_to = (1194 can_jump_to if can_jump_to is not None else getattr(func, "__can_jump_to__", [])1195 )11961197 if is_async:11981199 async def async_wrapped(1200 _self: AgentMiddleware[StateT, ContextT],1201 state: StateT,1202 runtime: Runtime[ContextT],1203 ) -> dict[str, Any] | Command[Any] | None:1204 return await func(state, runtime) # type: ignore[misc]12051206 # Preserve can_jump_to metadata on the wrapped function1207 if func_can_jump_to:1208 async_wrapped.__can_jump_to__ = func_can_jump_to # type: ignore[attr-defined]12091210 middleware_name = name or cast("str", getattr(func, "__name__", "AfterModelMiddleware"))12111212 return type(1213 middleware_name,1214 (AgentMiddleware,),1215 {1216 "state_schema": state_schema or AgentState,1217 "tools": tools or [],1218 "aafter_model": async_wrapped,1219 },1220 )()12211222 def wrapped(1223 _self: AgentMiddleware[StateT, ContextT],1224 state: StateT,1225 runtime: Runtime[ContextT],1226 ) -> dict[str, Any] | Command[Any] | None:1227 return func(state, runtime) # type: ignore[return-value]12281229 # Preserve can_jump_to metadata on the wrapped function1230 if func_can_jump_to:1231 wrapped.__can_jump_to__ = func_can_jump_to # type: ignore[attr-defined]12321233 # Use function name as default if no name provided1234 middleware_name = name or cast("str", getattr(func, "__name__", "AfterModelMiddleware"))12351236 return type(1237 middleware_name,1238 (AgentMiddleware,),1239 {1240 "state_schema": state_schema or AgentState,1241 "tools": tools or [],1242 "after_model": wrapped,1243 },1244 )()12451246 if func is not None:1247 return decorator(func)1248 return decorator124912501251@overload1252def before_agent(1253 func: _CallableWithStateAndRuntime[StateT, ContextT],1254) -> AgentMiddleware[StateT, ContextT]: ...125512561257@overload1258def before_agent(1259 func: None = None,1260 *,1261 state_schema: type[StateT] | None = None,1262 tools: list[BaseTool] | None = None,1263 can_jump_to: list[JumpTo] | None = None,1264 name: str | None = None,1265) -> Callable[1266 [_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]1267]: ...126812691270def before_agent(1271 func: _CallableWithStateAndRuntime[StateT, ContextT] | None = None,1272 *,1273 state_schema: type[StateT] | None = None,1274 tools: list[BaseTool] | None = None,1275 can_jump_to: list[JumpTo] | None = None,1276 name: str | None = None,1277) -> (1278 Callable[[_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]]1279 | AgentMiddleware[StateT, ContextT]1280):1281 """Decorator used to dynamically create a middleware with the `before_agent` hook.12821283 Args:1284 func: The function to be decorated.12851286 Must accept: `state: StateT, runtime: Runtime[ContextT]` - State and runtime1287 context1288 state_schema: Optional custom state schema type.12891290 If not provided, uses the default `AgentState` schema.1291 tools: Optional list of additional tools to register with this middleware.1292 can_jump_to: Optional list of valid jump destinations for conditional edges.12931294 Valid values are: `'tools'`, `'model'`, `'end'`1295 name: Optional name for the generated middleware class.12961297 If not provided, uses the decorated function's name.12981299 Returns:1300 Either an `AgentMiddleware` instance (if func is provided directly) or a1301 decorator function that can be applied to a function it is wrapping.13021303 The decorated function should return:13041305 - `dict[str, Any]` - State updates to merge into the agent state1306 - `Command` - A command to control flow (e.g., jump to different node)1307 - `None` - No state updates or flow control13081309 Examples:1310 !!! example "Basic usage"13111312 ```python1313 @before_agent1314 def log_before_agent(state: AgentState, runtime: Runtime) -> None:1315 print(f"Starting agent with {len(state['messages'])} messages")1316 ```13171318 !!! example "With conditional jumping"13191320 ```python1321 @before_agent(can_jump_to=["end"])1322 def conditional_before_agent(1323 state: AgentState, runtime: Runtime1324 ) -> dict[str, Any] | None:1325 if some_condition(state):1326 return {"jump_to": "end"}1327 return None1328 ```13291330 !!! example "With custom state schema"13311332 ```python1333 @before_agent(state_schema=MyCustomState)1334 def custom_before_agent(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:1335 return {"custom_field": "initialized_value"}1336 ```13371338 !!! example "Streaming custom events"13391340 Use `runtime.stream_writer` to emit custom events during agent execution.1341 Events are received when streaming with `stream_mode="custom"`.13421343 ```python1344 from langchain.agents import create_agent1345 from langchain.agents.middleware import before_agent, AgentState1346 from langchain.messages import HumanMessage1347 from langgraph.runtime import Runtime134813491350 @before_agent1351 async def notify_start(state: AgentState, runtime: Runtime) -> None:1352 '''Notify user that agent is starting.'''1353 runtime.stream_writer(1354 {1355 "type": "status",1356 "message": "Initializing agent session...",1357 }1358 )1359 # Perform prerequisite tasks here1360 runtime.stream_writer({"type": "status", "message": "Agent ready!"})136113621363 agent = create_agent(1364 model="openai:gpt-5.2",1365 tools=[...],1366 middleware=[notify_start],1367 )13681369 # Consume with stream_mode="custom" to receive events1370 async for mode, event in agent.astream(1371 {"messages": [HumanMessage("Hello")]},1372 stream_mode=["updates", "custom"],1373 ):1374 if mode == "custom":1375 print(f"Status: {event}")1376 ```1377 """13781379 def decorator(1380 func: _CallableWithStateAndRuntime[StateT, ContextT],1381 ) -> AgentMiddleware[StateT, ContextT]:1382 is_async = iscoroutinefunction(func)13831384 func_can_jump_to = (1385 can_jump_to if can_jump_to is not None else getattr(func, "__can_jump_to__", [])1386 )13871388 if is_async:13891390 async def async_wrapped(1391 _self: AgentMiddleware[StateT, ContextT],1392 state: StateT,1393 runtime: Runtime[ContextT],1394 ) -> dict[str, Any] | Command[Any] | None:1395 return await func(state, runtime) # type: ignore[misc]13961397 # Preserve can_jump_to metadata on the wrapped function1398 if func_can_jump_to:1399 async_wrapped.__can_jump_to__ = func_can_jump_to # type: ignore[attr-defined]14001401 middleware_name = name or cast(1402 "str", getattr(func, "__name__", "BeforeAgentMiddleware")1403 )14041405 return type(1406 middleware_name,1407 (AgentMiddleware,),1408 {1409 "state_schema": state_schema or AgentState,1410 "tools": tools or [],1411 "abefore_agent": async_wrapped,1412 },1413 )()14141415 def wrapped(1416 _self: AgentMiddleware[StateT, ContextT],1417 state: StateT,1418 runtime: Runtime[ContextT],1419 ) -> dict[str, Any] | Command[Any] | None:1420 return func(state, runtime) # type: ignore[return-value]14211422 # Preserve can_jump_to metadata on the wrapped function1423 if func_can_jump_to:1424 wrapped.__can_jump_to__ = func_can_jump_to # type: ignore[attr-defined]14251426 # Use function name as default if no name provided1427 middleware_name = name or cast("str", getattr(func, "__name__", "BeforeAgentMiddleware"))14281429 return type(1430 middleware_name,1431 (AgentMiddleware,),1432 {1433 "state_schema": state_schema or AgentState,1434 "tools": tools or [],1435 "before_agent": wrapped,1436 },1437 )()14381439 if func is not None:1440 return decorator(func)1441 return decorator144214431444@overload1445def after_agent(1446 func: _CallableWithStateAndRuntime[StateT, ContextT],1447) -> AgentMiddleware[StateT, ContextT]: ...144814491450@overload1451def after_agent(1452 func: None = None,1453 *,1454 state_schema: type[StateT] | None = None,1455 tools: list[BaseTool] | None = None,1456 can_jump_to: list[JumpTo] | None = None,1457 name: str | None = None,1458) -> Callable[1459 [_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]1460]: ...146114621463def after_agent(1464 func: _CallableWithStateAndRuntime[StateT, ContextT] | None = None,1465 *,1466 state_schema: type[StateT] | None = None,1467 tools: list[BaseTool] | None = None,1468 can_jump_to: list[JumpTo] | None = None,1469 name: str | None = None,1470) -> (1471 Callable[[_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]]1472 | AgentMiddleware[StateT, ContextT]1473):1474 """Decorator used to dynamically create a middleware with the `after_agent` hook.14751476 Async version is `aafter_agent`.14771478 Args:1479 func: The function to be decorated.14801481 Must accept: `state: StateT, runtime: Runtime[ContextT]` - State and runtime1482 context1483 state_schema: Optional custom state schema type.14841485 If not provided, uses the default `AgentState` schema.1486 tools: Optional list of additional tools to register with this middleware.1487 can_jump_to: Optional list of valid jump destinations for conditional edges.14881489 Valid values are: `'tools'`, `'model'`, `'end'`1490 name: Optional name for the generated middleware class.14911492 If not provided, uses the decorated function's name.14931494 Returns:1495 Either an `AgentMiddleware` instance (if func is provided) or a decorator1496 function that can be applied to a function.14971498 The decorated function should return:14991500 - `dict[str, Any]` - State updates to merge into the agent state1501 - `Command` - A command to control flow (e.g., jump to different node)1502 - `None` - No state updates or flow control15031504 Examples:1505 !!! example "Basic usage for logging agent completion"15061507 ```python1508 @after_agent1509 def log_completion(state: AgentState, runtime: Runtime) -> None:1510 print(f"Agent completed with {len(state['messages'])} messages")1511 ```15121513 !!! example "With custom state schema"15141515 ```python1516 @after_agent(state_schema=MyCustomState, name="MyAfterAgentMiddleware")1517 def custom_after_agent(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:1518 return {"custom_field": "finalized_value"}1519 ```15201521 !!! example "Streaming custom events on completion"15221523 Use `runtime.stream_writer` to emit custom events when agent completes.1524 Events are received when streaming with `stream_mode="custom"`.15251526 ```python1527 @after_agent1528 async def notify_completion(state: AgentState, runtime: Runtime) -> None:1529 '''Notify user that agent has completed.'''1530 runtime.stream_writer(1531 {1532 "type": "status",1533 "message": "Agent execution complete!",1534 "total_messages": len(state["messages"]),1535 }1536 )1537 ```1538 """15391540 def decorator(1541 func: _CallableWithStateAndRuntime[StateT, ContextT],1542 ) -> AgentMiddleware[StateT, ContextT]:1543 is_async = iscoroutinefunction(func)1544 # Extract can_jump_to from decorator parameter or from function metadata1545 func_can_jump_to = (1546 can_jump_to if can_jump_to is not None else getattr(func, "__can_jump_to__", [])1547 )15481549 if is_async:15501551 async def async_wrapped(1552 _self: AgentMiddleware[StateT, ContextT],1553 state: StateT,1554 runtime: Runtime[ContextT],1555 ) -> dict[str, Any] | Command[Any] | None:1556 return await func(state, runtime) # type: ignore[misc]15571558 # Preserve can_jump_to metadata on the wrapped function1559 if func_can_jump_to:1560 async_wrapped.__can_jump_to__ = func_can_jump_to # type: ignore[attr-defined]15611562 middleware_name = name or cast("str", getattr(func, "__name__", "AfterAgentMiddleware"))15631564 return type(1565 middleware_name,1566 (AgentMiddleware,),1567 {1568 "state_schema": state_schema or AgentState,1569 "tools": tools or [],1570 "aafter_agent": async_wrapped,1571 },1572 )()15731574 def wrapped(1575 _self: AgentMiddleware[StateT, ContextT],1576 state: StateT,1577 runtime: Runtime[ContextT],1578 ) -> dict[str, Any] | Command[Any] | None:1579 return func(state, runtime) # type: ignore[return-value]15801581 # Preserve can_jump_to metadata on the wrapped function1582 if func_can_jump_to:1583 wrapped.__can_jump_to__ = func_can_jump_to # type: ignore[attr-defined]15841585 # Use function name as default if no name provided1586 middleware_name = name or cast("str", getattr(func, "__name__", "AfterAgentMiddleware"))15871588 return type(1589 middleware_name,1590 (AgentMiddleware,),1591 {1592 "state_schema": state_schema or AgentState,1593 "tools": tools or [],1594 "after_agent": wrapped,1595 },1596 )()15971598 if func is not None:1599 return decorator(func)1600 return decorator160116021603@overload1604def dynamic_prompt(1605 func: _CallableReturningSystemMessage[StateT, ContextT],1606) -> AgentMiddleware[StateT, ContextT]: ...160716081609@overload1610def dynamic_prompt(1611 func: None = None,1612) -> Callable[1613 [_CallableReturningSystemMessage[StateT, ContextT]],1614 AgentMiddleware[StateT, ContextT],1615]: ...161616171618def dynamic_prompt(1619 func: _CallableReturningSystemMessage[StateT, ContextT] | None = None,1620) -> (1621 Callable[1622 [_CallableReturningSystemMessage[StateT, ContextT]],1623 AgentMiddleware[StateT, ContextT],1624 ]1625 | AgentMiddleware[StateT, ContextT]1626):1627 """Decorator used to dynamically generate system prompts for the model.16281629 This is a convenience decorator that creates middleware using `wrap_model_call`1630 specifically for dynamic prompt generation. The decorated function should return1631 a string that will be set as the system prompt for the model request.16321633 Args:1634 func: The function to be decorated.16351636 Must accept: `request: ModelRequest` - Model request (contains state and1637 runtime)16381639 Returns:1640 Either an `AgentMiddleware` instance (if func is provided) or a decorator1641 function that can be applied to a function.16421643 The decorated function should return:1644 - `str` – The system prompt string to use for the model request1645 - `SystemMessage` – A complete system message to use for the model request16461647 Examples:1648 Basic usage with dynamic content:16491650 ```python1651 @dynamic_prompt1652 def my_prompt(request: ModelRequest) -> str:1653 user_name = request.runtime.context.get("user_name", "User")1654 return f"You are a helpful assistant helping {user_name}."1655 ```16561657 Using state to customize the prompt:16581659 ```python1660 @dynamic_prompt1661 def context_aware_prompt(request: ModelRequest) -> str:1662 msg_count = len(request.state["messages"])1663 if msg_count > 10:1664 return "You are in a long conversation. Be concise."1665 return "You are a helpful assistant."1666 ```16671668 Using with agent:16691670 ```python1671 agent = create_agent(model, middleware=[my_prompt])1672 ```1673 """16741675 def decorator(1676 func: _CallableReturningSystemMessage[StateT, ContextT],1677 ) -> AgentMiddleware[StateT, ContextT]:1678 is_async = iscoroutinefunction(func)16791680 if is_async:16811682 async def async_wrapped(1683 _self: AgentMiddleware[StateT, ContextT],1684 request: ModelRequest[ContextT],1685 handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[Any]]],1686 ) -> ModelResponse[Any] | AIMessage:1687 prompt = await func(request) # type: ignore[misc]1688 if isinstance(prompt, SystemMessage):1689 request = request.override(system_message=prompt)1690 else:1691 request = request.override(system_message=SystemMessage(content=prompt))1692 return await handler(request)16931694 middleware_name = cast("str", getattr(func, "__name__", "DynamicPromptMiddleware"))16951696 return type(1697 middleware_name,1698 (AgentMiddleware,),1699 {1700 "state_schema": AgentState,1701 "tools": [],1702 "awrap_model_call": async_wrapped,1703 },1704 )()17051706 def wrapped(1707 _self: AgentMiddleware[StateT, ContextT],1708 request: ModelRequest[ContextT],1709 handler: Callable[[ModelRequest[ContextT]], ModelResponse[Any]],1710 ) -> ModelResponse[Any] | AIMessage:1711 prompt = cast("Callable[[ModelRequest[ContextT]], SystemMessage | str]", func)(request)1712 if isinstance(prompt, SystemMessage):1713 request = request.override(system_message=prompt)1714 else:1715 request = request.override(system_message=SystemMessage(content=prompt))1716 return handler(request)17171718 async def async_wrapped_from_sync(1719 _self: AgentMiddleware[StateT, ContextT],1720 request: ModelRequest[ContextT],1721 handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[Any]]],1722 ) -> ModelResponse[Any] | AIMessage:1723 # Delegate to sync function1724 prompt = cast("Callable[[ModelRequest[ContextT]], SystemMessage | str]", func)(request)1725 if isinstance(prompt, SystemMessage):1726 request = request.override(system_message=prompt)1727 else:1728 request = request.override(system_message=SystemMessage(content=prompt))1729 return await handler(request)17301731 middleware_name = cast("str", getattr(func, "__name__", "DynamicPromptMiddleware"))17321733 return type(1734 middleware_name,1735 (AgentMiddleware,),1736 {1737 "state_schema": AgentState,1738 "tools": [],1739 "wrap_model_call": wrapped,1740 "awrap_model_call": async_wrapped_from_sync,1741 },1742 )()17431744 if func is not None:1745 return decorator(func)1746 return decorator174717481749@overload1750def wrap_model_call(1751 func: _CallableReturningModelResponse[StateT, ContextT, ResponseT],1752) -> AgentMiddleware[StateT, ContextT]: ...175317541755@overload1756def wrap_model_call(1757 func: None = None,1758 *,1759 state_schema: type[StateT] | None = None,1760 tools: list[BaseTool] | None = None,1761 name: str | None = None,1762) -> Callable[1763 [_CallableReturningModelResponse[StateT, ContextT, ResponseT]],1764 AgentMiddleware[StateT, ContextT],1765]: ...176617671768def wrap_model_call(1769 func: _CallableReturningModelResponse[StateT, ContextT, ResponseT] | None = None,1770 *,1771 state_schema: type[StateT] | None = None,1772 tools: list[BaseTool] | None = None,1773 name: str | None = None,1774) -> (1775 Callable[1776 [_CallableReturningModelResponse[StateT, ContextT, ResponseT]],1777 AgentMiddleware[StateT, ContextT],1778 ]1779 | AgentMiddleware[StateT, ContextT]1780):1781 """Create middleware with `wrap_model_call` hook from a function.17821783 Converts a function with handler callback into middleware that can intercept model1784 calls, implement retry logic, handle errors, and rewrite responses.17851786 Args:1787 func: Function accepting (request, handler) that calls handler(request)1788 to execute the model and returns `ModelResponse` or `AIMessage`.17891790 Request contains state and runtime.1791 state_schema: Custom state schema.17921793 Defaults to `AgentState`.1794 tools: Additional tools to register with this middleware.1795 name: Middleware class name.17961797 Defaults to function name.17981799 Returns:1800 `AgentMiddleware` instance if func provided, otherwise a decorator.18011802 Examples:1803 !!! example "Basic retry logic"18041805 ```python1806 @wrap_model_call1807 def retry_on_error(request, handler):1808 max_retries = 31809 for attempt in range(max_retries):1810 try:1811 return handler(request)1812 except Exception:1813 if attempt == max_retries - 1:1814 raise1815 ```18161817 !!! example "Model fallback"18181819 ```python1820 @wrap_model_call1821 def fallback_model(request, handler):1822 # Try primary model1823 try:1824 return handler(request)1825 except Exception:1826 pass18271828 # Try fallback model1829 request = request.override(model=fallback_model_instance)1830 return handler(request)1831 ```18321833 !!! example "Rewrite response content (full `ModelResponse`)"18341835 ```python1836 @wrap_model_call1837 def uppercase_responses(request, handler):1838 response = handler(request)1839 ai_msg = response.result[0]1840 return ModelResponse(1841 result=[AIMessage(content=ai_msg.content.upper())],1842 structured_response=response.structured_response,1843 )1844 ```18451846 !!! example "Simple `AIMessage` return (converted automatically)"18471848 ```python1849 @wrap_model_call1850 def simple_response(request, handler):1851 # AIMessage is automatically converted to ModelResponse1852 return AIMessage(content="Simple response")1853 ```1854 """18551856 def decorator(1857 func: _CallableReturningModelResponse[StateT, ContextT, ResponseT],1858 ) -> AgentMiddleware[StateT, ContextT]:1859 is_async = iscoroutinefunction(func)18601861 if is_async:18621863 async def async_wrapped(1864 _self: AgentMiddleware[StateT, ContextT],1865 request: ModelRequest[ContextT],1866 handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],1867 ) -> ModelResponse[ResponseT] | AIMessage:1868 return await func(request, handler) # type: ignore[misc, arg-type]18691870 middleware_name = name or cast(1871 "str", getattr(func, "__name__", "WrapModelCallMiddleware")1872 )18731874 return type(1875 middleware_name,1876 (AgentMiddleware,),1877 {1878 "state_schema": state_schema or AgentState,1879 "tools": tools or [],1880 "awrap_model_call": async_wrapped,1881 },1882 )()18831884 def wrapped(1885 _self: AgentMiddleware[StateT, ContextT],1886 request: ModelRequest[ContextT],1887 handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],1888 ) -> ModelResponse[ResponseT] | AIMessage:1889 return func(request, handler)18901891 middleware_name = name or cast("str", getattr(func, "__name__", "WrapModelCallMiddleware"))18921893 return type(1894 middleware_name,1895 (AgentMiddleware,),1896 {1897 "state_schema": state_schema or AgentState,1898 "tools": tools or [],1899 "wrap_model_call": wrapped,1900 },1901 )()19021903 if func is not None:1904 return decorator(func)1905 return decorator190619071908@overload1909def wrap_tool_call(1910 func: _CallableReturningToolResponse,1911) -> AgentMiddleware: ...191219131914@overload1915def wrap_tool_call(1916 func: None = None,1917 *,1918 tools: list[BaseTool] | None = None,1919 name: str | None = None,1920) -> Callable[1921 [_CallableReturningToolResponse],1922 AgentMiddleware,1923]: ...192419251926def wrap_tool_call(1927 func: _CallableReturningToolResponse | None = None,1928 *,1929 tools: list[BaseTool] | None = None,1930 name: str | None = None,1931) -> (1932 Callable[1933 [_CallableReturningToolResponse],1934 AgentMiddleware,1935 ]1936 | AgentMiddleware1937):1938 """Create middleware with `wrap_tool_call` hook from a function.19391940 Async version is `awrap_tool_call`.19411942 Converts a function with handler callback into middleware that can intercept1943 tool calls, implement retry logic, monitor execution, and modify responses.19441945 Args:1946 func: Function accepting (request, handler) that calls1947 handler(request) to execute the tool and returns final `ToolMessage` or1948 `Command`.19491950 Can be sync or async.1951 tools: Additional tools to register with this middleware.1952 name: Middleware class name.19531954 Defaults to function name.19551956 Returns:1957 `AgentMiddleware` instance if func provided, otherwise a decorator.19581959 Examples:1960 !!! example "Retry logic"19611962 ```python1963 @wrap_tool_call1964 def retry_on_error(request, handler):1965 max_retries = 31966 for attempt in range(max_retries):1967 try:1968 return handler(request)1969 except Exception:1970 if attempt == max_retries - 1:1971 raise1972 ```19731974 !!! example "Async retry logic"19751976 ```python1977 @wrap_tool_call1978 async def async_retry(request, handler):1979 for attempt in range(3):1980 try:1981 return await handler(request)1982 except Exception:1983 if attempt == 2:1984 raise1985 ```19861987 !!! example "Modify request"19881989 ```python1990 @wrap_tool_call1991 def modify_args(request, handler):1992 modified_call = {1993 **request.tool_call,1994 "args": {1995 **request.tool_call["args"],1996 "value": request.tool_call["args"]["value"] * 2,1997 },1998 }1999 request = request.override(tool_call=modified_call)2000 return handler(request)
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.