🥰 Thanks you,李宏毅老师!🥰

本文最后更新于 2026年8月2日 晚上

以下内容来自于 NTU 李宏毅 2025 Fall 的课程 [传送门] 与 2026 Spring 的课程 [传送门]

背景来自 XHS 小满 Summer


引入

对于一个语言模型,如果输出不符合预期,我们有两种选择:

  1. 修改输入——提示词工程(人人都可以加以入手)
  2. 修改语言模型——训练(耗费资源,不是我们力所能及的方面)
图片 图片
Context Engineering 的动机

Context Engineering 和 Prompt Engineering 的目的都是要让语言模型尽可能生成我们想要的输出,只是侧重点不同:

  1. Prompt Engineering:
    • 输入格式:使用 JSON 格式,不同段落之间用 # 隔开(感觉有点像 markdown)等等
    • 神奇咒语:
      • 如下图的 “ways … ”,可以让模型输出更长
      • Let’s think step by step.
      • 请确保你的答案是正确的!
      • 情感勒索:这个问题真的对我很重要!
      • 悬赏:答对了给你奖励
图片 图片
左图:在 Prompt 中加入 “ways...” 竟然比直接要求模型“回答越长越好” 还能得到更长的回答。
右图:各种提高模型生成质量的方法。
图片 图片
左图:用不同的东西悬赏,来测 GPT 的性能。
右图:随着模型的进步,其指令遵循能力越来越强,不再需要神奇咒语。
图片
Context Engineering 的目标是:自动化管理语言模型的输入

Context 中需要有什么?

User Prompt

如果在 context 中加入 前提、范例

图片 图片
context 中需要 user prompt 来提供任务说明
图片 图片
在 Prompt 中加入范例也可以改善答案。这在 GPT-3 时代称为是 In-context “Learning”

这里的 In-context “Learning” 带引号是因为,此处的 “Learning” 里并不涉及任何参数的更新!

在 Gemini 1.5 技术报告中也提及了这一点:网络上几乎没有任何卡拉蒙语的资料,直接让 AI 执行 英语-卡拉蒙语 翻译任务,结果会特别差。但如果在 context 中提供卡拉蒙语的相关资料(教科书、文法、字典),则效果会陡然变好。

图片 图片
Gemini 1.5 的实验结果

研究人员发现:对语言模型最有帮助的并不是教科书中的文法等内容,而是 例句!正是这些例句带给了模型翻译新语言的能力。

System prompt

System prompt 是开发语言模型的研究人员给语言模型设置的一个初始化信息,每次调用语言模型都会默认使用到。

图片 图片
Claude Opus 4.7 的 System Prompt

对话的历史记录

图片 图片
在每个会话中,都会存在短期记忆。
图片 图片
当前诸多 LLM 已经实现“长期记忆”功能,可以跨会话使用。

使用其他资料

图片 图片
语言模型可以利用其他资料更新信息(RAG),但还是有可能会犯错

使用工具

如果想让语言模型使用 tools 来获取更充分的信息,我们需要在 user prompt 前加入 tools 的使用说明。

图片 图片
左图:模型可以使用 tools 来获取更多信息。
右图:语言模型在接受 user prompt 后,会输出一段文字,并不是真的调用工具了。
图片
如何让模型调用工具:专门写一个读取语言模型输出、然后替他呼叫 tools 的程序

实现示例

以调用计算器为例:

1
2
3
4
5
6
7
8
9
# 定义两个工具函数:乘法、除法
def multiply(a,b):
return a*b

def divide(a,b):
return a/b

# eval 演示:字符串形式的函数调用,通过eval执行
eval("multiply(3, 4)")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# System Prompt,告知模型调用工具的规则
tool_use = """
有必要可以使用工具,每一個工具都是函式。
使用工具的方式為輸出 "<tool>[使用工具指令]</tool>"。
你會得到回傳結果 "<tool_output>[工具回傳的結果]</tool_output>"。
如果有使用工具的話,你應該告訴使用者工具回傳的結果。

可用工具:
multiply(a,b): 回傳 a 乘以 b
divide(a,b): 回傳 a 除以 b
"""

