OpenClaw 上下文瘦身:3 个实验

这篇不是讲"提示词怎么写得更优雅"。我只看一个更硬的问题:Agent 跑久以后,上下文到底是怎么胖起来的,哪一刀最值得先砍。实验脚本和结果都放在本地目录里,可以复跑。

你大概见过这种故障:

Agent 前 10 分钟很听话,工具也能正常调。跑到后面,开始变慢、重复解释、忘记刚才的发布规则,甚至把已经验证过的 URL 又验证一遍。

很多人第一反应是"模型不稳"。

我现在更倾向于先怀疑上下文管线。模型只是消费上下文的人,真正把它喂撑的,通常是这三类东西:

  • 全量历史每轮重发
  • 工具结果原样塞回对话
  • 工具 schema 和说明越写越长

我用一个很小的 Python 脚本跑了 3 个实验。不是线上 API benchmark,也不拿它证明某个模型强弱;它只回答一个工程问题:在 Agent 系统里,哪些上下文最容易失控。

实验目录:experiments/run_experiments.py

什么是 OpenClaw 上下文工程

OpenClaw 上下文工程,指的是在多工具、多轮、多 agent 的运行过程中,控制"发给模型的输入材料":历史消息、工具 schema、工具返回值、规则锚点、摘要状态和验证证据。

它不是单纯缩短 prompt。更准确地说,它是一套预算系统:哪些内容必须每轮出现,哪些内容只保留摘要,哪些内容应该写入文件或 receipt,而不是继续塞进下一轮对话。

实验环境:只测 payload,不测模型能力

为了避免把话说大,我把实验限定得很窄:

  • Python 3 标准库
  • json.dumps(..., ensure_ascii=False) 序列化
  • UTF-8 bytes 作为 payload 大小指标
  • 固定随机种子 519
  • 合成 40 轮 Agent 对话,每轮包含用户请求、工具结果、助手回复

这里没有调用线上模型,所以我不会写"某模型实测提升多少"。我测的是输入材料本身的体积变化。

完整实验骨架如下:

python 复制代码
import json, random, string

random.seed(519)

def cjk(n: int) -> str:
    words = "上下文 工具调用 文件搜索 发布脚本 运行日志 失败重试 摘要 压缩 预算 规则 锚点 验证 响应 状态 证据 结构化 输出".split()
    s = []
    while len("".join(s)) < n:
        s.append(random.choice(words))
        s.append(",")
    return "".join(s)[:n]


def make_turn(i: int):
    tool = {
        "tool": "search_files",
        "args": {"pattern": "context|token|publish", "path": f"/repo/module_{i % 7}"},
        "matches": [
            {
                "path": f"src/module_{i % 7}/file_{j}.ts",
                "line": 20 + j,
                "content": cjk(90),
                "score": round(random.random(), 4),
                "mtime": "2026-05-19T09:00:00+08:00",
                "debug": {"rank": j, "trace": "x" * 48},
            }
            for j in range(10)
        ],
    }
    return [
        {"role": "user", "content": f"第 {i} 轮:请继续定位上下文膨胀问题。"},
        {"role": "tool", "name": "search_files", "content": json.dumps(tool, ensure_ascii=False)},
        {"role": "assistant", "content": cjk(420)},
    ]

这个构造不复杂,但很接近真实 Agent 的痛点:文件搜索、日志检索、发布脚本输出,都很容易返回一大坨结构化文本。

实验 1:全量历史不是慢慢变贵,是迟早把你拖死

我比较了三种历史策略:

轮数 全量历史 bytes 最近 6 轮 bytes 摘要 + 最近 6 轮 bytes
1 6,829 6,984 7,197
5 34,155 34,310 34,523
10 68,313 41,145 41,358
20 136,632 41,146 41,359
40 273,269 41,145 41,358

这里有个反直觉点:前 5 轮,全量历史看起来没什么问题,甚至比"摘要 + 窗口"更小一点。因为摘要和规则锚点本身也占空间。

但从第 10 轮开始,差距就拉开了。

到第 40 轮,全量重放已经到 273,269 bytes ;最近 6 轮策略稳定在 41,145 bytes 左右。也就是说,在这个合成场景里,滑动窗口把历史 payload 压到了全量的约 15.1%

这就是很多 Agent 故障"不在开头暴露"的原因。

系统刚启动时,你怎么写都行。历史还短,工具结果还少,规则还在上下文前面。等它跑到第 20 轮、第 40 轮,规则被挤远、工具日志开始堆积、模型要在一堆旧证据里找当前任务。它不是突然变笨,是你给它的输入变脏了。

最小修复代码很短:

python 复制代码
CRITICAL = """关键规则:
1) 缺证据不下结论。
2) 工具结果先摘要再回灌。
3) 发布后必须打开公开页验证。
"""


def build_messages(turns, keep=6, summary=""):
    recent = turns[-keep * 3:]
    messages = [{"role": "system", "content": CRITICAL}]
    if summary:
        messages.append({"role": "system", "content": "会话摘要:" + summary})
    messages.extend(recent)
    return messages

