第08章:中间件(7)

3.8 ContextEditingMiddleware中间件

1.实验组-启用上下文编辑

上下文编辑中间件,该中间件提供了上下文管理的一种方式。

通过更改发送给模型的消息列表来控制成本。

注意:不会更改消息列表。因此我们只能通过token用量来推测是否对消息列表进行了裁剪。

from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.agents.middleware import (
    ContextEditingMiddleware,
    ClearToolUsesEdit,
)
from langchain.messages import HumanMessage, AIMessage
from langgraph.checkpoint.memory import InMemorySaver
from langchain.tools import tool


load_dotenv()


count = 0


@tool
def get_weather(city: str):
    """查询指定城市天气"""
    global count

    return (
        f"当前是第 {count} 次调用工具,{city}今天天气晴朗"
        f"天气非常好,北风,非常适合出行,盼望着,盼望着,"
        f"春天来了。我喜欢春天,你喜欢吗,天气真的很不错"
        f"万里无云,天气晴朗,春和景明,哈哈哈哈哈哈,这是凑字数的"
        f"真不错,天气非常好,适合出行,这里 token 挺多的"
        f"可以出门玩,可以跑步,钓鱼,爬山,一切都很好哈哈哈"
    )


agent = create_agent(
    model="deepseek:deepseek-v4-pro",
    tools=[get_weather],
    middleware=[
        ContextEditingMiddleware(
            edits=[
                ClearToolUsesEdit(
                    trigger=50,
                    keep=0,
                ),
            ],
        ),
    ],
    checkpointer=InMemorySaver(),
)


config = {
    "configurable": {
        "thread_id": "1",
    }
}


for i in range(3):
    print(
        "=" * 30,
        f"当前是第 {i + 1} 轮调用",
        "=" * 30,
    )

    count = i + 1

    response = agent.invoke(
        {
            "messages": [
                HumanMessage(
                    f"第 {i + 1} 次询问:"
                    "今天北京天气如何,一句话回答"
                )
            ]
        },
        config=config,
    )

    print("---- 本次返回的 messages ----")

    for msg in response["messages"]:
        # msg.pretty_print()

        if isinstance(msg, AIMessage):
            if not msg.tool_calls:
                print(f"本次 token 用量:{msg.usage_metadata}")

说明

ContextEditingMiddleware 的价值:大模型多轮对话时,如果频繁调用产生大量文本的工具 (如代码执行、网页爬取),历史记录会急剧膨胀。这个中间件就像一个“上下文抽脂手术”,在不 影响当前对话的前提下,自动在后台删掉之前沉淀的工具调用废话,从而极大地节省 Token 费用 并防止超出模型最大上下文窗口(Context Window)。

InMemorySaver :它在内存中开辟了一个空间。第二轮和第三轮提问时,Agent 能通过 thread_id 自动找回前几轮的记忆。

2. 对照组-不裁剪上下文

agent = create_agent(
    model="deepseek:deepseek-v4-pro",
    tools=[get_weather],
    middleware=[],
    checkpointer=InMemorySaver(),
)

可以观察到,对照组的input_tokens明显大于实验组。

3.9 FilesystemFileSearchMiddleware中间件

基于系统的Glob和Grep检索工具,为Agent赋予本地文件搜索和分析的能力。

Glob根据文件路径检索

Grep根据文件内容检索

from langchain.agents import create_agent
from langchain.agents.middleware import FilesystemFileSearchMiddleware
from langchain.messages import HumanMessage
from dotenv import load_dotenv
load_dotenv(verbose=True)
agent = create_agent(
    model="deepseek:deepseek-v4-pro",
    tools=[],  # 自动添加 Glob 和 Grep 工具
    middleware=[
        FilesystemFileSearchMiddleware(
            root_path="../todo_workspace",  # 搜索目录
            # 【可选】限制搜索的文件后缀,防止模型读取非代码或无关文件
            # allowed_extensions=[".py", ".ipynb", ".js", ".md"],  # 允许的文件类型
            # 是否启用 ripgrep 搜索引擎:
            # 设为 True 可以获得比原生 Grep 更快的性能(前提是系统已安装 ripgrep)
            use_ripgrep=True,
            # 单个文件的最大读取限制(单位 MB):
            # 防止读取超大型日志或二进制文件导致 OOM
            max_file_size_mb=10,
        ),
    ],
)

result = agent.invoke({
    "messages": [
        HumanMessage("找到包含 add 函数的 Python 或 Jupyter 文件")
    ]
})

for msg in result["messages"]:
    msg.pretty_print()

4、多个中间件组合及执行顺序

问题:Middleware 可以叠加使用,那么多个中间件书写顺序重要吗?

非常重要!比如:

middleware=[
    TrimmerMiddleware(),          # 1. 先修剪消息
    SummarizationMiddleware(),    # 2. 再摘要
    LoggingMiddleware(),          # 3. 最后记录日志
]

比如:

agent = create_agent(
    model=model,
    tools=[get_weather, get_news],
    middleware=[
        PIIMiddleware(strategy="redact"),  # 1. 最先检查 PII
        ModelCallLimitMiddleware(run_limit=10),  # 2. 限制调用次数
        SummarizationMiddleware(max_tokens_before_summary=500),  # 3. 总结历史
        ToolRetryMiddleware(max_retries=3),  # 4. 重试工具
    ],
)

举例:

from langchain.agents.middleware import AgentMiddleware


class Middleware1(AgentMiddleware):
    def before_model(self, state, runtime):
        print("[中间件1] before_model")
        return None

    def after_model(self, state, runtime):
        print("[中间件1] after_model")
        return None


class Middleware2(AgentMiddleware):
    def before_model(self, state, runtime):
        print("[中间件2] before_model")
        return None

    def after_model(self, state, runtime):
        print("[中间件2] after_model")
        return None


class Middleware3(AgentMiddleware):
    def before_model(self, state, runtime):
        print("[中间件3] before_model")
        return None

    def after_model(self, state, runtime):
        print("[中间件3] after_model")
        return None


agent = create_agent(
    model="deepseek:deepseek-v4-pro",
    tools=[],
    middleware=[
        Middleware1(),
        Middleware2(),
        Middleware3(),
    ],
)

print("\n执行一次调用,观察顺序:")

agent.invoke({
    "messages": [
        {
            "role": "user",
            "content": "测试",
        }
    ]
})

print("\n关键点:")
print(" - before_model: 正序执行(1→2→3)")
print(" - after_model: 逆序执行(3→2→1)")
print(" - 类似洋葱模型:1→2→3→模型→3→2→1")


执行一次调用,观察顺序:
[中间件1] before_model
[中间件2] before_model
[中间件3] before_model
[中间件3] after_model
[中间件2] after_model
[中间件1] after_model

关键点:
 - before_model: 正序执行(1→2→3)
 - after_model: 逆序执行(3→2→1)
 - 类似洋葱模型:1→2→3→模型→3→2→1

3. 分析

  • Middleware1.before_model ↓ 正序
  • Middleware2.before_model
  • Middleware3.before_model
  • [模型调用]
  • Middleware3.after_model ↑ 逆序
  • Middleware2.after_model
  • Middleware1.after_model

类似洋葱模型:外层先进后出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端 贾公子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值