返回资源广场

Skills 资源 / 技能包

configuring-conversation-compaction

配置摘要中间件、把默认高触发阈值调到当堂可见,让长对话在撞上下文窗口前先被压缩,并看清压缩前后各发生了什么。Use when 长程智能体对话越积越长、要自定义压缩触发阈值与保留条数、想给摘要单独配一个更便宜的模型、排查压缩死活不触发、卸载写文件报只读文件系统、或保留条数与配置值对不上时。涵盖中间件配置写法、阈值量纲与近似计数、触发确认判据、工具参数截断、执行顺序与主动压缩工具;不含压缩造成的信息丢失回归检查(见 auditing-compaction-information-loss)。

SKILL.md 技能文档

能力目标

把默认「几乎永远够不着」的压缩阈值调成你自己的值,让长对话在可控的点位上被压缩;能确认压缩真的发生过(而不是凭感觉)、读到生成的摘要、算清这次压缩的真实开销,并知道截断与摘要的执行先后。

前置

  • 已能构造智能体(见 bootstrapping-deepagents-env)。

  • 一个先决事实:压缩不是你开启的,装具入口默认就挂了摘要中间件(主智能体与通用子智能体各一件),只是默认触发阈值高达十几万 token,短对话永远触达不到。你要做的不是「加上它」,而是把阈值调小并用自己的实例覆盖默认那件。

  • 验证安装:

    python -c "import deepagents; from deepagents.middleware.summarization import SummarizationMiddleware; print('ok')"
    

实操流程

  1. 写一个可复用的构造函数,把摘要中间件配好并覆盖默认实例:

    from deepagents import create_deep_agent
    from deepagents.middleware.summarization import SummarizationMiddleware
    from deepagents.backends import FilesystemBackend
    
    def make_agent_with_summarization(workspace_path, trigger_tokens=800, keep_messages=4):
        model = get_model()
        backend = FilesystemBackend(root_dir=workspace_path, virtual_mode=True)
    
        mw = SummarizationMiddleware(
            model=model,
            backend=backend,
            trigger=("tokens", trigger_tokens),   # 触发阈值:单位 + 数值
            keep=("messages", keep_messages),     # 压缩后保留最近几条原始消息
            truncate_args_settings={
                "trigger": ("messages", 100),
                "keep": ("messages", 20),
            },
        )
        agent = create_deep_agent(
            model=model,
            middleware=[mw],                       # 用自定义实例覆盖默认高阈值中间件
            system_prompt="你是一个有记忆的助理,需要记录并能随时调用之前对话中的术语。",
        )
        return agent, model, backend, mw
    

    触发阈值与保留条数都是「单位 + 数值」二元组,单位可取 token 数或消息条数;后端决定压缩前的原文落盘位置,给无损找回留后路。

  2. 定阈值前先认清计数口径。框架内部用快速的字符比例估算,与厂商实际计费的 token 数有差距,中文尤其被低估。实测同一份内容近似估算 675、厂商实报输入 1261、实报总计 1341,差约一半。结论:别把近似值当精确触发点,给阈值留足余量、并接受触发点会有抖动。

  3. 喂足够长的对话真触发一次压缩,然后确认它真的发生了。判据是压缩事件对象从空变成一个含三字段的记录:在第几条消息处切开、生成的摘要消息、原文落盘路径。压缩事件是私有状态字段,不会出现在调用返回的状态里,所以有两条观测路径:

    • 生产代码里:检查返回消息数是否骤降、或去文件系统里找那个落盘文件。
    • 要看内部值时:按「判断是否该压缩 → 定切点 → 生成摘要」的顺序直接调中间件的内部方法跑一次,把三个字段打出来。
  4. 读一遍生成的摘要,看清它是什么。摘要有固定的四节结构:会话意图、摘要正文、产物(其中嵌入原文落盘路径)、后续步骤。压缩是模型做的抽取式改写、不是截断删除——这也正是信息会丢的根源,默认摘要提示词明确要求「只保留最相关的信息」。

  5. 算这次压缩的账。每次压缩都是一次额外的真实模型调用,可以给摘要模型单独挂回调统计用量:

    from langchain_core.callbacks import BaseCallbackHandler
    
    class LLMCallTracker(BaseCallbackHandler):
        def on_llm_end(self, response, **kwargs):
            usage = response.llm_output.get("token_usage", {})
            self.total_input_tokens  += usage.get("prompt_tokens", 0)
            self.total_output_tokens += usage.get("completion_tokens", 0)
    
    summary_model = ChatOpenAI(model="deepseek-chat", base_url="...", api_key=...,
                               callbacks=[LLMCallTracker("summary-model")])
    mw = SummarizationMiddleware(model=summary_model, backend=backend,
                                 trigger=("tokens", 800), keep=("messages", 4))
    

    换一个更小更便宜的摘要模型,就是换这个 model 参数。

  6. 需要更轻量的省法时,单独用工具参数截断。它只动写文件与编辑文件两个工具的超长字符串参数、不调模型,且有自己独立的、比摘要更低的阈值:

    mw = SummarizationMiddleware(
        model=model, backend=backend,
        trigger=("tokens", 9999),              # 摘要阈值设高、本轮不触发摘要
        truncate_args_settings={
            "trigger": ("messages", 2),
            "keep": ("messages", 2),
            "max_length": 2000,                # 超过这个字符数的字符串参数才截
            "truncation_text": "...(argument truncated)",
        },
    )
    
  7. 想让模型自己决定何时压缩(比如任务切换时),就用摘要工具中间件。它不直接接模型与后端,必须先建一个摘要中间件再包进去,复用同一套摘要引擎与同一个压缩事件状态:

    SummarizationToolMiddleware(summarization=summ_mw)
    

