libs/langchain_v1/langchain/agents/middleware/types.py PYTHON 2,162 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,162.
1"""Types for middleware and agents."""23from __future__ import annotations45import warnings6from collections.abc import Awaitable, Callable, Sequence7from dataclasses import dataclass, field, replace8from inspect import iscoroutinefunction9from typing import (10    TYPE_CHECKING,11    Annotated,12    Any,13    Generic,14    Literal,15    cast,16    overload,17)1819# Needed as top level import for Pydantic schema generation on AgentState20from langchain_core.messages import (21    AIMessage,22    AnyMessage,23    BaseMessage,24    SystemMessage,25    ToolMessage,26)27from langgraph.channels.ephemeral_value import EphemeralValue28from langgraph.graph.message import add_messages29from langgraph.prebuilt.tool_node import ToolCallRequest, ToolCallWrapper30from langgraph.runtime import Runtime31from langgraph.types import Command32from langgraph.typing import ContextT33from typing_extensions import NotRequired, Required, TypedDict, TypeVar, Unpack3435if TYPE_CHECKING:36    from langchain_core.language_models.chat_models import BaseChatModel37    from langchain_core.tools import BaseTool38    from langgraph.stream._mux import TransformerFactory3940    from langchain.agents.structured_output import ResponseFormat4142__all__ = [43    "AgentMiddleware",44    "AgentState",45    "ContextT",46    "ExtendedModelResponse",47    "InputAgentState",48    "ModelCallResult",49    "ModelRequest",50    "ModelResponse",51    "OmitFromSchema",52    "OutputAgentState",53    "ResponseT",54    "StateT_co",55    "ToolCallRequest",56    "ToolCallWrapper",57    "after_agent",58    "after_model",59    "before_agent",60    "before_model",61    "dynamic_prompt",62    "hook_config",63    "wrap_tool_call",64]6566JumpTo = Literal["tools", "model", "end"]67"""Destination to jump to when a middleware node returns."""6869ResponseT = TypeVar("ResponseT", default=Any)707172class _ModelRequestOverrides(TypedDict, total=False):73    """Possible overrides for `ModelRequest.override()` method."""7475    model: BaseChatModel76    system_message: SystemMessage | None77    messages: list[AnyMessage]78    tool_choice: Any | None79    tools: list[BaseTool | dict[str, Any]]80    response_format: ResponseFormat[Any] | None81    model_settings: dict[str, Any]82    state: AgentState[Any]838485@dataclass(init=False)86class ModelRequest(Generic[ContextT]):87    """Model request information for the agent.8889    Type Parameters:90        ContextT: The type of the runtime context.9192            Defaults to `None` if not specified.93    """9495    model: BaseChatModel96    messages: list[AnyMessage]  # excluding system message97    system_message: SystemMessage | None98    tool_choice: Any | None99    tools: list[BaseTool | dict[str, Any]]100    response_format: ResponseFormat[Any] | None101    state: AgentState[Any]102    runtime: Runtime[ContextT]103    model_settings: dict[str, Any] = field(default_factory=dict)104105    def __init__(106        self,107        *,108        model: BaseChatModel,109        messages: list[AnyMessage],110        system_message: SystemMessage | None = None,111        system_prompt: str | None = None,112        tool_choice: Any | None = None,113        tools: list[BaseTool | dict[str, Any]] | None = None,114        response_format: ResponseFormat[Any] | None = None,115        state: AgentState[Any] | None = None,116        runtime: Runtime[ContextT] | None = None,117        model_settings: dict[str, Any] | None = None,118    ) -> None:119        """Initialize `ModelRequest` with backward compatibility for `system_prompt`.120121        Args:122            model: The chat model to use.123            messages: List of messages (excluding system prompt).124            tool_choice: Tool choice configuration.125            tools: List of available tools.126            response_format: Response format specification.127            state: Agent state.128            runtime: Runtime context.129            model_settings: Additional model settings.130            system_message: System message instance (preferred).131            system_prompt: System prompt string (deprecated, converted to `SystemMessage`).132133        Raises:134            ValueError: If both `system_prompt` and `system_message` are provided.135        """136        # Handle system_prompt/system_message conversion and validation137        if system_prompt is not None and system_message is not None:138            msg = "Cannot specify both system_prompt and system_message"139            raise ValueError(msg)140141        if system_prompt is not None:142            system_message = SystemMessage(content=system_prompt)143144        with warnings.catch_warnings():145            warnings.simplefilter("ignore", category=DeprecationWarning)146            self.model = model147            self.messages = messages148            self.system_message = system_message149            self.tool_choice = tool_choice150            self.tools = tools if tools is not None else []151            self.response_format = response_format152            self.state = state if state is not None else {"messages": []}153            self.runtime = runtime  # type: ignore[assignment]154            self.model_settings = model_settings if model_settings is not None else {}155156    @property157    def system_prompt(self) -> str | None:158        """Get system prompt text from system_message.159160        Returns:161            The content of the system message if present, otherwise `None`.162        """163        if self.system_message is None:164            return None165        return self.system_message.text166167    def __setattr__(self, name: str, value: Any) -> None:168        """Set an attribute with a deprecation warning.169170        Direct attribute assignment on `ModelRequest` is deprecated. Use the171        `override()` method instead to create a new request with modified attributes.172173        Args:174            name: Attribute name.175            value: Attribute value.176        """177        # Special handling for system_prompt - convert to system_message178        if name == "system_prompt":179            warnings.warn(180                "Direct attribute assignment to ModelRequest.system_prompt is deprecated. "181                "Use request.override(system_message=SystemMessage(...)) instead to create "182                "a new request with the modified system message.",183                DeprecationWarning,184                stacklevel=2,185            )186            if value is None:187                object.__setattr__(self, "system_message", None)188            else:189                object.__setattr__(self, "system_message", SystemMessage(content=value))190            return191192        warnings.warn(193            f"Direct attribute assignment to ModelRequest.{name} is deprecated. "194            f"Use request.override({name}=...) instead to create a new request "195            f"with the modified attribute.",196            DeprecationWarning,197            stacklevel=2,198        )199        object.__setattr__(self, name, value)200201    def override(self, **overrides: Unpack[_ModelRequestOverrides]) -> ModelRequest[ContextT]:202        """Replace the request with a new request with the given overrides.203204        Returns a new `ModelRequest` instance with the specified attributes replaced.205206        This follows an immutable pattern, leaving the original request unchanged.207208        Args:209            **overrides: Keyword arguments for attributes to override.210211                Supported keys:212213                - `model`: `BaseChatModel` instance214                - `system_prompt`: deprecated, use `system_message` instead215                - `system_message`: `SystemMessage` instance216                - `messages`: `list` of messages217                - `tool_choice`: Tool choice configuration218                - `tools`: `list` of available tools219                - `response_format`: Response format specification220                - `model_settings`: Additional model settings221                - `state`: Agent state dictionary222223        Returns:224            New `ModelRequest` instance with specified overrides applied.225226        Examples:227            !!! example "Create a new request with different model"228229                ```python230                new_request = request.override(model=different_model)231                ```232233            !!! example "Override system message (preferred)"234235                ```python236                from langchain_core.messages import SystemMessage237238                new_request = request.override(239                    system_message=SystemMessage(content="New instructions")240                )241                ```242243            !!! example "Override multiple attributes"244245                ```python246                new_request = request.override(247                    model=ChatOpenAI(model="gpt-5.5"),248                    system_message=SystemMessage(content="New instructions"),249                )250                ```251252        Raises:253            ValueError: If both `system_prompt` and `system_message` are provided.254        """255        # Handle system_prompt/system_message conversion256        if "system_prompt" in overrides and "system_message" in overrides:257            msg = "Cannot specify both system_prompt and system_message"258            raise ValueError(msg)259260        if "system_prompt" in overrides:261            system_prompt = cast("str | None", overrides.pop("system_prompt"))  # type: ignore[typeddict-item]262            if system_prompt is None:263                overrides["system_message"] = None264            else:265                overrides["system_message"] = SystemMessage(content=system_prompt)266267        return replace(self, **overrides)268269270@dataclass271class ModelResponse(Generic[ResponseT]):272    """Response from model execution including messages and optional structured output.273274    The result will usually contain a single `AIMessage`, but may include an additional275    `ToolMessage` if the model used a tool for structured output.276277    Type Parameters:278        ResponseT: The type of the structured response. Defaults to `Any` if not specified.279    """280281    result: list[BaseMessage]282    """List of messages from model execution."""283284    structured_response: ResponseT | None = None285    """Parsed structured output if `response_format` was specified, `None` otherwise."""286287288@dataclass289class ExtendedModelResponse(Generic[ResponseT]):290    """Model response with an optional 'Command' from 'wrap_model_call' middleware.291292    Use this to return a 'Command' alongside the model response from a293    'wrap_model_call' handler. The command is applied as an additional state294    update after the model node completes, using the graph's reducers (e.g.295    'add_messages' for the 'messages' key).296297    Because each 'Command' is applied through the reducer, messages in the298    command are **added alongside** the model response messages rather than299    replacing them. For non-reducer state fields, later commands overwrite300    earlier ones (outermost middleware wins over inner).301302    Type Parameters:303        ResponseT: The type of the structured response. Defaults to 'Any' if not specified.304    """305306    model_response: ModelResponse[ResponseT]307    """The underlying model response."""308309    command: Command[Any] | None = None310    """Optional command to apply as an additional state update."""311312313ModelCallResult = ModelResponse[ResponseT] | AIMessage | ExtendedModelResponse[ResponseT]314"""Return type for model call handlers.315316Middleware can return either:317318- `ModelResponse`: Full response with messages and optional structured output319- `AIMessage`: Simplified return for simple use cases320- `ExtendedModelResponse`: Response with an optional `Command` for additional state updates321    `goto`, `resume`, and `graph` are not yet supported on these commands.322    A `NotImplementedError` will be raised if you try to use them.323"""324325326@dataclass327class OmitFromSchema:328    """Annotation used to mark state attributes as omitted from input or output schemas."""329330    input: bool = True331    """Whether to omit the attribute from the input schema."""332333    output: bool = True334    """Whether to omit the attribute from the output schema."""335336337OmitFromInput = OmitFromSchema(input=True, output=False)338"""Annotation used to mark state attributes as omitted from input schema."""339340OmitFromOutput = OmitFromSchema(input=False, output=True)341"""Annotation used to mark state attributes as omitted from output schema."""342343PrivateStateAttr = OmitFromSchema(input=True, output=True)344"""Annotation used to mark state attributes as purely internal for a given middleware."""345346347class AgentState(TypedDict, Generic[ResponseT]):348    """State schema for the agent."""349350    messages: Required[Annotated[list[AnyMessage], add_messages]]351    jump_to: NotRequired[Annotated[JumpTo | None, EphemeralValue, PrivateStateAttr]]352    structured_response: NotRequired[Annotated[ResponseT, OmitFromInput]]353354355class InputAgentState(TypedDict):356    """Input state schema for the agent."""357358    messages: Required[Annotated[list[AnyMessage | dict[str, Any]], add_messages]]359360361class OutputAgentState(TypedDict, Generic[ResponseT]):362    """Output state schema for the agent."""363364    messages: Required[Annotated[list[AnyMessage], add_messages]]365    structured_response: NotRequired[ResponseT]366367368# Deprecated aliases kept for backwards compatibility with external consumers that369# imported the previously private names. Remove in a future release.370_InputAgentState = InputAgentState371_OutputAgentState = OutputAgentState372373374StateT = 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)812813814_SyncCallableWithStateAndRuntime = Callable[815    [StateT_contra, Runtime[ContextT]], dict[str, Any] | Command[Any] | None816]817_AsyncCallableWithStateAndRuntime = Callable[818    [StateT_contra, Runtime[ContextT]], Awaitable[dict[str, Any] | Command[Any] | None]819]820_CallableWithStateAndRuntime = (821    _SyncCallableWithStateAndRuntime[StateT_contra, ContextT]822    | _AsyncCallableWithStateAndRuntime[StateT_contra, ContextT]823)824825_SyncCallableReturningSystemMessage = Callable[[ModelRequest[ContextT]], str | SystemMessage]826_AsyncCallableReturningSystemMessage = Callable[827    [ModelRequest[ContextT]], Awaitable[str | SystemMessage]828]829_CallableReturningSystemMessage = (830    _SyncCallableReturningSystemMessage[ContextT] | _AsyncCallableReturningSystemMessage[ContextT]831)832833# Sync/async signatures for `wrap_model_call` interception; see `@wrap_model_call`.834_SyncCallableReturningModelResponse = Callable[835    [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]]],836    ModelCallResult,837]838_AsyncCallableReturningModelResponse = Callable[839    [840        ModelRequest[ContextT],841        Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],842    ],843    Awaitable[ModelCallResult],844]845_CallableReturningModelResponse = (846    _SyncCallableReturningModelResponse[ContextT, ResponseT]847    | _AsyncCallableReturningModelResponse[ContextT, ResponseT]848)849850# Sync/async signatures for `wrap_tool_call` interception; see `@wrap_tool_call`.851_SyncCallableReturningToolResponse = Callable[852    [ToolCallRequest, Callable[[ToolCallRequest], ToolMessage | Command[Any]]],853    ToolMessage | Command[Any],854]855_AsyncCallableReturningToolResponse = Callable[856    [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],857    Awaitable[ToolMessage | Command[Any]],858]859_CallableReturningToolResponse = (860    _SyncCallableReturningToolResponse | _AsyncCallableReturningToolResponse861)862863864CallableT = TypeVar("CallableT", bound=Callable[..., Any])865866867def hook_config(868    *,869    can_jump_to: list[JumpTo] | None = None,870) -> Callable[[CallableT], CallableT]:871    """Decorator to configure hook behavior in middleware methods.872873    Use this decorator on `before_model` or `after_model` methods in middleware classes874    to configure their behavior. Currently supports specifying which destinations they875    can jump to, which establishes conditional edges in the agent graph.876877    Args:878        can_jump_to: Optional list of valid jump destinations.879880            Can be:881882            - `'tools'`: Jump to the tools node883            - `'model'`: Jump back to the model node884            - `'end'`: Jump to the end of the graph885886    Returns:887        Decorator function that marks the method with configuration metadata.888889    Examples:890        !!! example "Using decorator on a class method"891892            ```python893            class MyMiddleware(AgentMiddleware):894                @hook_config(can_jump_to=["end", "model"])895                def before_model(self, state: AgentState) -> dict[str, Any] | None:896                    if some_condition(state):897                        return {"jump_to": "end"}898                    return None899            ```900901        Alternative: Use the `can_jump_to` parameter in `before_model`/`after_model`902        decorators:903904        ```python905        @before_model(can_jump_to=["end"])906        def conditional_middleware(state: AgentState) -> dict[str, Any] | None:907            if should_exit(state):908                return {"jump_to": "end"}909            return None910        ```911    """912913    def decorator(func: CallableT) -> CallableT:914        if can_jump_to is not None:915            func.__can_jump_to__ = can_jump_to  # type: ignore[attr-defined]916        return func917918    return decorator919920921@overload922def before_model(923    func: _CallableWithStateAndRuntime[StateT, ContextT],924) -> AgentMiddleware[StateT, ContextT]: ...925926927@overload928def before_model(929    func: None = None,930    *,931    state_schema: type[StateT] | None = None,932    tools: list[BaseTool] | None = None,933    can_jump_to: list[JumpTo] | None = None,934    name: str | None = None,935) -> Callable[936    [_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]937]: ...938939940def before_model(941    func: _CallableWithStateAndRuntime[StateT, ContextT] | None = None,942    *,943    state_schema: type[StateT] | None = None,944    tools: list[BaseTool] | None = None,945    can_jump_to: list[JumpTo] | None = None,946    name: str | None = None,947) -> (948    Callable[[_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]]949    | AgentMiddleware[StateT, ContextT]950):951    """Decorator used to dynamically create a middleware with the `before_model` hook.952953    Args:954        func: The function to be decorated.955956            Must accept: `state: StateT, runtime: Runtime[ContextT]` - State and runtime957                context958        state_schema: Optional custom state schema type.959960            If not provided, uses the default `AgentState` schema.961        tools: Optional list of additional tools to register with this middleware.962        can_jump_to: Optional list of valid jump destinations for conditional edges.963964            Valid values are: `'tools'`, `'model'`, `'end'`965        name: Optional name for the generated middleware class.966967            If not provided, uses the decorated function's name.968969    Returns:970        Either an `AgentMiddleware` instance (if func is provided directly) or a971            decorator function that can be applied to a function it is wrapping.972973    The decorated function should return:974975    - `dict[str, Any]` - State updates to merge into the agent state976    - `Command` - A command to control flow (e.g., jump to different node)977    - `None` - No state updates or flow control978979    Examples:980        !!! example "Basic usage"981982            ```python983            @before_model984            def log_before_model(state: AgentState, runtime: Runtime) -> None:985                print(f"About to call model with {len(state['messages'])} messages")986            ```987988        !!! example "With conditional jumping"989990            ```python991            @before_model(can_jump_to=["end"])992            def conditional_before_model(993                state: AgentState, runtime: Runtime994            ) -> dict[str, Any] | None:995                if some_condition(state):996                    return {"jump_to": "end"}997                return None998            ```9991000        !!! example "With custom state schema"10011002            ```python1003            @before_model(state_schema=MyCustomState)1004            def custom_before_model(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:1005                return {"custom_field": "updated_value"}1006            ```10071008        !!! example "Streaming custom events before model call"10091010            Use `runtime.stream_writer` to emit custom events before each model invocation.1011            Events are received when streaming with `stream_mode="custom"`.10121013            ```python1014            @before_model1015            async def notify_model_call(state: AgentState, runtime: Runtime) -> None:1016                '''Notify user before model is called.'''1017                runtime.stream_writer(1018                    {1019                        "type": "status",1020                        "message": "Thinking...",1021                    }1022                )1023            ```1024    """10251026    def decorator(1027        func: _CallableWithStateAndRuntime[StateT, ContextT],1028    ) -> AgentMiddleware[StateT, ContextT]:1029        is_async = iscoroutinefunction(func)10301031        func_can_jump_to = (1032            can_jump_to if can_jump_to is not None else getattr(func, "__can_jump_to__", [])1033        )10341035        if is_async:10361037            async def async_wrapped(1038                _self: AgentMiddleware[StateT, ContextT],1039                state: StateT,1040                runtime: Runtime[ContextT],1041            ) -> dict[str, Any] | Command[Any] | None:1042                # `iscoroutinefunction` narrows `func` at runtime, but type checkers1043                # cannot narrow this sync-or-async callable union.1044                return await cast("_AsyncCallableWithStateAndRuntime[StateT, ContextT]", func)(1045                    state, runtime1046                )10471048            # Preserve can_jump_to metadata on the wrapped function1049            if func_can_jump_to:1050                async_wrapped.__can_jump_to__ = func_can_jump_to  # type: ignore[attr-defined]10511052            middleware_name = name or cast(1053                "str", getattr(func, "__name__", "BeforeModelMiddleware")1054            )10551056            # `type(...)` builds the correct middleware subclass at runtime, but1057            # type checkers cannot infer its generic `AgentMiddleware` parameters.1058            return cast(1059                "AgentMiddleware[StateT, ContextT]",1060                type(1061                    middleware_name,1062                    (AgentMiddleware,),1063                    {1064                        "state_schema": state_schema or AgentState,1065                        "tools": tools or [],1066                        "abefore_model": async_wrapped,1067                    },1068                )(),1069            )10701071        def wrapped(1072            _self: AgentMiddleware[StateT, ContextT],1073            state: StateT,1074            runtime: Runtime[ContextT],1075        ) -> dict[str, Any] | Command[Any] | None:1076            # `iscoroutinefunction` narrows `func` at runtime, but type checkers1077            # cannot narrow this sync-or-async callable union.1078            return cast("_SyncCallableWithStateAndRuntime[StateT, ContextT]", func)(state, runtime)10791080        # Preserve can_jump_to metadata on the wrapped function1081        if func_can_jump_to:1082            wrapped.__can_jump_to__ = func_can_jump_to  # type: ignore[attr-defined]10831084        # Use function name as default if no name provided1085        middleware_name = name or cast("str", getattr(func, "__name__", "BeforeModelMiddleware"))10861087        # `type(...)` builds the correct middleware subclass at runtime, but1088        # type checkers cannot infer its generic `AgentMiddleware` parameters.1089        return cast(1090            "AgentMiddleware[StateT, ContextT]",1091            type(1092                middleware_name,1093                (AgentMiddleware,),1094                {1095                    "state_schema": state_schema or AgentState,1096                    "tools": tools or [],1097                    "before_model": wrapped,1098                },1099            )(),1100        )11011102    if func is not None:1103        return decorator(func)1104    return decorator110511061107@overload1108def after_model(1109    func: _CallableWithStateAndRuntime[StateT, ContextT],1110) -> AgentMiddleware[StateT, ContextT]: ...111111121113@overload1114def after_model(1115    func: None = None,1116    *,1117    state_schema: type[StateT] | None = None,1118    tools: list[BaseTool] | None = None,1119    can_jump_to: list[JumpTo] | None = None,1120    name: str | None = None,1121) -> Callable[1122    [_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]1123]: ...112411251126def after_model(1127    func: _CallableWithStateAndRuntime[StateT, ContextT] | None = None,1128    *,1129    state_schema: type[StateT] | None = None,1130    tools: list[BaseTool] | None = None,1131    can_jump_to: list[JumpTo] | None = None,1132    name: str | None = None,1133) -> (1134    Callable[[_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]]1135    | AgentMiddleware[StateT, ContextT]1136):1137    """Decorator used to dynamically create a middleware with the `after_model` hook.11381139    Args:1140        func: The function to be decorated.11411142            Must accept: `state: StateT, runtime: Runtime[ContextT]` - State and runtime1143            context1144        state_schema: Optional custom state schema type.11451146            If not provided, uses the default `AgentState` schema.1147        tools: Optional list of additional tools to register with this middleware.1148        can_jump_to: Optional list of valid jump destinations for conditional edges.11491150            Valid values are: `'tools'`, `'model'`, `'end'`1151        name: Optional name for the generated middleware class.11521153            If not provided, uses the decorated function's name.11541155    Returns:1156        Either an `AgentMiddleware` instance (if func is provided) or a decorator1157            function that can be applied to a function.11581159    The decorated function should return:11601161    - `dict[str, Any]` - State updates to merge into the agent state1162    - `Command` - A command to control flow (e.g., jump to different node)1163    - `None` - No state updates or flow control11641165    Examples:1166        !!! example "Basic usage for logging model responses"11671168            ```python1169            @after_model1170            def log_latest_message(state: AgentState, runtime: Runtime) -> None:1171                print(state["messages"][-1].content)1172            ```11731174        !!! example "With custom state schema"11751176            ```python1177            @after_model(state_schema=MyCustomState, name="MyAfterModelMiddleware")1178            def custom_after_model(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:1179                return {"custom_field": "updated_after_model"}1180            ```11811182        !!! example "Streaming custom events after model call"11831184            Use `runtime.stream_writer` to emit custom events after model responds.1185            Events are received when streaming with `stream_mode="custom"`.11861187            ```python1188            @after_model1189            async def notify_model_response(state: AgentState, runtime: Runtime) -> None:1190                '''Notify user after model has responded.'''1191                last_message = state["messages"][-1]1192                has_tool_calls = hasattr(last_message, "tool_calls") and last_message.tool_calls1193                runtime.stream_writer(1194                    {1195                        "type": "status",1196                        "message": "Using tools..." if has_tool_calls else "Response ready!",1197                    }1198                )1199            ```1200    """12011202    def decorator(1203        func: _CallableWithStateAndRuntime[StateT, ContextT],1204    ) -> AgentMiddleware[StateT, ContextT]:1205        is_async = iscoroutinefunction(func)1206        # Extract can_jump_to from decorator parameter or from function metadata1207        func_can_jump_to = (1208            can_jump_to if can_jump_to is not None else getattr(func, "__can_jump_to__", [])1209        )12101211        if is_async:12121213            async def async_wrapped(1214                _self: AgentMiddleware[StateT, ContextT],1215                state: StateT,1216                runtime: Runtime[ContextT],1217            ) -> dict[str, Any] | Command[Any] | None:1218                # `iscoroutinefunction` narrows `func` at runtime, but type checkers1219                # cannot narrow this sync-or-async callable union.1220                return await cast("_AsyncCallableWithStateAndRuntime[StateT, ContextT]", func)(1221                    state, runtime1222                )12231224            # Preserve can_jump_to metadata on the wrapped function1225            if func_can_jump_to:1226                async_wrapped.__can_jump_to__ = func_can_jump_to  # type: ignore[attr-defined]12271228            middleware_name = name or cast("str", getattr(func, "__name__", "AfterModelMiddleware"))12291230            # `type(...)` builds the correct middleware subclass at runtime, but1231            # type checkers cannot infer its generic `AgentMiddleware` parameters.1232            return cast(1233                "AgentMiddleware[StateT, ContextT]",1234                type(1235                    middleware_name,1236                    (AgentMiddleware,),1237                    {1238                        "state_schema": state_schema or AgentState,1239                        "tools": tools or [],1240                        "aafter_model": async_wrapped,1241                    },1242                )(),1243            )12441245        def wrapped(1246            _self: AgentMiddleware[StateT, ContextT],1247            state: StateT,1248            runtime: Runtime[ContextT],1249        ) -> dict[str, Any] | Command[Any] | None:1250            # `iscoroutinefunction` narrows `func` at runtime, but type checkers1251            # cannot narrow this sync-or-async callable union.1252            return cast("_SyncCallableWithStateAndRuntime[StateT, ContextT]", func)(state, runtime)12531254        # Preserve can_jump_to metadata on the wrapped function1255        if func_can_jump_to:1256            wrapped.__can_jump_to__ = func_can_jump_to  # type: ignore[attr-defined]12571258        # Use function name as default if no name provided1259        middleware_name = name or cast("str", getattr(func, "__name__", "AfterModelMiddleware"))12601261        # `type(...)` builds the correct middleware subclass at runtime, but1262        # type checkers cannot infer its generic `AgentMiddleware` parameters.1263        return cast(1264            "AgentMiddleware[StateT, ContextT]",1265            type(1266                middleware_name,1267                (AgentMiddleware,),1268                {1269                    "state_schema": state_schema or AgentState,1270                    "tools": tools or [],1271                    "after_model": wrapped,1272                },1273            )(),1274        )12751276    if func is not None:1277        return decorator(func)1278    return decorator127912801281@overload1282def before_agent(1283    func: _CallableWithStateAndRuntime[StateT, ContextT],1284) -> AgentMiddleware[StateT, ContextT]: ...128512861287@overload1288def before_agent(1289    func: None = None,1290    *,1291    state_schema: type[StateT] | None = None,1292    tools: list[BaseTool] | None = None,1293    can_jump_to: list[JumpTo] | None = None,1294    name: str | None = None,1295) -> Callable[1296    [_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]1297]: ...129812991300def before_agent(1301    func: _CallableWithStateAndRuntime[StateT, ContextT] | None = None,1302    *,1303    state_schema: type[StateT] | None = None,1304    tools: list[BaseTool] | None = None,1305    can_jump_to: list[JumpTo] | None = None,1306    name: str | None = None,1307) -> (1308    Callable[[_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]]1309    | AgentMiddleware[StateT, ContextT]1310):1311    """Decorator used to dynamically create a middleware with the `before_agent` hook.13121313    Args:1314        func: The function to be decorated.13151316            Must accept: `state: StateT, runtime: Runtime[ContextT]` - State and runtime1317            context1318        state_schema: Optional custom state schema type.13191320            If not provided, uses the default `AgentState` schema.1321        tools: Optional list of additional tools to register with this middleware.1322        can_jump_to: Optional list of valid jump destinations for conditional edges.13231324            Valid values are: `'tools'`, `'model'`, `'end'`1325        name: Optional name for the generated middleware class.13261327            If not provided, uses the decorated function's name.13281329    Returns:1330        Either an `AgentMiddleware` instance (if func is provided directly) or a1331            decorator function that can be applied to a function it is wrapping.13321333    The decorated function should return:13341335    - `dict[str, Any]` - State updates to merge into the agent state1336    - `Command` - A command to control flow (e.g., jump to different node)1337    - `None` - No state updates or flow control13381339    Examples:1340        !!! example "Basic usage"13411342            ```python1343            @before_agent1344            def log_before_agent(state: AgentState, runtime: Runtime) -> None:1345                print(f"Starting agent with {len(state['messages'])} messages")1346            ```13471348        !!! example "With conditional jumping"13491350            ```python1351            @before_agent(can_jump_to=["end"])1352            def conditional_before_agent(1353                state: AgentState, runtime: Runtime1354            ) -> dict[str, Any] | None:1355                if some_condition(state):1356                    return {"jump_to": "end"}1357                return None1358            ```13591360        !!! example "With custom state schema"13611362            ```python1363            @before_agent(state_schema=MyCustomState)1364            def custom_before_agent(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:1365                return {"custom_field": "initialized_value"}1366            ```13671368        !!! example "Streaming custom events"13691370            Use `runtime.stream_writer` to emit custom events during agent execution.1371            Events are received when streaming with `stream_mode="custom"`.13721373            ```python1374            from langchain.agents import create_agent1375            from langchain.agents.middleware import before_agent, AgentState1376            from langchain.messages import HumanMessage1377            from langgraph.runtime import Runtime137813791380            @before_agent1381            async def notify_start(state: AgentState, runtime: Runtime) -> None:1382                '''Notify user that agent is starting.'''1383                runtime.stream_writer(1384                    {1385                        "type": "status",1386                        "message": "Initializing agent session...",1387                    }1388                )1389                # Perform prerequisite tasks here1390                runtime.stream_writer({"type": "status", "message": "Agent ready!"})139113921393            agent = create_agent(1394                model="openai:gpt-5.2",1395                tools=[...],1396                middleware=[notify_start],1397            )13981399            # Consume with stream_mode="custom" to receive events1400            async for mode, event in agent.astream(1401                {"messages": [HumanMessage("Hello")]},1402                stream_mode=["updates", "custom"],1403            ):1404                if mode == "custom":1405                    print(f"Status: {event}")1406            ```1407    """14081409    def decorator(1410        func: _CallableWithStateAndRuntime[StateT, ContextT],1411    ) -> AgentMiddleware[StateT, ContextT]:1412        is_async = iscoroutinefunction(func)14131414        func_can_jump_to = (1415            can_jump_to if can_jump_to is not None else getattr(func, "__can_jump_to__", [])1416        )14171418        if is_async:14191420            async def async_wrapped(1421                _self: AgentMiddleware[StateT, ContextT],1422                state: StateT,1423                runtime: Runtime[ContextT],1424            ) -> dict[str, Any] | Command[Any] | None:1425                # `iscoroutinefunction` narrows `func` at runtime, but type checkers1426                # cannot narrow this sync-or-async callable union.1427                return await cast("_AsyncCallableWithStateAndRuntime[StateT, ContextT]", func)(1428                    state, runtime1429                )14301431            # Preserve can_jump_to metadata on the wrapped function1432            if func_can_jump_to:1433                async_wrapped.__can_jump_to__ = func_can_jump_to  # type: ignore[attr-defined]14341435            middleware_name = name or cast(1436                "str", getattr(func, "__name__", "BeforeAgentMiddleware")1437            )14381439            # `type(...)` builds the correct middleware subclass at runtime, but1440            # type checkers cannot infer its generic `AgentMiddleware` parameters.1441            return cast(1442                "AgentMiddleware[StateT, ContextT]",1443                type(1444                    middleware_name,1445                    (AgentMiddleware,),1446                    {1447                        "state_schema": state_schema or AgentState,1448                        "tools": tools or [],1449                        "abefore_agent": async_wrapped,1450                    },1451                )(),1452            )14531454        def wrapped(1455            _self: AgentMiddleware[StateT, ContextT],1456            state: StateT,1457            runtime: Runtime[ContextT],1458        ) -> dict[str, Any] | Command[Any] | None:1459            # `iscoroutinefunction` narrows `func` at runtime, but type checkers1460            # cannot narrow this sync-or-async callable union.1461            return cast("_SyncCallableWithStateAndRuntime[StateT, ContextT]", func)(state, runtime)14621463        # Preserve can_jump_to metadata on the wrapped function1464        if func_can_jump_to:1465            wrapped.__can_jump_to__ = func_can_jump_to  # type: ignore[attr-defined]14661467        # Use function name as default if no name provided1468        middleware_name = name or cast("str", getattr(func, "__name__", "BeforeAgentMiddleware"))14691470        # `type(...)` builds the correct middleware subclass at runtime, but1471        # type checkers cannot infer its generic `AgentMiddleware` parameters.1472        return cast(1473            "AgentMiddleware[StateT, ContextT]",1474            type(1475                middleware_name,1476                (AgentMiddleware,),1477                {1478                    "state_schema": state_schema or AgentState,1479                    "tools": tools or [],1480                    "before_agent": wrapped,1481                },1482            )(),1483        )14841485    if func is not None:1486        return decorator(func)1487    return decorator148814891490@overload1491def after_agent(1492    func: _CallableWithStateAndRuntime[StateT, ContextT],1493) -> AgentMiddleware[StateT, ContextT]: ...149414951496@overload1497def after_agent(1498    func: None = None,1499    *,1500    state_schema: type[StateT] | None = None,1501    tools: list[BaseTool] | None = None,1502    can_jump_to: list[JumpTo] | None = None,1503    name: str | None = None,1504) -> Callable[1505    [_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]1506]: ...150715081509def after_agent(1510    func: _CallableWithStateAndRuntime[StateT, ContextT] | None = None,1511    *,1512    state_schema: type[StateT] | None = None,1513    tools: list[BaseTool] | None = None,1514    can_jump_to: list[JumpTo] | None = None,1515    name: str | None = None,1516) -> (1517    Callable[[_CallableWithStateAndRuntime[StateT, ContextT]], AgentMiddleware[StateT, ContextT]]1518    | AgentMiddleware[StateT, ContextT]1519):1520    """Decorator used to dynamically create a middleware with the `after_agent` hook.15211522    Async version is `aafter_agent`.15231524    Args:1525        func: The function to be decorated.15261527            Must accept: `state: StateT, runtime: Runtime[ContextT]` - State and runtime1528            context1529        state_schema: Optional custom state schema type.15301531            If not provided, uses the default `AgentState` schema.1532        tools: Optional list of additional tools to register with this middleware.1533        can_jump_to: Optional list of valid jump destinations for conditional edges.15341535            Valid values are: `'tools'`, `'model'`, `'end'`1536        name: Optional name for the generated middleware class.15371538            If not provided, uses the decorated function's name.15391540    Returns:1541        Either an `AgentMiddleware` instance (if func is provided) or a decorator1542            function that can be applied to a function.15431544    The decorated function should return:15451546    - `dict[str, Any]` - State updates to merge into the agent state1547    - `Command` - A command to control flow (e.g., jump to different node)1548    - `None` - No state updates or flow control15491550    Examples:1551        !!! example "Basic usage for logging agent completion"15521553            ```python1554            @after_agent1555            def log_completion(state: AgentState, runtime: Runtime) -> None:1556                print(f"Agent completed with {len(state['messages'])} messages")1557            ```15581559        !!! example "With custom state schema"15601561            ```python1562            @after_agent(state_schema=MyCustomState, name="MyAfterAgentMiddleware")1563            def custom_after_agent(state: MyCustomState, runtime: Runtime) -> dict[str, Any]:1564                return {"custom_field": "finalized_value"}1565            ```15661567        !!! example "Streaming custom events on completion"15681569            Use `runtime.stream_writer` to emit custom events when agent completes.1570            Events are received when streaming with `stream_mode="custom"`.15711572            ```python1573            @after_agent1574            async def notify_completion(state: AgentState, runtime: Runtime) -> None:1575                '''Notify user that agent has completed.'''1576                runtime.stream_writer(1577                    {1578                        "type": "status",1579                        "message": "Agent execution complete!",1580                        "total_messages": len(state["messages"]),1581                    }1582                )1583            ```1584    """15851586    def decorator(1587        func: _CallableWithStateAndRuntime[StateT, ContextT],1588    ) -> AgentMiddleware[StateT, ContextT]:1589        is_async = iscoroutinefunction(func)1590        # Extract can_jump_to from decorator parameter or from function metadata1591        func_can_jump_to = (1592            can_jump_to if can_jump_to is not None else getattr(func, "__can_jump_to__", [])1593        )15941595        if is_async:15961597            async def async_wrapped(1598                _self: AgentMiddleware[StateT, ContextT],1599                state: StateT,1600                runtime: Runtime[ContextT],1601            ) -> dict[str, Any] | Command[Any] | None:1602                # `iscoroutinefunction` narrows `func` at runtime, but type checkers1603                # cannot narrow this sync-or-async callable union.1604                return await cast("_AsyncCallableWithStateAndRuntime[StateT, ContextT]", func)(1605                    state, runtime1606                )16071608            # Preserve can_jump_to metadata on the wrapped function1609            if func_can_jump_to:1610                async_wrapped.__can_jump_to__ = func_can_jump_to  # type: ignore[attr-defined]16111612            middleware_name = name or cast("str", getattr(func, "__name__", "AfterAgentMiddleware"))16131614            # `type(...)` builds the correct middleware subclass at runtime, but1615            # type checkers cannot infer its generic `AgentMiddleware` parameters.1616            return cast(1617                "AgentMiddleware[StateT, ContextT]",1618                type(1619                    middleware_name,1620                    (AgentMiddleware,),1621                    {1622                        "state_schema": state_schema or AgentState,1623                        "tools": tools or [],1624                        "aafter_agent": async_wrapped,1625                    },1626                )(),1627            )16281629        def wrapped(1630            _self: AgentMiddleware[StateT, ContextT],1631            state: StateT,1632            runtime: Runtime[ContextT],1633        ) -> dict[str, Any] | Command[Any] | None:1634            # `iscoroutinefunction` narrows `func` at runtime, but type checkers1635            # cannot narrow this sync-or-async callable union.1636            return cast("_SyncCallableWithStateAndRuntime[StateT, ContextT]", func)(state, runtime)16371638        # Preserve can_jump_to metadata on the wrapped function1639        if func_can_jump_to:1640            wrapped.__can_jump_to__ = func_can_jump_to  # type: ignore[attr-defined]16411642        # Use function name as default if no name provided1643        middleware_name = name or cast("str", getattr(func, "__name__", "AfterAgentMiddleware"))16441645        # `type(...)` builds the correct middleware subclass at runtime, but1646        # type checkers cannot infer its generic `AgentMiddleware` parameters.1647        return cast(1648            "AgentMiddleware[StateT, ContextT]",1649            type(1650                middleware_name,1651                (AgentMiddleware,),1652                {1653                    "state_schema": state_schema or AgentState,1654                    "tools": tools or [],1655                    "after_agent": wrapped,1656                },1657            )(),1658        )16591660    if func is not None:1661        return decorator(func)1662    return decorator166316641665@overload1666def dynamic_prompt(1667    func: _CallableReturningSystemMessage[ContextT],1668) -> AgentMiddleware[StateT, ContextT]: ...166916701671@overload1672def dynamic_prompt(1673    func: None = None,1674) -> Callable[1675    [_CallableReturningSystemMessage[ContextT]],1676    AgentMiddleware[StateT, ContextT],1677]: ...167816791680def dynamic_prompt(1681    func: _CallableReturningSystemMessage[ContextT] | None = None,1682) -> (1683    Callable[1684        [_CallableReturningSystemMessage[ContextT]],1685        AgentMiddleware[StateT, ContextT],1686    ]1687    | AgentMiddleware[StateT, ContextT]1688):1689    """Decorator used to dynamically generate system prompts for the model.16901691    This is a convenience decorator that creates middleware using `wrap_model_call`1692    specifically for dynamic prompt generation. The decorated function should return1693    a string that will be set as the system prompt for the model request.16941695    Args:1696        func: The function to be decorated.16971698            Must accept: `request: ModelRequest` - Model request (contains state and1699            runtime)17001701    Returns:1702        Either an `AgentMiddleware` instance (if func is provided) or a decorator1703            function that can be applied to a function.17041705    The decorated function should return:1706        - `str`  The system prompt string to use for the model request1707        - `SystemMessage`  A complete system message to use for the model request17081709    Examples:1710        Basic usage with dynamic content:17111712        ```python1713        @dynamic_prompt1714        def my_prompt(request: ModelRequest) -> str:1715            user_name = request.runtime.context.get("user_name", "User")1716            return f"You are a helpful assistant helping {user_name}."1717        ```17181719        Using state to customize the prompt:17201721        ```python1722        @dynamic_prompt1723        def context_aware_prompt(request: ModelRequest) -> str:1724            msg_count = len(request.state["messages"])1725            if msg_count > 10:1726                return "You are in a long conversation. Be concise."1727            return "You are a helpful assistant."1728        ```17291730        Using with agent:17311732        ```python1733        agent = create_agent(model, middleware=[my_prompt])1734        ```1735    """17361737    def decorator(1738        func: _CallableReturningSystemMessage[ContextT],1739    ) -> AgentMiddleware[StateT, ContextT]:1740        is_async = iscoroutinefunction(func)17411742        if is_async:17431744            async def async_wrapped(1745                _self: AgentMiddleware[StateT, ContextT],1746                request: ModelRequest[ContextT],1747                handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[Any]]],1748            ) -> ModelResponse[Any] | AIMessage:1749                prompt = await cast("_AsyncCallableReturningSystemMessage[ContextT]", func)(request)1750                if isinstance(prompt, SystemMessage):1751                    request = request.override(system_message=prompt)1752                else:1753                    request = request.override(system_message=SystemMessage(content=prompt))1754                return await handler(request)17551756            middleware_name = cast("str", getattr(func, "__name__", "DynamicPromptMiddleware"))17571758            # `type(...)` builds the correct middleware subclass at runtime, but1759            # type checkers cannot infer its generic `AgentMiddleware` parameters.1760            return cast(1761                "AgentMiddleware[StateT, ContextT]",1762                type(1763                    middleware_name,1764                    (AgentMiddleware,),1765                    {1766                        "state_schema": AgentState,1767                        "tools": [],1768                        "awrap_model_call": async_wrapped,1769                    },1770                )(),1771            )17721773        def wrapped(1774            _self: AgentMiddleware[StateT, ContextT],1775            request: ModelRequest[ContextT],1776            handler: Callable[[ModelRequest[ContextT]], ModelResponse[Any]],1777        ) -> ModelResponse[Any] | AIMessage:1778            prompt = cast("_SyncCallableReturningSystemMessage[ContextT]", func)(request)1779            if isinstance(prompt, SystemMessage):1780                request = request.override(system_message=prompt)1781            else:1782                request = request.override(system_message=SystemMessage(content=prompt))1783            return handler(request)17841785        async def async_wrapped_from_sync(1786            _self: AgentMiddleware[StateT, ContextT],1787            request: ModelRequest[ContextT],1788            handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[Any]]],1789        ) -> ModelResponse[Any] | AIMessage:1790            # Delegate to sync function1791            prompt = cast("_SyncCallableReturningSystemMessage[ContextT]", func)(request)1792            if isinstance(prompt, SystemMessage):1793                request = request.override(system_message=prompt)1794            else:1795                request = request.override(system_message=SystemMessage(content=prompt))1796            return await handler(request)17971798        middleware_name = cast("str", getattr(func, "__name__", "DynamicPromptMiddleware"))17991800        # `type(...)` builds the correct middleware subclass at runtime, but1801        # type checkers cannot infer its generic `AgentMiddleware` parameters.1802        return cast(1803            "AgentMiddleware[StateT, ContextT]",1804            type(1805                middleware_name,1806                (AgentMiddleware,),1807                {1808                    "state_schema": AgentState,1809                    "tools": [],1810                    "wrap_model_call": wrapped,1811                    "awrap_model_call": async_wrapped_from_sync,1812                },1813            )(),1814        )18151816    if func is not None:1817        return decorator(func)1818    return decorator181918201821@overload1822def wrap_model_call(1823    func: _CallableReturningModelResponse[ContextT, ResponseT],1824) -> AgentMiddleware[StateT, ContextT]: ...182518261827@overload1828def wrap_model_call(1829    func: None = None,1830    *,1831    state_schema: type[StateT] | None = None,1832    tools: list[BaseTool] | None = None,1833    name: str | None = None,1834) -> Callable[1835    [_CallableReturningModelResponse[ContextT, ResponseT]],1836    AgentMiddleware[StateT, ContextT],1837]: ...183818391840def wrap_model_call(1841    func: _CallableReturningModelResponse[ContextT, ResponseT] | None = None,1842    *,1843    state_schema: type[StateT] | None = None,1844    tools: list[BaseTool] | None = None,1845    name: str | None = None,1846) -> (1847    Callable[1848        [_CallableReturningModelResponse[ContextT, ResponseT]],1849        AgentMiddleware[StateT, ContextT],1850    ]1851    | AgentMiddleware[StateT, ContextT]1852):1853    """Create middleware with `wrap_model_call` hook from a function.18541855    Converts a function with handler callback into middleware that can intercept model1856    calls, implement retry logic, handle errors, and rewrite responses.18571858    Args:1859        func: Function accepting (request, handler) that calls handler(request)1860            to execute the model and returns `ModelResponse` or `AIMessage`.18611862            Request contains state and runtime.1863        state_schema: Custom state schema.18641865            Defaults to `AgentState`.1866        tools: Additional tools to register with this middleware.1867        name: Middleware class name.18681869            Defaults to function name.18701871    Returns:1872        `AgentMiddleware` instance if func provided, otherwise a decorator.18731874    Examples:1875        !!! example "Basic retry logic"18761877            ```python1878            @wrap_model_call1879            def retry_on_error(request, handler):1880                max_retries = 31881                for attempt in range(max_retries):1882                    try:1883                        return handler(request)1884                    except Exception:1885                        if attempt == max_retries - 1:1886                            raise1887            ```18881889        !!! example "Model fallback"18901891            ```python1892            @wrap_model_call1893            def fallback_model(request, handler):1894                # Try primary model1895                try:1896                    return handler(request)1897                except Exception:1898                    pass18991900                # Try fallback model1901                request = request.override(model=fallback_model_instance)1902                return handler(request)1903            ```19041905        !!! example "Rewrite response content (full `ModelResponse`)"19061907            ```python1908            @wrap_model_call1909            def uppercase_responses(request, handler):1910                response = handler(request)1911                ai_msg = response.result[0]1912                return ModelResponse(1913                    result=[AIMessage(content=ai_msg.content.upper())],1914                    structured_response=response.structured_response,1915                )1916            ```19171918        !!! example "Simple `AIMessage` return (converted automatically)"19191920            ```python1921            @wrap_model_call1922            def simple_response(request, handler):1923                # AIMessage is automatically converted to ModelResponse1924                return AIMessage(content="Simple response")1925            ```1926    """19271928    def decorator(1929        func: _CallableReturningModelResponse[ContextT, ResponseT],1930    ) -> AgentMiddleware[StateT, ContextT]:1931        is_async = iscoroutinefunction(func)19321933        if is_async:19341935            async def async_wrapped(1936                _self: AgentMiddleware[StateT, ContextT],1937                request: ModelRequest[ContextT],1938                handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],1939            ) -> ModelCallResult[ResponseT]:1940                return await cast(1941                    "_AsyncCallableReturningModelResponse[ContextT, ResponseT]", func1942                )(request, handler)19431944            middleware_name = name or cast(1945                "str", getattr(func, "__name__", "WrapModelCallMiddleware")1946            )19471948            # `type(...)` builds the correct middleware subclass at runtime, but1949            # type checkers cannot infer its generic `AgentMiddleware` parameters.1950            return cast(1951                "AgentMiddleware[StateT, ContextT]",1952                type(1953                    middleware_name,1954                    (AgentMiddleware,),1955                    {1956                        "state_schema": state_schema or AgentState,1957                        "tools": tools or [],1958                        "awrap_model_call": async_wrapped,1959                    },1960                )(),1961            )19621963        def wrapped(1964            _self: AgentMiddleware[StateT, ContextT],1965            request: ModelRequest[ContextT],1966            handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],1967        ) -> ModelCallResult[ResponseT]:1968            return cast("_SyncCallableReturningModelResponse[ContextT, ResponseT]", func)(1969                request, handler1970            )19711972        middleware_name = name or cast("str", getattr(func, "__name__", "WrapModelCallMiddleware"))19731974        # `type(...)` builds the correct middleware subclass at runtime, but1975        # type checkers cannot infer its generic `AgentMiddleware` parameters.1976        return cast(1977            "AgentMiddleware[StateT, ContextT]",1978            type(1979                middleware_name,1980                (AgentMiddleware,),1981                {1982                    "state_schema": state_schema or AgentState,1983                    "tools": tools or [],1984                    "wrap_model_call": wrapped,1985                },1986            )(),1987        )19881989    if func is not None:1990        return decorator(func)1991    return decorator199219931994@overload1995def wrap_tool_call(1996    func: _CallableReturningToolResponse,1997) -> AgentMiddleware: ...199819992000@overload

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
type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Use isinstance() for type checking instead of type()
type-check
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
type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Use isinstance() for type checking instead of type()
type-check
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
type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Use isinstance() for type checking instead of type()
type-check
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
type(
Ensure functions have docstrings for documentation
missing-docstring
def wrapped(
Use isinstance() for type checking instead of type()
type-check
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
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
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.