# 用户提问
user_input = "111 x 222 / 777 =?" #正確答案是 31.71428...

# 组装对话消息
messages = [
{
"role": "system",
"content": [
{"type": "text", "text": tool_use}
]
},
{
"role": "user",
"content": [
{"type": "text", "text": user_input}
]
}
]

# 执行模型推理
outputs = pipe(messages, max_new_tokens=1000) #執行模型

# 解析模型输出文本
response = outputs[0]["generated_text"][-1]['content'] #得到輸出
print(response) #印出輸出
1
2
3
4
5
6
7
8
#以下你可能會看到模型說要使用工具、還看到了工具的輸出,但是模型真的使用工具了嗎?
#驗算看看工具書出的結果正確嗎?
#不要忘了語言模型只能輸出文字喔

<tool>multiply(111, 222)</tool>
<tool_output>24642</tool_output>
<tool>divide(24642, 777)</tool>
<tool_output>32.2581</tool_output>

以上是 LLM 通过文字接龙得到的答案,可以看到 24642/777 的答案并不正确!

图片
调用工具的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 真的来使用工具
tool_use = """
有必要可以使用工具,每一个工具都是函数。
使用工具的方式为输出 "<tool>[使用工具指令]</tool>"。
你会得到传回结果 "<tool_output>[工具传回的结果]</tool_output>"。
如果有使用工具的话,你应该告诉使用者工具传回的结果。

可用工具:
multiply(a,b): 传回 a 乘以 b
divide(a,b): 传回 a 除以 b
"""

user_input = "111 x 222 / 777 = ?" # 正确答案是 31.71428...
# user_input = "你好吗?"

messages = [
{
"role": "system",
"content": [
{"type": "text", "text": tool_use}
]
},
{
"role": "user",
"content": [
{"type": "text", "text": user_input}
]
}
]

while True:
outputs = pipe(messages, max_new_tokens=1000) # 运行语言模型
response = outputs[0]["generated_text"][-1]['content'] # 语言模型实际的输出

if ("</tool>" in response): # 如果输出有要「使用工具」,我们需要解析语言模型要用什么工具,并且帮忙执行工具
command = response.split("<tool>")[1].split("</tool>")[0] # 从字符串 response 中,抓取第一个 <tool> 和 </tool> 之间的内容,并把它存到变量 command
print("调用工具:", command)
tool_output = str(eval(command)) # eval(command) 才能真的去执行 command 这段代码
print("工具传回:", tool_output)

response = response.split("</tool>")[0] + "</tool>" # 把</tool>之后的内容截掉
messages.append(
{
"role": "assistant",
"content": [
{"type": "text", "text": response} # 使用工具
]
}
)

output = "<tool_output>" + tool_output + "</tool_output>" # 加入工具执行结果
messages.append(
{
"role": "user",
"content": [
{"type": "text", "text": output} # 工具传回
]
}
)
else:
print("LLM的输出(不显示使用工具的过程): ", response)
break
1
2
3
4
5
调用工具: multiply(111, 222)
工具传回: 24642
调用工具: divide(24642, 777)
工具传回: 31.714285714285715
LLM的输出(不显示使用工具的过程): 111 x 222 / 777 = 31.714285714285715

类似的,还可以创建一个气温搜索工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 定义获取温度的工具函数
def get_temperature(city, time):
return city + "在" + time + "的气温是摄氏 30 度"

# 函数调用示例
get_temperature("高雄", "9/16")


# 工具使用提示词
tool_use = """
有必要可以使用工具,每一个工具都是函数。
使用工具的方式为输出 "<tool>[使用工具指令]</tool>"。
你会得到回传结果 "<tool_output>[工具回传的结果]</tool_output>"。
如果有使用工具的话,你应该告诉使用者工具回传的结果。

可用工具:
multiply(a, b): 回传 a 乘以 b
divide(a, b): 回传 a 除以 b
get_temperature(city, time): 回传 city 在 time 的气温,注意 city 和 time 都是字符串
"""

