首页/Agent 核心/提示词工程与上下文工程
Agent 核心 · 第 13 章

提示词工程与上下文工程

这里是第五章「怎么做得可靠」的第一章。前四章解决了"能干活":模型接上了工具、跑起了自主循环、长出了文档能力、说起了通用协议。但把一个 Agent 从"能跑"带到"可靠",第一站不在模型、也不在工具,而在一个容易被忽视的…

约 18 分钟7,245 字14 个代码示例

这里是第五章「怎么做得可靠」的第一章。前四章解决了"能干活":模型接上了工具、跑起了自主循环、长出了文档能力、说起了通用协议。但把一个 Agent 从"能跑"带到"可靠",第一站不在模型、也不在工具,而在一个容易被忽视的地方——上下文窗口。进入模型窗口的每个 token 都是设计对象:系统提示词、会话历史、工具定义、工具结果、检索内容、长期记忆,什么进、进多少、放哪里、留多久,每一项都是显式的工程决策。本章按"从部分到整体"的顺序展开:前半章打磨提示词这个组分——它是成本最低的杠杆,五个已验证的模式、一张反模式清单、一套工程化管理方法;后半章升级到上下文这个整体——建立七组分的总账本,理解注意力经济学,掌握预算分配与压缩策略。你会看到前沿从业者口中的关键词正在从 Prompt Engineering 换成 Context Engineering,这一章把两者一次讲透。

1. 先祛魅:提示词的边界

提示词工程被两种极端声音夹击:"会写提示词就能年薪百万"和"提示词工程已死"。真相在中间:它是每个 Agent 开发者的基本功,但重要性远不如后半章的上下文工程——因为提示词只是上下文的一部分。这一部分给出可复制的工程方法,而非玄学话术。

第 5 章讲过系统提示词的约束力来自后训练的统计倾向。由此推出提示词工程的三个边界认知:

  1. 它调的是概率分布,不是逻辑电路。同一提示词跑两次结果可能不同(第 4 章采样随机性)
  2. 上限被模型能力锁定。提示词无法让 4B 模型做出 70B 的推理深度——它是"发挥模型现有的能力",不是"创造新能力"
  3. 它是最便宜的杠杆。改一行文字就能试验,迭代成本秒级——这是为什么值得先优化提示词再动其他层(第 9 章调试心法的"行为层")

所以提示词工程的正确定位:用最低成本逼近模型当前能力上限的方法论。

2. 五个已被验证的模式

2.1 角色设定(Persona)

你是一位资深数据库管理员,专精 PostgreSQL 查询优化。
你习惯先确认表结构和数据量,再给出索引建议。

有效的原理:角色词汇关联着训练语料中的专业语境(DBA 的讨论风格、术语密度),设定角色 = 把模型拉进高相关专业文本的分布区域。要点:角色的价值在"行为暗示"而非头衔——"习惯先确认表结构"比"资深专家"有用。

2.2 Few-shot 示例(少样本学习)

第 4 章说过:模型对示例的模仿力远超对形容词的理解力。这是最强力的模式:

将用户评论分类为:正面 / 负面 / 中性。

评论:物流很快,包装也仔细
输出:正面

评论:东西还行,就是客服半天不回
输出:中性

评论:用了一周就坏了,退货还各种扯皮
输出:负面

评论:{user_input}
输出:

三个工程要点:

  • 示例要覆盖边界情况(上面第三条带了点混合情绪,教模型判断主导倾向),只给简单正例学不到判别力
  • 示例的格式就是输出格式——模型会精确模仿包括空格和标点的模式
  • 示例数量 2~5 个通常饱和,堆 20 个不如挑 3 个有代表性的

2.3 思维链(CoT)

第 4 章详细讲过原理(生成即上下文,中间结论做脚手架)。工程上的两种用法:

# 显式触发(提示词版)
请一步步分析,先列出已知条件和约束,再推导结论。

# 结构化触发(更强:把思考过程格式化)
请按以下步骤处理:
1. 复述任务目标
2. 列出所需信息与已有信息
3. 给出推理过程
4. 输出最终答案(以 "答案:" 开头)

结构化版本的好处:第 4 步给了稳定的输出锚点,方便下游解析(第 6 章的接口思维)。另注意:简单任务不要加 CoT——"今天星期几"式的直接问答,加"一步步思考"反而增加啰嗦和出错面。

