返回文章列表

Deep agents

21 min read
Deep agents

https://zhuanlan.zhihu.com/p/1979938537802597911

[Agent Loop:从 for 循环到自治系统的进化之路]

Agent Harness

Backends

Subagents

Protocols

Human-in-the-Loop

记忆

中间件(Middleware)

langchain/agents/middleware/types.py,基类 AgentMiddleware(L380)定义了以下钩子方法:

基类的这些方法都是空实现(直接返回 None),子类按需 override。EvoScientist 的 EvoMemoryMiddleware 就是继承这个基类,只重写了需要的钩子。

此外,LangChain 还提供了装饰器形式的快捷方式,让你不用写完整的类:

# 用装饰器快速创建一个只有 before_model 的 middleware
@before_model
def log_before(state, runtime):
    print(f"Messages: {len(state['messages'])}")

agent = create_agent(model, middleware=[log_before])

这些装饰器(@before_model@before_agent@after_model@after_agent@dynamic_prompt@wrap_model_call@wrap_tool_call)内部会动态 type() 创建一个 AgentMiddleware 子类并实例化,本质上和写 class 继承是等价的。

六大生命周期钩子

LangChain 1.0 的 Middleware 系统提供了 两类六种钩子,覆盖了 Agent Loop 中每一个关键节点:

Node-style 钩子

before_agentafter_agent:在整个 Agent 调用的开始和结束各执行一次,适合做初始化(如加载用户偏好)和清理(如持久化最终状态)。

before_model:在每次 LLM 调用之前执行,可以完全访问和修改 Agent 状态。这是 Context Engineering 的主战场——你可以在这里做消息历史摘要、Token 计数检查、动态注入上下文,甚至直接跳转到其他节点来中断循环。

after_model:在模型返回之后、工具执行之前执行。适合做输出验证、安全护栏、Human-in-the-Loop 审批。值得注意的是,after_model 钩子以反向顺序执行——最后添加的 Middleware 最先看到模型输出。这是一种经典的分层策略模式。

Wrap-style 钩子

wrap_model_call:包裹整个模型调用过程。你可以决定调用零次(短路)、一次(正常流程)、或多次(重试逻辑)。例如:

from langchain.agents.middleware import wrap_model_call

@wrap_model_call
def retry_on_failure(request, handler):
    for attempt in range(3):
        try:
            return handler(request)
        except Exception as e:
            if attempt == 2:
                raise
            print(f"Retry {attempt + 1}/3: {e}")

wrap_tool_call:包裹单个工具调用。适合实现工具级别的缓存、监控、速率限制、错误处理。例如对相同参数的工具调用做结果缓存:

from langchain.agents.middleware import wrap_tool_call

cache = {}

@wrap_tool_call
def cache_tool_results(request, handler):
    key = f"{request.tool.name}:{json.dumps(request.toolCall.args)}"
    if key in cache:
        return cache[key]
    result = handler(request)
    cache[key] = result
    return result

还有一个特别的便捷钩子

modify_model_request:这个钩子在 before_model 之后、实际模型调用之前执行。与 before_model 不同的是,它对状态的修改不会持久化——只影响当前这一次请求。这非常适合做临时性的 Prompt 注入、工具列表过滤或模型切换,而不污染全局状态。

完整的执行顺序

当你注册多个 Middleware 时,执行顺序如下:

image

这种"进去顺序执行、出来反向执行"的模式,与 Express.js、Django 的中间件机制完全同构。

13 个内置 Middleware

LangChain v1 提供了 13 个开箱即用的 Middleware 实现,覆盖了生产环境中最常见的需求:

Middleware使用的钩子功能
SummarizationMiddlewarebefore_model当消息历史接近 Token 上限时自动摘要
HumanInTheLoopMiddlewareafter_model对指定工具调用要求人工审批
ContextEditingMiddlewarebefore_model超限时清除旧的工具输出
TodoListMiddlewarebefore_model + 工具注入注入 write_todos 计划工具
ModelFallbackMiddlewarewrap_model_call主模型失败时自动切换备用模型
ToolRetryMiddlewarewrap_tool_call工具失败时指数退避重试
ToolCallLimitMiddlewarewrap_tool_call限制工具调用频率
ModelCallLimitMiddlewarewrap_model_call限制模型调用次数
ShellToolMiddleware工具注入 + wrap_tool_call提供持久化 Shell 会话
LLMToolSelectorMiddlewarebefore_model用 LLM 动态筛选相关工具
AnthropicPromptCachingMiddlewaremodify_model_request缓存系统提示和工具定义
PIIRedactionMiddlewarebefore_model用模式匹配脱敏 PII 数据
LLMToolEmulatorMiddlewarewrap_tool_call用 LLM 模拟工具执行

这些 Middleware 可以像乐高积木一样任意组合。例如,一个典型的生产级配置可能是:

from langchain.agents import create_agent
from langchain.agents.middleware import (
    SummarizationMiddleware,
    HumanInTheLoopMiddleware,
    ToolRetryMiddleware,
    ModelFallbackMiddleware,
)

agent = create_agent(
    model="anthropic:claude-sonnet-4-20250514",
    tools=[search, execute_code, send_email],
    middleware=[
        SummarizationMiddleware(token_threshold=100_000),
        ToolRetryMiddleware(max_retries=3, backoff_factor=2.0),
        ModelFallbackMiddleware(fallback_models=["openai:gpt-4o"]),
        HumanInTheLoopMiddleware(
            interrupt_on={"send_email": True, "execute_code": True}
        ),
    ],
)