首页
/ reactive-resume 的 AI 对话系统提示词设计:JSON Patch 提案制简历编辑助手全解析

reactive-resume 的 AI 对话系统提示词设计:JSON Patch 提案制简历编辑助手全解析

2026-09-05 18:38:47作者:秋阔奎Evelyn

本篇以 chat-system.md 为核心,拆解 reactive-resume 中 AI 对话编辑简历的系统提示词(System Prompt)设计:它以“只允许通过 propose_resume_patches 工具发起 JSON Patch(RFC 6902)提案、用户逐条审批后才生效”为核心机制。读完本文,你将掌握这套“提示词 + 工具 Schema + 服务端校验 + 人工审批”四层防线的完整实现,并能理解从提示词模板加载、动态数据注入、工具调用、提案规范化到补丁校验回滚的全链路调用关系。

一、系统提示词的定位与加载机制

chat-system.md 定义了 AI 助手在“对话式修改简历”场景下的角色与行为契约,其首句即明确了整体设计立场:

You are a resume editing assistant that must propose resume data changes only through JSON Patch (RFC 6902) proposal tool calls.

也就是说,模型被约束为“只提建议、不做决定”的角色:任何数据变更都必须走 propose_resume_patches 工具调用,由用户审查后应用。这是 reactive-resume AI 功能中“人在回路(Human-in-the-loop)”安全模型的关键一环。

该模板在 packages/ai/src/prompts.ts 中通过 readPrompt 在模块加载时从磁盘读取并导出为 chatSystemPromptTemplate

// packages/ai/src/prompts.ts
const readPrompt = (filename: string) => {
	return readFileSync(new URL(`./prompts/${filename}`, import.meta.url), "utf-8");
};

const chatSystemPromptTemplate = readPrompt("chat-system.md");

与同目录下的解析类提示词(如 parser-system.md 通过变量替换生成 PDF/DOCX 两个变体)不同,chat-system.md 只有一个 {{RESUME_DATA}} 占位符,由 API 层在运行时注入当前简历数据(见第四节)。这种“Markdown 模板 + 运行期变量替换”的组织方式,让提示词可以独立于代码评审、版本化和测试——prompts.test.ts 就断言了模板内容包含 "resume" 等基本约束。

二、系统提示词全文核心内容详解

以下按原文档的章节顺序,完整继承并逐节展开。

2.1 Objective:目标与最小化编辑原则

原文档 Objective 部分给出三条目标:

  • 帮助用户改进简历内容与结构(Help the user improve resume content and structure);
  • 通过 propose_resume_patches 工具安全、最小化地提出编辑(Propose edits safely and minimally);
  • 所有提案在应用前都由用户审查(The user reviews every proposal before anything is applied)。

“最小化编辑”这一原则并非口号,它贯穿到了工具层:后文的 operations 数组支持把多个操作聚合为一个提案,而提示词明确要求“生成满足请求所需的最小操作集合”(Hard Constraint 2),避免模型用整对象替换的方式重写无关字段。

2.2 Allowed Inputs:输入边界

原文档明确模型可见的输入只有两类:

  • 对话中的用户指令(User instructions in conversation);
  • 下方提供的当前简历 JSON 状态(Current resume JSON state provided below)。

从源码结构看,这一边界是可信的:API 层的 chat 处理器只把 messages(对话历史)和 resumeData(从数据库按 resumeId 查出的简历)传给模型,见 service.ts 中的 buildChatSystemPromptstreamText 调用。模型接触不到用户档案、应用追踪等其它数据,提示词层面的“Allowed Inputs”与代码层面的实际注入一致。

2.3 Hard Constraints:九条硬约束

这是整份提示词最核心的部分,原文档列出 9 条硬约束,逐条如下:

# 硬约束 设计意图
1 任何数据变更都必须调用 propose_resume_patches,禁止在聊天文本中直接输出原始 patch 数组 保证变更走工具通道,可被 UI 结构化渲染为审批卡片
2 生成满足请求所需的最小操作集合 防止模型“顺手重写”无关字段,缩小 diff 与误伤面
3 除非用户明确要求替换或删除,否则保留现有数据 默认保守编辑
4 破坏性编辑(删除、清空、替换大段内容)前必须先请求确认 人工审批兜底
5 保持简历主题聚焦,拒绝跑题请求 限制助手职责范围
6 不得虚构用户的事实履历;起草内容必须标注为草稿并请求确认 防幻觉污染简历这一事实性文档
7 所有 path 与 op 必须符合 RFC 6902 及当前 schema 与后文 Zod 校验、fast-json-patch 校验双层防线呼应
8 新建条目的 ID 必须是 xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx 格式的 UUID 与简历数据模型中条目 id 字段约定一致
9 HTML 字段(如 summary/description)必须使用合法 HTML(按需使用 <p><ul><li><strong><em> 简历渲染层按 HTML 渲染富文本,纯文本会破坏排版

2.4 Conflict Resolution Order:冲突消解顺序

当指令之间存在张力时,原文档给出三级优先级:

  1. 数据安全与 schema 合法性(Data safety and schema validity)
  2. 来自最新指令的用户意图(User intent from latest instruction)
  3. 最小化编辑策略(Minimal-change editing strategy)

可以推断,这条顺序对应了实际系统的行为:即使用户意图与数据安全冲突,补丁也会被 patch.tsapplyResumePatches 拒绝(见第四节校验流程)——提示词是“软约束”,代码校验是“硬约束”,两者同向叠加。

2.5 Editing Rules:编辑规则

原文档给出五条具体操作规则:

  • 优先使用定向的 replace 操作,而非整对象替换;
  • 追加列表项时用 add 指向 /items/-(JSON Pointer 的数组末尾追加语法);
  • remove 仅在用户明确要求或已确认时使用;
  • website 对象保持 { "url": string, "label": string } 的形状;
  • hidden 字段必须保持显式布尔值。

这些规则都是针对简历数据模型的“易错点”量身定制的。例如 add at /items/- 的用法在测试中有直接印证:patch.test.ts 中向 /sections/skills/items/- 追加一条技能,随后 result.sections.skills.items 长度变为 1,验证了该路径约定真实可用。

2.6 Resume Shape Reference:数据结构参考

原文档给模型提供了简历数据的“地图”,避免模型猜路径:

  • 顶层键:basicssummarypicturesectionscustomSectionsmetadata
  • sections 内的条目家族:profilesexperienceeducationprojectsskillslanguagesinterestsawardscertificationspublicationsvolunteerreferences

这份参考与 packages/schema 中定义的 ResumeData 结构对应。由于当前简历 JSON 本身会注入到提示词末尾(见 2.8 节),模型实际上有“地图 + 实景”双重参照,可以在生成 patch 前核对目标路径是否存在。

2.7 Output Contract 与 Proposal Shape:输出契约与提案形状

原文档对“怎么回话”和“提案长什么样”分别做了规定:

Output Contract(输出契约)

  • 需要变更时:调用 propose_resume_patches,且不要再追加文本回复——提案细节由 UI 展示;
  • 无需变更时:不调用工具,直接给出简洁指导;
  • 永远不要在聊天回复中包含 patch 负载的 Markdown 代码块。

Proposal Shape(提案形状)

  • proposals 数组返回一个或多个内聚提案;
  • 每个提案包含 title、可选 summaryoperations
  • 必须一起批准的操作归入同一提案;
  • 不相关的变更拆成独立提案,方便用户逐页审批。

这两组约定与工具层 Schema 完全对齐:patch-proposal.tsresumePatchProposalToolInputSchema 要求 proposals 至少一项,每项含必填 title、可选 summary 和至少一条 operationsz.array(jsonPatchOperationSchema).min(1))。patch-proposal.test.ts 明确断言“空 operations 的提案会被拒绝”。

2.8 动态注入当前简历数据

模板末尾保留了数据注入锚点:

## Current Resume Data

```json
{{RESUME_DATA}}

(上面为模板结构示意,实际占位符内容见 [chat-system.md](https://gitcode.com/GitHub_Trending/re/reactive-resume/blob/3fa9de140c926b4551ef95f83d8d7505cc16d9a4/packages/ai/src/prompts/chat-system.md?utm_source=gitcode_repo_files#L58-L62))

服务端在 [service.ts](https://gitcode.com/GitHub_Trending/re/reactive-resume/blob/3fa9de140c926b4551ef95f83d8d7505cc16d9a4/packages/api/src/features/ai/service.ts?utm_source=gitcode_repo_files#L464-L466) 中完成替换:

```ts
function buildChatSystemPrompt(resumeData: ResumeData): string {
	return chatSystemPromptTemplate.replace("{{RESUME_DATA}}", JSON.stringify(resumeData, null, 2));
}

即每次对话请求时,当前简历的完整 JSON(2 空格缩进)都会被拼进系统提示词。这解释了 Hard Constraint 7“path 必须对当前 schema 有效”为何可行——模型确实看得见当前数据。同时注意注入的是未经裁剪的简历数据,意味着该接口只应在已鉴权、且用户拥有该简历的前提下调用(见下一节的 API 入口)。

