第 4 章:系统提示词的拼装

个人公众号
1
2
3
Browser -> HTTP -> FastAPI -> Runner -> Agent -> [Prompt] -> ReAct -> LLM -> Tool -> Response
^
you are here

上一章 Agent 诞生了——模型、工具、记忆全部就位。但 Agent 还不知道自己是”谁”、能做什么、应该怎么回答用户。这些信息来自系统提示词。这一章,我们看看一堆 Markdown 文件是怎么被拼装成一段完整的提示词的。


问题

Agent 怎么知道自己是”谁”?它怎么知道自己能做什么、应该用什么语气说话?答案藏在三个 Markdown 文件里:AGENTS.mdSOUL.mdPROFILE.md。它们是怎么被读取、处理、拼装成最终发送给大模型的提示词的?

术语其实很简单

术语:System Prompt(系统提示词)
想象你给新员工的一份入职手册——上面写了你的职位、职责、行为规范。系统提示词就是 AI 的”入职手册”——它告诉大模型”你是谁、你能做什么、你应该怎么回答”。

术语:模板拼装(Template Assembly)
想象拼乐高积木——每个零件有自己的形状,按说明书拼起来就是一个完整的模型。模板拼装就是把多个文本片段按特定顺序拼接成最终文本。AGENTS.md 是一块积木,SOUL.md 是另一块,拼起来就是完整的提示词。

术语:工作区(Workspace)
想象你在公司里有一张办公桌——桌上有你的名片、个人物品、工作文件。工作区就是 Agent 的”办公桌”——在 QwenPaw 的房子(工作目录)里,每个 Agent 有自己的房间(工作区),房间里有身份文件(AGENTS.md)、个性设定(SOUL.md)、技能档案(PROFILE.md)。目录路径是 ~/.qwenpaw/workspaces/<agent_id>/

探索

三个文件,三种角色

Agent 的系统提示词由三个 Markdown 文件组成,每个有不同职责:

  • AGENTS.md:行为规范——你能做什么、不能做什么、怎么使用工具
  • SOUL.md:个性设定——你的性格、说话风格、价值观
  • PROFILE.md:技能档案——你掌握的具体技能和知识领域

这三个文件住在 Agent 的工作区目录里(默认是 ~/.qwenpaw/workspaces/default/agents/)。

拼装过程

上一章我们看到 __init__ 第三步调用了 _build_sys_prompt()。它最终调用 build_system_prompt_from_working_dir(),定义在 src/qwenpaw/agents/prompt.py

拼装流程(伪代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
def build_system_prompt_from_working_dir(working_dir, agent_id=None, ...):
# 1. 确定要加载的文件列表(默认:AGENTS.md, SOUL.md, PROFILE.md)
enabled_files = 从配置中读取 or PromptConfig.DEFAULT_FILES

# 2. 创建 PromptBuilder 并构建
builder = PromptBuilder(working_dir, enabled_files, ...)
prompt = builder.build()

# 3. 如果有 agent_id,在最前面加上身份标识
if agent_id:
prompt = "# Agent Identity\n\nYour agent id is `{agent_id}`...\n\n" + prompt

return prompt

PromptBuilder.build() 按顺序加载每个文件:

1
2
3
4
def build(self):
for filename in self.enabled_files: # AGENTS.md, SOUL.md, PROFILE.md
self._load_file(filename)
return "\n\n".join(self.prompt_parts) or "You are a helpful assistant."

如果所有文件都不存在,返回兜底的 "You are a helpful assistant."

文件加载的细节

每个文件的加载经过三步处理:

第一步:读取文件。文件不存在则默默跳过。读取时尝试多种编码(UTF-8、GBK、Latin-1),确保不同系统的文件都能正确读取。

第二步:剥离 YAML frontmatter。文件开头可能有 YAML 元数据:

1
2
3
4
5
6
---
summary: Agent behavior specification
read_when: always
---
# Your Role
You are a helpful assistant...

两个 --- 之间的内容被去掉,只保留正文——这是给开发者看的元数据,不是给 AI 看的。

第三步:条件过滤AGENTS.md 里可能包含条件块:

1
2
3
<!-- heartbeat:start -->
Heartbeat instructions: check your inbox every 30 minutes...
<!-- heartbeat:end -->

如果 heartbeat_enabled 为 True,保留内容(去掉注释标记);如果为 False,整个块被移除。memory 块的逻辑相同。

最终拼出来的提示词

假设三个文件都存在,最终结果类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Agent Identity

Your agent id is `default`.

# AGENTS.md

[Agent 的行为规范,心跳和记忆指令视配置保留或移除]

# SOUL.md

[Agent 的个性设定]

# PROFILE.md

[Agent 的技能档案]

每个文件被包在 # 文件名 标题下,各部分用空行分隔。

用户能自定义什么?

提示词是普通 Markdown 文件,用户可以直接编辑:

  • 修改 SOUL.md 改变 Agent 性格(从”正式”改成”幽默”)
  • 修改 PROFILE.md 增加技能描述
  • 修改 AGENTS.md 调整行为规范

Agent 在 rebuild_sys_prompt() 方法中会重新读取文件,修改后下次对话就生效。

实验

修改 SOUL.md 观察 Agent 行为变化:

  1. 打开 ~/.qwenpaw/workspaces/default/agents/SOUL.md
  2. 在末尾加一行:你总是用一句话回答问题。
  3. 保存,在浏览器发一条消息
  4. 改回原内容,再发一条消息

预期输出:修改后 Agent 倾向于用一句话回答;恢复后回到正常风格。

工程权衡

为什么用多个文件而非一个大文件?

一个 2000 行的 system_prompt.md 看似简单,但改性格时要在 2000 行里找相关部分。三个文件让职责分离——改性格改 SOUL.md,改行为改 AGENTS.md,改技能改 PROFILE.md。每个文件短小精悍。

为什么支持 YAML frontmatter?

frontmatter 存的是给开发者看的元数据(用途、何时使用),不是给 AI 看的。拼装时去掉它,省 Token、减少干扰。

常见误区

误区:系统提示词是硬编码在源码里的?

实际上系统提示词完全由工作区目录下的 Markdown 文件决定。用户可以随意编辑这些文件来定制 Agent 的行为,不需要改任何源码。QwenPaw 代码只是”拼装工”——读文件、去 frontmatter、按顺序拼接。

动手环节

任务:修改 Agent 的系统提示词,观察行为变化。

步骤

  1. 找到 ~/.qwenpaw/workspaces/default/agents/ 目录
  2. 用文本编辑器打开 SOUL.md
  3. 在末尾添加:你总是用三个字以内回答问题。
  4. 保存,在浏览器发一条消息(比如”介绍一下你自己”)
  5. 改回原内容,再发一条消息验证恢复

预期输出:修改后 Agent 回复明显变短;恢复后回到正常风格。

自检

  • 找到了工作区目录下的三个 Markdown 文件
  • 理解了三个文件的不同职责
  • 修改 SOUL.md 后观察到了 Agent 行为变化

系统提示词拼装好了,Agent 知道自己是谁了。接下来它要开始”思考”——什么是 ReAct 循环?为什么不是直接回答?下一章我们走进 Agent 的推理引擎。