我不建议一上来就做复杂记忆系统。先做 3 件事就够了:

  • 关键规则每轮重复,短一点
  • 最近 N 轮完整保留
  • 更早的内容只保留摘要和 receipt 路径

在 OpenClaw 这种会跨渠道、跨 agent、跨 cron 跑的系统里,receipt 路径很重要。比如发布结果、截图、日志,不要全塞进对话;写入文件,再把文件路径和 2 行摘要放回上下文。

实验 2:工具结果原样回灌,是最容易砍的一刀

第二个实验更直接:构造 80 条 search match,每条带 path、line、content、before、after、score、mtime、sha、debug trace。然后做一个紧凑版,只保留:

  • 总匹配数
  • top 8
  • path / line / score
  • 45 字 snippet

结果:

工具结果版本 bytes 相对原始体积
原始 JSON 98,387 100%
摘要 JSON 2,043 2.1%
减少比例 - 97.9%

这个结果比我预期更夸张。

原因不是 JSON 本身"坏",而是工具结果里混了太多对下一步决策没用的字段。mtimeshadebug.trace、完整上下文行,在定位工具自身 bug 时有价值;但在让 Agent 决定"下一步读哪个文件"时,它们大多是噪声。

我现在更喜欢把工具分成两层输出:

python 复制代码
def compact_search_result(raw: dict, top_k: int = 8) -> dict:
    matches = raw.get("matches", [])
    matches = sorted(matches, key=lambda x: x.get("score", 0), reverse=True)
    return {
        "query": raw.get("query"),
        "match_count": len(matches),
        "top_matches": [
            {
                "path": m["path"],
                "line": m["line"],
                "score": round(m.get("score", 0), 3),
                "snippet": m.get("content", "")[:80],
            }
            for m in matches[:top_k]
        ],
    }

如果下一步真的需要完整证据,再让 Agent 读文件或打开日志。不要因为"可能有用",就把所有字段每轮都带着跑。

这里有个实际经验:很多失败不是"缺信息",而是"信息太多但没有优先级"。模型看到 80 条 match,每条都有很多字段,它会尝试做阅读理解;你给它 top 8 + score + snippet,它更像是在做决策。

实验 3:schema 也会胖,而且胖得很安静

第三个实验测工具 schema。每个工具包含 name、description、6 个参数说明。工具数从 6 增加到 100。

工具数量 schema bytes Python JSON parse p50 p95
6 11,771 0.015 ms 0.031 ms
12 23,533 0.029 ms 0.043 ms
24 47,065 0.053 ms 0.064 ms
60 117,661 0.131 ms 0.169 ms
100 196,101 0.227 ms 0.269 ms

别误会,parse 时间不是重点。Python 本地解析这点 JSON 很快。

真正的问题是:这些 schema 最终会变成模型输入。100 个工具的说明,在这个构造里已经接近 196KB。如果每轮都带上,再叠加历史和工具结果,你的上下文预算会被"静态说明书"吃掉一大块。

这也是为什么我不喜欢给一个 agent 挂 50 个"可能会用到"的工具。工具越多,不只是选择更难,输入也更脏。

更稳的做法是按任务装载工具:

python 复制代码
TOOLSETS = {
    "publish": ["read_file", "write_file", "browser_publish", "screenshot"],
    "debug": ["read_file", "search_files", "run_command", "process_log"],
    "research": ["web_search", "web_fetch", "note_save"],
}


def select_tools(intent: str) -> list[str]:
    if "发布" in intent or "CSDN" in intent or "掘金" in intent:
        return TOOLSETS["publish"]
    if "报错" in intent or "日志" in intent or "定位" in intent:
        return TOOLSETS["debug"]
    return TOOLSETS["research"]

这段代码很粗糙,但方向对:先缩工具面,再让模型选工具。

我更推荐把"工具选择"前置成路由问题,而不是把所有工具摊开让模型自己挑。尤其是 OpenClaw 这种多 agent 场景,writing-studiocoderbrainpublisher 本来就应该有不同工具边界。

一个可落地的上下文预算表

我会把 Agent 输入拆成 5 个桶,每个桶给一个粗预算。这里的数字不是行业标准,是这次实验后我自己的默认值:

输入桶 建议预算 超预算时怎么处理
关键规则 1-2KB 只留不可破坏的规则,删解释
当前任务 2-6KB 让用户意图保持原文,不要摘要过度
最近历史 30-50KB 滑动窗口,保留最近 4-8 轮
工具结果 单次 2-8KB top_k + snippet + 证据路径
工具 schema 10-60KB 按任务装载,不全量挂载

如果你只能改一处,我建议先改"工具结果"。

原因很简单:它收益最大、风险最小。历史摘要可能会丢细节,工具裁剪则可以保留原始文件路径,需要时再读。你不是删除证据,只是不把证据全文塞进下一轮。

