提示词工程 & 上下文工程

大模型提示词工程(Prompt Engineering)是什么?提示词技巧有哪些?
上下文工程(Context Engineering) 是什么?和 Prompt Engineering 有什么区别?

提示词工程

Prompt 该怎么写?

Prompt 一般四要素

要素 作用 常见表述
Role(角色) 告诉模型该用哪个领域的知识和语气 “你是一位 10 年经验的 Java 架构师”
Task(任务) 说明要完成什么动作 “请评审以下代码的性能问题”
Context(上下文) 补充和任务相关的背景 “当前线上 QPS 2000,响应时间超 500ms”
Format(格式) 规定输出长什么样 “输出 JSON,包含 bottleneck、solution 两个字段”

其实归根结底,就是给模型明确的边界与约束,告诉模型xxx之内的随便做,xxx之外的不能做,这个xxx就是要告诉模型的边界。

而在顺序上,为什么一般遵循 角色、任务、上下文、格式 呢?因为模型容易更加重视提示词的开头和结尾,所以对于需要严格定义的要素,一般放在开头和结尾。

Prompt 的长度

源于大模型 Transformer 的物理缺陷,上下文越长,重点越容易被稀释,所以我们在写 prompt 的时候要尽量用最少的长度表达最多的信息。

一条最佳 prompt 需要反复试错

写一版,跑几个 case,看边缘情况,再补约束。

Prompt 技巧

角色扮演

为什么要让 llm 角色扮演?本质也是渐进式披露的一种,通过赋予角色来告知 llm 你该聚焦于哪些领域。所以,重要的不是角色扮演,而是角色背后所隐含的任务目标、评价标准、关注维度和表达方式。

而类似 你是一个顶尖专家 这种,完全不含信息量的角色,一点作用都没有。

思维链 CoT

在早期的大模型时期,思维链的作用是激活LLM拆解任务的能力,这是受限于当时大模型普遍能力低下导致的问题,我们需要明文地诱导 LLM 去主动触发思考。

然而大模型发展到如今,思考越发成为大模型的一种基础能力(类似于工具意图),我们不再需要人为诱导,大模型本身已经具备主动将一个复杂问题进行拆分的能力。

然而,思维链却并没有消失,其价值已经发生了转变:我们不再为了激活思考能力使用思维链,而是为了约束行为。我们不再给出 试着将这个任务按照流程拆分成一个一个的顺序子任务,现在开始任务拆分... 这种为了激活 LLM 思考的语句,取而代之的是 你先分析需求,根据当前项目环境书写规划,然后执行,最后需要进行代码审查和联调测试 这种工作流式的语句。

在实践中,我们通常将思维链-工作流本身进行留存,作为可审查的 LLM 执行检查点。

不过,我们也可以说其价值本质上并没有转变,以前使用思维链强制拆分任务,是因为我们只能够保证 LLM 不出错地完成子任务;而现在我们使用思维链-工作流式强制流程,也是因为 LLM 已经能够不出错地完成一个独立大任务。在未来,也许当 LLM 能够完全不出错地完成任何一个级别的任务,思维链模式、或者是由人类定义工作流程的使用方法才会完全消失。

少样本学习

一项任务,给出一或多个实例,LLM 更容易按要求输出。

提示词与结构化输出

结构化输出是 agent 工作的重要环节,我们已经知道从大模型到模型网关会经过好几层兜底机制保证结构化输出的完备性,这个时候我们再思考一下作为源头的 prompt 是否会影响结构化输出?答案是会的。

如果我们要输出的结构化输出完全按照 schema、结构完全符合要求,最先可以想到在 prompt 里面给出 schema,这是完全没问题的,如果 LLM 智商过得去它能够很清楚地知道要按照这个 schema 输出。

或者,可以不依赖于 prompt,使用各个厂商的原生结构化输出模式,但是这个看厂商支持,有的话最好用,没有的话不强求。

另外,我们可以采用预填充,即在 prompt 末尾接上预想的结构化输出的开头,例如:

1
2
3
......
完成任务,并按照以上 Schema 输出 JSON 格式。
{

注意最下面的 {,由于 LLM 是基于 Transformer 的自回归生成,生成是基于 prompt 的,如果我们的 prompt 最后有一个 {,那么模型大概率会直接接在后面输出完整发 JSON 格式,这样就能有效排除 好的,我来帮你生成: 这种废话,直接返回后端就可以扫描转换。

举一反三,如果要输出 xml,prompt 后面加个 <response>

Prompt 安全

如果 prompt 注入、jailbreak 问题发生,导致 agent 误执行了非法指令,造成数据安全问题、数据损失等资损,就是 prompt 安全需要考虑的。

一般做三个层次:

  1. 执行层:沙箱隔离、权限控制;

  2. 认知层:着重理清 system prompt 和 user prompt 的区别,将 user prompt 用不可信分隔符隔开,且特别注意区分 user prompt 是否恶意;

  3. 决策层:不能让 agent 直接执行转账、删改数据、发邮件等敏感任务,必须要像大模型网关申请代理执行,而大模型网关则做兜底校验,必要时人工干预。

上下文工程

为什么还引入上下文工程

提示词工程是大模型还不强悍时期的产物,彼时 Agent 概念尚未成形,大家也没有产生 LLM 能够发展到足以解决超级大型复杂任务的心理预期,大家对于 LLM 的使用方法也倾向于单次或短程交互,提示词工程就是大家试图花很大精力去高质量地解决一个明确的问题,例如 给定io格式,写一个方法写一段脚本 等任务。

在有限轮次内的对话,我们可以保证 LLM 处于相对干净的上下文环境内,而即便发生劣化(信息之间互相矛盾、充满无关信息等问题,我将其称之为劣化),在极少的轮次内劣化程度也极低,所以提示词工程在一开始就没有考虑当对话轮次变多时上下文劣化的情况,因为没人会想到今天这种状况,事实上现在也有很多人也会在使用 agent 时保持这个习惯:上下文窗口占用变高,就立刻重开 session。

但随着 Agent 概念成形,长程任务成为常态,传统的提示词工程的缺陷被放大。提示词工程不是为了长程任务而生,所有内容全凭用户控制,但问题是用户的长期管理能力真的信得过吗?如何保证到最后拖后腿的不是用户本人?这样的工程实践下,反映出的问题就是 LLM 越到后面越蠢。此外,长程任务中同时聚集着海量不同类型的任务,每个任务都要让用户去构建最适合的提示词吗?这样的负担,还不如一开始就让用户自己干活。

所以,为了适应长程任务,上下文工程应运而生,它的设计哲学从一开始就是基于长程任务展开的,它不要求用户承担构建质量的责任,而是将责任移交到了 Agent 本身,Agent 从各种权威信源中抓取信息,构建成最适合当前任务的 prompt,也就是所谓的 context,以此保证每次的输入都是干净的。

这样的设计,也将用户彻底解放了,用户只需要给定较为详细的任务描述,不需要绞尽脑汁想提示词,用户能够将精力放在业务上,而非如何调整一个工具到最佳状态。

Context 的本质

就是每次输入给 LLM 的 prompt。

以 pi agent 为例,每次用户键入请求,到开始调用 LLM 之间,agent 先搜索与用户任务最适合的上下文,结合系统提示词、用户输入、工具调用结构等内容,拼接成一个结构严谨的 prompt,作为真正调用 LLM 的 prompt,换言之就是一个 str。

如果以单次交互看,Context 包含了 System Prompt User Prompt Memory Tools Structured Schema 等要素,比传统提示词工程的设计更为细致规范;而在交互持续迭代的情况下,Context 的内容也会持续增长,在先前上下文的基础上,增添后续任务的上下文,让 LLM 在每一轮交互中始终能够理解上下文。

这就是为什么在上下文工程中,要称 Context 而非 Prompt,因为 Agent 始终能够保证,LLM 当前能够读到的内容就是我们想给他看的

但是,如果仅从这个角度看,提示词工程不是也能保证 LLM 的上下文完全由我们掌控吗?这只是客观上达成的效果,提示词工程的设计哲学还是围绕任务本身展开的,换言之,掌握上下文的最终目的还是保证任务本身的顺利执行。

这是两种设计哲学的区别,并不代表二者完全不同,反而二者的关联大于区别。

Context 失效

Context Rot、Context Drift,基本都是由于 Context 长度过大导致的。

不过这是 Transformer 的底层限制导致的缺陷,即便是上下文工程也不能根除,只能减缓。

最佳 Context 需要反复试错

怎么评估上下文工程有没有变好?这个问题和前面的 “一条最佳 prompt 需要反复试错” 这个问题差不多,都是需要反复试错的。

用 20 到 50 条真实任务轨迹做个小评测集,然后更改上下文变量跑测试,重点关注以下指标:

指标类型 具体看什么
任务成功率 是否完成目标、是否需要人工补救、是否能稳定复现成功路径
工具质量 错选工具、漏调工具、参数错误、重复调用、危险操作拦截率
上下文成本 输入 Token、输出 Token、缓存命中率、压缩后信息保留比例
延迟指标 首 Token 延迟、端到端耗时、工具等待时间、p95 / p99 响应时间
结果质量 幻觉率、证据引用准确率、摘要丢失率、关键字段遗漏率

Context 的构建

预检索

接下来是关于 Context 加载的问题,在一般的 agent,比如各种聊天机器人、问答系统,Context 的构建用的都是预检索:在调用 LLM 之前,根据用户的 query,在记忆系统中检索相关信息,再将其与另外几个要素共同构成 Context 发往 LLM。比如:

1
输入 query -> Embedding 检索出信息 info -> query + info + ... -> LLM -> 输出 response

预检索对于简单问答场景来说够用,但到了复杂 Agent 任务里就暴露问题了。原因在于很大 Agent 并非简单的直接输入 LLM,LLM 再输出最终回答,而是 Agent 需要多次调用 LLM,才能一步步得到最终回答,就比如 Coding Agent:

1
2
3
4
5
6
7
8
9
10
11
12
13
输入 query
|
Agent 调用 LLM
|
LLM 激活工具意图 <——————————————————
| |
工具意图返回 Agent 调用工具 |
| |
工具 result 返回 LLM ———————————————
|
LLM 处理所有信息
|
输出最终 response

即时检索

这个时候就需要即时检索了。简单来说,即时检索预设 Context 是需要 LLM 参与共同建设的。为了完成一件任务所需要的全部上下文,很多时候都不一定包含在 Context 内,LLM 才需要反复与 Agent 直接通信,确认 “你有没有这些上下文?”,Agent 需要出示全部所需的上下文,一直到 LLM 认为上下文足够后,才进行最终回答。

思想基于按需加载渐进式披露,前者保证不加载无关信息只加载相关信息,后者则保证 LLM 知道哪些是相关信息。

cc 和 pi agent 的检索,就是将文件统一索引存储,每个文件的路径、名称提供该文件的作用,然后 Agent 才能按需精准查找。

当然,任何与即时有关的都离不开一个问题:性能,不过对于即时检索而言,性能反而只是最小的问题。最大的问题在于,极其依赖工程师提供好用的导航工具、需要准确的引导规则。

混合检索

更现实的做法,是将二者结合,重要度高、检索频次高、相对稳定很少修改的信息,就使用预检索;对于其他信息就采用索引化,按需加载。

Context 的维护

Compact

Context 长度高于模型上下文窗口时,百分之百出现问题,所以绝对不能让 Context 太长,在接近上下文窗口的时候就要考虑压缩了。

cc 的压缩思路是:把历史消息交给模型做摘要,保留架构决策、未解决 Bug、关键实现细节,丢掉冗余的工具调用结果,然后拿着压缩后的上下文再加上最近访问的 5 个文件,继续工作。

Pi 的做法类似,区别在于其会保留历史会话 jsonl 中最近的一段长度为 20k 的原文参与后续工作。

Note

Note 顾名思义,就是 Agent 自己写的笔记,一般是持久化存储当前任务进展,比如进入到了哪一步?哪一步发生了问题需要重试?等信息。

可以注意到,cc、pi、codex 等 Agent 都会自发维护很多文档,这些其实就是 Note。

Subagent

Subagent,让 Main Agent 给 subagent 干净的上下文,和专门的沙箱环境,并行执行不同的任务,Main Agent 更容易从实现需求中抽离,集中注意力对任务进展进行管理。

但到底用不用 Sub-agent,还得看任务能不能拆分、子任务之间依赖强不强、汇总阶段会不会丢证据。

Context 落地:以 PI Agent 为例

前面已经提到了,Context 应该包含 System Prompt User Prompt Memory Tools Structured Schema 等要素,而一个合格的 Context,不仅要求各个要素的内容合理,要素之间的编排也要合理。

我们以 pi 为例,pi 的上下文设计突出的就是一个简洁原生态,很适合用来理解 Agent 的 Context,其主要分为三大部分:

  1. systemPrompt(系统提示词)

  2. messages(对话历史)

  3. tools(工具 schema)

当然,会有一些动态修改点,一般与 pi 的 Extension 相关,默认配置的 pi 不必理会。

System prompt

以 pi agent 的系统提示词为例:

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
You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.

Available tools:
${toolsList} ← 所有工具,pi 默认的工具有以下四个:
- read: <工具一行简介,来自工具定义>
- bash: <工具一行简介>
- edit: <工具一行简介>
- write: <工具一行简介>

In addition to the tools above, you may have access to other custom tools depending on the project.

Guidelines:
${guidelines} ← guideline,就是告诉 Agent 具体怎么工作,例如:
- Be concise in your responses
- Show file paths clearly when working with files

Pi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):
- Main documentation: ${readmePath}
- Additional docs: ${docsPath}
- Examples: ${examplesPath} (extensions, custom tools, SDK)
- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory
- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md), environment variables (docs/environment-variables.md)
- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing
- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;

{$appendSection}

<project_context> ← 仅当发现 AGENTS.md / CLAUDE.md(当前目录及祖先目录)
<project_instructions path=".../AGENTS.md">
<文件全文>
</project_instructions>
</project_context>

{formatSkillsForPrompt(skills)} ← 仅当有 skills 且 read 工具可用

Current working directory: ${promptCwd}

pi 的系统提示词非常简短,几乎只有一句话:You are an expert coding assistant,而后面还有补充说明 Agent 应该干的事情:You help users by reading files, executing commands, editing code, and writing new files.,这就是有效的角色扮演,用简短的语句告诉 LLM 你该干什么。

接下来是 toolslist,是引用式的,只给出了 namedescription 这两个基本属性,工具的所有详细信息,比如 Schema,全部在下方给出。

Messages

Pi 的 Messages 是存在 session.jsonl 里面的,以 jsonl 格式存储。这是我在一次真实对话中,获得的一次较为简洁的 jsonl 文件,将其转为 json :

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
[
{
"type": "session",
"version": 3,
"id": "01a0136e-ba6c-7b7b-8205-ba7f1767f298",
"timestamp": "2026-08-18T05:53:38.924Z",
"cwd": "C:\\Users\\a1829\\Desktop\\pi"
},
{
"type": "model_change",
"id": "94db3e22",
"parentId": null,
"timestamp": "2026-08-18T05:53:39.119Z",
"provider": "deepseek",
"modelId": "deepseek-v4-flash"
},
{
"type": "thinking_level_change",
"id": "6f707f7c",
"parentId": "94db3e22",
"timestamp": "2026-08-18T05:53:39.119Z",
"thinkingLevel": "max"
},
{
"type": "custom",
"customType": "pi-deck-todo",
"data": {
"todos": [],
"nextId": 1
},
"id": "edb88bc9",
"parentId": "6f707f7c",
"timestamp": "2026-08-18T05:53:39.121Z"
},
{
"type": "message",
"id": "54f05dc6",
"parentId": "edb88bc9",
"timestamp": "2026-08-18T05:57:42.054Z",
"message": {
"role": "user",
"content": [
{
"type": "text",
"text": "我正在学习pi的session.jsonl的结构,本次交互中你只能read一次目录下的README.md,然后总结内容告诉我,也就是说,本次的交互流程应该是:```我输入->llm启动->返回工具意图->内核使用工具,返回结构->llm第二次启动->向我输出总结->结束```,这样我能很清晰地看到session.jsonl里面的简洁内容。"
}
],
"timestamp": 1787032662053
}
},
{
"type": "message",
"id": "3410567f",
"parentId": "54f05dc6",
"timestamp": "2026-08-18T05:57:45.863Z",
"message": {
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": "The user is learning about pi's session.jsonl structure. They want me to:\n1. Only use `read` once on the README.md in the current directory\n2. Then summarize the content to them\n\nWait, let me re-read. \"本次交互中你只能read一次目录下的README.md\" - \"In this interaction, you can only read the README.md in the directory once\"\n\nHmm, but which README.md? The current working directory is C:/Users/a1829/Desktop/pi. Let me check what's in there. Actually, the user says \"目录下的README.md\" - README.md in the directory (current working directory presumably).\n\nWait, but the project context says the AGENTS.md is at C:\\Users\\a1829\\Desktop\\pi\\AGENTS.md. So the cwd is the pi repo root. There should be a README.md there.\n\nThe user wants me to:\n1. Read README.md once (only once)\n2. Summarize the content\n3. Not do anything else with tools\n\nThis is to demonstrate the session.jsonl flow: user input -> llm start -> tool intent returned -> kernel uses tool, returns structure -> llm second start -> output summary -> end.\n\nSo I should do exactly one read call, then summarize. Let me do that.\n\nActually, should I check if README.md exists first? The user explicitly said I can only read once. So I should just read it. If it doesn't exist, the error would be informative too, but let me just try reading it.\n\nThe file might be large. Let me just read it once.",
"thinkingSignature": "reasoning_content"
},
{
"type": "toolCall",
"id": "call_00_HqSiet6TDVJULMCZQdEG3395",
"name": "read",
"arguments": {
"path": "C:/Users/a1829/Desktop/pi/README.md"
}
}
],
"api": "openai-completions",
"provider": "deepseek",
"model": "deepseek-v4-flash",
"usage": {
"input": 160,
"output": 393,
"cacheRead": 22784,
"cacheWrite": 0,
"reasoning": 338,
"totalTokens": 23337,
"cost": {
"input": 0.000022400000000000002,
"output": 0.00011004000000000001,
"cacheRead": 0.0000637952,
"cacheWrite": 0,
"total": 0.0001962352
}
},
"stopReason": "toolUse",
"timestamp": 1787032662054,
"responseId": "b726f8ab-b209-4661-8575-6f66389443a0"
}
},
{
"type": "message",
"id": "b4226897",
"parentId": "3410567f",
"timestamp": "2026-08-18T05:57:45.866Z",
"message": {
"role": "toolResult",
"toolCallId": "call_00_HqSiet6TDVJULMCZQdEG3395",
"toolName": "read",
"content": [
{
"type": "text",
"text": "<p align=\"center\">\n <a href=\"https://pi.dev\">\n <img alt=\"pi logo\" src=\"https://pi.dev/logo-auto.svg\" width=\"128\">\n </a>\n</p>\n<p align=\"center\">\n <a href=\"https://discord.com/invite/3cU7Bz4UPx\"><img alt=\"Discord\" src=\"https://img.shields.io/badge/discord-community-5865F2?
......(此处省略)"
}
],
"isError": false,
"timestamp": 1787032665865
}
},
{
"type": "message",
"id": "d873db64",
"parentId": "b4226897",
"timestamp": "2026-08-18T05:57:50.593Z",
"message": {
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": "The user wanted me to read README.md once and summarize. I did that. Now I summarize the content. Keep it concise, in Chinese since the user wrote in Chinese.",
"thinkingSignature": "reasoning_content"
},
{
"type": "text",
"text": "已读取完毕,这是 `README.md` 的内容总结:\n\n**项目简介**:这是 Pi Agent Harness 项目仓库,包含一个可自我扩展的编码 agent。\n\n**核心包**(3 个 npm 包):\n- `@earendil-works/pi-coding-agent`:交互式编码 agent CLI\n- `@earendil-works/pi-agent-core`:agent 运行时(工具调用 + 状态管理)\n- `@earendil-works/pi-ai`:统一多提供商 LLM API(OpenAI、Anthropic、Google 等)\n- (另有 `pi-tui`:带差异渲染的终端 UI 库)\n\n**权限与容器化**:Pi 没有内置权限系统,默认以启动它的用户权限运行;如需隔离,可用 Gondolin 扩展、Docker 或 OpenShell 三种沙箱模式。\n\n**开发命令**:`npm install --ignore-scripts`、`npm run build`、`npm run check`(lint/format/typecheck)、`./test.sh`(跑测试)、`./pi-test.sh`(从源码运行)。\n\n**供应链安全**:直接依赖精确锁定版本、lockfile 为依赖事实依据、pre-commit 阻止意外的 lockfile 提交、发布前用 `release:local` 在仓库外做冒烟测试、CI 用 `npm ci --ignore-scripts` 并定时跑 npm audit。\n\n**开源会话共享**:鼓励分享 OSS 编码会话,可用 `badlogic/pi-share-hf` 发布到 Hugging Face,作者自己在 [badlogicgames/pi-mono](https://huggingface.co/datasets/badlogicgames/pi-mono) 发布会话。\n\n**其他**:MIT 协议;新贡献者的 issue/PR 默认自动关闭,维护者每日审查;学习资料见 pi.dev。\n\n---\n\n这就是本次交互在 session.jsonl 中的记录结构:你的输入 → LLM 第一次调用(生成了 `read` 工具调用意图)→ 内核执行工具并返回结果 → LLM 第二次调用(基于结果生成总结)→ 输出给用户。你可以打开 session.jsonl 对照查看这几次消息的记录格式。"
}
],
"api": "openai-completions",
"provider": "deepseek",
"model": "deepseek-v4-flash",
"usage": {
"input": 1726,
"output": 510,
"cacheRead": 23296,
"cacheWrite": 0,
"reasoning": 36,
"totalTokens": 25532,
"cost": {
"input": 0.00024164000000000002,
"output": 0.0001428,
"cacheRead": 0.0000652288,
"cacheWrite": 0,
"total": 0.0004496688
}
},
"stopReason": "stop",
"timestamp": 1787032665866,
"responseId": "9c1da671-a712-4295-83b4-5acd2a967b43"
}
}
]

