libs/langchain_v1/langchain/agents/factory.py PYTHON 2,091 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 2,091.
1"""Agent factory for creating agents with middleware support."""23from __future__ import annotations45import functools6import importlib7import itertools8import re9from dataclasses import dataclass, field, fields10from typing import (11    TYPE_CHECKING,12    Annotated,13    Any,14    Generic,15    cast,16    get_args,17    get_origin,18    get_type_hints,19)2021from langchain_core.language_models.chat_models import BaseChatModel22from langchain_core.messages import AIMessage, AnyMessage, SystemMessage, ToolMessage23from langchain_core.tools import BaseTool24from langgraph._internal._runnable import RunnableCallable25from langgraph.constants import END, START26from langgraph.graph.state import StateGraph27from langgraph.prebuilt import ToolCallTransformer28from langgraph.prebuilt.tool_node import ToolNode29from langgraph.types import Command, Send30from langsmith import traceable31from typing_extensions import NotRequired, Required, TypedDict, overload3233from langchain.agents._subagent_transformer import SubagentTransformer34from langchain.agents.middleware._trace_policy import (35    _node_trace_policy,36    _resolved_transform,37)38from langchain.agents.middleware.types import (39    AgentMiddleware,40    AgentState,41    ContextT,42    ExtendedModelResponse,43    InputAgentState,44    JumpTo,45    ModelRequest,46    ModelResponse,47    OmitFromSchema,48    OutputAgentState,49    ResponseT,50    StateT_co,51    ToolCallRequest,52)53from langchain.agents.structured_output import (54    AutoStrategy,55    MultipleStructuredOutputsError,56    OutputToolBinding,57    ProviderStrategy,58    ProviderStrategyBinding,59    ResponseFormat,60    StructuredOutputError,61    StructuredOutputValidationError,62    ToolStrategy,63)64from langchain.chat_models import init_chat_model656667@dataclass68class _ComposedExtendedModelResponse(Generic[ResponseT]):69    """Internal result from composed `wrap_model_call` middleware.7071    Unlike `ExtendedModelResponse` (user-facing, single command), this holds the72    full list of commands accumulated across all middleware layers during73    composition.74    """7576    model_response: ModelResponse[ResponseT]77    """The underlying model response."""7879    commands: list[Command[Any]] = field(default_factory=list)80    """Commands accumulated from all middleware layers (inner-first, then outer)."""818283if TYPE_CHECKING:84    from collections.abc import Awaitable, Callable, Iterable, Sequence8586    from langchain_core.runnables import Runnable, RunnableConfig87    from langgraph.cache.base import BaseCache88    from langgraph.graph.state import CompiledStateGraph89    from langgraph.runtime import Runtime90    from langgraph.store.base import BaseStore91    from langgraph.stream._mux import TransformerFactory92    from langgraph.types import Checkpointer9394    from langchain.agents.middleware.types import ToolCallWrapper9596    _ModelCallHandler = Callable[97        [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], ModelResponse]],98        ModelResponse | AIMessage | ExtendedModelResponse,99    ]100101    _ComposedModelCallHandler = Callable[102        [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], ModelResponse]],103        _ComposedExtendedModelResponse,104    ]105106    _AsyncModelCallHandler = Callable[107        [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse]]],108        Awaitable[ModelResponse | AIMessage | ExtendedModelResponse],109    ]110111    _ComposedAsyncModelCallHandler = Callable[112        [ModelRequest[ContextT], Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse]]],113        Awaitable[_ComposedExtendedModelResponse],114    ]115116117STRUCTURED_OUTPUT_ERROR_TEMPLATE = "Error: {error}\n Please fix your mistakes."118119DYNAMIC_TOOL_ERROR_TEMPLATE = """120Middleware added tools that the agent doesn't know how to execute.121122Unknown tools: {unknown_tool_names}123Registered tools: {available_tool_names}124125This happens when middleware modifies `request.tools` in `wrap_model_call` to include126tools that weren't passed to `create_agent()`.127128How to fix this:129130Option 1: Register tools at agent creation (recommended for most cases)131    Pass the tools to `create_agent(tools=[...])` or set them on `middleware.tools`.132    This makes tools available for every agent invocation.133134Option 2: Handle dynamic tools in middleware (for tools created at runtime)135    Implement `wrap_tool_call` to execute tools that are added dynamically:136137    class MyMiddleware(AgentMiddleware):138        def wrap_tool_call(self, request, handler):139            if request.tool_call["name"] == "dynamic_tool":140                # Execute the dynamic tool yourself or override with tool instance141                return handler(request.override(tool=my_dynamic_tool))142            return handler(request)143""".strip()144145146def _scrub_inputs(inputs: dict[str, Any]) -> dict[str, Any]:147    """Remove `runtime` and `handler` from trace inputs before sending to LangSmith."""148    filtered = inputs.copy()149    filtered.pop("handler", None)150    req = filtered.get("request")151    if isinstance(req, (ModelRequest, ToolCallRequest)):152        filtered["request"] = {153            f.name: getattr(req, f.name) for f in fields(req) if f.name != "runtime"154        }155    return filtered156157158def _wrap_trace_kwargs(middleware: AgentMiddleware[Any, Any]) -> dict[str, Any]:159    """`traceable` kwargs for a middleware's `wrap_*` hook spans.160161    The `_scrub_inputs` baseline (strip the unserializable `handler`/`runtime`) always162    runs first; the effective `TracePolicy` (the middleware's own, else the process-wide163    default) composes on top. The effective policy is resolved at call time, so a164    `configure_trace_policy` call after `create_agent` still applies.165    """166    process_inputs = _resolved_transform(middleware.trace_policy, "process_inputs")167    process_outputs = _resolved_transform(middleware.trace_policy, "process_outputs")168    return {169        "process_inputs": lambda inputs: process_inputs(_scrub_inputs(inputs)),170        "process_outputs": process_outputs,171    }172173174FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT = [175    # If model profile data are not available, model names matching these patterns176    # are assumed to support provider-native structured output. These are regexes177    # so matches stay bounded to model-name segments instead of arbitrary substrings.178    r"(^|[/:.])gpt-4\.1($|[-/:])",179    r"(^|[/:.])gpt-4o($|[-/:])",180    r"(^|[/:.])gpt-5($|[-/:])",181    r"(^|[/:.])gpt-5\.1($|[-/:])",182    r"(^|[/:.])gpt-5\.2(-\d{4}-\d{2}-\d{2})?($|[/:])",183    r"(^|[/:.])gpt-5\.2-(chat|codex)($|[-/:])",184    r"(^|[/:.])gpt-5\.3($|[-/:])",185    r"(^|[/:.])gpt-5\.4(-\d{4}-\d{2}-\d{2})?($|[/:])",186    r"(^|[/:.])gpt-5\.4-(mini|nano)($|[-/:])",187    r"(^|[/:.])gpt-5\.5($|[-/:])",188    r"(^|[/:.])claude-(fable|mythos)-5(?:-\d{8})?(?:-v\d(?::\d)?)?($|[/:])",189    r"(^|[/:.])claude-haiku-4-5(?:-\d{8})?(?:-v\d(?::\d)?)?($|[/:])",190    r"(^|[/:.])claude-opus-4-(5|6|7|8)(?:-\d{8})?(?:-v\d(?::\d)?)?($|[/:])",191    r"(^|[/:.])claude-sonnet-4-(5|6)(?:-\d{8})?(?:-v\d(?::\d)?)?($|[/:])",192    r"(^|[/:.])grok-4($|[-.:/])",193    r"(^|[/:.])grok-build($|[-/:])",194]195196197def _normalize_to_model_response(198    result: ModelResponse | AIMessage | ExtendedModelResponse,199) -> ModelResponse:200    """Normalize middleware return value to ModelResponse.201202    At inner composition boundaries, `ExtendedModelResponse` is unwrapped to its203    underlying `ModelResponse` so that inner middleware always sees `ModelResponse`204    from the handler.205    """206    if isinstance(result, AIMessage):207        return ModelResponse(result=[result], structured_response=None)208    if isinstance(result, ExtendedModelResponse):209        return result.model_response210    return result211212213def _build_commands(214    model_response: ModelResponse,215    middleware_commands: list[Command[Any]] | None = None,216    *,217    has_structured_output: bool = False,218) -> list[Command[Any]]:219    """Build a list of Commands from a model response and middleware commands.220221    The first Command contains the model response state (messages and optional222    structured_response). Middleware commands are appended as-is.223224    Args:225        model_response: The model response containing messages and optional226            structured output.227        middleware_commands: Commands accumulated from middleware layers during228            composition (inner-first ordering).229        has_structured_output: Whether the agent was configured with a230            `response_format`. When `True` and no structured response was231            produced, `structured_response` is explicitly cleared to avoid a232            stale value from a previous checkpointed turn.233234    Returns:235        List of `Command` objects ready to be returned from a model node.236    """237    state: dict[str, Any] = {"messages": model_response.result}238239    if model_response.structured_response is not None:240        state["structured_response"] = model_response.structured_response241    elif has_structured_output:242        state["structured_response"] = None243244    for cmd in middleware_commands or []:245        if cmd.goto:246            msg = (247                "Command goto is not yet supported in wrap_model_call middleware. "248                "Use the jump_to state field with before_model/after_model hooks instead."249            )250            raise NotImplementedError(msg)251        if cmd.resume:252            msg = "Command resume is not yet supported in wrap_model_call middleware."253            raise NotImplementedError(msg)254        if cmd.graph:255            msg = "Command graph is not yet supported in wrap_model_call middleware."256            raise NotImplementedError(msg)257258    commands: list[Command[Any]] = [Command(update=state)]259    commands.extend(middleware_commands or [])260    return commands261262263def _chain_model_call_handlers(264    handlers: Sequence[_ModelCallHandler[ContextT]],265) -> _ComposedModelCallHandler[ContextT] | None:266    """Compose multiple `wrap_model_call` handlers into single middleware stack.267268    Composes handlers so first in list becomes outermost layer. Each handler receives a269    handler callback to execute inner layers. Commands from each layer are accumulated270    into a list (inner-first, then outer) without merging.271272    Args:273        handlers: List of handlers.274275            First handler wraps all others.276277    Returns:278        Composed handler returning `_ComposedExtendedModelResponse`,279        or `None` if handlers empty.280    """281    if not handlers:282        return None283284    def _to_composed_result(285        result: ModelResponse | AIMessage | ExtendedModelResponse | _ComposedExtendedModelResponse,286        extra_commands: list[Command[Any]] | None = None,287    ) -> _ComposedExtendedModelResponse:288        """Normalize any handler result to _ComposedExtendedModelResponse."""289        commands: list[Command[Any]] = list(extra_commands or [])290        if isinstance(result, _ComposedExtendedModelResponse):291            commands.extend(result.commands)292            model_response = result.model_response293        elif isinstance(result, ExtendedModelResponse):294            model_response = result.model_response295            if result.command is not None:296                commands.append(result.command)297        else:298            model_response = _normalize_to_model_response(result)299300        return _ComposedExtendedModelResponse(model_response=model_response, commands=commands)301302    if len(handlers) == 1:303        single_handler = handlers[0]304305        def normalized_single(306            request: ModelRequest[ContextT],307            handler: Callable[[ModelRequest[ContextT]], ModelResponse],308        ) -> _ComposedExtendedModelResponse:309            return _to_composed_result(single_handler(request, handler))310311        return normalized_single312313    def compose_two(314        outer: _ModelCallHandler[ContextT] | _ComposedModelCallHandler[ContextT],315        inner: _ModelCallHandler[ContextT] | _ComposedModelCallHandler[ContextT],316    ) -> _ComposedModelCallHandler[ContextT]:317        """Compose two handlers where outer wraps inner."""318319        def composed(320            request: ModelRequest[ContextT],321            handler: Callable[[ModelRequest[ContextT]], ModelResponse],322        ) -> _ComposedExtendedModelResponse:323            # Closure variable to capture inner's commands before normalizing324            accumulated_commands: list[Command[Any]] = []325326            def inner_handler(req: ModelRequest[ContextT]) -> ModelResponse:327                # Clear on each call for retry safety328                accumulated_commands.clear()329                inner_result = inner(req, handler)330                if isinstance(inner_result, _ComposedExtendedModelResponse):331                    accumulated_commands.extend(inner_result.commands)332                    return inner_result.model_response333                if isinstance(inner_result, ExtendedModelResponse):334                    if inner_result.command is not None:335                        accumulated_commands.append(inner_result.command)336                    return inner_result.model_response337                return _normalize_to_model_response(inner_result)338339            outer_result = outer(request, inner_handler)340            return _to_composed_result(341                outer_result,342                extra_commands=accumulated_commands or None,343            )344345        return composed346347    # Compose right-to-left: outer(inner(innermost(handler)))348    composed_handler = compose_two(handlers[-2], handlers[-1])349    for h in reversed(handlers[:-2]):350        composed_handler = compose_two(h, composed_handler)351352    return composed_handler353354355def _chain_async_model_call_handlers(356    handlers: Sequence[_AsyncModelCallHandler[ContextT]],357) -> _ComposedAsyncModelCallHandler[ContextT] | None:358    """Compose multiple async `wrap_model_call` handlers into single middleware stack.359360    Commands from each layer are accumulated into a list (inner-first, then outer)361    without merging.362363    Args:364        handlers: List of async handlers.365366            First handler wraps all others.367368    Returns:369        Composed async handler returning `_ComposedExtendedModelResponse`,370        or `None` if handlers empty.371    """372    if not handlers:373        return None374375    def _to_composed_result(376        result: ModelResponse | AIMessage | ExtendedModelResponse | _ComposedExtendedModelResponse,377        extra_commands: list[Command[Any]] | None = None,378    ) -> _ComposedExtendedModelResponse:379        """Normalize any handler result to _ComposedExtendedModelResponse."""380        commands: list[Command[Any]] = list(extra_commands or [])381        if isinstance(result, _ComposedExtendedModelResponse):382            commands.extend(result.commands)383            model_response = result.model_response384        elif isinstance(result, ExtendedModelResponse):385            model_response = result.model_response386            if result.command is not None:387                commands.append(result.command)388        else:389            model_response = _normalize_to_model_response(result)390391        return _ComposedExtendedModelResponse(model_response=model_response, commands=commands)392393    if len(handlers) == 1:394        single_handler = handlers[0]395396        async def normalized_single(397            request: ModelRequest[ContextT],398            handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse]],399        ) -> _ComposedExtendedModelResponse:400            return _to_composed_result(await single_handler(request, handler))401402        return normalized_single403404    def compose_two(405        outer: _AsyncModelCallHandler[ContextT] | _ComposedAsyncModelCallHandler[ContextT],406        inner: _AsyncModelCallHandler[ContextT] | _ComposedAsyncModelCallHandler[ContextT],407    ) -> _ComposedAsyncModelCallHandler[ContextT]:408        """Compose two async handlers where outer wraps inner."""409410        async def composed(411            request: ModelRequest[ContextT],412            handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse]],413        ) -> _ComposedExtendedModelResponse:414            # Closure variable to capture inner's commands before normalizing415            accumulated_commands: list[Command[Any]] = []416417            async def inner_handler(req: ModelRequest[ContextT]) -> ModelResponse:418                # Clear on each call for retry safety419                accumulated_commands.clear()420                inner_result = await inner(req, handler)421                if isinstance(inner_result, _ComposedExtendedModelResponse):422                    accumulated_commands.extend(inner_result.commands)423                    return inner_result.model_response424                if isinstance(inner_result, ExtendedModelResponse):425                    if inner_result.command is not None:426                        accumulated_commands.append(inner_result.command)427                    return inner_result.model_response428                return _normalize_to_model_response(inner_result)429430            outer_result = await outer(request, inner_handler)431            return _to_composed_result(432                outer_result,433                extra_commands=accumulated_commands or None,434            )435436        return composed437438    # Compose right-to-left: outer(inner(innermost(handler)))439    composed_handler = compose_two(handlers[-2], handlers[-1])440    for h in reversed(handlers[:-2]):441        composed_handler = compose_two(h, composed_handler)442443    return composed_handler444445446@functools.lru_cache(maxsize=100)447def _get_schema_type_hints(schema: type) -> dict[str, Any]:448    """Return cached type hints for a schema."""449    return get_type_hints(schema, include_extras=True)450451452def _resolve_schemas(schemas: list[type]) -> tuple[type, type, type]:453    """Resolve state, input, and output schemas for the given schemas.454455    Schemas are merged in list order; later entries override earlier ones when the456    same field is declared by multiple schemas.  Duplicates are harmless  a type457    that appears more than once is processed at its last position.458    """459    schema_hints: dict[type, dict[str, Any]] = {}460    for schema in schemas:461        # Reinsert duplicates so dict iteration reflects their final position.462        schema_hints.pop(schema, None)463        schema_hints[schema] = _get_schema_type_hints(schema)464    return (465        _resolve_schema(schema_hints, "StateSchema", None),466        _resolve_schema(schema_hints, "InputSchema", "input"),467        _resolve_schema(schema_hints, "OutputSchema", "output"),468    )469470471def _resolve_schema(472    schema_hints: dict[type, dict[str, Any]],473    schema_name: str,474    omit_flag: str | None = None,475) -> type:476    """Resolve schema by merging schemas and optionally respecting `OmitFromSchema` annotations.477478    Args:479        schema_hints: Resolved schema annotations to merge480        schema_name: Name for the generated `TypedDict`481        omit_flag: If specified, omit fields with this flag set (`'input'` or482            `'output'`)483484    Returns:485        Merged schema as `TypedDict`486    """487    all_annotations = {}488489    for hints in schema_hints.values():490        for field_name, field_type in hints.items():491            should_omit = False492493            if omit_flag:494                metadata = _extract_metadata(field_type)495                for meta in metadata:496                    if isinstance(meta, OmitFromSchema) and getattr(meta, omit_flag) is True:497                        should_omit = True498                        break499500            if not should_omit:501                all_annotations[field_name] = field_type502503    # `TypedDict` dynamically creates a class, but type checkers don't infer that504    # the runtime result satisfies this function's `type` return contract.505    return cast("type", TypedDict(schema_name, all_annotations))  # type: ignore[operator]506507508def _extract_metadata(type_: type) -> list[Any]:509    """Extract metadata from a field type, handling `Required`/`NotRequired` and `Annotated` wrappers."""  # noqa: E501510    # Handle Required[Annotated[...]] or NotRequired[Annotated[...]]511    if get_origin(type_) in {Required, NotRequired}:512        inner_type = get_args(type_)[0]513        if get_origin(inner_type) is Annotated:514            return list(get_args(inner_type)[1:])515516    # Handle direct Annotated[...]517    elif get_origin(type_) is Annotated:518        return list(get_args(type_)[1:])519520    return []521522523def _get_can_jump_to(middleware: AgentMiddleware[Any, Any], hook_name: str) -> list[JumpTo]:524    """Get the `can_jump_to` list from either sync or async hook methods.525526    Args:527        middleware: The middleware instance to inspect.528        hook_name: The name of the hook (`'before_model'` or `'after_model'`).529530    Returns:531        List of jump destinations, or empty list if not configured.532    """533    # Get the base class method for comparison534    base_sync_method = getattr(AgentMiddleware, hook_name, None)535    base_async_method = getattr(AgentMiddleware, f"a{hook_name}", None)536537    # Try sync method first - only if it's overridden from base class538    sync_method = getattr(middleware.__class__, hook_name, None)539    if (540        sync_method541        and sync_method is not base_sync_method542        and hasattr(sync_method, "__can_jump_to__")543    ):544        # `hasattr` proves the metadata exists at runtime, but not its value type.545        return cast("list[JumpTo]", sync_method.__can_jump_to__)546547    # Try async method - only if it's overridden from base class548    async_method = getattr(middleware.__class__, f"a{hook_name}", None)549    if (550        async_method551        and async_method is not base_async_method552        and hasattr(async_method, "__can_jump_to__")553    ):554        # `hasattr` proves the metadata exists at runtime, but not its value type.555        return cast("list[JumpTo]", async_method.__can_jump_to__)556557    return []558559560def _supports_provider_strategy(561    model: str | BaseChatModel, tools: list[BaseTool | dict[str, Any]] | None = None562) -> bool:563    """Check if a model supports provider-specific structured output.564565    Args:566        model: Model name string or `BaseChatModel` instance.567        tools: Optional list of tools provided to the agent.568569            Needed because some models don't support structured output together with tool calling.570571    Returns:572        `True` if the model supports provider-specific structured output, `False` otherwise.573    """574    model_name: str | None = None575    if isinstance(model, str):576        model_name = model577    elif isinstance(model, BaseChatModel):578        model_name = (579            getattr(model, "model_name", None)580            or getattr(model, "model", None)581            or getattr(model, "model_id", "")582        )583        model_profile = model.profile584        if (585            model_profile is not None586            and model_profile.get("structured_output")587            # We make an exception for Gemini < 3-series models, which currently do not support588            # simultaneous tool use with structured output; 3-series can.589            and not (590                tools591                and isinstance(model_name, str)592                and "gemini" in model_name.lower()593                and "gemini-3" not in model_name.lower()594            )595        ):596            return True597598    return (599        any(600            re.search(pattern, model_name.lower())601            for pattern in FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT602        )603        if model_name604        else False605    )606607608def _is_openai_compatible_model(model: BaseChatModel) -> bool:609    """Check if a model inherits from `BaseChatOpenAI`.610611    Used to redundantly set `strict=True` on tools when `response_format` is612    provided, as older versions of `langchain-openai` do not auto-set it.613    Covers `ChatOpenAI`, `ChatDeepSeek`, `ChatXAI`, etc.614615    Args:616        model: The chat model to check.617618    Returns:619        `True` if the model inherits from `BaseChatOpenAI`, `False` otherwise.620    """621    try:622        base_chat_openai = importlib.import_module("langchain_openai.chat_models.base")623    except ImportError:624        return False625    return isinstance(model, base_chat_openai.BaseChatOpenAI)626627628def _handle_structured_output_error(629    exception: Exception,630    response_format: ResponseFormat[Any],631) -> tuple[bool, str]:632    """Handle structured output error.633634    Returns `(should_retry, retry_tool_message)`.635    """636    if not isinstance(response_format, ToolStrategy):637        return False, ""638639    handle_errors = response_format.handle_errors640641    if handle_errors is False:642        return False, ""643    if handle_errors is True:644        return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))645    if isinstance(handle_errors, str):646        return True, handle_errors647    if isinstance(handle_errors, type):648        if issubclass(handle_errors, Exception) and isinstance(exception, handle_errors):649            return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))650        return False, ""651    if isinstance(handle_errors, tuple):652        if any(isinstance(exception, exc_type) for exc_type in handle_errors):653            return True, STRUCTURED_OUTPUT_ERROR_TEMPLATE.format(error=str(exception))654        return False, ""655    return True, handle_errors(exception)656657658def _chain_tool_call_wrappers(659    wrappers: Sequence[ToolCallWrapper],660) -> ToolCallWrapper | None:661    """Compose wrappers into middleware stack (first = outermost).662663    Args:664        wrappers: Wrappers in middleware order.665666    Returns:667        Composed wrapper, or `None` if empty.668669    Example:670        ```python671        wrapper = _chain_tool_call_wrappers([auth, cache, retry])672        # Request flows: auth -> cache -> retry -> tool673        # Response flows: tool -> retry -> cache -> auth674        ```675    """676    if not wrappers:677        return None678679    if len(wrappers) == 1:680        return wrappers[0]681682    def compose_two(outer: ToolCallWrapper, inner: ToolCallWrapper) -> ToolCallWrapper:683        """Compose two wrappers where outer wraps inner."""684685        def composed(686            request: ToolCallRequest,687            execute: Callable[[ToolCallRequest], ToolMessage | Command[Any]],688        ) -> ToolMessage | Command[Any]:689            # Create a callable that invokes inner with the original execute690            def call_inner(req: ToolCallRequest) -> ToolMessage | Command[Any]:691                return inner(req, execute)692693            # Outer can call call_inner multiple times694            return outer(request, call_inner)695696        return composed697698    # Chain all wrappers: first -> second -> ... -> last699    result = wrappers[-1]700    for wrapper in reversed(wrappers[:-1]):701        result = compose_two(wrapper, result)702703    return result704705706def _chain_async_tool_call_wrappers(707    wrappers: Sequence[708        Callable[709            [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],710            Awaitable[ToolMessage | Command[Any]],711        ]712    ],713) -> (714    Callable[715        [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],716        Awaitable[ToolMessage | Command[Any]],717    ]718    | None719):720    """Compose async wrappers into middleware stack (first = outermost).721722    Args:723        wrappers: Async wrappers in middleware order.724725    Returns:726        Composed async wrapper, or `None` if empty.727    """728    if not wrappers:729        return None730731    if len(wrappers) == 1:732        return wrappers[0]733734    def compose_two(735        outer: Callable[736            [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],737            Awaitable[ToolMessage | Command[Any]],738        ],739        inner: Callable[740            [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],741            Awaitable[ToolMessage | Command[Any]],742        ],743    ) -> Callable[744        [ToolCallRequest, Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]]],745        Awaitable[ToolMessage | Command[Any]],746    ]:747        """Compose two async wrappers where outer wraps inner."""748749        async def composed(750            request: ToolCallRequest,751            execute: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command[Any]]],752        ) -> ToolMessage | Command[Any]:753            # Create an async callable that invokes inner with the original execute754            async def call_inner(req: ToolCallRequest) -> ToolMessage | Command[Any]:755                return await inner(req, execute)756757            # Outer can call call_inner multiple times758            return await outer(request, call_inner)759760        return composed761762    # Chain all wrappers: first -> second -> ... -> last763    result = wrappers[-1]764    for wrapper in reversed(wrappers[:-1]):765        result = compose_two(wrapper, result)766767    return result768769770# No `response_format`: there is no structured output, so `ResponseT` resolves to `Any`.771@overload772def create_agent(773    model: str | BaseChatModel,774    tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,775    *,776    system_prompt: str | SystemMessage | None = None,777    middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),778    response_format: None = None,779    state_schema: None = None,780    context_schema: type[ContextT] | None = None,781    checkpointer: Checkpointer | None = None,782    store: BaseStore | None = None,783    interrupt_before: list[str] | None = None,784    interrupt_after: list[str] | None = None,785    debug: bool = False,786    name: str | None = None,787    cache: BaseCache[Any] | None = None,788    transformers: Sequence[TransformerFactory] | None = None,789) -> CompiledStateGraph[AgentState[Any], ContextT, InputAgentState, OutputAgentState[Any]]: ...790791792# Raw-dict `response_format`: structured output is an untyped `dict[str, Any]`.793@overload794def create_agent(795    model: str | BaseChatModel,796    tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,797    *,798    system_prompt: str | SystemMessage | None = None,799    middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),800    response_format: dict[str, Any],801    state_schema: type[AgentState[dict[str, Any]]] | None = None,802    context_schema: type[ContextT] | None = None,803    checkpointer: Checkpointer | None = None,804    store: BaseStore | None = None,805    interrupt_before: list[str] | None = None,806    interrupt_after: list[str] | None = None,807    debug: bool = False,808    name: str | None = None,809    cache: BaseCache[Any] | None = None,810    transformers: Sequence[TransformerFactory] | None = None,811) -> CompiledStateGraph[812    AgentState[dict[str, Any]], ContextT, InputAgentState, OutputAgentState[dict[str, Any]]813]: ...814815816# Schema-typed `response_format`: `ResponseT` is inferred from the schema/type.817@overload818def create_agent(819    model: str | BaseChatModel,820    tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,821    *,822    system_prompt: str | SystemMessage | None = None,823    middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),824    response_format: ResponseFormat[ResponseT] | type[ResponseT] | None = None,825    state_schema: type[AgentState[ResponseT]] | None = None,826    context_schema: type[ContextT] | None = None,827    checkpointer: Checkpointer | None = None,828    store: BaseStore | None = None,829    interrupt_before: list[str] | None = None,830    interrupt_after: list[str] | None = None,831    debug: bool = False,832    name: str | None = None,833    cache: BaseCache[Any] | None = None,834    transformers: Sequence[TransformerFactory] | None = None,835) -> CompiledStateGraph[836    AgentState[ResponseT], ContextT, InputAgentState, OutputAgentState[ResponseT]837]: ...838839840def create_agent(841    model: str | BaseChatModel,842    tools: Sequence[BaseTool | Callable[..., Any] | dict[str, Any]] | None = None,843    *,844    system_prompt: str | SystemMessage | None = None,845    middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),846    response_format: ResponseFormat[ResponseT] | type[ResponseT] | dict[str, Any] | None = None,847    state_schema: type[AgentState[ResponseT]] | None = None,848    context_schema: type[ContextT] | None = None,849    checkpointer: Checkpointer | None = None,850    store: BaseStore | None = None,851    interrupt_before: list[str] | None = None,852    interrupt_after: list[str] | None = None,853    debug: bool = False,854    name: str | None = None,855    cache: BaseCache[Any] | None = None,856    transformers: Sequence[TransformerFactory] | None = None,857) -> CompiledStateGraph[858    AgentState[ResponseT], ContextT, InputAgentState, OutputAgentState[ResponseT]859]:860    """Creates an agent graph that calls tools in a loop until a stopping condition is met.861862    For more details on using `create_agent`,863    visit the [Agents](https://docs.langchain.com/oss/python/langchain/agents) docs.864865    Args:866        model: The language model for the agent.867868            Can be a string identifier (e.g., `"openai:gpt-5.5"`) or a direct chat model869            instance (e.g., [`ChatOpenAI`][langchain_openai.ChatOpenAI] or other another870            [LangChain chat model](https://docs.langchain.com/oss/python/integrations/chat)).871872            For a full list of supported model strings, see873            [`init_chat_model`][langchain.chat_models.init_chat_model(model_provider)].874875            !!! tip ""876877                See the [Models](https://docs.langchain.com/oss/python/langchain/models)878                docs for more information.879        tools: A list of tools, `dict`, or `Callable`.880881            If `None` or an empty list, the agent will consist of a model node without a882            tool calling loop.883884885            !!! tip ""886887                See the [Tools](https://docs.langchain.com/oss/python/langchain/tools)888                docs for more information.889        system_prompt: An optional system prompt for the LLM.890891            Can be a `str` (which will be converted to a `SystemMessage`) or a892            `SystemMessage` instance directly. The system message is added to the893            beginning of the message list when calling the model.894        middleware: A sequence of middleware instances to apply to the agent.895896            Middleware can intercept and modify agent behavior at various stages.897898            !!! tip ""899900                See the [Middleware](https://docs.langchain.com/oss/python/langchain/middleware)901                docs for more information.902        response_format: An optional configuration for structured responses.903904            Can be a `ToolStrategy`, `ProviderStrategy`, or a Pydantic model class.905906            If provided, the agent will handle structured output during the907            conversation flow.908909            Raw schemas will be wrapped in an appropriate strategy based on model910            capabilities.911912            !!! tip ""913914                See the [Structured output](https://docs.langchain.com/oss/python/langchain/structured-output)915                docs for more information.916        state_schema: An optional `TypedDict` schema that extends `AgentState`.917918            When provided, this schema is used instead of `AgentState` as the base919            schema for merging with middleware state schemas. This allows users to920            add custom state fields without needing to create custom middleware.921922            Generally, it's recommended to use `state_schema` extensions via middleware923            to keep relevant extensions scoped to corresponding hooks / tools.924        context_schema: An optional schema for runtime context.925        checkpointer: An optional checkpoint saver object.926927            Used for persisting the state of the graph (e.g., as chat memory) for a928            single thread (e.g., a single conversation).929        store: An optional store object.930931            Used for persisting data across multiple threads (e.g., multiple932            conversations / users).933        interrupt_before: An optional list of node names to interrupt before.934935            Useful if you want to add a user confirmation or other interrupt936            before taking an action.937        interrupt_after: An optional list of node names to interrupt after.938939            Useful if you want to return directly or run additional processing940            on an output.941        debug: Whether to enable verbose logging for graph execution.942943            When enabled, prints detailed information about each node execution, state944            updates, and transitions during agent runtime. Useful for debugging945            middleware behavior and understanding agent execution flow.946        name: An optional name for the `CompiledStateGraph`.947948            This name will be automatically used when adding the agent graph to949            another graph as a subgraph node - particularly useful for building950            multi-agent systems.951        cache: An optional `BaseCache` instance to enable caching of graph execution.952        transformers: Optional sequence of scope-aware `StreamTransformer`953            factories to register on the compiled graph in addition to954            the agent defaults. Each factory is invoked as `factory(scope)`955            so every invocation receives a fresh instance. The final order956            on the compiled graph is: `ToolCallTransformer`, then any957            factories declared by middleware via958            `AgentMiddleware.transformers`, then any factories supplied here.959960    Returns:961        A compiled `StateGraph` that can be used for chat interactions.962963    Raises:964        AssertionError: If duplicate middleware instances are provided.965966    The agent node calls the language model with the messages list (after applying967    the system prompt). If the resulting [`AIMessage`][langchain.messages.AIMessage]968    contains `tool_calls`, the graph will then call the tools. The tools node executes969    the tools and adds the responses to the messages list as970    [`ToolMessage`][langchain.messages.ToolMessage] objects. The agent node then calls971    the language model again. The process repeats until no more `tool_calls` are present972    in the response. The agent then returns the full list of messages.973974    Example:975        ```python976        from langchain.agents import create_agent977978979        def check_weather(location: str) -> str:980            '''Return the weather forecast for the specified location.'''981            return f"It's always sunny in {location}"982983984        graph = create_agent(985            model="anthropic:claude-sonnet-4-5-20250929",986            tools=[check_weather],987            system_prompt="You are a helpful assistant",988        )989        inputs = {"messages": [{"role": "user", "content": "what is the weather in sf"}]}990        for chunk in graph.stream(inputs, stream_mode="updates"):991            print(chunk)992        ```993    """994    # init chat model995    if isinstance(model, str):996        model = init_chat_model(model)997998    # Convert system_prompt to SystemMessage if needed999    system_message: SystemMessage | None = None1000    if system_prompt is not None:1001        if isinstance(system_prompt, SystemMessage):1002            system_message = system_prompt1003        else:1004            system_message = SystemMessage(content=system_prompt)10051006    # Handle tools being None or empty1007    if tools is None:1008        tools = []10091010    # Convert response format and setup structured output tools1011    # Raw schemas are wrapped in AutoStrategy to preserve auto-detection intent.1012    # AutoStrategy is converted to ToolStrategy upfront to calculate tools during agent creation,1013    # but may be replaced with ProviderStrategy later based on model capabilities.1014    initial_response_format: ToolStrategy[Any] | ProviderStrategy[Any] | AutoStrategy[Any] | None1015    if response_format is None:1016        initial_response_format = None1017    elif isinstance(response_format, (ToolStrategy, ProviderStrategy, AutoStrategy)):1018        # Explicit Tool/Provider strategy, or AutoStrategy for later capability detection1019        initial_response_format = response_format1020    else:1021        # Raw schema - wrap in AutoStrategy to enable auto-detection1022        initial_response_format = AutoStrategy(schema=response_format)10231024    # For AutoStrategy, convert to ToolStrategy to setup tools upfront1025    # (may be replaced with ProviderStrategy later based on model)1026    tool_strategy_for_setup: ToolStrategy[Any] | None = None1027    if isinstance(initial_response_format, AutoStrategy):1028        tool_strategy_for_setup = ToolStrategy(schema=initial_response_format.schema)1029    elif isinstance(initial_response_format, ToolStrategy):1030        tool_strategy_for_setup = initial_response_format10311032    structured_output_tools: dict[str, OutputToolBinding[Any]] = {}1033    if tool_strategy_for_setup:1034        for response_schema in tool_strategy_for_setup.schema_specs:1035            structured_tool_info = OutputToolBinding.from_schema_spec(response_schema)1036            structured_output_tools[structured_tool_info.tool.name] = structured_tool_info1037    middleware_tools = [t for m in middleware for t in getattr(m, "tools", [])]10381039    # Collect middleware with wrap_tool_call or awrap_tool_call hooks1040    # Include middleware with either implementation to ensure NotImplementedError is raised1041    # when middleware doesn't support the execution path1042    middleware_w_wrap_tool_call = [1043        m1044        for m in middleware1045        if m.__class__.wrap_tool_call is not AgentMiddleware.wrap_tool_call1046        or m.__class__.awrap_tool_call is not AgentMiddleware.awrap_tool_call1047    ]10481049    # Chain all wrap_tool_call handlers into a single composed handler1050    wrap_tool_call_wrapper = None1051    if middleware_w_wrap_tool_call:1052        wrappers = [1053            traceable(name=f"{m.name}.wrap_tool_call", **_wrap_trace_kwargs(m))(m.wrap_tool_call)1054            for m in middleware_w_wrap_tool_call1055        ]1056        wrap_tool_call_wrapper = _chain_tool_call_wrappers(wrappers)10571058    # Collect middleware with awrap_tool_call or wrap_tool_call hooks1059    # Include middleware with either implementation to ensure NotImplementedError is raised1060    # when middleware doesn't support the execution path1061    middleware_w_awrap_tool_call = [1062        m1063        for m in middleware1064        if m.__class__.awrap_tool_call is not AgentMiddleware.awrap_tool_call1065        or m.__class__.wrap_tool_call is not AgentMiddleware.wrap_tool_call1066    ]10671068    # Chain all awrap_tool_call handlers into a single composed async handler1069    awrap_tool_call_wrapper = None1070    if middleware_w_awrap_tool_call:1071        async_wrappers = [1072            traceable(name=f"{m.name}.awrap_tool_call", **_wrap_trace_kwargs(m))(m.awrap_tool_call)1073            for m in middleware_w_awrap_tool_call1074        ]1075        awrap_tool_call_wrapper = _chain_async_tool_call_wrappers(async_wrappers)10761077    # Setup tools1078    tool_node: ToolNode | None = None1079    # Extract built-in provider tools (dict format) and regular tools (BaseTool/callables)1080    built_in_tools = [t for t in tools if isinstance(t, dict)]1081    regular_tools = [t for t in tools if not isinstance(t, dict)]10821083    # Tools that require client-side execution (must be in ToolNode)1084    available_tools = middleware_tools + regular_tools10851086    # Create ToolNode if we have client-side tools OR if middleware defines wrap_tool_call1087    # (which may handle dynamically registered tools)1088    tool_node = (1089        ToolNode(1090            tools=available_tools,1091            wrap_tool_call=wrap_tool_call_wrapper,1092            awrap_tool_call=awrap_tool_call_wrapper,1093        )1094        if available_tools or wrap_tool_call_wrapper or awrap_tool_call_wrapper1095        else None1096    )10971098    # Default tools for ModelRequest initialization1099    # Use converted BaseTool instances from ToolNode (not raw callables)1100    # Include built-ins and converted tools (can be changed dynamically by middleware)1101    # Structured tools are NOT included - they're added dynamically based on response_format1102    if tool_node:1103        default_tools = list(tool_node.tools_by_name.values()) + built_in_tools1104    else:1105        default_tools = list(built_in_tools)11061107    # validate middleware1108    if len({m.name for m in middleware}) != len(middleware):1109        msg = "Please remove duplicate middleware instances."1110        raise AssertionError(msg)1111    middleware_w_before_agent = [1112        m1113        for m in middleware1114        if m.__class__.before_agent is not AgentMiddleware.before_agent1115        or m.__class__.abefore_agent is not AgentMiddleware.abefore_agent1116    ]1117    middleware_w_before_model = [1118        m1119        for m in middleware1120        if m.__class__.before_model is not AgentMiddleware.before_model1121        or m.__class__.abefore_model is not AgentMiddleware.abefore_model1122    ]1123    middleware_w_after_model = [1124        m1125        for m in middleware1126        if m.__class__.after_model is not AgentMiddleware.after_model1127        or m.__class__.aafter_model is not AgentMiddleware.aafter_model1128    ]1129    middleware_w_after_agent = [1130        m1131        for m in middleware1132        if m.__class__.after_agent is not AgentMiddleware.after_agent1133        or m.__class__.aafter_agent is not AgentMiddleware.aafter_agent1134    ]1135    # Collect middleware with wrap_model_call or awrap_model_call hooks1136    # Include middleware with either implementation to ensure NotImplementedError is raised1137    # when middleware doesn't support the execution path1138    middleware_w_wrap_model_call = [1139        m1140        for m in middleware1141        if m.__class__.wrap_model_call is not AgentMiddleware.wrap_model_call1142        or m.__class__.awrap_model_call is not AgentMiddleware.awrap_model_call1143    ]1144    # Collect middleware with awrap_model_call or wrap_model_call hooks1145    # Include middleware with either implementation to ensure NotImplementedError is raised1146    # when middleware doesn't support the execution path1147    middleware_w_awrap_model_call = [1148        m1149        for m in middleware1150        if m.__class__.awrap_model_call is not AgentMiddleware.awrap_model_call1151        or m.__class__.wrap_model_call is not AgentMiddleware.wrap_model_call1152    ]11531154    # Compose wrap_model_call handlers into a single middleware stack (sync)1155    wrap_model_call_handler = None1156    if middleware_w_wrap_model_call:1157        sync_handlers = [1158            traceable(name=f"{m.name}.wrap_model_call", **_wrap_trace_kwargs(m))(m.wrap_model_call)1159            for m in middleware_w_wrap_model_call1160        ]1161        wrap_model_call_handler = _chain_model_call_handlers(sync_handlers)11621163    # Compose awrap_model_call handlers into a single middleware stack (async)1164    awrap_model_call_handler = None1165    if middleware_w_awrap_model_call:1166        async_handlers = [1167            traceable(name=f"{m.name}.awrap_model_call", **_wrap_trace_kwargs(m))(1168                m.awrap_model_call1169            )1170            for m in middleware_w_awrap_model_call1171        ]1172        awrap_model_call_handler = _chain_async_model_call_handlers(async_handlers)11731174    base_state = state_schema if state_schema is not None else AgentState1175    # Build an ordered list: middleware schemas first (in registration order),1176    # base_state last so it wins any field conflict.  This lets the caller's1177    # explicit state_schema override middleware annotations  e.g. passing1178    # a DeltaChannel-annotated schema wins over BinaryOperatorAggregate from1179    # AgentState without requiring a post-compilation patch.1180    state_schemas: list[type] = [*(m.state_schema for m in middleware), base_state]11811182    resolved_state_schema, input_schema, output_schema = _resolve_schemas(state_schemas)11831184    # create graph, add nodes1185    graph: StateGraph[1186        AgentState[ResponseT], ContextT, InputAgentState, OutputAgentState[ResponseT]1187    ] = StateGraph(1188        state_schema=resolved_state_schema,1189        input_schema=input_schema,1190        output_schema=output_schema,1191        context_schema=context_schema,1192    )11931194    def _handle_model_output(1195        output: AIMessage, effective_response_format: ResponseFormat[Any] | None1196    ) -> dict[str, Any]:1197        """Handle model output including structured responses.11981199        Args:1200            output: The AI message output from the model.1201            effective_response_format: The actual strategy used (may differ from initial1202                if auto-detected).1203        """1204        # Handle structured output with provider strategy1205        if isinstance(effective_response_format, ProviderStrategy):1206            if not output.tool_calls:1207                provider_strategy_binding = ProviderStrategyBinding.from_schema_spec(1208                    effective_response_format.schema_spec1209                )1210                try:1211                    structured_response = provider_strategy_binding.parse(output)1212                except Exception as exc:1213                    schema_name = getattr(1214                        effective_response_format.schema_spec.schema, "__name__", "response_format"1215                    )1216                    validation_error = StructuredOutputValidationError(schema_name, exc, output)1217                    raise validation_error from exc1218                else:1219                    return {"messages": [output], "structured_response": structured_response}1220            return {"messages": [output]}12211222        # Handle structured output with tool strategy1223        if (1224            isinstance(effective_response_format, ToolStrategy)1225            and isinstance(output, AIMessage)1226            and output.tool_calls1227        ):1228            structured_tool_calls = [1229                tc for tc in output.tool_calls if tc["name"] in structured_output_tools1230            ]12311232            if structured_tool_calls:1233                exception: StructuredOutputError | None = None1234                if len(structured_tool_calls) > 1:1235                    # Handle multiple structured outputs error1236                    tool_names = [tc["name"] for tc in structured_tool_calls]1237                    exception = MultipleStructuredOutputsError(tool_names, output)1238                    should_retry, error_message = _handle_structured_output_error(1239                        exception, effective_response_format1240                    )1241                    if not should_retry:1242                        raise exception12431244                    # Add error messages and retry1245                    tool_messages = [1246                        ToolMessage(1247                            content=error_message,1248                            tool_call_id=tc["id"],1249                            name=tc["name"],1250                        )1251                        for tc in structured_tool_calls1252                    ]1253                    return {"messages": [output, *tool_messages]}12541255                # Handle single structured output1256                tool_call = structured_tool_calls[0]1257                try:1258                    structured_tool_binding = structured_output_tools[tool_call["name"]]1259                    structured_response = structured_tool_binding.parse(tool_call["args"])12601261                    tool_message_content = (1262                        effective_response_format.tool_message_content1263                        or f"Returning structured response: {structured_response}"1264                    )12651266                    return {1267                        "messages": [1268                            output,1269                            ToolMessage(1270                                content=tool_message_content,1271                                tool_call_id=tool_call["id"],1272                                name=tool_call["name"],1273                            ),1274                        ],1275                        "structured_response": structured_response,1276                    }1277                except Exception as exc:1278                    exception = StructuredOutputValidationError(tool_call["name"], exc, output)1279                    should_retry, error_message = _handle_structured_output_error(1280                        exception, effective_response_format1281                    )1282                    if not should_retry:1283                        raise exception from exc12841285                    return {1286                        "messages": [1287                            output,1288                            ToolMessage(1289                                content=error_message,1290                                tool_call_id=tool_call["id"],1291                                name=tool_call["name"],1292                            ),1293                        ],1294                    }12951296        return {"messages": [output]}12971298    def _get_bound_model(1299        request: ModelRequest[ContextT],1300    ) -> tuple[Runnable[Any, Any], ResponseFormat[Any] | None]:1301        """Get the model with appropriate tool bindings.13021303        Performs auto-detection of strategy if needed based on model capabilities.13041305        Args:1306            request: The model request containing model, tools, and response format.13071308        Returns:1309            Tuple of `(bound_model, effective_response_format)` where1310            `effective_response_format` is the actual strategy used (may differ from1311            initial if auto-detected).13121313        Raises:1314            ValueError: If middleware returned unknown client-side tool names.1315            ValueError: If `ToolStrategy` specifies tools not declared upfront.1316        """1317        # Validate ONLY client-side tools that need to exist in tool_node1318        # Skip validation when wrap_tool_call is defined, as middleware may handle1319        # dynamic tools that are added at runtime via wrap_model_call1320        has_wrap_tool_call = wrap_tool_call_wrapper or awrap_tool_call_wrapper13211322        # Build map of available client-side tools from the ToolNode1323        # (which has already converted callables)1324        available_tools_by_name = {}1325        if tool_node:1326            available_tools_by_name = tool_node.tools_by_name.copy()13271328        # Check if any requested tools are unknown CLIENT-SIDE tools1329        # Only validate if wrap_tool_call is NOT defined (no dynamic tool handling)1330        if not has_wrap_tool_call:1331            unknown_tool_names = []1332            for t in request.tools:1333                # Only validate BaseTool instances (skip built-in dict tools)1334                if isinstance(t, dict):1335                    continue1336                if isinstance(t, BaseTool) and t.name not in available_tools_by_name:1337                    unknown_tool_names.append(t.name)13381339            if unknown_tool_names:1340                available_tool_names = sorted(available_tools_by_name.keys())1341                msg = DYNAMIC_TOOL_ERROR_TEMPLATE.format(1342                    unknown_tool_names=unknown_tool_names,1343                    available_tool_names=available_tool_names,1344                )1345                raise ValueError(msg)13461347        # Normalize raw schemas to AutoStrategy1348        # (handles middleware override with raw Pydantic classes)1349        response_format: ResponseFormat[Any] | Any | None = request.response_format1350        if response_format is not None and not isinstance(1351            response_format, (AutoStrategy, ToolStrategy, ProviderStrategy)1352        ):1353            response_format = AutoStrategy(schema=response_format)13541355        # Determine effective response format (auto-detect if needed)1356        effective_response_format: ResponseFormat[Any] | None1357        if isinstance(response_format, AutoStrategy):1358            # User provided raw schema via AutoStrategy - auto-detect best strategy based on model1359            if _supports_provider_strategy(request.model, tools=request.tools):1360                # Model supports provider strategy - use it1361                effective_response_format = ProviderStrategy(schema=response_format.schema)1362            elif response_format is initial_response_format and tool_strategy_for_setup is not None:1363                # Model doesn't support provider strategy - use ToolStrategy1364                # Reuse the strategy from setup if possible to preserve tool names1365                effective_response_format = tool_strategy_for_setup1366            else:1367                effective_response_format = ToolStrategy(schema=response_format.schema)1368        else:1369            # User explicitly specified a strategy - preserve it1370            effective_response_format = response_format13711372        # Build final tools list including structured output tools1373        # request.tools now only contains BaseTool instances (converted from callables)1374        # and dicts (built-ins)1375        final_tools = list(request.tools)1376        if isinstance(effective_response_format, ToolStrategy):1377            # Add structured output tools to final tools list, narrowed to only the1378            # schemas present in the (possibly middleware-narrowed) response format.1379            # This ensures middleware that narrows a union `ToolStrategy` to a subset1380            # via `request.override()` actually restricts which structured output1381            # tools the model can choose from.1382            narrowed_tool_names = {spec.name for spec in effective_response_format.schema_specs}1383            structured_tools = [1384                info.tool1385                for name, info in structured_output_tools.items()1386                if name in narrowed_tool_names1387            ]1388            final_tools.extend(structured_tools)13891390        # Bind model based on effective response format1391        if isinstance(effective_response_format, ProviderStrategy):1392            # (Backward compatibility) Use OpenAI format structured output1393            # Redundantly set strict=True on tools for OpenAI-compatible models, as older1394            # versions of langchain-openai do not auto-set it in bind_tools.1395            kwargs = effective_response_format.to_model_kwargs()1396            bind_kwargs: dict[str, Any] = {**kwargs, **request.model_settings}1397            if _is_openai_compatible_model(request.model) and not getattr(1398                request.model, "use_responses_api", False1399            ):1400                bind_kwargs["strict"] = True1401            return (1402                request.model.bind_tools(final_tools, **bind_kwargs),1403                effective_response_format,1404            )14051406        if isinstance(effective_response_format, ToolStrategy):1407            # Current implementation requires that tools used for structured output1408            # have to be declared upfront when creating the agent as part of the1409            # response format. Middleware is allowed to change the response format1410            # to a subset of the original structured tools when using ToolStrategy,1411            # but not to add new structured tools that weren't declared upfront.1412            # Compute output binding1413            for tc in effective_response_format.schema_specs:1414                if tc.name not in structured_output_tools:1415                    msg = (1416                        f"ToolStrategy specifies tool '{tc.name}' "1417                        "which wasn't declared in the original "1418                        "response format when creating the agent."1419                    )1420                    raise ValueError(msg)14211422            # Force tool use if we have structured output tools1423            tool_choice = "any" if structured_output_tools else request.tool_choice1424            return (1425                request.model.bind_tools(1426                    final_tools, tool_choice=tool_choice, **request.model_settings1427                ),1428                effective_response_format,1429            )14301431        # No structured output - standard model binding1432        if final_tools:1433            return (1434                request.model.bind_tools(1435                    final_tools, tool_choice=request.tool_choice, **request.model_settings1436                ),1437                None,1438            )1439        return request.model.bind(**request.model_settings), None14401441    def _execute_model_sync(request: ModelRequest[ContextT]) -> ModelResponse:1442        """Execute model and return response.14431444        This is the core model execution logic wrapped by `wrap_model_call` handlers.14451446        Raises any exceptions that occur during model invocation.1447        """1448        # Get the bound model (with auto-detection if needed)1449        model_, effective_response_format = _get_bound_model(request)1450        messages = request.messages1451        if request.system_message:1452            messages = [request.system_message, *messages]14531454        output = model_.invoke(messages)1455        if name:1456            output.name = name14571458        # Handle model output to get messages and structured_response1459        handled_output = _handle_model_output(output, effective_response_format)1460        messages_list = handled_output["messages"]1461        structured_response = handled_output.get("structured_response")14621463        return ModelResponse(1464            result=messages_list,1465            structured_response=structured_response,1466        )14671468    def model_node(state: AgentState[Any], runtime: Runtime[ContextT]) -> list[Command[Any]]:1469        """Sync model request handler with sequential middleware processing."""1470        request = ModelRequest(1471            model=model,1472            tools=default_tools,1473            system_message=system_message,1474            response_format=initial_response_format,1475            messages=state["messages"],1476            tool_choice=None,1477            state=state,1478            runtime=runtime,1479        )14801481        has_structured_output = initial_response_format is not None1482        if wrap_model_call_handler is None:1483            model_response = _execute_model_sync(request)1484            return _build_commands(model_response, has_structured_output=has_structured_output)14851486        result = wrap_model_call_handler(request, _execute_model_sync)1487        return _build_commands(1488            result.model_response, result.commands, has_structured_output=has_structured_output1489        )14901491    async def _execute_model_async(request: ModelRequest[ContextT]) -> ModelResponse:1492        """Execute model asynchronously and return response.14931494        This is the core async model execution logic wrapped by `wrap_model_call`1495        handlers.14961497        Raises any exceptions that occur during model invocation.1498        """1499        # Get the bound model (with auto-detection if needed)1500        model_, effective_response_format = _get_bound_model(request)1501        messages = request.messages1502        if request.system_message:1503            messages = [request.system_message, *messages]15041505        output = await model_.ainvoke(messages)1506        if name:1507            output.name = name15081509        # Handle model output to get messages and structured_response1510        handled_output = _handle_model_output(output, effective_response_format)1511        messages_list = handled_output["messages"]1512        structured_response = handled_output.get("structured_response")15131514        return ModelResponse(1515            result=messages_list,1516            structured_response=structured_response,1517        )15181519    async def amodel_node(state: AgentState[Any], runtime: Runtime[ContextT]) -> list[Command[Any]]:1520        """Async model request handler with sequential middleware processing."""1521        request = ModelRequest(1522            model=model,1523            tools=default_tools,1524            system_message=system_message,1525            response_format=initial_response_format,1526            messages=state["messages"],1527            tool_choice=None,1528            state=state,1529            runtime=runtime,1530        )15311532        has_structured_output = initial_response_format is not None1533        if awrap_model_call_handler is None:1534            model_response = await _execute_model_async(request)1535            return _build_commands(model_response, has_structured_output=has_structured_output)15361537        result = await awrap_model_call_handler(request, _execute_model_async)1538        return _build_commands(1539            result.model_response, result.commands, has_structured_output=has_structured_output1540        )15411542    # Use sync or async based on model capabilities1543    graph.add_node("model", RunnableCallable(model_node, amodel_node, trace=False))15441545    # Only add tools node if we have tools1546    if tool_node is not None:1547        graph.add_node("tools", tool_node)15481549    # Add middleware nodes1550    for m in middleware:1551        if (1552            m.__class__.before_agent is not AgentMiddleware.before_agent1553            or m.__class__.abefore_agent is not AgentMiddleware.abefore_agent1554        ):1555            # Use RunnableCallable to support both sync and async1556            # Pass None for sync if not overridden to avoid signature conflicts1557            sync_before_agent = (1558                m.before_agent1559                if m.__class__.before_agent is not AgentMiddleware.before_agent1560                else None1561            )1562            async_before_agent = (1563                m.abefore_agent1564                if m.__class__.abefore_agent is not AgentMiddleware.abefore_agent1565                else None1566            )1567            before_agent_node = RunnableCallable(sync_before_agent, async_before_agent, trace=False)1568            graph.add_node(1569                f"{m.name}.before_agent",1570                before_agent_node,1571                input_schema=resolved_state_schema,1572                trace_policy=_node_trace_policy(m.trace_policy),1573            )15741575        if (1576            m.__class__.before_model is not AgentMiddleware.before_model1577            or m.__class__.abefore_model is not AgentMiddleware.abefore_model1578        ):1579            # Use RunnableCallable to support both sync and async1580            # Pass None for sync if not overridden to avoid signature conflicts1581            sync_before = (1582                m.before_model1583                if m.__class__.before_model is not AgentMiddleware.before_model1584                else None1585            )1586            async_before = (1587                m.abefore_model1588                if m.__class__.abefore_model is not AgentMiddleware.abefore_model1589                else None1590            )1591            before_node = RunnableCallable(sync_before, async_before, trace=False)1592            graph.add_node(1593                f"{m.name}.before_model",1594                before_node,1595                input_schema=resolved_state_schema,1596                trace_policy=_node_trace_policy(m.trace_policy),1597            )15981599        if (1600            m.__class__.after_model is not AgentMiddleware.after_model1601            or m.__class__.aafter_model is not AgentMiddleware.aafter_model1602        ):1603            # Use RunnableCallable to support both sync and async1604            # Pass None for sync if not overridden to avoid signature conflicts1605            sync_after = (1606                m.after_model1607                if m.__class__.after_model is not AgentMiddleware.after_model1608                else None1609            )1610            async_after = (1611                m.aafter_model1612                if m.__class__.aafter_model is not AgentMiddleware.aafter_model1613                else None1614            )1615            after_node = RunnableCallable(sync_after, async_after, trace=False)1616            graph.add_node(1617                f"{m.name}.after_model",1618                after_node,1619                input_schema=resolved_state_schema,1620                trace_policy=_node_trace_policy(m.trace_policy),1621            )16221623        if (1624            m.__class__.after_agent is not AgentMiddleware.after_agent1625            or m.__class__.aafter_agent is not AgentMiddleware.aafter_agent1626        ):1627            # Use RunnableCallable to support both sync and async1628            # Pass None for sync if not overridden to avoid signature conflicts1629            sync_after_agent = (1630                m.after_agent1631                if m.__class__.after_agent is not AgentMiddleware.after_agent1632                else None1633            )1634            async_after_agent = (1635                m.aafter_agent1636                if m.__class__.aafter_agent is not AgentMiddleware.aafter_agent1637                else None1638            )1639            after_agent_node = RunnableCallable(sync_after_agent, async_after_agent, trace=False)1640            graph.add_node(1641                f"{m.name}.after_agent",1642                after_agent_node,1643                input_schema=resolved_state_schema,1644                trace_policy=_node_trace_policy(m.trace_policy),1645            )16461647    # Determine the entry node (runs once at start): before_agent -> before_model -> model1648    if middleware_w_before_agent:1649        entry_node = f"{middleware_w_before_agent[0].name}.before_agent"1650    elif middleware_w_before_model:1651        entry_node = f"{middleware_w_before_model[0].name}.before_model"1652    else:1653        entry_node = "model"16541655    # Determine the loop entry node (beginning of agent loop, excludes before_agent)1656    # This is where tools will loop back to for the next iteration1657    if middleware_w_before_model:1658        loop_entry_node = f"{middleware_w_before_model[0].name}.before_model"1659    else:1660        loop_entry_node = "model"16611662    # Determine the loop exit node (end of each iteration, can run multiple times)1663    # This is after_model or model, but NOT after_agent1664    if middleware_w_after_model:1665        loop_exit_node = f"{middleware_w_after_model[0].name}.after_model"1666    else:1667        loop_exit_node = "model"16681669    # Determine the exit node (runs once at end): after_agent or END1670    if middleware_w_after_agent:1671        exit_node = f"{middleware_w_after_agent[-1].name}.after_agent"1672    else:1673        exit_node = END16741675    graph.add_edge(START, entry_node)1676    # add conditional edges only if tools exist1677    if tool_node is not None:1678        # Only include exit_node in destinations if any tool has return_direct=True1679        # or if there are structured output tools1680        tools_to_model_destinations = [loop_entry_node]1681        if (1682            any(tool.return_direct for tool in tool_node.tools_by_name.values())1683            or structured_output_tools1684        ):1685            tools_to_model_destinations.append(exit_node)16861687        graph.add_conditional_edges(1688            "tools",1689            RunnableCallable(1690                _make_tools_to_model_edge(1691                    tool_node=tool_node,1692                    model_destination=loop_entry_node,1693                    structured_output_tools=structured_output_tools,1694                    end_destination=exit_node,1695                ),1696                trace=False,1697            ),1698            tools_to_model_destinations,1699        )17001701        # Include loop_entry_node when middleware can inject synthetic tool1702        # messages, or when structured output or after-model hooks can reroute there.1703        model_to_tools_destinations = ["tools", exit_node]1704        if response_format or loop_exit_node != "model" or middleware_w_wrap_model_call:1705            model_to_tools_destinations.append(loop_entry_node)17061707        graph.add_conditional_edges(1708            loop_exit_node,1709            RunnableCallable(1710                _make_model_to_tools_edge(1711                    model_destination=loop_entry_node,1712                    structured_output_tools=structured_output_tools,1713                    end_destination=exit_node,1714                ),1715                trace=False,1716            ),1717            model_to_tools_destinations,1718        )1719    elif len(structured_output_tools) > 0:1720        graph.add_conditional_edges(1721            loop_exit_node,1722            RunnableCallable(1723                _make_model_to_model_edge(1724                    model_destination=loop_entry_node,1725                    end_destination=exit_node,1726                ),1727                trace=False,1728            ),1729            [loop_entry_node, exit_node],1730        )1731    elif loop_exit_node == "model":1732        # If no tools and no after_model, go directly to exit_node1733        graph.add_edge(loop_exit_node, exit_node)1734    # No tools but we have after_model - connect after_model to exit_node1735    else:1736        _add_middleware_edge(1737            graph,1738            name=f"{middleware_w_after_model[0].name}.after_model",1739            default_destination=exit_node,1740            model_destination=loop_entry_node,1741            end_destination=exit_node,1742            can_jump_to=_get_can_jump_to(middleware_w_after_model[0], "after_model"),1743        )17441745    # Add before_agent middleware edges1746    if middleware_w_before_agent:1747        for m1, m2 in itertools.pairwise(middleware_w_before_agent):1748            _add_middleware_edge(1749                graph,1750                name=f"{m1.name}.before_agent",1751                default_destination=f"{m2.name}.before_agent",1752                model_destination=loop_entry_node,1753                end_destination=exit_node,1754                can_jump_to=_get_can_jump_to(m1, "before_agent"),1755            )1756        # Connect last before_agent to loop_entry_node (before_model or model)1757        _add_middleware_edge(1758            graph,1759            name=f"{middleware_w_before_agent[-1].name}.before_agent",1760            default_destination=loop_entry_node,1761            model_destination=loop_entry_node,1762            end_destination=exit_node,1763            can_jump_to=_get_can_jump_to(middleware_w_before_agent[-1], "before_agent"),1764        )17651766    # Add before_model middleware edges1767    if middleware_w_before_model:1768        for m1, m2 in itertools.pairwise(middleware_w_before_model):1769            _add_middleware_edge(1770                graph,1771                name=f"{m1.name}.before_model",1772                default_destination=f"{m2.name}.before_model",1773                model_destination=loop_entry_node,1774                end_destination=exit_node,1775                can_jump_to=_get_can_jump_to(m1, "before_model"),1776            )1777        # Go directly to model after the last before_model1778        _add_middleware_edge(1779            graph,1780            name=f"{middleware_w_before_model[-1].name}.before_model",1781            default_destination="model",1782            model_destination=loop_entry_node,1783            end_destination=exit_node,1784            can_jump_to=_get_can_jump_to(middleware_w_before_model[-1], "before_model"),1785        )17861787    # Add after_model middleware edges1788    if middleware_w_after_model:1789        graph.add_edge("model", f"{middleware_w_after_model[-1].name}.after_model")1790        for idx in range(len(middleware_w_after_model) - 1, 0, -1):1791            m1 = middleware_w_after_model[idx]1792            m2 = middleware_w_after_model[idx - 1]1793            _add_middleware_edge(1794                graph,1795                name=f"{m1.name}.after_model",1796                default_destination=f"{m2.name}.after_model",1797                model_destination=loop_entry_node,1798                end_destination=exit_node,1799                can_jump_to=_get_can_jump_to(m1, "after_model"),1800            )1801        # Note: Connection from after_model to after_agent/END is handled above1802        # in the conditional edges section18031804    # Add after_agent middleware edges1805    if middleware_w_after_agent:1806        # Chain after_agent middleware (runs once at the very end, before END)1807        for idx in range(len(middleware_w_after_agent) - 1, 0, -1):1808            m1 = middleware_w_after_agent[idx]1809            m2 = middleware_w_after_agent[idx - 1]1810            _add_middleware_edge(1811                graph,1812                name=f"{m1.name}.after_agent",1813                default_destination=f"{m2.name}.after_agent",1814                model_destination=loop_entry_node,1815                end_destination=exit_node,1816                can_jump_to=_get_can_jump_to(m1, "after_agent"),1817            )18181819        # Connect the last after_agent to END1820        _add_middleware_edge(1821            graph,1822            name=f"{middleware_w_after_agent[0].name}.after_agent",1823            default_destination=END,1824            model_destination=loop_entry_node,1825            end_destination=exit_node,1826            can_jump_to=_get_can_jump_to(middleware_w_after_agent[0], "after_agent"),1827        )18281829    # Set recursion limit to 9_9991830    # https://github.com/langchain-ai/langgraph/issues/73131831    config: RunnableConfig = {"recursion_limit": 9_999}1832    config["metadata"] = {"ls_integration": "langchain_create_agent"}1833    if name:1834        config["metadata"]["lc_agent_name"] = name18351836    # Middleware that makes internal model calls (e.g. `SummarizationMiddleware`)1837    # each declare `InternalCallTransformer` on their own `transformers` tuple so1838    # it's only registered when one of them is actually in use.1839    middleware_transformers = [t for m in middleware for t in getattr(m, "transformers", ())]18401841    return graph.compile(1842        checkpointer=checkpointer,1843        store=store,1844        interrupt_before=interrupt_before,1845        interrupt_after=interrupt_after,1846        debug=debug,1847        name=name,1848        cache=cache,1849        transformers=_dedupe_transformers(1850            [1851                ToolCallTransformer,1852                SubagentTransformer,1853                *middleware_transformers,1854                *(transformers or ()),1855            ]1856        ),1857    ).with_config(config)185818591860def _dedupe_transformers(1861    factories: Iterable[TransformerFactory],1862) -> list[TransformerFactory]:1863    """Order-preserving de-dup of transformer factories, by identity.18641865    `AgentMiddleware.transformers` accepts any scope-aware callable, and1866    callables aren't required to be hashable (e.g. a dataclass-based factory1867    with `__hash__ = None`), so this can't use a `set`/`dict` keyed on the1868    factories themselves. Combining several middleware that each declare the1869    same shared transformer class (e.g. `InternalCallTransformer`) would1870    otherwise register it once per middleware.18711872    Args:1873        factories: Transformer factories to de-dup, in registration order.18741875    Returns:1876        The same factories with exact repeats (by `is`) removed, order kept.1877    """1878    seen_ids: set[int] = set()1879    deduped: list[TransformerFactory] = []1880    for factory in factories:1881        if id(factory) not in seen_ids:1882            seen_ids.add(id(factory))1883            deduped.append(factory)1884    return deduped188518861887def _resolve_jump(1888    jump_to: JumpTo | None,1889    *,1890    model_destination: str,1891    end_destination: str,1892) -> str | None:1893    if jump_to == "model":1894        return model_destination1895    if jump_to == "end":1896        return end_destination1897    if jump_to == "tools":1898        return "tools"1899    return None190019011902def _fetch_last_ai_and_tool_messages(1903    messages: list[AnyMessage],1904) -> tuple[AIMessage | None, list[ToolMessage]]:1905    """Return the last AI message and any subsequent tool messages.19061907    Args:1908        messages: List of messages to search through.19091910    Returns:1911        A tuple of (last_ai_message, tool_messages). If no AIMessage is found,1912        returns (None, []). Callers must handle the None case appropriately.1913    """1914    for i in range(len(messages) - 1, -1, -1):1915        if isinstance(messages[i], AIMessage):1916            last_ai_message = cast("AIMessage", messages[i])1917            tool_messages = [m for m in messages[i + 1 :] if isinstance(m, ToolMessage)]1918            return last_ai_message, tool_messages19191920    return None, []192119221923def _make_model_to_tools_edge(1924    *,1925    model_destination: str,1926    structured_output_tools: dict[str, OutputToolBinding[Any]],1927    end_destination: str,1928) -> Callable[[dict[str, Any]], str | list[Send] | None]:1929    def model_to_tools(1930        state: dict[str, Any],1931    ) -> str | list[Send] | None:1932        # 1. If there's an explicit jump_to in the state, use it1933        if jump_to := state.get("jump_to"):1934            return _resolve_jump(1935                jump_to,1936                model_destination=model_destination,1937                end_destination=end_destination,1938            )19391940        last_ai_message, tool_messages = _fetch_last_ai_and_tool_messages(state["messages"])19411942        # 2. if no AIMessage exists (e.g., messages were cleared), exit the loop1943        if last_ai_message is None:1944            return end_destination19451946        tool_message_ids = [m.tool_call_id for m in tool_messages]19471948        # 3. If the model hasn't called any tools, exit the loop1949        # this is the classic exit condition for an agent loop1950        if len(last_ai_message.tool_calls) == 0:1951            return end_destination19521953        pending_tool_calls = [1954            c1955            for c in last_ai_message.tool_calls1956            if c["id"] not in tool_message_ids and c["name"] not in structured_output_tools1957        ]19581959        # 4. If there are pending tool calls, jump to the tool node.1960        # The tool node hydrates ToolRuntime.state from channels via1961        # CONFIG_KEY_READ at execution time, so we no longer inline the1962        # full state into each Send (previously O(N^2) in TASKS writes).1963        if pending_tool_calls:1964            return [Send("tools", [tool_call]) for tool_call in pending_tool_calls]19651966        # 5. If a fresh structured response was produced this call, exit the loop1967        if state.get("structured_response") is not None:1968            return end_destination19691970        # 6. AIMessage has tool calls, but there are no pending tool calls which suggests1971        # the injection of artificial tool messages. Jump to the model node1972        return model_destination19731974    return model_to_tools197519761977def _make_model_to_model_edge(1978    *,1979    model_destination: str,1980    end_destination: str,1981) -> Callable[[dict[str, Any]], str | list[Send] | None]:1982    def model_to_model(1983        state: dict[str, Any],1984    ) -> str | list[Send] | None:1985        # 1. Priority: Check for explicit jump_to directive from middleware1986        if jump_to := state.get("jump_to"):1987            return _resolve_jump(1988                jump_to,1989                model_destination=model_destination,1990                end_destination=end_destination,1991            )19921993        # 2. Exit condition: a fresh structured response was generated this call1994        if state.get("structured_response") is not None:1995            return end_destination19961997        # 3. Default: Continue the loop, there may have been an issue with structured1998        # output generation, so we need to retry1999        return model_destination

Code quality findings 72

Ensure try blocks have corresponding except or finally blocks
warning correctness try-without-except
try:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def wrap_tool_call(self, request, handler):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(req, (ModelRequest, ToolCallRequest)):
Avoid complex 'lambda' functions; prefer named functions for clarity and debugging
info maintainability complex-lambda
"process_inputs": lambda inputs: process_inputs(_scrub_inputs(inputs)),
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(result, AIMessage):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(result, ExtendedModelResponse):
Avoid unnecessary list conversions; use generators where possible
info performance unnecessary-list
commands: list[Command[Any]] = list(extra_commands or [])
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(result, _ComposedExtendedModelResponse):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
elif isinstance(result, ExtendedModelResponse):
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def normalized_single(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def compose_two(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def composed(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def inner_handler(req: ModelRequest[ContextT]) -> ModelResponse:
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(inner_result, _ComposedExtendedModelResponse):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(inner_result, ExtendedModelResponse):
Avoid unnecessary list conversions; use generators where possible
info performance unnecessary-list
commands: list[Command[Any]] = list(extra_commands or [])
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(result, _ComposedExtendedModelResponse):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
elif isinstance(result, ExtendedModelResponse):
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def normalized_single(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def compose_two(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def composed(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def inner_handler(req: ModelRequest[ContextT]) -> ModelResponse:
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(inner_result, _ComposedExtendedModelResponse):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(inner_result, ExtendedModelResponse):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(meta, OmitFromSchema) and getattr(meta, omit_flag) is True:
Avoid unnecessary list conversions; use generators where possible
info performance unnecessary-list
return list(get_args(inner_type)[1:])
Avoid unnecessary list conversions; use generators where possible
info performance unnecessary-list
return list(get_args(type_)[1:])
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(model, str):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
elif isinstance(model, BaseChatModel):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
and isinstance(model_name, str)
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
return isinstance(model, base_chat_openai.BaseChatOpenAI)
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if not isinstance(response_format, ToolStrategy):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(handle_errors, str):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(handle_errors, type):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if issubclass(handle_errors, Exception) and isinstance(exception, handle_errors):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(handle_errors, tuple):
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def composed(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def call_inner(req: ToolCallRequest) -> ToolMessage | Command[Any]:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def compose_two(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def composed(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
async def call_inner(req: ToolCallRequest) -> ToolMessage | Command[Any]:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def create_agent(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def create_agent(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def create_agent(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def create_agent(
Use logging module for better control and configurability
info maintainability print-statement
print(chunk)
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(model, str):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(system_prompt, SystemMessage):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
elif isinstance(response_format, (ToolStrategy, ProviderStrategy, AutoStrategy)):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(initial_response_format, AutoStrategy):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
elif isinstance(initial_response_format, ToolStrategy):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
built_in_tools = [t for t in tools if isinstance(t, dict)]
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
regular_tools = [t for t in tools if not isinstance(t, dict)]
Avoid unnecessary list conversions; use generators where possible
info performance unnecessary-list
default_tools = list(tool_node.tools_by_name.values()) + built_in_tools
Avoid unnecessary list conversions; use generators where possible
info performance unnecessary-list
default_tools = list(built_in_tools)
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(effective_response_format, ProviderStrategy):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
isinstance(effective_response_format, ToolStrategy)
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
and isinstance(output, AIMessage)
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(t, dict):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(t, BaseTool) and t.name not in available_tools_by_name:
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if response_format is not None and not isinstance(
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(response_format, AutoStrategy):
Avoid unnecessary list conversions; use generators where possible
info performance unnecessary-list
final_tools = list(request.tools)
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(effective_response_format, ToolStrategy):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(effective_response_format, ProviderStrategy):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(effective_response_format, ToolStrategy):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
if isinstance(messages[i], AIMessage):
Overuse may indicate design issues; consider polymorphism
info maintainability isinstance-overuse
tool_messages = [m for m in messages[i + 1 :] if isinstance(m, ToolMessage)]
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def model_to_tools(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def model_to_model(
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def tools_to_model(state: dict[str, Any]) -> str | None:
Ensure functions have docstrings for documentation
info maintainability missing-docstring
def jump_edge(state: dict[str, Any]) -> str:

Get this view in your editor

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