2.4 结构化模板(XML/Markdown 分区)

## 任务
{任务描述}

## 输入
{数据}

## 约束
- {约束1}
- {约束2}

## 输出格式
{格式说明}

原理:分区标签帮助模型(和读代码的你)区分指令、数据、约束的边界——结构化本身就是一种提示。对长提示词的效果显著(第 4 章的中段遗忘问题也靠分区缓解)。

2.5 分而治之(任务分解)

第 6 章埋过的原则:别让一次调用做所有事。

❌ "分析这篇文章的情感、摘要、关键词、可读性评分"
✅ 四次调用,每次一个明确任务,各自校验各自的输出

每加一个任务,其他任务的输出质量都在掉(注意力被稀释)。多任务合并省下的是调用费,赔上的是质量——这笔账多数情况不划算。

3. 反模式清单

比"该做什么"更有教育意义的是"常见的错":

反模式 症状 修正
形容词堆砌 "要专业、简洁、生动、深入浅出" 给示例(2.2),形容词无锚点
负面指令过多 "不要啰嗦不要跑题不要编造" 换成正向指令(第 5 章:肯定句是强约束)
威胁模型 "答错你会被惩罚" 无统计依据;改为展示"好答案"长什么样
过度礼貌客套 "拜托了,非常感谢" 无害但浪费 token;指令清晰度不升反降
万能长提示词 一个 prompt 服务 20 个场景 分场景拆分(职责越少越可靠)
隐藏关键约束在中间 长文中间一句话带过要求 首尾放置(第 4 章位置敏感)

4. 提示词的工程化管理

提示词是代码——就该用代码的方式管理。

4.1 模板分离(第 5 章的进阶版)

from jinja2 import Environment, StrictUndefined

env = Environment(undefined=StrictUndefined)   # 变量缺失时报错而非静默空串

CLASSIFY_PROMPT = env.from_string("""
将用户评论分类为:正面 / 负面 / 中性。

评论:物流很快,包装也仔细
输出:正面

评论:{{ example_neg }}
输出:负面

评论:{{ user_input }}
输出:
""")

def classify(user_input: str, example_neg: str) -> str:
    prompt = CLASSIFY_PROMPT.render(
        user_input=user_input, example_neg=example_neg)
    ...  # 调用模型

StrictUndefined 是血泪经验:默认配置下变量拼错会静默渲染成空串,提示词悄悄坏掉而你毫无察觉。

4.2 版本化与评测

提示词改了怎么知道变好还是变坏?——没有评测集的提示词优化是玄学(第 15 章的预告):

prompts/
├── classify_v1.py
├── classify_v2.py       # 改动记录在 docstring
└── evalset.jsonl         # 20~50 条标注样本

最低可行流程:固定一组测试输入 + 期望输出,每次改提示词跑一遍对比准确率。20 个样本就能拦住大部分"负优化"。

5. 站位:提示词技巧的黄昏

一个值得诚实讨论的趋势:提示词技巧的半衰期越来越短。

  • 第 4 章讲过:指令遵循能力随模型代际提升,老技巧在贬值("让我们一步步思考"在强模型上已无明显增益——它已经默认会思考)
  • 结构化输出(第 6 章)接管了格式约束——曾经的"输出 JSON 的 12 种咒语"已无必要
  • 提示词正在从"面向模型的咒语"演变为"面向系统的配置"——角色定义、边界声明、工具说明,本质是系统设计

但三个东西不会贬值:清晰的任务定义、好的示例、以及知道什么时候不该用提示词解决(该用工具的用工具、该用代码的用代码、该改模型的换模型)。

本章后半的 Context Engineering 就是这个演进的方向:提示词只是上下文的一个组分,真正的设计对象是进入模型窗口的一切——提示词工程的主场正在让位,上下文才是真正的战场,窗口里每一个 token 的进出,都是设计决策。

6. 视角升级:上下文的总账本

2024 年后,前沿从业者的共识悄悄换了词:从 Prompt Engineering 到 Context Engineering。因为提示词只是冰山一角——真正决定 Agent 表现的,是进入上下文窗口的一切:系统提示词、历史、工具定义、工具结果、检索内容、记忆。本章后半就讲怎么系统地设计这条"总装流水线"。

先建立总账本。Agent 每轮请求时,上下文窗口的构成:

┌─────────────────────── 上下文窗口(如 32K)───────────────────────┐
│                                                                    │
│  ① 系统提示词(常驻)          ~500-2000 tok                        │
│  ② 工具定义 schema(常驻)     ~100-200 tok × 工具数                 │
│  ③ Skill 索引(常驻)          ~10 tok × 技能数                      │
│  ④ 长期记忆注入(每轮变化)    ~200-500 tok                          │
│  ⑤ 会话历史(逐轮增长)        无上限 ← 最危险的增长项                │
│  ⑥ 工具结果(随轮次累积)      单次可达数万 tok ← 第二危险            │
│  ⑦ 当前输入 + 生成空间         动态                                  │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

Context Engineering 的定义就一句话:对上述每一项,有意识地决定"放什么、放多少、放多久"。前面的章节都在做局部优化(第 6 章裁历史、第 7 章控工具数、第 10 章省 skill 索引、第 15 章控记忆注入)——本章把它们装进一个统一框架。

7. 注意力经济学:两条核心原则

7.1 每个 token 都在付费——以"注意力学"计价

第 4 章讲过 lost in the middle:上下文越长,中段信息被忽略的概率越高。推论:

窗口里每个 token 不只花你的钱,还在稀释其他所有 token 的注意力。

这是 Context Engineering 的第一性原理。它推翻"上下文越多越好"的直觉:塞进去的无关内容不是"无害的冗余",是主动的干扰。有个被广泛引用的比喻:上下文不是硬盘(存得越多越好),是 CPU 的寄存器——每个位置都该放当前要用的东西。

7.2 结构决定命运——位置敏感的排版

第 4 章的位置规律升格为设计规则:

窗口开头(高注意力)→ 身份、目标、核心约束
窗口结尾(高注意力)→ 当前任务、最新指令、格式要求
窗口中间(低注意力)→ 详细背景、参考材料、历史细节

实操含义:系统提示词放头、当前用户输入和"格式要求"放尾,中间塞"软性内容"。这也是为什么第 6 章的摘要压缩块放在 system 之后历史之前——摘要降权到中段,最新的原文交互留在尾部。

8. 各组分的经营策略汇总

把散落各章的策略收进统一账本(含本章新增的细化):

组分 策略 章节
系统提示词 精简;首尾放关键规则;运行时变量放尾部 user 消息(保缓存) 第 5 章
工具定义 按职责装配 5~8 个;schema 描述精炼 第 7 章
Skill 索引 一行索引常驻;正文按需加载 第 10 章
长期记忆 top-k 限定 + 相似度阈值 + "以最新为准"声明 第 15 章
会话历史 三级裁剪:截断 → 摘要压缩 → 检索外挂 第 6 章
工具结果 写入前截断(而非事后裁剪)——见下文 本章
检索文档 分块 + 相关性排序 + 两端放置 第 1、9 章

重点展开工具结果——Agent 上下文管理的最大盲区:

9. 工具结果:最大的隐形杀手

第 8 章提过"工具结果永久留在 messages 里"。算一笔具体的账:

Agent 跑一个 8 轮任务,每轮调用 read_webpage(3000 字符 ≈ 1500 tok)
→ 第 8 轮请求时,上下文里有 8 × 1500 = 12000 tok 的历史工具结果
→ 其中第 1~5 轮的结果大概率已无用处(占 7500 tok)
→ 它们不只是占空间:还在稀释模型对当前任务相关内容的注意力

三种对策,按彻底程度排序:

对策一:入口截断(第 7 章原则的重申)——工具返回前就压到最小必要信息。

对策二:结果替换——历史轮次的工具结果,只留摘要:

def compact_tool_results(messages: list, keep_last: int = 2) -> list:
    """把旧轮次的工具结果替换为占位摘要"""
    tool_result_positions = [
        i for i, m in enumerate(messages)
        if m.get("role") == "tool"
    ]
    for i in tool_result_positions[:-keep_last] if keep_last else []:
        original = messages[i]["content"]
        messages[i] = {
            **messages[i],
            "content": f"(历史工具结果,已省略 {len(original)} 字符)",
        }
    return messages

注意边界:必须保留成对结构(第 6 章的裁剪边界),只改 content 不删消息,tool_call_id 配对不破。信息论的权衡:旧工具结果以"被压缩"的形式存在(可追溯),注意力被释放给当前任务。

