本页目录
Middleware / 中间件Note 88

第二章:`wrap_model_call`

wrapmodelcall 是包住模型调用的 middleware:你可以选择调用 handler(request),也可以不调用它直接返回。它解决的是模型层控制问题,比如改模型请求、统计调用、fallback、缓存命中、短路返回。

wrap_model_call 是包住模型调用的 middleware:你可以选择调用 handler(request),也可以不调用它直接返回。它解决的是模型层控制问题,比如改模型请求、统计调用、fallback、缓存命中、短路返回。

最小代码

代码在 deepagent_src/middleware_teach/02_wrap_model_call.py

@wrap_model_call
def guard_and_rewrite_model(request, handler):
    user_text = request.state["messages"][-1].text
    if "skip-model" in user_text:
        return ModelResponse(result=[AIMessage(content="SHORT_CIRCUIT_OK")])

    response = handler(request)
    model_text = response.result[0].text
    return ModelResponse(result=[AIMessage(content=f"WRAPPED:{model_text}")])

正常路径会调用 handler(request),所以模型真的执行;短路路径直接返回 ModelResponse,所以模型不会执行。

运行命令

uv run python -m deepagent_src.middleware_teach.02_wrap_model_call

预期现象

normal: WRAPPED:MODEL_OK
skipped: SHORT_CIRCUIT_OK
handler_calls: 1
wrap_model_call local check ok

handler_calls 是 1,说明两次 agent 调用里只有正常路径真正进了模型;skip-model 路径被 middleware 短路了。

常见误区

wrap_model_call 不是 before_model 的增强版。before_model 返回状态更新;wrap_model_call 返回模型响应,可以决定是否调用模型。艹,这两个搞混,代码就会一会儿像 LangGraph state update,一会儿像模型 response,迟早炸。

和 Deep Agents 的关系

Deep Agents 的动态模型路由、provider fallback、部分安全策略都适合放在 wrap_model_call 这一层,因为它刚好包住“模型将要被调用”的瞬间。前面学过的 08_model_capability_routing.py 就是同一机制:读取 request.runtime.context,再通过 request.override(model=...) 替换本轮使用的模型。

相关资源

  • 查看示例代码:deepagent_src/middleware_teach/02_wrap_model_call.py
    from __future__ import annotations
    
    from collections.abc import Callable
    
    from langchain.agents import create_agent
    from langchain.agents.middleware import ModelRequest, ModelResponse, wrap_model_call
    from langchain_core.language_models.fake_chat_models import FakeListChatModel
    from langchain_core.messages import AIMessage
    
    handler_calls = 0
    
    
    @wrap_model_call(name="guard_and_rewrite_model")
    def guard_and_rewrite_model(
        request: ModelRequest,
        handler: Callable[[ModelRequest], ModelResponse],
    ) -> ModelResponse:
        global handler_calls
    
        user_text = request.state["messages"][-1].text
        if "skip-model" in user_text:
            return ModelResponse(result=[AIMessage(content="SHORT_CIRCUIT_OK")])
    
        handler_calls += 1
        response = handler(request)
        model_text = response.result[0].text
        return ModelResponse(result=[AIMessage(content=f"WRAPPED:{model_text}")])
    
    
    def main() -> None:
        global handler_calls
        handler_calls = 0
    
        agent = create_agent(
            model=FakeListChatModel(responses=["MODEL_OK"]),
            tools=[],
            middleware=[guard_and_rewrite_model],
        )
    
        normal_state = agent.invoke(
            {"messages": [{"role": "user", "content": "call model"}]}
        )
        skipped_state = agent.invoke(
            {"messages": [{"role": "user", "content": "skip-model"}]}
        )
    
        normal_text = normal_state["messages"][-1].text
        skipped_text = skipped_state["messages"][-1].text
    
        print("normal:", normal_text)
        print("skipped:", skipped_text)
        print("handler_calls:", handler_calls)
    
        assert normal_text == "WRAPPED:MODEL_OK", normal_text
        assert skipped_text == "SHORT_CIRCUIT_OK", skipped_text
        assert handler_calls == 1, handler_calls
        print("wrap_model_call local check ok")
    
    
    if __name__ == "__main__":
        main()