本节摘要:主循环四拍中的第一拍——感知——值得单独一节,因为它决定了模型"此刻的智商"。LLM API 无状态,messages 列表就是模型的全部世界;因此每一步决策前都是一次重新供料,而不是开局组装一次管到底。本节回答三个问题:装什么(上下文五层清单:系统提示词、任务与对话历史、工具定义、工具结果、环境状态);怎么裁(预算分配 + 截断 / 摘要 / 引用化三板斧);以及与主循环的接口约定——
assemble_context()必须是循环里一个可替换的策略函数,而不是散落各处的拼接代码。本节是第 7 章上下文工程的序章:那里将展开压缩、记忆分层与 Claude Code 式 compaction 的完整设计,本章只立骨架。
阅读完本节,你应当能够:
assemble_context() 策略函数。新手最常见的误解是"上下文在任务开始时构建一次"。实际上:
第 1 步前:组装(系统提示词 + 任务 + 工具定义) 第 2 步前:重组装(上面全部 + 第 1 步的工具结果) 第 3 步前:再重组装(…… + 第 2 步的结果;旧的日志可能要裁掉)
两条推论:
| 层 | 内容 | 来源 | 典型占比(示意) |
|---|---|---|---|
| L1 系统提示词 | 身份、环境、约束、输出格式 | harness 写死 / 配置 | 5~10% |
| L2 任务与历史 | 用户任务 + 对话与决策记录 | 会话累积 | 20~30% |
| L3 工具定义 | 可用工具的 schema 清单 | 工具系统(第 4 章) | 10~15% |
| L4 工具结果 | 文件内容、命令输出、检索片段 | 每步执行产出 | 40~60%(大头) |
| L5 环境状态 | 文件树、git 状态、任务清单 | 按需探查 | 5~10% |
(占比为教学示意值;真实分布随任务类型漂移——数据处理类任务的 L4 占比会显著更高。)
要点:L4 是唯一的"无限增长层",所有裁剪策略都主要冲它去;L3 是常驻税——工具一多,schema 清单本身就吃掉可观预算,这是第 4.2 节工具路由的动机之一(按任务只挂子集);L1+L2+L3+L5 加起来,就是一个 harness 的"供料个性"——第 2 章看到的 CLAUDE.md(随仓库走的项目约定)就住在 L1/L5 的交界处。
先分预算再裁剪。示意性的预算分配:总窗口预留 20% 给模型输出与余量,L1/L3 常驻固定,剩余按"近期优先"原则在 L2/L4 间动态分配。
超预算时,三板斧按代价从低到高:
| 斧法 | 做法 | 适用 | 代价 |
|---|---|---|---|
| 截断 | 只保留最近 N 轮历史 / 单条结果截首尾 | 陈旧对话、超长日志 | 丢失的信息可能正是关键 |
| 摘要 | 用模型把旧历史压成一段摘要替换原文 | 长任务中途压缩 | 一次额外模型调用 + 摘要有损 |
| 引用化 | 大内容存盘,上下文里只放"路径 + 摘要行",要用再读 | 大文件、批量检索结果 | 多一次工具往返 |
💡 预算第一课:永远给模型输出留 20% 余量。供料塞满窗口的循环,第一个症状是"模型说话越来越短"——很多人误以为是模型降智,其实是窗口见底(示意场景,第 7 章展开压缩的系统性解法)。
💡 引用化是 harness 独有的招:聊天产品里做不到"上下文里放个指针、要用再取",harness 有工具系统就能做到——把大文件读一次存到本地,上下文里只留
app.py(约 800 行,含 auth 模块)→ .cache/app.py。模型后续要用时自己调read_file取段。这一招把"上下文窗口"扩成了"文件系统 + 窗口"的两级存储。第 7 章的记忆分层是它的体系化版本。
反模式:把消息拼接散在循环各处(append 完就不管)。正模式:循环只持有一句话——messages = assemble_context(state)——供料是一个可替换的策略:
# context.py —— 上下文组装策略接口(写法示意,以实际工程为准) def assemble_context(state: "RunState", budget: int) -> list[dict]: """循环第一拍的唯一入口。state 含任务、历史、工具结果、环境。 职责:在 budget(token 数)内产出本步 messages。 第 7 章将替换为:分层预算 + 自动压缩 + 记忆存取的完整实现。""" msgs = [system_prompt(state), user_task(state), *tool_schemas(state)] msgs += fit_to_budget(recent_history(state), remaining(msgs, budget)) return msgs
这个接口约定的价值:第 7 章升级供料策略时,循环代码一行不改;第 9 章做观测时,在 assemble_context 出口记一行"本步装了什么、裁了什么",供料决策第一次变得可审计。组件化的意义再次出现:上下文工程不是一个模块目录,而是循环第一拍上的一族可替换策略。
第 3 章结束时,你的仓库应该有三块积木:mini_loop.py(四拍循环 + 终止判断)、breaker.py(熔断器)、context.py(供料策略接口)。它们就是第 10 章 mini-harness 的心脏、免疫系统和视网膜——但循环要转得有价值,还差一样东西:让模型真正有手有脚的工具系统。下一章进入。
assemble_context(state, budget)。循环有了心脏(四拍)、免疫系统(熔断)、视网膜(供料)。下一章给它装上手脚——工具系统:怎么定义工具模型才用得对,工具多了怎么路由,以及失败时怎么把错误变成信息。
延伸阅读:同站教程库《smolagents》。