对策三:外置存储 + 按需引用(终极方案)——工具结果不进 messages,存外部(文件/向量库),上下文里只放"句柄":

tool result → 存为 artifact_001.txt,messages 里只有:
"结果已存储。需要时可调用 read_file('artifact_001.txt') 查看细节"

模型需要时会主动去取。这是"寄存器 vs 硬盘"的彻底贯彻,代价是多一跳工具调用。生产 Agent(如 Claude Code 的长会话)都在往这个方向演进。

10. 预算分配:一个实操框架

给一个可以直接抄的分配模板(以 32K 窗口、长任务 Agent 为例):

class ContextBudget:
    """上下文预算表:各组分的配额与实时水位"""

    def __init__(self, window: int = 32768):
        self.window = window
        # 配额(留 20% 给生成空间)
        usable = int(window * 0.8)
        self.quota = {
            "system":      1500,      # 系统提示词
            "tools":        800,      # 4 个工具 × ~200
            "memory":       400,      # 记忆注入
            "history":     usable - 2700,   # 弹性大头
            "generation": int(window * 0.2),
        }

    def check(self, current_usage: dict) -> list[str]:
        """返回超支组件清单(第 6 章估算函数的水位应用)"""
        violations = []
        for key, used in current_usage.items():
            if used > self.quota.get(key, float("inf")):
                violations.append(key)
        return violations

超支时的处置动作(对应第 6 章的三级策略):history 超了先压缩、再截断;memory 超了降 top-k;tools 超了是架构问题(该拆 Agent 或上 Skill)。

预算思维的价值不在数字精确,在于"每组分有水位警报"——没有预算表的 Agent,第一次知道上下文出问题的时候就是任务失败的时候。

11. 什么时候该"满窗口"

平衡一下:上下文不是越小越好。三种情况值得花 token:

  1. 关键约束的重复:长任务里,把核心目标每 10 轮重申一次(锚定注意力)——不是浪费是投资
  2. few-shot 示例:3 个好示例的 600 token 往往比省下它换来 10% 的格式错误率划算
  3. 任务必需的背景:该给的全给。砍背景省下的 token 会以模型瞎猜的形式加倍偿还

判断标准始终是那一条:这个 token 是在帮助当前任务,还是在稀释注意力。

12. 分界线:说清楚事 vs 装对东西

最后一图流总结本章前后两半的关系:

Prompt Engineering(本章前半)      Context Engineering(本章后半)
─────────────────────           ─────────────────────────
设计对象:单条指令的质量            设计对象:窗口里的一切
关心:怎么"说"一件事              关心:什么该"进"窗口
静态:写好一个 prompt              动态:每轮实时组装
类比:写好一句广告词               类比:设计整个货架的陈列

提示词工程是必要条件(说清楚事),上下文工程是决定性因素(装对东西)。当 Agent 出问题,先查窗口(上下文工程),再改措辞(提示词工程)——顺序反了事倍功半。

本章小结

  • 定位:用最低成本逼近模型当前能力上限;调的是分布不是电路
  • 五模式:角色(行为暗示)、few-shot(最强力)、CoT(复杂任务结构化)、模板分区、任务分解
  • 反模式核心:形容词无锚点,示例才有;负面指令弱于正向;约束放首尾
  • 工程化:模板分离(StrictUndefined)、版本化、最小评测集
  • 趋势:咒语在贬值,系统化在升值;不变的是清晰的任务定义和好示例
  • 统一账本:上下文七组分(系统/工具/技能/记忆/历史/工具结果/当前输入),每组分的进出都是设计决策
  • 第一性原理:token 不只花钱,还稀释其他 token 的注意力——上下文是寄存器不是硬盘
  • 位置规律:核心约束放首尾,背景放中段
  • 工具结果三对策:入口截断 → 历史结果替换(保配对)→ 外置存储+句柄
  • 预算表:各组分配额+水位警报;没有预算的 Agent 只能被动等死
  • 与提示词工程的分界:说清楚事 vs 装对东西

上下文是"每一轮装什么",还有一层更大的结构在决定"整个系统怎么搭"——脚手架工程。

下一篇:14 · 循环、图与骨架工程 —— 模型只是发动机,真正决定 Agent 上限的是围绕它的工程骨架:工具、循环、反馈、错误恢复的系统设计。