我是安徽最忧郁程序员无隅

目录
-
- 前言
- 一、为什么要替换默认人设
- [二、系统提示词怎样进入 Session](#二、系统提示词怎样进入 Session)
- [三、用 `systemPromptOverride` 定义 DataAgent](#三、用
systemPromptOverride定义 DataAgent) - 四、按用户装配提示词,并守住权限边界
前言
用 Pi Agent SDK 做企业数据分析助手时,模型可能仍按"编程助手"的习惯回答:谈文件、命令和代码,却没有先核对业务数据。问题往往在系统提示词的基础人设。本文以 Pi Agent
v0.87.1为准,沿着"看清提示词来源 → 替换人设 → 按用户装配"的顺序,做出一个简化的 DataAgent,并说明提示词与真实数据权限的边界。
一、为什么要替换默认人设
Pi Agent 的默认系统提示词把模型定义为编程助手,适合读文件、执行命令、改代码。企业数据分析场景期待的行为却不同:面对"上月销售额下降了 15%,为什么",助手应该先辨认这个数字是否可靠、比较口径是否一致,再提出可验证的原因。如果直接给出"某产品销量下滑"这样的确定结论,就把猜测当成了事实。
系统提示词会影响模型如何理解自己的职责,因此替换基础人设是定制垂直 Agent 的第一步 。它能约束回答方式,却不能凭空提供真实销售数据,也不能替代数据库的访问控制。Pi v0.87.1 的提示词构建源码可以看到默认编程助手前缀和自定义前缀的分支。
二、系统提示词怎样进入 Session
替换之前,先分清"基础人设"和其他来源。下面这张图按 Pi v0.87.1 的构建顺序概括五个主要部分;图中的箭头表示段落组合顺序 ,不是五个运行步骤。构建逻辑、资源加载逻辑

第一段是基础人设。资源加载器先找可用的自定义内容:代码传入的 systemPromptOverride 优先;没有覆盖时,可使用显式 systemPromptSource,或自动发现的项目级 .pi/SYSTEM.md、全局 SYSTEM.md。这些都没有时,构建器才使用默认编程助手前缀。项目级文件只有项目被信任时才参与自动发现;全局文件会影响使用同一全局配置目录的会话。因此,多项目并行开发时,优先考虑代码覆盖或项目级文件。资源加载器源码
后面几段各有职责:
| 部分 | 常见来源 | 应检查什么 |
|---|---|---|
| 追加规则 | .pi/APPEND_SYSTEM.md、全局同名文件,或代码覆盖 |
是否混入了与业务角色冲突的规则 |
| 项目上下文 | 工作目录及其父目录、全局配置目录中的 AGENTS.md / CLAUDE.md |
是否带入了代码仓库的开发指令 |
| 技能摘要 | 已加载的技能 | 只有启用 read 或 bash 等可读取技能文件的工具时才会加入 |
| 工作目录 | Loader 的 cwd |
模型是否需要知道目录;路径是否适合暴露 |
这里最容易误解的是"覆盖"二字。systemPromptOverride 改的是基础人设 ;追加规则、项目上下文、技能和工作目录仍按各自条件构建。若要做职责清晰的业务 Agent,应该检查实际加载了哪些来源,而不是假定覆盖后整份系统提示词只剩自己写的两句话。系统提示词源码
三、用 systemPromptOverride 定义 DataAgent
下面是一个最小的 SDK 示例。运行前需要有可用模型;示例用 noTools: "all" 关闭工具,先单独观察提示词对回答方式的影响。保存为 04a-replace-prompt.ts,在已安装 @earendil-works/pi-coding-agent@0.87.1 和 tsx 的项目中运行 npx tsx 04a-replace-prompt.ts。官方自定义提示词示例
typescript
import {
createAgentSession,
DefaultResourceLoader,
getAgentDir,
ModelRuntime,
SessionManager,
} from "@earendil-works/pi-coding-agent";
const runtime = await ModelRuntime.create();
const model = (await runtime.getAvailable())[0];
if (!model) throw new Error("请先配置可用模型");
const loader = new DefaultResourceLoader({
cwd: process.cwd(),
agentDir: getAgentDir(),
systemPromptOverride: () => `你是企业数据分析助手。
用户提供的数字未经核实;没有数据就不下确定结论。
最多列出三个可能原因,每个原因附一个验证方法。`,
appendSystemPromptOverride: () => [],
});
await loader.reload();
const { session } = await createAgentSession({
model,
modelRuntime: runtime,
resourceLoader: loader,
sessionManager: SessionManager.inMemory(),
noTools: "all",
});
try {
session.subscribe((event) => {
if (event.type === "message_update" &&
event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("上月销售额下降了 15%,可能的原因有哪些?");
console.log();
} finally {
session.dispose();
}
理解这段代码,抓住三处数据流即可。systemPromptOverride 返回新的基础人设;appendSystemPromptOverride: () => [] 把 Loader 发现的追加规则清空;resourceLoader: loader 把这份配置交给 Session。reload() 先完成资源加载,随后创建会话。资源加载器源码、SDK 说明
观察回答时,重点看它是否把"下降 15%"当作用户提供、尚待核实的前提,是否区分可能原因与已证实原因,是否给每个原因提供数据验证路径。即使回答符合这些要求,也只验证了回答方式;没有真实业务数据,就无法验证原因是否正确。 示例关闭了工具,因此也不会调用数据库或把技能摘要加入系统提示词。
如果人设是固定文本,也可以把它放在项目的 .pi/SYSTEM.md。这适合静态角色说明。需要按用户、部门或会话状态生成不同内容时,代码覆盖更直接,因为返回值可以在创建会话前由程序拼装。
四、按用户装配提示词,并守住权限边界
同一个数据分析助手服务销售部和财务部时,人设与工作规则大体相同,用户身份和允许讨论的数据范围却不同。把稳定内容放在文件中,把动态信息从已认证的服务端上下文取出,最后组合成一份提示词。这张图上半部分表示提示词的两条输入支路;下半部分是独立的数据访问链路。

例如,prompts/analyst/ 下放三份文本:persona.md 写"你是谁",rules.md 写分析规则,output-format.md 写回答格式。读取文件并取得当前用户后,关键拼装代码可以写成:
typescript
import { readFile } from "node:fs/promises";
import { join } from "node:path";
const dir = join(process.cwd(), "prompts", "analyst");
const [persona, rules, outputFormat] = await Promise.all([
readFile(join(dir, "persona.md"), "utf8"),
readFile(join(dir, "rules.md"), "utf8"),
readFile(join(dir, "output-format.md"), "utf8"),
]);
// user 必须由服务端认证与权限系统提供;这里仅展示拼装位置。
const user = { department: "销售部", dataScope: "本部门销售数据" };
const fullPrompt = [
persona,
`## 当前用户\n部门:${user.department}\n数据范围:${user.dataScope}`,
rules,
outputFormat,
].join("\n\n");
const loader = new DefaultResourceLoader({
cwd: process.cwd(),
agentDir: getAgentDir(),
systemPromptOverride: () => fullPrompt,
appendSystemPromptOverride: () => [],
});
await loader.reload();
这里的 Promise.all 让三份静态文件并行读取;fullPrompt 则把它们与当前用户信息组合起来。示例中的 user 是演示数据。真实服务中,应在认证后取得用户身份及授权范围,并为该用户的请求构建对应会话;不要把前一位用户的提示词或会话状态复用到下一位用户。
"只讨论本部门数据"写进提示词,属于模型行为约束。 当 Agent 能调用数据工具时,服务端仍须根据认证身份校验查询条件,并让数据工具只返回被授权的数据。模型可能误解或忽略文字规则,权限判断应发生在真正读取数据之前。提示词可以解释边界,实际的数据访问必须由程序执行。
最后看一个可选细节。Pi v0.87.1 将工作目录放在结构化的 cwd 段中。如果业务 Agent 不需要向模型展示目录值,可以在 before_agent_start 钩子中把本轮的 event.systemPromptOptions.cwd 设为空字符串;这会清空目录值,但可能留下空的 <cwd> 段 。它也不会改变工具实际使用的工作目录,后者仍由 Loader 的 cwd 决定。原材料中针对 v0.83 整段字符串做正则替换的示例,不应直接照搬到这个版本。系统提示词构建源码、扩展事件源码
到这里,DataAgent 已有自己的角色说明,也能组合不同用户的上下文。下一步若要让它分析真实销售数据,需要给 Session 接入受控的数据工具,并在工具层落实查询权限。