libs/langchain_v1/langchain/agents/middleware/types.py PYTHON 2,066 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,066.
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)

Code quality findings 100

Ensure functions have docstrings for documentation
missing-docstring
async def abefore_agent(
Ensure functions have docstrings for documentation
missing-docstring
async def abefore_model(
Ensure functions have docstrings for documentation
missing-docstring
async def aafter_model(
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(self, request, handler):
Catch specific exceptions instead of Exception to avoid masking bugs
broad-except
except Exception:
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(self, request, handler):
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(self, request, handler):
Catch specific exceptions instead of Exception to avoid masking bugs
broad-except
except Exception:
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(self, request, handler):
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(self, request, handler):
Ensure functions have docstrings for documentation
missing-docstring
async def awrap_model_call(
Ensure functions have docstrings for documentation
missing-docstring
async def awrap_model_call(self, request, handler):
Catch specific exceptions instead of Exception to avoid masking bugs
broad-except
except Exception:
Ensure functions have docstrings for documentation
missing-docstring
async def aafter_agent(
Ensure functions have docstrings for documentation
missing-docstring
def wrap_tool_call(
Ensure functions have docstrings for documentation
missing-docstring
def wrap_tool_call(self, request, handler):
Ensure functions have docstrings for documentation
missing-docstring
def wrap_tool_call(self, request, handler):
Catch specific exceptions instead of Exception to avoid masking bugs
broad-except
except Exception:
Ensure functions have docstrings for documentation
missing-docstring
def wrap_tool_call(self, request, handler):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(result, ToolMessage) and result.status != "error":
Ensure functions have docstrings for documentation
missing-docstring
async def awrap_tool_call(
Ensure functions have docstrings for documentation
missing-docstring
async def awrap_tool_call(self, request, handler):
Catch specific exceptions instead of Exception to avoid masking bugs
broad-except
except Exception:
Ensure functions have docstrings for documentation
missing-docstring
async def awrap_tool_call(self, request, handler):
Ensure functions have docstrings for documentation
missing-docstring
def hook_config(
Ensure functions have docstrings for documentation
missing-docstring
def before_model(self, state: AgentState) -> dict[str, Any] | None:
Ensure functions have docstrings for documentation
missing-docstring
def conditional_middleware(state: AgentState) -> dict[str, Any] | None:
Ensure functions have docstrings for documentation
missing-docstring
def decorator(func: CallableT) -> CallableT:
Ensure functions have docstrings for documentation
missing-docstring
def before_model(
Ensure functions have docstrings for documentation
missing-docstring
def before_model(
Ensure functions have docstrings for documentation
missing-docstring
def before_model(
Ensure functions have docstrings for documentation
missing-docstring
def log_before_model(state: AgentState, runtime: Runtime) -> None:
Use logging module for better control and configurability
print-statement
print(f"About to call model with {len(state['messages'])} messages")
Ensure functions have docstrings for documentation
missing-docstring
def conditional_before_model(
Ensure functions have docstrings for documentation
missing-docstring
def custom_before_model(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:
Ensure functions have docstrings for documentation
missing-docstring
def decorator(
Ensure functions have docstrings for documentation
missing-docstring
async def async_wrapped(
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def after_model(
Ensure functions have docstrings for documentation
missing-docstring
def after_model(
Ensure functions have docstrings for documentation
missing-docstring
def after_model(
Ensure functions have docstrings for documentation
missing-docstring
def log_latest_message(state: AgentState, runtime: Runtime) -> None:
Use logging module for better control and configurability
print-statement
print(state["messages"][-1].content)
Ensure functions have docstrings for documentation
missing-docstring
def custom_after_model(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:
Ensure functions have docstrings for documentation
missing-docstring
def decorator(
Ensure functions have docstrings for documentation
missing-docstring
async def async_wrapped(
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def before_agent(
Ensure functions have docstrings for documentation
missing-docstring
def before_agent(
Ensure functions have docstrings for documentation
missing-docstring
def before_agent(
Ensure functions have docstrings for documentation
missing-docstring
def log_before_agent(state: AgentState, runtime: Runtime) -> None:
Use logging module for better control and configurability
print-statement
print(f"Starting agent with {len(state['messages'])} messages")
Ensure functions have docstrings for documentation
missing-docstring
def conditional_before_agent(
Ensure functions have docstrings for documentation
missing-docstring
def custom_before_agent(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:
Use logging module for better control and configurability
print-statement
print(f"Status: {event}")
Ensure functions have docstrings for documentation
missing-docstring
def decorator(
Ensure functions have docstrings for documentation
missing-docstring
async def async_wrapped(
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def after_agent(
Ensure functions have docstrings for documentation
missing-docstring
def after_agent(
Ensure functions have docstrings for documentation
missing-docstring
def after_agent(
Ensure functions have docstrings for documentation
missing-docstring
def log_completion(state: AgentState, runtime: Runtime) -> None:
Use logging module for better control and configurability
print-statement
print(f"Agent completed with {len(state['messages'])} messages")
Ensure functions have docstrings for documentation
missing-docstring
def custom_after_agent(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:
Ensure functions have docstrings for documentation
missing-docstring
def decorator(
Ensure functions have docstrings for documentation
missing-docstring
async def async_wrapped(
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def dynamic_prompt(
Ensure functions have docstrings for documentation
missing-docstring
def dynamic_prompt(
Ensure functions have docstrings for documentation
missing-docstring
def dynamic_prompt(
Ensure functions have docstrings for documentation
missing-docstring
def my_prompt(request: ModelRequest) -> str:
Ensure functions have docstrings for documentation
missing-docstring
def context_aware_prompt(request: ModelRequest) -> str:
Ensure functions have docstrings for documentation
missing-docstring
def decorator(
Ensure functions have docstrings for documentation
missing-docstring
async def async_wrapped(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(prompt, SystemMessage):
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(prompt, SystemMessage):
Ensure functions have docstrings for documentation
missing-docstring
async def async_wrapped_from_sync(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(prompt, SystemMessage):
Use isinstance() for type checking instead of type()
type-check
return type(
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(
Ensure functions have docstrings for documentation
missing-docstring
def wrap_model_call(
Ensure functions have docstrings for documentation
missing-docstring
def retry_on_error(request, handler):
Catch specific exceptions instead of Exception to avoid masking bugs
broad-except
except Exception:
Ensure functions have docstrings for documentation
missing-docstring
def fallback_model(request, handler):
Catch specific exceptions instead of Exception to avoid masking bugs
broad-except
except Exception:
Ensure functions have docstrings for documentation
missing-docstring
def uppercase_responses(request, handler):
Ensure functions have docstrings for documentation
missing-docstring
def simple_response(request, handler):
Ensure functions have docstrings for documentation
missing-docstring
def decorator(

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.