我在这次交互中,明确让 Agent 只能 read 一次文件,目的是让 session.jsonl 尽量简洁,我们可以看到从其结构上看到交互过程:

  1. 首先是我们输入 prompt;

  2. 先调用 llm,让其思考,得到的是思考的过程,以及一次工具调用;

  3. 工具调用结构;

  4. llm 拿到工具调用结果后,完成了我们的任务

同时,我们可以看到 session.jsonl 的结构,几乎都是由llm调用结果工具结果构成的,输入和最终输出只占很少一部分。

另外,session.jsonl 最上方 session model thinking_level custom 不会进入 Context。

那么,pi 是怎么将 jsonl 转为 context 的?有没有经过非常严苛的检索过滤机制?这里说一下:几乎没有,pi 在处理 jsonl 时,几乎不会更改里面的内容,只会在结构和格式上进行修改,不过主要目的也只是将 jsonl 改为 json 方便 LLM 阅读。唯一值得说道的是 pi 的 compact 机制,pi 在读取 jsonl 时,只会保留最近一次的 compact 记录,和后续的 message,以及 compact 记录前 20k 的原文,具体机制在我之前的文章有提及。

Tool Schema

Pi 默认激活 4 种工具,举一个例子 read 的 Schema 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "read",
"description": "Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images aresent as attachments. For text files, output is truncated to 2000 lines or 50KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.",
"input_schema": {
"type": "object",
"properties": {
"path": { "type": "string", "description": "Path to the file to read (relative or absolute)" },
"offset": { "type": "number", "description": "Line number to start reading from (1-indexed)" },
"limit": { "type": "number", "description": "Maximum number of lines to read" }
},
"required": ["path"]
}
}

Pi 种只保留了四种工具,且每种工具都只包含一个单一功能,工具直接没有直接的功能重叠,这有助于 LLM 立刻判断应该用什么工具,而不会花费精力去横向对比,这也是一个工具定义的一个哲学。

另外,工具 Schema 的书写也有说法,这就又是八股了,pi 的 Schema 写法还是非常标准的,一个工具只做一件事,工具的参数、类型、哪些参数是必要的等。

总结

Pi 的 Context 设计,高情商:遵循简洁、Agent 不过多干预的思想;低情商:毛坯房。不过,用于学习 Context 确实是很友好的,我们能够完全明白和掌握每次交给 LLM 的 Context,而因为没有其他组件的条条框框,Pi 的 Context 也很适合我们进行拓展和客制化。