WeClaw_87|19K tokens 的「你好」:我们把系统提示词减掉了 80%,还顺手让一篇论文的措辞名副其实
系列文章第 87 篇 - 提示词预算 · 锚点切分 · 按意图注入 · 整模块丢弃 · 灰度回退
📚 专栏信息
《从零到一构建跨平台 AI 助手:WeClaw 实战指南》专栏
专栏定位:面向开发者和技术决策者的实战专栏,用真实案例和完整代码带你理解如何构建生产级 AI 应用
本文记录一次「给系统提示词减肥」的完整战役。用户说一句「你好」,模型要先读完 19K tokens 的股票操作纪律、OCR 防泄漏规则和论文生命周期管理,然后才回一句「你好呀」。这不是修辞,是 WeClaw 改造前的真实加载行为。这篇讲我们怎么在不破坏任何既有行为、不触碰任何论文复现契约的前提下,把 10 个典型场景的系统提示词总量从 244,357 tokens 砍到 48,845(−80.0%),以及中途否决掉的三条弯路。
👨💻 作者与项目
作者简介:翁勇刚 WENG YONGGANG 新概念龙虾-WeClaw 开发团队负责人,一群专注于跨平台 AI 应用的实践者 理念:"再复杂的技术,也能用代码讲清楚"
- 💻 项目地址:https://github.com/wyg5208/weclaw.git
- 🌐 官网地址:https://weclaw.link
- 📝 作者 CSDN:https://blog.csdn.net/yweng18
- ⭐ 欢迎 Star⭐、Fork🍴、贡献代码🤝
📝 摘要
本文结构概览: 一句「你好」为什么要读 19K tokens(问题的量化)→ 三条被否决的瘦身路线以及否决理由(思辨)→ 两轮评审把一个方案从「看起来对」磨到「构造性对」(方案演进)→ 锚点切分 + 意图注入 + 模块预算三件套的技术实现 → 10 场景实测数据与门禁验收 → 一个意外收获:论文里 "lightweight" 的措辞从虚假宣传变成了事实陈述。
核心结论:
- 系统提示词膨胀的根因不是「写多了」,而是装配方式错了——全量注入让每个请求都为所有领域付费;
- 安全的瘦身不是删文本,而是重构装配链:内容一行不丢(行多重集等价),加载量按需付费;
- 灰度开关 + 字节级回退不是保守,而是让高风险重构敢动手的前提。
一、问题:每个请求都在为全部 13 个领域付费
WeClaw 的系统提示词由一段核心常量 CORE_SYSTEM_PROMPT 构成,33,533 字符,实测约 19.3K tokens。里面装着 13 个领域的工具选择指南:浏览器、文档、金融、数据分析、创作、语音、古诗词、专业文档、学术、OCR 扫描、菜谱、通讯天气、定时任务——每个领域都是一段带「严禁」「必须」字样的硬规则。
装配逻辑简单粗暴:无论用户说什么,全量注入。
于是出现了这样的账单:
| 场景 | 用户实际说了什么 | 模型要先读多少 |
|---|---|---|
| 闲聊 | 「你好呀」 | 27,983 tokens |
| 问天气 | 「今天北京天气怎么样」 | 22,401 tokens |
| 写首诗 | 「写一首秋天的诗」 | 27,983 tokens |
一个只涉及天气的请求,为什么要携带「量化分析严禁手工绕行 stock_screen」「论文项目须用 paper_lifecycle.update_phase 记录进度」这些纪律?答案是:没有任何为什么,装配函数只会做加法。
更尴尬的是学术层面的连锁问题。我们有一篇在投论文(CFTA,异步工具编排模式)声称语音快聊路径使用 "a lightweight system prompt"——实测这条路径注入的是全量核心,19.9K tokens。"lightweight" 是纯粹的虚假宣传,是一颗等着被审稿人引爆的雷。
诊断结论:这不是「提示词写多了」的问题。每段指南在其领域内都是必要的硬规则,删不得。问题在于装配方式——全量注入让成本与请求无关,只与系统总规模挂钩。
二、思辨:三条被否决的路线
动手之前我们认真评估过四条路线,否决了三条。这部分「不做什么」的思辨,比最终方案本身更值钱。
路线 A:纯去重——否决,但保留为前置批次
最直觉的想法:33K 字符里有多少重复?实测扫了一遍,重复文本只有约 960 字符的收益。去重是干净的、零风险的,值得做(后来作为 P0 批次落地),但它解决不了问题:去重后日常请求仍要读约 13K+ tokens,稳定压在 15K 安全线上方。去重是卫生,不是手术。
路线 B:LLM 在线摘要——否决,理由有四
用一个小模型在每轮请求前把系统提示词「压缩」成摘要,听起来很优雅。评估后否决,四条理由一条比一条硬:
- 延迟与成本:每轮请求多一次 API 调用,恰好打击我们最想优化的首响应延迟;
- 不可控:摘要模型可能丢掉「严禁」「必须」这类硬规则——而恰恰是这些规则在防事故;
- 破坏 prefix 缓存:摘要结果每轮漂移,LLM 服务端的前缀缓存彻底失效,实际 token 成本不降反升;
- 不可审计:压缩后的提示词出了行为问题,你无法定位是哪条规则丢了。
路线 C:按 tier/置信度收窄——否决,风险外溢
意图分类器本来就输出置信度,「低置信度就少注入」看似顺理成章。但排查发现 tier 机制自身存在既有缺陷(收窄逻辑与工具 schema 暴露耦合),在它上面再叠一层提示词收窄,等于把两个有缺陷的判断串联。我们没有修 tier,而是绕开了它——这个决策后来被证明是对的:提示词装配与 tier 解耦后,tier 的任何后续修复都不会波及这里。
路线 D(采纳):结构化拆分 + 按意图装配
把全量常量原样保留,从它派生出一套结构化视图:核心残量 + 13 个领域指南 + 一行式索引,装配时按意图只取需要的部分。关键设计前提:原常量不删不改,任何异常一个配置开关回到原点。
三、方案演进:两轮评审把「看起来对」磨成「构造性对」
方案初稿(V1)自我感觉良好,然后被两轮评审打出了 4 个 Critical、10 个 Warning、7 个 Suggestion。挑三个改变了最终形态的发现:
发现一:切分不能靠复制粘贴。 V1 打算把 CORE 的 33K 文本手工切成 14 份新常量。评审指出这等于凭空制造 33K 字符的漂移面——CORE 改一个字,切分副本就静默失真。改成运行时锚点切分:切分代码只记录每个章节的起止锚点(章节标题字符串),导入时从活体 CORE 现场切出。CORE 是单一事实源,切分是它的视图。锚点失效(章节改名、被删)在导入时直接抛异常——红灯优于静默。
发现二:等价性必须可证明,不能靠目测。 「切完应该没丢东西吧」这种话在评审里是不及格的。最终方案给出构造性证明:CORE 中未被任何指南区间覆盖的部分,按原顺序拼接就是核心残量——由定义可知 核心残量 + 全部指南 ≡ 原 CORE(行多重集)。验收脚本把这句话变成可执行断言,一行内容丢失即红。
发现三:三篇在投论文是冻结面。 全库 grep 后确认:多篇论文的实验脚本依赖意图分类器等符号的精确行为,而提示词改造极易顺手「优化」到它们。于是写入冻结契约:detect_intent_with_confidence 等符号只增不改,另配 10 条 query 的快照护栏——任何破坏意图行为的改动,门禁当场红灯。
这三条把方案从「一个瘦身技巧」变成了「一次有安全骨架的重构」。最终方案文档 463 行,其中约三分之一是风险表与否决记录。
四、技术栈:三件套实现
4.1 锚点切分(新模块 domain_guides.py)
切分核心逻辑不到 60 行,骨架是「先校验后切割」:
_GUIDE_SEGMENTS = [
# (指南名, 起始锚点, 结束锚点)——锚点必须是 CORE 中唯一子串
("browser_guide", "**浏览器工具选择指南】**", "【文档处理工具选择指南】"),
("finance_guide", "6. **stock_query**", "【数据分析工具选择指南】"),
# …共 15 段(ocr_scan_guide 由三段合并),覆盖 13 个指南
]
def _build_split(core: str):
intervals = []
for name, start, end in _GUIDE_SEGMENTS:
si, ei = core.find(start), core.find(end)
if si < 0 or ei < 0:
raise ValueError(f"锚点未找到: {name}") # 红灯,绝不静默
if core.find(start, si + 1) >= 0:
raise ValueError(f"锚点不唯一: {name}") # 唯一性校验
intervals.append((si, ei, name))
# 段间按序、不重叠校验 → 指南段 strip 合并
# 核心残量 = 未覆盖区间按序拼接(构造性等价的关键)
切分结果(P0 去重后的实测规模):
| 产物 | 规模 | 用途 |
|---|---|---|
| CORE_RULES 核心残量 | 3,260 字符 | 始终注入(身份/通用纪律) |
| 13 个领域指南 | 362 ~ 7,345 字符/个 | 按意图注入,单请求 ≤3 个 ≤12K 字符 |
| GUIDE_INDEX 一行式索引 | 1,208 字符 | 未命中意图时的兜底速查表 |
4.2 按意图注入:三条规则 + 一个反查表
装配函数接受一个 guide_override 参数,语义三分:
None:按INTENT_GUIDE_MAPPING(15 个意图 → 指南列表)收集,命中意图按(-score, 名称)确定性排序取前 3;"__index__":强制注入一行式索引(direct_tool 反查失败时的兜底);- 具体指南键:只注入该指南(斜杠直达工具时,
TOOL_GUIDE_REVERSE反查目标工具所属指南)。
两条互斥与兜底规则值得展开:
- 互斥:用户明问「你都有哪些功能」时注入的是能力菜单,此时跳过索引兜底——两个「概览型」内容不得同场;
- 兜底定义:「未命中」被定义为映射结果为空而不是「置信度低」。这个区分来自实测:10 条护栏 query 里有 4 条(含「写一首秋天的诗」)返回空意图——分类器存在覆盖盲区,这些请求一律由索引兜底,不做任何置信度判断。
4.3 模块列表 + 预算渲染器:丢弃的艺术
装配产物不再是字符串,而是带丢弃优先级的模块列表:
(core_rules, 内容, -1) # -1 = 永不丢弃
(companion, 内容, -1) # 心理关怀锚点,不可丢
(skills, 内容, 45)
(files, 内容, 50)
(guide_*, 内容, 60/65/70) # 相关性越低越先丢
(guide_index,内容, 75)
(capabilities,内容, 80) # 最先被丢
渲染器在双预算下工作:字符硬上限 26,000(从 50,000 收紧)+ token 软预算 15,000(用真实估算器,废弃了沿用已久的 len//2 假设——实测中文密度 0.571 tok/字符,旧系数高估容量 14%)。超限时的纪律有两条反直觉但重要的规定:
- 整模块丢弃,禁止句中截断。旧代码的
prompt[:N]会把一条「严禁」规则从中间切开,留下语义残骸;整模块丢弃保证活下来的每段都是完整的; - 丢弃后回补索引。如果领域指南被丢光了,渲染器补回 1,208 字符的一行式索引——模型至少知道「有哪些领域、细节按需」,而不是对工具生态一无所知。
4.4 灰度开关:一行配置的回退契约
# config/default.toml
[prompts.domain_guides]
enabled = false # 一键回退:原 CORE 全量路径,逐字节等价改造前
因为原常量从未被修改,回退路径与改造前的输出逐字节一致——这不是「行为差不多」,是可用断言验证的 ==。验收脚本真的 monkeypatch 了开关、真的断言了字节级等价。
五、五条链路,一套装配
WeClaw 共有五条会生成系统提示词的链路,改造前各有各的拼接代码(复制粘贴分叉是双路径行为漂移的温床)。P2 批次抽出共享装配函数后全部归一:
| 链路 | 装配方式 | 实测规模 |
|---|---|---|
| 普通对话(流式/非流式) | 意图装配 + 技能/经验/文件注入 + 预算渲染 | 闲聊 3,465 tokens |
| 语音快聊 | 固定三模块(核心残量+陪伴+索引) | ~3,465 tokens |
| 斜杠直达工具 | 工具反查指南,未命中强制索引 | 单指南 ≤7.3K 字符 |
| 斜杠直达技能 | 复用指令意图正常装配 | 同意图场景 |
| CFTA 异步链路 | 正常装配 + 不可丢弃的判定指令模块 | ≤26K 硬上限内 |
统一之后,「同一输入经两条路径产出不同提示词」这类漂移在构造上不再可能——验收门禁里有逐字节一致性断言守着。
六、效果:数据说话
6.1 token 账单(真实估算器,10 场景 × 改造前后)
| 场景 | 改造前 | 改造后 | 降幅 |
|---|---|---|---|
| 闲聊 | 27,983 | 3,465 | −87.6% |
| 创作-诗词 / 语音 / 学术 | 27,983 | 3,465 | −87.6% |
| 浏览器 | 19,880 | 3,582 | −82.0% |
| 定时 | 19,880 | 3,465 | −82.6% |
| 文档-格式转换 | 22,401 | 5,265 | −76.5% |
| 日常-天气 | 22,401 | 5,984 | −73.3% |
| 金融-股票分析 | 19,880 | 5,794 | −70.9% |
| 能力菜单 | 27,983 | 10,895 | −61.1% |
| 合计 | 244,357 | 48,845 | −80.0% |
降幅最大的恰是最常见的请求:闲聊和空意图场景不再为 13 个领域付费,只带一张 1,208 字符的索引地图。
6.2 质量账单:内容一行没丢
- 切分等价:
核心残量 + 13 指南 ≡ 原 CORE,非空行多重集断言通过; - 场景正确性:天气请求含默认城市机制、不含股票纪律;股票分析含「严禁绕行」;学术场景含论文生命周期规则——7 场景逐项断言;
- 冻结护栏:10 条 query 的意图快照与基线全一致,论文复现面零漂移;
- 回归:定向回归 274 项,零新增失败(12 个失败经
git stash干净 HEAD 对照,全部为既有问题)。
6.3 意外收获:论文措辞从虚假变成事实
CFTA 论文里 "lightweight system prompt" 的论断,改造前是 19.9K tokens 的虚假宣传,改造后是 ~3.9K tokens 的事实陈述。我们按论文一致性清单同步修订了三篇在投论文的 5 处漂移(含预算数字口径、意图类别数统一、一处成本论证的前提限定),并主动在 Limitations 补了一段敏感性披露:瘦身后 CFTA 的绝对延迟收益会从 ~11.5s 缩到 ~8-10s,但相对降幅稳定在 55-58%——收益来源是把工具执行移出首响应关键路径,而不是掩盖臃肿提示词。主动披露比被审稿人发现体面得多。
七、沉淀:四条可迁移的经验
- 膨胀问题的根因常在装配而非内容。先问「这段话在这个请求里为什么出现」,再决定删不删;
- 高风险重构的三件套:原物保留(回退面)+ 构造性等价(可证明不丢东西)+ 灰度开关(一键回到原点)。三件齐备,重构才敢动手;
- 丢弃要有优先级,截断是懒政。
prompt[:N]切出来的语义残骸比不切更危险; - 给论文时代的项目加冻结护栏。实验脚本依赖的符号就是公共 API,改动前先立快照断言,红灯比道歉便宜。
完整的验收脚本在仓库 scripts/ 下(verify_domain_guides_split / verify_prompt_assembly_p2 / probe_sp_tokens),全部只读、可重复执行。下一篇(第 88 篇)讲这场战役里容易被忽略的另一半:如何在「三篇论文依赖活体代码」的约束下设计安全骨架。
📎 本文涉及的代码:
src/core/domain_guides.py(新模块)、src/core/prompts.py、src/core/agent.py、config/default.toml📊 版本:v9.13.0 | 迭代文档:docs/开发记录/v9.13.0_2026-08-10_系统提示词瘦身方案V3落地.md