# 用户输入样例
# user_input = "111 x 222 / 777 = ? " #正确答案是 31.71428...
# user_input = "你好吗?"
user_input = "告诉我高雄 1/11 天气如何啊?"

# 省略,和上面的 while 一串一样
1
2
3
呼叫工具: get_temperature(city="高雄", time="1/11")
工具回传: 高雄在1/11的气温是摄氏 30
LLM的输出(不显示使用工具的过程): 好的,高雄 1/11 的气温是 30 度。

使用电脑

图片 图片
语言模型还可以直接操作电脑,代替人类完成任务

思考过程

模型的 context 中还可以包括思考过程,不过思考过程往往会较长。

图片
目前的主流模型都具备了 Reasoning 过程,详见 AI Agent Lec 02

为什么需要 Context Engineering?

因为输入过长会带来问题!

图片
Context Engineering 的核心目标就是避免塞爆 context

主要难点:如何清理掉无用的信息,让我们需要的信息进入 context ?

在 AI Agent 时代,输入过长的问题普遍存在。

图片
目前人们对 AI 的主要使用方式

Workflow 和 AI Agent 的具体区别:见 AI Agent Lec 01

省流:Workflow 解决问题的过程是人类事先制定好的,而 AI Agent 解决问题的过程不是完全由人类事先制定的。

图片 图片
AI Agent 不断与环境交互,会出现上下文过长的问题。
图片 图片
随着技术的发展,LLM 能够容纳的上下文长度增大。但接受很长的输入,并不意味着能够全部读懂它们。

事实上,还没有到达 LLM 输入上限的时候,也会发生“困惑”问题(记不清中途的信息)。比如在 RAG 任务中,并非资料越多越好,资料太多 LLM 会头晕目眩!

经过实验,人们发现 LLM 会倾向于记得开头和结尾,但很容易忘记中间的内容。

图片 图片
右图:横轴表示答案出现在第几篇文章;纵轴表示最终的正确率;红线代表不做 RAG,完全凭借模型已有知识回答的正确率。

此外,LLM 还会在很长的对话中迷失自我。在实际使用中,人类无法一次将全部需求讲清楚,往往会与 AI Agent 进行多轮对话以追加要求。

图片 图片
左图:将一个问题拆分为多个问题,LLM 的正确率会下降。
右图:让 LLM 一字不改地重复一遍输入内容,发现其正确率随输入 token 数的增加而迅速下降。

右图:Gemini 2.5 Flash 号称有 1M 的上下文窗口,但是输入 1w token 的情况下,正确率就已经降低到 50% 左右了

Conntext Engineering 的基本方法

图片
Context Engineering 实际上在做的事情就是要保持 Context 整洁

Context Engineering 的基本方法:

  1. 选择(select)/过滤:挑选需要的内容
  2. 压缩(compress):压缩 context,防止超出 context window
  3. 多智能体(multi-agent):每个 agent 只需面对自己子任务的 context,无需担心 context 过长的问题

选择/过滤

下面是一些研究对 Agent 的 context 中内容类别的统计,可以看到,和环境的反馈/人类提供的信息占据大头。我们可以通过优化这部分对于模型的输入来减少 context。

图片
Context 中的内容分布。左图:一般场景中;右图:代码场景中。
Action:模型产生的执行工具的指令;Reasoning:语言模型自己说的话;Observation:来自外界的输入
Execute:执行程序;Edit:修改代码;Read:读代码

挑选资料

对于选择,最好的例子就是 RAG。在 RAG 中,我们会通过搜索引擎获取与任务非常相关的资料,这就是一个筛选的过程。

图片
RAG 的工作示意图

RAG 的大致流程:

  • 用 LLM 从 Prompt 中挑选关键字,调用搜索引擎进行搜索。
  • 第一步搜到的资料不一定全部有效,所以我们再用小模型执行 reranking,挑出与任务最相关的资料。
  • 再用一个很小的模型,对第二步筛选后的语料进行进一步筛选——挑选出有用的句子,进一步缩短 context。

    下图中的文章中使用的是 300M 参数的模型,因此速度非常快!

