上下文工程是什么?

所谓“上下文”,是指在对大语言模型(LLM)进行采样时所包含的那组 tokens。手头的工程问题,是在 LLM 的固有约束之下,优化这些 tokens 的效用,以便稳定地得到预期结果。

上下文工程 vs. 提示工程

提示工程关注如何编写与组织 LLM 的指令以获得更优结果(例如系统提示的写法与结构化策略);而上下文工程则是在推理阶段,如何策划与维护“最优的信息集合(tokens)”,其中不仅包含提示本身,还包含其他会进入上下文窗口的一切信息。

上下文腐蚀(context rot)

是指随着上下文窗口中的 tokens 增加,模型从上下文中准确回忆信息的能力反而下降。因此,上下文必须被视作一种有限资源,且具有边际收益递减

上下文工程的目标与实现

用尽可能少、但高信号密度的 tokens,最大化获得期望结果的概率。

  • 系统提示(System Prompt):语言清晰、直白,信息层级把握在“刚刚好”的高度。常见两极误区:
    • 过度硬编码:在提示中写入复杂、脆弱的 if-else 逻辑,长期维护成本高、易碎。
    • 过于空泛:只给出宏观目标与泛化指引,缺少对期望输出的具体信号或假定了错误的“共享上下文”。
  • 工具(Tools):工具定义了智能体与信息/行动空间的契约,必须促进效率:既要返回token 友好的信息,又要鼓励高效的智能体行为。工具应当:
    • 职责单一、相互低重叠,接口语义清晰;
    • 对错误鲁棒;
    • 入参描述明确、无歧义,充分发挥模型擅长的表达与推理能力。
  • 示例(Few-shot)

长时程任务的上下文工程

无限增大上下文窗口并不能根治“上下文污染”与相关性退化的问题,因此需要直接面向这些约束的工程手段:压缩整合(Compaction)结构化笔记(Structured note-taking)子代理架构(Sub-agent architectures)

压缩整合(Compaction)

  • 定义:当对话接近上下文上限时,对其进行高保真总结,并用该摘要重启一个新的上下文窗口,以维持长程连贯性。
  • 实践:让模型压缩并保留架构性决策、未解决缺陷、实现细节,丢弃重复的工具输出与噪声;新窗口携带压缩摘要 + 最近少量高相关工件(如“最近访问的若干文件”)。

结构化笔记(Structured note-taking)

  • 定义:也称“智能体记忆”。智能体以固定频率将关键信息写入上下文外的持久化存储,在后续阶段按需拉回。
  • 实践:上下文保存外部文件的索引+概括,而不是整个文件,需要用了再去读。
  • 价值:以极低的上下文开销维持持久状态与依赖关系。

子代理架构(Sub-agent architectures)

  • 定义:由主代理负责高层规划与综合,多个专长子代理在“干净的上下文窗口”中各自深挖、调用工具并探索,最后仅回传凝练摘要(常见 1,000–2,000 tokens)。

三者评价:

  • 压缩整合:适合需要长对话连续性的任务,强调上下文的“接力”。
  • 结构化笔记:适合有里程碑/阶段性成果的迭代式开发与研究。
  • 子代理架构:适合复杂研究与分析,能从并行探索中获益。

上下文管理器设计

候选信息包

ContextPacket 是系统中信息的基本单元。每个候选信息都会被封装为一个 ContextPacket,包含内容、时间戳、token 数量和相关性分数等核心属性。

1
2
3
4
5
6
Attributes:
content: 信息内容
timestamp: 时间戳
token_count: Token 数量
relevance_score: 相关性分数(0.0-1.0)
metadata: 可选的元数据

上下文生成流水线

这边叫GSSC(Gather-Select-Structure-Compress)流水线,它将上下文构建过程分解为四个清晰的阶段。

1.Gather:多源信息汇集

其实就是做出一份候选上下文列表,进行评级、裁剪、融合。核心机制是 优先级+数量限制

流程:

  1. 系统指令:有就先放进去(最高优先级),直接给满分相关性 1.0,确保一定保留。
  2. 记忆检索:调用 memory_tooluser_query 搜索(limit 10),把结果解析成 ContextPacket 加入;失败就告警但不中断。
  3. RAG 检索:调用 rag_tool 搜索知识(limit 5),解析后加入;失败同样只告警。
  4. 对话历史:只取最近 5 条,每条包装成 ContextPacket,给一个基础相关性 0.6
  5. 自定义包:外部传进来的 custom_packets 直接追加。

2.Select:智能信息选择

_select() 负责把 _gather() 收集到的候选信息 打分、排序、过滤,再在 token 预算内 挑出最值得放进最终上下文的一组(系统指令优先保留)。

流程:

1. **系统指令单独处理**:先把 `type=system_instruction` 的包分离出来,永远保留且不参与竞争。

2. **算 token 预算**:系统指令先占坑,剩余 token 不够就直接只返回系统指令。

3. **给其他包算综合分**:

- 相关性:若还是默认值 `0.5`,就用 `_calculate_relevance()` 重新算

- 新近性:用 `_calculate_recency()` 算时间越近分越高

- 综合分:`relevance_weight * relevance + recency_weight * recency`

4. **过滤低质量**:相关性低于 `min_relevance` 的直接丢掉。

5. **排序 + 贪心选入**:按综合分从高到低依次加入,直到 token 用满为止。

总结:

  • 评分机制:相关性 + 新近性加权(可配置权重)
  • 贪心选择:预算有限时,优先塞“单位价值最高”的内容
  • 过滤机制:用 min_relevance 把低相关噪声挡掉

3.Structure:结构化输出

_structure() 把选中的 ContextPacket 按类型分组,再拼成一个带分区标题的 结构化上下文模板字符串,用于最终喂给模型。

流程:

  1. 分类分组
    • system_instruction → 放到 system_instructions
    • rag_result / knowledge → 当作证据放到 evidence
    • 其他 → 放到一般 context
  2. 按固定模板输出 5 个区块
    • [Role & Policies]:系统指令(如果有)
    • [Task]:用户问题 user_query(始终有)
    • [Evidence]:检索到的知识/证据(用 --- 分隔)
    • [Context]:对话历史/自定义信息等背景
    • [Output]:输出要求(提示“基于以上信息回答”)

最后用空行把各区块连接成一个完整 prompt。

4.Compress:兜底压缩

_compress() 是上下文超出 token 上限时的兜底压缩:尽量按分区保留结构,只在必要时截断最后一部分内容,让最终上下文不超过 max_tokens

流程:

  1. 先估算 token_count_tokens(context),没超限就直接返回。
  2. 按分区处理:用空行 \n\n 把上下文切成多个 section(对应你前面的结构化分区)。
  3. 贪心保留:从前到后逐段加入,只要不超 token 就“整段保留”。
  4. 需要超限时
    • 计算剩余 token(remaining_tokens
    • 如果剩余还够 50 tokens,就对该 section 做截断_truncate_text),并追加提示 "[... 内容已压缩 ...]"
    • 然后停止(后面的分区不再保留)
  5. 输出压缩结果:拼回文本,并打印压缩前后 token 数。

结论:

基本就是先分区压缩,还不够就直接从5个区块从上往下放,token一不够,直接截断,抛弃后面内容。