langchain - ✅(Solved) Fix @wrap_model_call: async functions fail type checking (missing async Protocol overload) [4 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#35244Fetched 2026-04-08 00:26:59
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2labeled ×1

wrap_model_call correctly handles async functions at runtime (checks iscoroutinefunction internally), but its type overloads only reference _CallableReturningModelResponse — a sync-only Protocol:

class _CallableReturningModelResponse(Protocol[StateT_contra, ContextT]):
    def __call__(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelCallResult: ...

An async function returns Coroutine[..., ModelCallResult], which doesn't satisfy this sync Protocol.

Error Message

Error Message and Stack Trace

error[invalid-argument-type]: Argument is incorrect mypy produces an equivalent error about incompatible callable signatures.

Root Cause

wrap_model_call correctly handles async functions at runtime (checks iscoroutinefunction internally), but its type overloads only reference _CallableReturningModelResponse — a sync-only Protocol:

class _CallableReturningModelResponse(Protocol[StateT_contra, ContextT]):
    def __call__(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelCallResult: ...

An async function returns Coroutine[..., ModelCallResult], which doesn't satisfy this sync Protocol.

Fix Action

Fixed

PR fix notes

PR #35267: chore(langchain): add async overload variants for wrap_model_call and wrap_tool_call

Description (problem / solution / changelog)

Description: Add async wrap_model_call/wrap_tool_call Protocol overloads and typing tests so async decorators type‑check correctly, with no runtime changes.

Fixes: #35244

Changed files

  • libs/langchain_v1/langchain/agents/middleware/types.py (modified, +73/-16)
  • libs/langchain_v1/tests/unit_tests/agents/middleware_typing/test_middleware_typing.py (modified, +30/-2)

PR #34584: chore(langchain): improve typing of agent decorators

Description (problem / solution / changelog)

  • Fix typing of async functions for @wrap_model_call and @wrap_tool_call
  • Use type aliases instead of protocols. This is better regarding open/close principle: this way the user can use any names for the function parameters, only the position is important.
  • Remove useless generics
  • Use cast when mypy can't narrow the type (iscoroutinefunction doesn't narrow the callables, and the types in the unions are too complex for a TypeGuard to work)

related: https://github.com/langchain-ai/langchain/pull/34583 https://github.com/langchain-ai/langchain/pull/34249

Fixes #35244

Changed files

  • libs/langchain_v1/langchain/agents/middleware/types.py (modified, +86/-87)

Code Example

from langchain.agents.middleware import wrap_model_call, AgentState

class MyState(AgentState):
    pass

@wrap_model_call(state_schema=MyState)
async def add_cache_control(request, handler):
    return await handler(request)

---

error[invalid-argument-type]: Argument is incorrect
  --> src/graph.py:101:1
    |
101 | @wrap_model_call(state_schema=MyState)
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected `_CallableReturningModelResponse[MyState, None]`,
    |                                         found `def add_cache_control(request, handler) -> CoroutineType[Any, Any, Unknown]`

---

class _CallableReturningModelResponse(Protocol[StateT_contra, ContextT]):
    def __call__(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelCallResult: ...

---

class _AsyncCallableReturningModelResponse(Protocol[StateT_contra, ContextT]):
    async def __call__(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
    ) -> ModelCallResult: ...

@overload
def wrap_model_call(
    func: _AsyncCallableReturningModelResponse[StateT, ContextT],
) -> AgentMiddleware[StateT, ContextT]: ...

@overload
def wrap_model_call(
    func: None = None,
    *,
    state_schema: type[StateT] | None = None,
    tools: list[BaseTool] | None = None,
    name: str | None = None,
) -> Callable[
    [_CallableReturningModelResponse[StateT, ContextT] | _AsyncCallableReturningModelResponse[StateT, ContextT]],
    AgentMiddleware[StateT, ContextT],
]: ...

---

langchain==1.0.0a14
Python 3.12
ty 0.0.1a12 (Astral)
RAW_BUFFERClick to expand / collapse

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangChain documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.

Example Code

from langchain.agents.middleware import wrap_model_call, AgentState

class MyState(AgentState):
    pass

@wrap_model_call(state_schema=MyState)
async def add_cache_control(request, handler):
    return await handler(request)

Error Message and Stack Trace

Using ty (Astral's type checker):

error[invalid-argument-type]: Argument is incorrect
  --> src/graph.py:101:1
    |
101 | @wrap_model_call(state_schema=MyState)
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected `_CallableReturningModelResponse[MyState, None]`,
    |                                         found `def add_cache_control(request, handler) -> CoroutineType[Any, Any, Unknown]`

mypy produces an equivalent error about incompatible callable signatures.

Description

wrap_model_call correctly handles async functions at runtime (checks iscoroutinefunction internally), but its type overloads only reference _CallableReturningModelResponse — a sync-only Protocol:

class _CallableReturningModelResponse(Protocol[StateT_contra, ContextT]):
    def __call__(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelCallResult: ...

An async function returns Coroutine[..., ModelCallResult], which doesn't satisfy this sync Protocol.

Suggested Fix

Add an async Protocol variant and a third overload:

class _AsyncCallableReturningModelResponse(Protocol[StateT_contra, ContextT]):
    async def __call__(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
    ) -> ModelCallResult: ...

@overload
def wrap_model_call(
    func: _AsyncCallableReturningModelResponse[StateT, ContextT],
) -> AgentMiddleware[StateT, ContextT]: ...

@overload
def wrap_model_call(
    func: None = None,
    *,
    state_schema: type[StateT] | None = None,
    tools: list[BaseTool] | None = None,
    name: str | None = None,
) -> Callable[
    [_CallableReturningModelResponse[StateT, ContextT] | _AsyncCallableReturningModelResponse[StateT, ContextT]],
    AgentMiddleware[StateT, ContextT],
]: ...

This is the same pattern applied in LangGraph's functional API fix: langchain-ai/langgraph#4140 → PR #6126.

The same issue likely affects @before_agent and @after_agent decorators too.

System Info

langchain==1.0.0a14
Python 3.12
ty 0.0.1a12 (Astral)

extent analysis

Fix Plan

Add async Protocol variant and third overload

Update the langchain code to include the suggested fix:

class _AsyncCallableReturningModelResponse(Protocol[StateT_contra, ContextT]):
    async def __call__(
        self,
        request: ModelRequest,
        handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
    ) -> ModelCallResult: ...

@overload
def wrap_model_call(
    func: _AsyncCallableReturningModelResponse[StateT, ContextT],
) -> AgentMiddleware[StateT, ContextT]: ...

@overload
def wrap_model_call(
    func: None = None,
    *,
    state_schema: type[StateT] | None = None,
    tools: list[BaseTool] | None = None,
    name: str | None = None,
) -> Callable[
    [_CallableReturningModelResponse[StateT, ContextT] | _AsyncCallableReturningModelResponse[StateT, ContextT]],
    AgentMiddleware[StateT, ContextT],
]: ...

Update wrap_model_call to handle async functions

Update the wrap_model_call function to correctly handle async functions:

@wrap_model_call(state_schema=MyState)
async def add_cache_control(request, handler):
    return await handler(request)

Verify the fix

  1. Run ty and mypy again to ensure the errors are resolved.
  2. Test the add_cache_control function to ensure it works as expected.

Extra Tips

  • Make sure to update the langchain code to include the suggested fix.
  • Verify the fix by running ty and mypy again and testing the add_cache_control function.
  • This fix should also resolve the issue with @before_agent and @after_agent decorators.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

langchain - ✅(Solved) Fix @wrap_model_call: async functions fail type checking (missing async Protocol overload) [4 pull requests, 1 participants]