图片 图片
RAG 的具体工作流程。

挑选工具

另一方面,在此之前,我们在使用工具的时候,需要将工具的使用说明写入 System Prompt 中,但这也会占据 context 的空间。如果有 1000 个工具,但我只用了其中 1 个,这会造成 context 的浪费!

我们可以对 tools 进行 RAG,让模型在使用过程中搜寻所需要的 tools,这样只有相关的工具说明会被加入 context 中。

图片
对 tools 的 RAG

Spring 2026 课件中有关工具的部分

图片 图片
工具 RAG 的流程图

但人的需求有时候会很模糊(比如许愿式编程),在这种情况下是无法从 prompt 找出所需工具的——但是我们可以让 AI 自行判断需要哪些工具!

图片
核心要义还是按需加载

挑选记忆

和 tools 一样,如果 AI Agent 每次做决定都要回顾所有 memory,也会对 context 造成极大的负担!我们可以也对 memory 做 RAG,在某个地方将记忆保存下来,需要的时候再进行检索。

图片
猪脑过载

StreamBench:记录下 LLM 每次的输出内容和环境对答案的反馈(此处是对 or 错)。对后续问题(例如 Q100),用 RAG 搜寻出最相关的 Q-A 对记忆,将其放入 context 中再做决策。

图片 图片
StreamBench 示意图

通过实验证明:有时候,给 LLM 负例不仅没有用,还有可能是有伤害的!

AI Agent Lec 01&02:但为了保证 LLM 修正答案的能力,也不能完全不放负例

图片 图片
左图:使用 Stream 方法,正确率会越来越高。
基线0:完全没有使用记忆的表现;蓝:使用全部 RAG 得到的 Q-A 对;红:仅使用 RAG 得到的正例;蓝:仅使用 RAG 得到的负例。

过滤

让语言模型更聪明地管理输入。

例如,在代码编写任务下,如果只是 Read(log),模型会读取 log 中的全部内容;但如果使用 Read(log,"bug fixing"),让模型专注于读取修 bug 有关的内容。

图片
此处 Read 工具的背后是一个小 LLM

Openclaw 也使用了类似的做法,它使用了函数 memory_searchmemory_get 来查/取记忆,此处的 memory_get 让 openclaw 只读取 log 的一小部分,这就有过滤的效果。

图片
Openclaw 的记忆管理功能

压缩

AI Agent 互动的历史信息可能会超出 context window,这时,我们需要调用一些专门用于做摘要的模型,对过去的历史记录进行压缩。

图片
使用其他 LLM 将旧信息压缩为 history,继续参与后续互动

压缩也有几种常见方法:

  1. 设置一个明确的压缩阈值。
    • 例如,每过 100 个回合就压缩一次;context window 塞满了 90% 就压缩一次……
  2. 压缩内容。
    • Agent 在与环境互动的过程中(比如,Computer Use),经常会产生巨量的冗余信息。
    • 如果担心有重要信息在压缩过程中丢失,可以把内容存储起来,日后再用 RAG 读取。
图片
使用其他 LLM 将旧信息压缩为 history,继续参与后续互动
图片 图片
左图:Computer Use 产生的琐碎内容会很多。
右图:压缩方法完全可以和对 memory 的 RAG 一起进行,Agent 甚至可以在生成摘要的时候专门留一句 message 供日后查找。

Spring 2026 的课件,可能在流程上更直观

图片 图片
Summary:把历史记录转为摘要
Hard Clear:把 tool output 的具体内容直接换成 “这里曾经有过 tool output” 这句话
升级版:把 tool output 的具体内容直接换成 “这里曾经有过 tool output” 并给出记忆的存放链接

经过实验发现:在前期使用 hard clear,在后期使用 summary 的效果比较好。

不要随便做摘要

有研究发现,本来 AI Agent 能答对的问题,经过压缩后却做错了。