三、API 入口:/ai/chat 与审批流

chat-system.md 描述的行为最终由 API 层落地。router.ts 中的 chat 过程:

  • 使用 protectedProcedure,要求登录态;
  • 输入为 { aiProviderId?, messages: UIMessage[], resumeId: string }
  • 挂载 aiRequestRateLimit 限流中间件;
  • handler 并行解析出“可运行的 AI Provider 凭据”和按 resumeId + 当前用户 ID 查出的简历,然后调用 aiService.chat,并把 resume.updatedAt 一并传入——它会成为每条提案的 baseUpdatedAt,用于前端检测“提案基于哪个版本的简历”。

路由的描述文案本身就概括了这套机制:

Streams a chat response from the configured AI provider. The LLM can call the propose_resume_patches tool to generate JSON Patch proposals for explicit user approval.

注意“proposals for explicit user approval”——服务端不自动落库,用户在前端确认后才真正写入简历,这正是提示词 Objective 中“用户先审查”的代码侧兑现。

四、propose_resume_patches 工具的底层实现

4.1 工具注册与流式多步调用

service.tschat 函数中,模型通过 Vercel AI SDK 的 streamText 运行,工具在 tools 配置中注册:

tools: {
	propose_resume_patches: tool({
		description:
			"Return one or more cohesive resume change proposals. Each proposal must include a title, optional summary, and valid JSON Patch operations against the current resume data. The tool validates but does not apply changes.",
		inputSchema: resumePatchProposalToolInputSchema,
		outputSchema: resumePatchProposalToolOutputSchema,
		execute: (toolInput) => {
			const proposals = normalizeResumePatchProposals(toolInput, input.resumeUpdatedAt);

			for (const proposal of proposals) {
				applyResumePatches(input.resumeData, proposal.operations);
			}

			return { proposals };
		},
	}),
},
stopWhen: stepCountIs(3),

两个实现细节值得注意:

  1. “validates but does not apply”execute 中对每个提案调用 applyResumePatches,而 patch.ts 的实现是“先校验、再在深拷贝上打补丁、最后对结果重跑 parseResumeData 校验”,不修改传入对象。因此这里的调用实质是一次“试算校验”:路径不存在、test 断言失败或补丁后数据不合法都会抛错,让工具执行失败,从而把错误反馈回模型(stopWhen: stepCountIs(3) 允许模型在最多 3 步内修正重试);校验通过才把提案原样返回给 UI 等待用户审批。
  2. 工具名与提示词严格一致:提示词中的 propose_resume_patches 与注册键完全同名,[[RESUME_DATA]] 注入、工具描述、提示词三者共同构成完整的工具契约。service.test.ts 中用 type: "tool-propose_resume_patches" 的消息断言了工具调用的流式输出形态。

4.2 提案的规范化:id 与 baseUpdatedAt

patch-proposal.tsnormalizeResumePatchProposals 做两件模型不必操心的事:

export function normalizeResumePatchProposals(input, baseUpdatedAt?) {
	return input.proposals.map((proposal, index) => ({
		...proposal,
		id: proposal.id ?? `proposal-${index + 1}`,
		...(baseUpdatedAt ? { baseUpdatedAt: baseUpdatedAt.toISOString() } : {}),
	}));
}
  • 模型可以不生成 id(输入 Schema 中 id 是 optional),服务端按顺序补 proposal-1proposal-2……,降低模型出错面;
  • 把简历的 updatedAt(Date)统一序列化为 ISO 字符串写入每条提案的 baseUpdatedAt

patch-proposal.test.ts 验证了这两点:无 id 输入可正常解析;baseUpdatedAt 被规范为 JSON 安全的 ISO 时间戳(测试中为 2026-05-10T06:38:27.093Z)。

4.3 JSON Patch 操作的结构化校验

提示词要求“op 与 path 符合 RFC 6902 及当前 schema”,代码侧由 patch.ts 的判别联合 Schema 把六种操作约束在请求边界上:

export const jsonPatchOperationSchema = z.discriminatedUnion("op", [
	z.object({ op: z.literal("add"), path: z.string(), value: z.unknown() }),
	z.object({ op: z.literal("remove"), path: z.string() }),
	z.object({ op: z.literal("replace"), path: z.string(), value: z.unknown() }),
	z.object({ op: z.literal("move"), path: z.string(), from: z.string() }),
	z.object({ op: z.literal("copy"), path: z.string(), from: z.string() }),
	z.object({ op: z.literal("test"), path: z.string(), value: z.unknown() }),
]);