校验回路

  • 压缩确认三件套:压缩事件非空且带切点下标、消息数从压缩前的规模骤降到「1 条摘要 + 保留的若干条」、落盘目录下出现对应的会话原文文件。任一为空就是没触发,加长对话或继续降阈值,直到它非空。
  • 压缩比:用真实 token 数算压缩前后的比值,实测一次为 3.70 倍(2604 降到 703、消息 40 条降到 5 条)。这个数可以反过来指导你调阈值。
  • 截断生效的判据:超长参数被压成「前若干字符 + 截断标记」,而工具名、调用 ID、消息结构原样保留;同时非目标工具的大参数不受影响。
  • 主动压缩工具的接口确认:中间件建好后其工具列表含一个名为 compact_conversation 的工具。

常见陷阱

  • 压缩死活不触发:对话太短或阈值太高。加长对话、降阈值,并记住近似计数会低估、真实触发点比预设更早。
  • 后端没开虚拟模式:原文落盘会试图写到系统根路径、报只读文件系统错误。直接用磁盘后端并开启虚拟模式即可,不要再套一层组合后端包装,否则路径会双重叠加。
  • 保留条数与配置值对不上:这是正常现象。切点若正好落在一条工具消息上,框架会回溯找到它配对的那条模型消息,宁可多压一点也不留下孤立的工具结果——因为厂商接口会拒收找不到对应调用的工具结果。
  • 以为三条路径是三个并列开关:每次模型调用走的是固定流水线:先做最轻量的参数截断,截断后再判断要不要摘要,最后执行调用;若仍然超窗则兜底触发一次摘要再重试。所以光靠截断就省够了的那一轮,会直接跳过摘要。
  • 只看单次压缩的账就断言亏了:实测单次压缩名义省下 1345 token,而摘要本身花了 5670 token,单次净亏。收益在之后每一轮:历史缩短后每轮请求都按更短的历史发送、省下的量随轮数累加。判断要不要压、压多频,本质是看剩余对话还有多长。
  • 直接调用主动压缩工具:脱离智能体运行时上下文调用会报运行时字段缺失的校验错误,必须在完整会话内由模型调用。它还有一道资格门:实报用量达到自动触发阈值的一半才允许压缩,防止在空会话上瞎压。
  • 把它和大工具结果卸载混为一谈:摘要是把旧对话历史交给模型凝练、有损;卸载是把单条大工具结果整段搬进文件、无损可回读。两者的执行顺序是卸载先行、摘要兜底。

适用范围与前置条件

  • 已能构造智能体(见 bootstrapping-deepagents-env)。

  • 一个先决事实:压缩不是你开启的,装具入口默认就挂了摘要中间件(主智能体与通用子智能体各一件),只是默认触发阈值高达十几万 token,短对话永远触达不到。你要做的不是「加上它」,而是把阈值调小并用自己的实例覆盖默认那件。

  • 验证安装:

    python -c "import deepagents; from deepagents.middleware.summarization import SummarizationMiddleware; print('ok')"
    

怎么使用