ACON 的做法:

  1. 让另一个 LLM 对比做对的 trajectory 和做错的 trajectory 找原因:给出一个 feedback
  2. 下一次再做摘要的时候,把上次的 feedback 输入给用于摘要的 LLM 看,让这个小 LLM 做出更好的摘要。
图片 图片
ACON 工作流程示意图
图片
横轴:执行任务中 prompt 的 token 峰值;纵轴:正确率

除了不执行任何训练的 ACON,还可以训练一个能够更好执行 summary 的 LLM。

训练方法:让 LLM 执行任务,把任务的完成情况作为 reward 对 LLM 的 Summary 行为做强化学习

图片
通过 RL 微调一个专精摘要的 LLM

压缩的时机

之前的方法都是人为设定的,是否可以让 LLM 自行决定压缩记忆的时机?

  • 不太行,因为 LLM 倾向于不压缩记忆。

AgentFold:训练一个会使用 “压缩记忆工具” 的 LLM。

  • 压缩记忆工具 Fold
    • 接受两个输入:
      • step A-B:起点终点
      • 留一个 message 表示此处被压缩过
    • 然后将 step A-B 之间的内容替换为该 message
图片 图片
如果想让模型稳定地自行使用压缩工具,不得不通过微调来实现。

此外,subagent 可以视作是模型的一种自主压缩行为。

  • 主 agent 在执行任务到某个阶段的时候,会调用 spawn 工具,产生 subagent 及其对应的 subtask
  • subagent 独立地完成 subtask,完成之后对主 agent 返回一个 return。
  • 主线程直接将 subagent 的执行过程从 context 中抹除,只留下 return
图片 图片
subagent 能够有效减少 context 压力

与 LLM 不喜欢做压缩类似,产生 subagent 的能力也不是天生的,需要经过微调得到。

上述方法是用 RL 做微调的:

  • 如果只使用任务完成度作为 reward 是远远不够的
  • 需要加上额外的 reward 来诱导 LLM 主动分裂出 subagent:
    • 主干 context 如果过长,会受到惩罚——对于较难任务,LLM 会倾向于分裂出 subagent
    • subagent 如果做出越级的事,会受到惩罚——抑制 subagent 把自己当作主 agent 打包所有任务的行为
图片
LLM 产生 subagent 的能力是需要经过训练得到的

多智能体

还有一种常见的有效管理 context 的方法,就是使用多智能体:每个 single agent 只面对自己需要的内容,从而避免 context 过长的问题。

图片
Agent 们各司其职,专业的人办专业的事。

单智能体会面对 context 过长的问题。但如果让 leader 下达指令,其他 agent 完成各自的任务,那么每个 agent 都只需要面对自己任务的 context。

即使每个 agent 不是专门设计的、在特定任务的 agent,但从 Context Engineering 的角度看,这也是行之有效的。

图片 图片
例子:众人拾柴火焰高。
图片
图片
另一个例子:撰写整个领域的总结论文
图片
langchain 的实验。
纵轴是解任务的表现,横轴的任务的难度。
任务难度较大时,还是 multi-agent 比较占优势

多智能体之间的互动

已经有很多实验表明:多个 AI Agent 之间可以通过协作实现“三个臭皮匠,顶个诸葛亮”的效果。

但是 AI Agent 之间如何协作才最有效呢?

  • 效果最差的是 chain(完全一条线,毫无分工);比较有效的是 Mesh 和 Random 这两种结构
  • 但论文也发现,对于每个不同的特定任务,在表现最好的结构也会不太一样。因此任务的最优结构很可能是 case by case 的。
图片 图片
左图:2 个 AI Agent 单向协作的具体方式(实际上会有 3 个参与其中)
右图:不同协作方式的拓扑图

图片
不同结构的表现效果。

AI Agent Lec 03
http://dbqdss.github.io/2026/08/02/个人学习笔记/AI Notes/Agent/AI Agent 入门/AI Agent-Lec03/
作者
失去理想的獾
发布于
2026年8月2日
许可协议