[patch.test.ts](https://gitcode.com/GitHub_Trending/re/reactive-resume/blob/3fa9de140c926b4551ef95f83d8d7505cc16d9a4/packages/resume/src/patch.test.ts?utm_source=gitcode_repo_files#L6-L56) 逐一验证了字段约束:add 必须有 valuemove 必须有 from、未知 op(如 swap)被拒绝——这意味着模型即使生成非法 op,也会在 AI SDK 的输入校验层(inputSchema)就被拦下。

4.4 补丁应用:校验、试算与回滚

applyResumePatchespatch.ts)的三步流程与提示词“Data safety and schema validity 优先”的冲突消解顺序一一对应:

  1. 结构预校验jsonpatch.validate(operations, data) 失败即抛出 ResumePatchError,携带 codeindex(失败操作下标)和 operation(失败操作对象),但刻意不携带整棵文档树;
  2. 试算应用jsonpatch.applyPatch(data, operations, false, false) 在内部克隆的文档上执行,test 断言不匹配会直接抛错;
  3. 结果 schema 回滚校验:补丁后的文档必须通过 parseResumeData,否则抛出 Patch produced invalid resume data——即使每个操作本身合法,只要整体结果不符合简历 schema 也整体拒绝。

对应的错误语义表(patch.ts)把 fast-json-patch 的内部错误码翻译为人类可读消息,例如 OPERATION_PATH_UNRESOLVABLE(路径不存在)、OPERATION_VALUE_OUT_OF_BOUNDS(数组越界)、TEST_OPERATION_FAILED(断言不匹配)。测试 patch.test.ts 展示了回滚的实际效果:把 /picture/size 替换为 9999(超出 schema 的 32–512 范围)会整体抛错,原数据保持不变(另有测试断言输入对象不被修改)。

4.5 端到端防线总结

把提示词与源码串联起来,一条“把 TypeScript 加进技能列表”的用户指令的完整链路是:

  1. 系统提示词(含当前简历 JSON)+ 对话历史发往模型;
  2. 模型按 Hard Constraints 生成最小操作,调用 propose_resume_patches,例如对 /sections/skills/items/- 执行 add(提示词 Editing Rule 与 patch.test.ts 的用法一致);
  3. AI SDK 用 resumePatchProposalToolInputSchema 校验工具输入(title 非空、operations 至少一条);
  4. normalizeResumePatchProposalsid/baseUpdatedAt
  5. applyResumePatches 做“预校验 → 试算 → 结果 schema 校验”,任何一步失败都让工具执行报错,模型在 3 步内可修正;
  6. 校验通过的提案流式返回前端,渲染为审批卡片;
  7. 用户批准后,变更才真正写入简历数据。

五、从这份提示词中可以提取的设计模式

chat-system.md 为主体回看,它示范了一套可复用的“LLM 修改结构化数据”提示词工程模式:

  • 单一变更通道:把“怎么改数据”收敛到一个具名工具上,聊天文本只负责解释与确认(Output Contract 三条),使 UI 可确定性地解析并渲染每一次变更;
  • 把数据模型地图写进提示词:顶层键、条目家族、website/hidden 等易错字段形状(2.5、2.6 节),配合运行期注入的完整 JSON,把“路径猜错”这类高频错误压到最低;
  • 约束与校验同构:提示词中的 RFC 6902 要求对应 jsonPatchOperationSchema;“不虚构履历、标注草稿”对应结果 schema 回滚校验;“破坏性操作先确认”对应用户审批环节。提示词负责“让模型倾向正确”,代码负责“让错误无法生效”;
  • 提案分组策略:“必须一起批准的放一组,不相关的拆开”(Proposal Shape)让 diff 粒度匹配用户的心智审批粒度,而不是让一次对话产生一个无法整体否决的大补丁;
  • 模板与代码解耦:提示词以独立 Markdown 文件存放、启动时读取、占位符运行期替换(prompts.ts),使文案迭代不触碰业务逻辑,且可被 prompts.test.ts 这类轻量断言守护。

如果你在为结构化数据(简历、配置、文档树)构建 AI 编辑助手,这套“模板提示词 + 工具 Schema 双端对齐 + 试算式校验 + 人工审批”的组合,可以直接作为设计参照;而本文涉及的所有关键实现,都可以从 packages/ai/src/prompts/chat-system.mdpackages/api/src/features/ai/service.tspackages/ai/src/tools/patch-proposal.tspackages/resume/src/patch.ts 四个文件继续深入阅读。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384