我会怎么改一个真实 Agent

拿一个发布型 agent 举例。它需要写文章、生成封面、发 CSDN、发掘金、验证公开页。最容易出问题的地方不是写作,而是发布后状态回灌。

错误做法:

python 复制代码
# 不要这样:把整段浏览器日志、HTML、Network 输出全部塞回对话
messages.append({
    "role": "tool",
    "content": full_browser_log + full_html + full_network_trace,
})

更好的做法:

python 复制代码
from pathlib import Path
import json


def save_receipt(platform: str, payload: dict, run_dir: str) -> dict:
    path = Path(run_dir) / "receipts" / f"{platform}.json"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    return {
        "platform": platform,
        "status": payload.get("status"),
        "url": payload.get("url"),
        "receipt_path": str(path),
        "evidence": payload.get("evidence", [])[:3],
    }

对 Agent 来说,下一轮只需要知道:成功没、URL 是什么、证据在哪。如果它要复盘,再读 receipt 文件。

这个模式还有一个好处:人也更容易审计。你不用在一大段对话里找"到底发没发成功",直接看 receipts。

非显而易见的发现:摘要不是越早越好

实验 1 里,第 1 轮和第 5 轮,"摘要 + 最近 6 轮"比全量历史略大。

这说明一个小坑:别把摘要当成银弹。对短会话,摘要本身也是负担。真正该做的是阈值触发:

  • 低于 6 轮:不摘要
  • 超过 6 轮:滑动窗口
  • 出现跨阶段任务:生成阶段摘要
  • 发布、部署、删除这类动作:写 receipt,不靠记忆

伪代码如下:

python 复制代码
def should_summarize(turn_count: int, stage_changed: bool, payload_bytes: int) -> bool:
    if stage_changed:
        return True
    if turn_count <= 6:
        return False
    return payload_bytes > 50_000

这个判断比"每 5 轮自动摘要一次"更实用。因为真正的切点不是轮数,而是阶段和 payload。

OpenClaw 场景里的 4 条规则

我现在会给 OpenClaw agent 加这 4 条默认规则:

  1. 工具结果默认摘要,完整原文写文件
  2. 每个 agent 只挂当前任务需要的工具集
  3. 发布、部署、外部副作用必须写 receipt
  4. 最终状态必须二次验证,不能只信脚本返回

第 4 条尤其重要。发布脚本返回 success,只能说明"提交动作完成";公开页是否可见、是否审核通过,还要打开 URL 看一次。

这不是洁癖,是为了防 phantom publish。自动化最危险的不是失败,而是假成功。

常见问题

Q: 为什么不用 token 计数,而用 bytes? A: 这次实验只想测 payload 体积变化,不比较模型。bytes 可以稳定复现,也不依赖具体 tokenizer。真实上线时,应该把 bytes 和模型 tokenizer 计数都打进日志。

Q: 工具结果摘要会不会漏掉关键证据? A: 会,所以不要只保留摘要。正确做法是"摘要进上下文,原文进文件"。Agent 需要细节时再按路径读取。这样既保留证据,又不污染每一轮输入。

Q: OpenClaw Agent 最先该优化哪里? A: 先看 receipts 和工具日志。如果一次工具调用返回超过 20KB,却只用其中 3 行做决策,就先做 compaction。比改 prompt 更快见效。

结论:别先调模型,先管输入

这 3 个小实验给我的结论很朴素:Agent 变慢、变飘、重复工作,很多时候不是"智商问题",而是输入治理问题。

全量历史会线性膨胀;工具结果原样回灌最浪费;schema 数量太多会安静吃掉预算。

我自己后面会把这套规则直接放进 OpenClaw 写作和发布 agent:工具结果先压缩,关键动作写 receipt,公开页再验证。等这些基础管住了,再讨论换模型、换路由、换工作流,才有意义。

相关推荐
冰凌时空7 小时前
30 Apps 第 2 天:待办清单 App —— MVVM + Combine 响应式 UI
ios·openai·ai编程
冰凌时空7 小时前
手写 Swift 运行时:objc_msgSend 的汇编级解析
ios·openai·ai编程
子兮曰7 小时前
AI Coding 为什么全选了 TUI?从 Claude Code 到 Codex CLI,终端架构的底层逻辑
前端·后端·ai编程
JiaHao汤7 小时前
深入理解 Claude Code 规则体系.md
ai编程
AI原来如此7 小时前
[特殊字符]2026AI Agent入门学习路径
学习·ai·大模型·ai编程
小坏讲微服务8 小时前
SpringBoot整合SpringAI配置多平台API密钥
java·人工智能·spring boot·后端·flask·ai编程·claude code
暗不需求8 小时前
深入浅出 LangChain Memory:从无状态到有记忆的智能对话
面试·langchain·ai编程
han_8 小时前
如何寻找、安装和管理 AI Skill?
人工智能·ai编程·claude
政采云技术9 小时前
如何从0到1开发一个AI Agent
agent·ai编程