使用步骤

  1. 写一个可复用的构造函数,把摘要中间件配好并覆盖默认实例:

    from deepagents import create_deep_agent
    from deepagents.middleware.summarization import SummarizationMiddleware
    from deepagents.backends import FilesystemBackend
    
    def make_agent_with_summarization(workspace_path, trigger_tokens=800, keep_messages=4):
        model = get_model()
        backend = FilesystemBackend(root_dir=workspace_path, virtual_mode=True)
    
        mw = SummarizationMiddleware(
            model=model,
            backend=backend,
            trigger=("tokens", trigger_tokens),   # 触发阈值:单位 + 数值
            keep=("messages", keep_messages),     # 压缩后保留最近几条原始消息
            truncate_args_settings={
                "trigger": ("messages", 100),
                "keep": ("messages", 20),
            },
        )
        agent = create_deep_agent(
            model=model,
            middleware=[mw],                       # 用自定义实例覆盖默认高阈值中间件
            system_prompt="你是一个有记忆的助理,需要记录并能随时调用之前对话中的术语。",
        )
        return agent, model, backend, mw
    

    触发阈值与保留条数都是「单位 + 数值」二元组,单位可取 token 数或消息条数;后端决定压缩前的原文落盘位置,给无损找回留后路。

  2. 定阈值前先认清计数口径。框架内部用快速的字符比例估算,与厂商实际计费的 token 数有差距,中文尤其被低估。实测同一份内容近似估算 675、厂商实报输入 1261、实报总计 1341,差约一半。结论:别把近似值当精确触发点,给阈值留足余量、并接受触发点会有抖动。

  3. 喂足够长的对话真触发一次压缩,然后确认它真的发生了。判据是压缩事件对象从空变成一个含三字段的记录:在第几条消息处切开、生成的摘要消息、原文落盘路径。压缩事件是私有状态字段,不会出现在调用返回的状态里,所以有两条观测路径:

    • 生产代码里:检查返回消息数是否骤降、或去文件系统里找那个落盘文件。
    • 要看内部值时:按「判断是否该压缩 → 定切点 → 生成摘要」的顺序直接调中间件的内部方法跑一次,把三个字段打出来。
  4. 读一遍生成的摘要,看清它是什么。摘要有固定的四节结构:会话意图、摘要正文、产物(其中嵌入原文落盘路径)、后续步骤。压缩是模型做的抽取式改写、不是截断删除——这也正是信息会丢的根源,默认摘要提示词明确要求「只保留最相关的信息」。

  5. 算这次压缩的账。每次压缩都是一次额外的真实模型调用,可以给摘要模型单独挂回调统计用量:

    from langchain_core.callbacks import BaseCallbackHandler
    
    class LLMCallTracker(BaseCallbackHandler):
        def on_llm_end(self, response, **kwargs):
            usage = response.llm_output.get("token_usage", {})
            self.total_input_tokens  += usage.get("prompt_tokens", 0)
            self.total_output_tokens += usage.get("completion_tokens", 0)
    
    summary_model = ChatOpenAI(model="deepseek-chat", base_url="...", api_key=...,
                               callbacks=[LLMCallTracker("summary-model")])
    mw = SummarizationMiddleware(model=summary_model, backend=backend,
                                 trigger=("tokens", 800), keep=("messages", 4))
    

    换一个更小更便宜的摘要模型,就是换这个 model 参数。

  6. 需要更轻量的省法时,单独用工具参数截断。它只动写文件与编辑文件两个工具的超长字符串参数、不调模型,且有自己独立的、比摘要更低的阈值:

    mw = SummarizationMiddleware(
        model=model, backend=backend,
        trigger=("tokens", 9999),              # 摘要阈值设高、本轮不触发摘要
        truncate_args_settings={
            "trigger": ("messages", 2),
            "keep": ("messages", 2),
            "max_length": 2000,                # 超过这个字符数的字符串参数才截
            "truncation_text": "...(argument truncated)",
        },
    )
    
  7. 想让模型自己决定何时压缩(比如任务切换时),就用摘要工具中间件。它不直接接模型与后端,必须先建一个摘要中间件再包进去,复用同一套摘要引擎与同一个压缩事件状态:

    SummarizationToolMiddleware(summarization=summ_mw)
    

继续探索

全部资源
Skills 资源 / 技能包

bootstrapping-deepagents-env

在一台干净机器上装好 DeepAgents 运行环境、接入一个 OpenAI 兼容大模型凭证,并跑通第一个 create_deep_agent 工具调用闭环。Use when 需要初始化 DeepAgents 开发环境、系统 Python 版本不达标装不上包、不确定装到了哪个版本、接 DeepSeek 之类国产模型报 ImportError 或 404 这类环境层故障时。涵盖解释器版本核对、虚拟环境置备、主包与提供方包安装、版本核验、凭证注入、最小示例验收;不含 Agent 各项能力的用法(见 tracking-task-progress-with-todos 等能力型 skill)。

Skills 资源 / 技能包

inspecting-agent-graph-and-tools

把一个 create_deep_agent 建出来的智能体拆开看:列出执行图节点、列出实际挂载的工具、捕获框架预装的中间件清单、抓取每轮真正发给模型的工具集。Use when 需要确认某项能力是否真的挂上了、排查「我的工具去哪了 / 这些工具哪来的 / 内置工具到底几个」、验证自定义中间件是否进了图、或要在改配置前后做结构对照时。涵盖图节点自省、工具清单反查、中间件清单捕获、编译期与运行期工具集差异;不含具体能力的用法。

Skills 资源 / 技能包

tracking-task-progress-with-todos

让智能体把多步任务拆成结构化待办清单写进状态,并从调用结果里取出清单、渲染成实时进度、兜底检测「勾完清单却没给答案」的失败形态。Use when 需要给长任务做进度面板、想稳定触发 write_todos、发现规划没被触发、或要把 todos 推给前端 UI 与日志时。涵盖稳定触发写法、取清单的两条路径、三态进度渲染、失败形态检测;不含子任务委派(见 delegating-subtasks-to-subagents)。