第 02 篇我们建立了成本模型,发现两个成本大头:system prompt 里的候选文章 和 历史对话的重复计费。本篇就从这两个大头下手,同时解决一个更本质的问题——怎么让模型”记得住”而不”记爆”。
“上下文工程(Context Engineering)”在 2026 年已经是一个成熟的研究方向,核心观点:长上下文不是越多越好,上下文的质量取决于信息的组织方式,而非 token 数量。Anthropic 等团队的研究一再表明,在超长上下文中,模型对关键信息的提取能力会随无关信息增多而下降(俗称”大海捞针”难题)。所以工程化的核心不是”塞满”,而是”精选 + 分层 + 压缩”。
一、ByteBot 的上下文现状
ai-assistant.js 里的 truncateMessages() 是唯一的内存管理:
1 | function truncateMessages(msgs) { |
这个方案的三个问题:
- “头部保底”逻辑有缺陷:第一组 user+assistant 永远保留,但对话到第 20 轮时,第 1 轮的信息早已和当前话题无关,白占 token。
- 截断即遗忘,无摘要:中间被截掉的消息完全消失,如果用户后来问”我们刚才聊了什么”,模型答不上来。
- system prompt 全文注入:
buildSystemPrompt()每轮都塞 8 篇文章的完整摘要(每条 150 字符)+ 全部系列列表,这个长度远超历史消息,却不被truncateMessages()管理。
二、记忆分层架构
工程级的对话记忆应该分三层,ByteBot 目前只有一层(原始消息)。
1 | ┌──────────────────────────────────────────┐ |
2.1 滚动摘要(Rolling Summary)——核心改造
核心思想:维护一个 topic_summary 字符串,每当对话超过阈值(比如 6 轮),调用一次模型把”最旧的一部分对话 + 现有摘要”压缩成新摘要,然后丢弃旧消息。
1 | 第 6 轮触发压缩: |
ByteBot 落地方案:
1 | async function compressHistory(messages, topicSummary, model) { |
注意摘要器用低 temperature(0.3),因为摘要任务要确定性不要创造性。这个”摘要器”也是第 10 篇多 Agent 里”记录员 Agent”的原型。
2.2 记忆分层的 token 预算
给每轮请求一个固定预算,各层按优先级分配:
| 层级 | 预算占比 | 说明 |
|---|---|---|
| 系统角色指令 | 10% | 人设 + 输出规则,精简后固定 |
| 话题摘要 | 15% | 压缩后恒定的历史记忆 |
| 当前轮对话 | 25% | 最近 2~4 轮原始消息 |
| 检索上下文 | 40% | 候选文章,本系列第 04 篇重点优化 |
| 输出预留 | 10% | max_tokens 之外的余量 |
这个预算表把第 02 篇的”成本大头”治理成了固定预算制:无论聊多久,每次请求的 token 消耗都被钳制在预算内——成本可控,质量也可控。
三、上下文压缩的三层技术
3.1 文本级:压缩 prompt 本身
- 保留原则:摘要保留”信息密度高”的部分,删除寒暄、重复、细节。
- 结构化替换:文章推荐从”完整标题+摘要”改为”ID 引用 + 模型需要时才展开”。比如 system prompt 里写
@doc_17,回答生成时再映射回真实标题链接。ByteBot 的validateLinks()已经强制链接必须存在于索引,这个机制可以和引用压缩天然结合。
3.2 语义级:检索相关性截断
注入 context 时按”与当前问题相关性”排序,只取前 N 条(ByteBot 是 8 条),且每条只保留命中片段而不是全文摘要。这是第 04 篇 RAG 的内容,但它是上下文压缩的组成部分——检索不是把数据库搬进 prompt,而是把最相关的证据搬进去。
3.3 模型级:用强模型摘要、弱模型执行
摘要任务(信息密度高、token 敏感)用强模型(或 reasoner),执行任务用快模型。离线流水线(钓鱼日记)里这个分工尤其明显——第 10 篇会展开。
四、结构化输出:JSON Schema 与函数调用
ByteBot 现在最大的”脏活”全在前端:preLinkArticles() 把裸标题改写成链接、validateLinks() 校验并修正链接、norm() 归一化全半角、marked+DOMPurify 渲染 Markdown。这些函数存在,正是因为模型输出是不受控的字符串。
工程化的解法是让输出结构化——用 DeepSeek 支持的 response_format 或 function calling,让模型直接输出 JSON:
1 | const body = { |
改造收益立竿见影:
| 对比项 | 改造前(自由文本) | 改造后(JSON) |
|---|---|---|
| 前端修正函数 | preLink/validateLinks/norm 三件套 | 前端只需渲染,砍掉一半代码 |
| 链接幻觉 | 需后置校验拦截 | Schema 里强制 title 来自候选列表 |
| 数据分析 | 无法结构化 | 直接进评测/统计(第 05/11 篇) |
| 渲染安全 | marked + DOMPurify | 白名单渲染,风险大降 |
一个重要的工程细节:结构化输出必须配合”约束注入”。仅靠 JSON Schema 还不够,system prompt 里要同步声明”title 必须逐字取自候选列表,禁止改写”——模型往往在 JSON 模式下更守规矩,但双重约束更稳。
五、长上下文策略:ByteBot 需要吗?
ByteBot 是”导览型助手”,正确答案集中在站内内容,几乎不需要超长上下文。超长上下文(64K~200K token)对三类场景才有价值:代码仓库级理解、长文档问答、复杂推理链。
对不需要长上下文的场景,硬上长上下文有明确代价:
- 成本上升:上下文按输入 token 计费,200K 上下文单次可能几毛钱。
- 注意力稀释:长上下文里信息检索精度下降(”大海捞针”实验),反而变笨。
- 延迟上升:prefill 变长,首 token 延迟增加。
正确的策略是”够用就好 + 按需外置”:需要长文档理解时,用 RAG 把相关片段拉进来,而不是把整本书塞进去。这个原则贯穿整个系列。
六、落地清单与小结
针对 ByteBot 的改造优先级:
| 优先级 | 改造 | 收益 |
|---|---|---|
| P0 | 滚动摘要替代简单截断 | 多轮对话不丢失话题、token 恒定 |
| P0 | 上下文固定预算制 | 成本可预测 |
| P1 | 结构化输出(function calling) | 干掉前端三件套、数据可分析 |
| P2 | 长期记忆(用户画像存 TiDB) | 跨会话个性化 |
| P3 | 强摘要模型/快执行模型分工 | 质量和成本双优化 |
核心结论:
- 上下文工程 = 精选 + 分层 + 压缩,不是”塞满窗口”。
- 记忆分三层:短期保真、工作层滚动摘要、长期外置存储;ByteBot 从”单层截断”升级为”三层架构”。
- 滚动摘要用低 temperature 的专用摘要器,这是多 Agent 里记录员 Agent 的原型。
- 结构化输出是 AI 应用工程化的分水岭——从”调通模型”到”模型输出可直接进系统”,JSON Schema + 函数调用是必经之路。
- 长上下文是有代价的,ByteBot 用 RAG 外置而非硬塞。
下一篇进入 RAG 深度优化(上):把 ByteBot 的单路 BM25 检索升级为混合检索 + 向量 + Rerank 的完整链路,用博客 260 篇文章做真实知识库。
本系列持续更新中,每周一篇。