向直聘注入能力落盘节奏指南。
约束声明与写入时机,避免抢跑下游模块。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+27
-12
@@ -8,6 +8,8 @@
|
|||||||
export function buildDictateSystemPrompt(opts?: {
|
export function buildDictateSystemPrompt(opts?: {
|
||||||
recipeName?: string;
|
recipeName?: string;
|
||||||
recipeBrief?: string;
|
recipeBrief?: string;
|
||||||
|
/** 来自 modules catalog meta:何时落盘 / insert 目标 */
|
||||||
|
moduleGuide?: string;
|
||||||
}): string {
|
}): string {
|
||||||
const recipeBlock =
|
const recipeBlock =
|
||||||
opts?.recipeName || opts?.recipeBrief
|
opts?.recipeName || opts?.recipeBrief
|
||||||
@@ -16,7 +18,13 @@ export function buildDictateSystemPrompt(opts?: {
|
|||||||
名称:${opts.recipeName ?? "(未命名)"}
|
名称:${opts.recipeName ?? "(未命名)"}
|
||||||
${opts.recipeBrief ? `要点:\n${opts.recipeBrief}` : ""}
|
${opts.recipeBrief ? `要点:\n${opts.recipeBrief}` : ""}
|
||||||
按上述适用与原则整理产物;材料只够做本配方时,就只做本配方范围内的事。
|
按上述适用与原则整理产物;材料只够做本配方时,就只做本配方范围内的事。
|
||||||
本配方若含跨轮数值:优先走 D(declare_variable / declare_map),不要只 insert 散文表。
|
能力「何时落盘」以【能力 · 何时落盘】为准;本配方只给顺序骨架,不代替各能力调用条件。
|
||||||
|
`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const moduleBlock = opts?.moduleGuide?.trim()
|
||||||
|
? `
|
||||||
|
${opts.moduleGuide.trim()}
|
||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
@@ -24,43 +32,50 @@ ${opts.recipeBrief ? `要点:\n${opts.recipeBrief}` : ""}
|
|||||||
|
|
||||||
**本提示只用于创作。** 开玩后叙事由游玩 worker 执行;不要假装在跑游玩回合。
|
**本提示只用于创作。** 开玩后叙事由游玩 worker 执行;不要假装在跑游玩回合。
|
||||||
|
|
||||||
|
## 落盘节奏(先读)
|
||||||
|
1. 对照【能力 · 何时落盘】与已有产物:条件成立才落对应能力;未成立 → 本轮不 insert / 不 declare 该能力产物。
|
||||||
|
2. 每轮通常只推进一刀(一个能力或一组紧密相关的真值);用户一次说很多也不要批量抢跑下游。
|
||||||
|
3. 配方顺序是骨架;具体能不能落,以各能力「何时用 / 何时不用」为准。
|
||||||
|
|
||||||
## 落盘(仅允许 toolcall)
|
## 落盘(仅允许 toolcall)
|
||||||
禁止把设定/变量/映射全文只写在聊天里冒充已落盘。
|
禁止把设定/变量/映射全文只写在聊天里冒充已落盘。
|
||||||
|
|
||||||
### insert — 不变型固定产物
|
### insert — 不变型固定产物
|
||||||
- position:\`用户.*\` / \`设计.*\`(如需求、美学、正文组成、开场白)
|
- position:\`用户.*\` / \`设计.*\`(优先用【能力 · 何时落盘】里的落盘 tag)
|
||||||
- content:正文;order 可选(越小越靠前;越常改越大)
|
- content:正文;order 可选(越小越靠前;越常改越大)
|
||||||
- 勿用 insert 写变量目录或映射(用下面两个工具)
|
- 勿用 insert 写变量目录或映射(用下面两个工具)
|
||||||
|
|
||||||
### declare_variable — 跨轮真值
|
### declare_variable — 跨轮真值
|
||||||
- key / type / initial / user_visible(用户是否可见)
|
- key / type / initial / user_visible(用户是否可见)
|
||||||
- 只立必须跨轮记住的状态;派生文案不要立成可写变量
|
- 只立必须跨轮记住的状态;派生文案不要立成可写变量
|
||||||
|
- 时机对照「变量设计与更新规则」的何时用;美学未锚定前不要抢 declare
|
||||||
|
|
||||||
### declare_map — 真值→投影上下文
|
### declare_map — 真值→投影上下文
|
||||||
- id / field(真值名)/ target_tag(\`上下文.*\` 或 \`大纲.*\`)/ bands[]
|
- id / field(真值名)/ target_tag(\`上下文.*\` 或 \`大纲.*\`)/ bands[]
|
||||||
- bands:\`{min,max,content}\` 区间(min 含 max 不含),或 \`{value,content}\`,或 \`{when:"default",content}\`
|
- bands:\`{min,max,content}\` 区间(min 含 max 不含),或 \`{value,content}\`,或 \`{when:"default",content}\`
|
||||||
- 槽位固定、内容随真值换档;旧档离开上下文
|
- 槽位固定、内容随真值换档;旧档离开上下文
|
||||||
|
|
||||||
**自然语言回复**只写:短确认、是否这个、建议补充 A/B/C、提问。不要复述 tool 全文。
|
**自然语言回复**只写:短确认、是否这个、建议补充 A/B/C(须是条件已接近成立的下一刀)、提问。不要复述 tool 全文。
|
||||||
|
|
||||||
**分清:**
|
**分清:**
|
||||||
- 正文组成(格式)→ insert \`设计.正文组成\`
|
- 正文组成(格式)→ insert \`设计.正文组成\`(须「回复呈现」等上游条件已满足,见能力表)
|
||||||
- 开场白(内容)→ insert \`设计.开场白\`;落档/开玩同步 \`输出.开场白\`
|
- 开场白(内容)→ insert \`设计.开场白\`;落档/开玩同步 \`输出.开场白\`(收口;上游未齐不硬塞)
|
||||||
- 多篇无关联短文(生产在游玩)→ 创作期钉 \`设计.模仿范例\` / \`设计.模仿要点\` / \`设计.叙事指南与故事推进\`(大纲扩写)
|
- 多篇无关联短文(生产在游玩)→ 创作期钉 \`设计.模仿范例\` / \`设计.模仿要点\` / \`设计.叙事指南与故事推进\`(大纲扩写)
|
||||||
- 好感分档性格等 → declare_variable + declare_map,不要 insert 一整张表进设定散文
|
- 好感分档性格等 → declare_variable + declare_map,不要 insert 一整张表进设定散文
|
||||||
${recipeBlock}
|
${recipeBlock}${moduleBlock}
|
||||||
## 按用户本轮意图择一为主
|
## 按用户本轮意图择一为主(仍须过「何时用」)
|
||||||
### A · 钉设定
|
### A · 钉体验 / 设定
|
||||||
insert 设定类;order 偏小。回复:确认体验 + 建议补充。
|
优先美学纲领与体验核心(条件总是先做);其它设定类 insert 仅当对应能力何时用成立。回复:确认本刀 + 建议条件已近的下一刀。
|
||||||
|
|
||||||
### B · 钉正文格式
|
### B · 钉正文格式
|
||||||
必须 insert \`设计.正文组成\`。
|
「回复呈现」等上游已齐时才 insert \`设计.正文组成\`。
|
||||||
|
|
||||||
### C · 生成开场白
|
### C · 生成开场白
|
||||||
先有正文组成;insert \`设计.开场白\`(Markdown;岔路用 \`## 岔路\`)。
|
收口:先有正文组成且体验已锚定;insert \`设计.开场白\`(Markdown;岔路用 \`## 岔路\`)。
|
||||||
|
**content 规格:** 正文必须含字面 \`@玩家\`(至少一处)作用户角色名位;禁止写死姓名;禁止全文只有「你」而零个 \`@玩家\`。缺则改写后再 insert。进游玩时程序替换 \`@玩家\`。
|
||||||
|
|
||||||
### D · 钉变量与映射
|
### D · 钉变量与映射
|
||||||
用户提到跨轮状态、分档态度、章大纲切换时:declare_variable(含是否对用户可见)→ declare_map(分档正文)。可同轮多次调用。
|
仅当「变量设计」何时用成立(体验须跨轮记住状态等):declare_variable → 有分档换文再 declare_map。勿因配方名叫数据化就抢跑 D。
|
||||||
|
|
||||||
### E · 文本生成器(创作收料 → 开玩挂载)
|
### E · 文本生成器(创作收料 → 开玩挂载)
|
||||||
必须用 insert 写齐游玩上下文:\`设计.模仿范例\`、\`设计.模仿要点\`、\`设计.叙事指南与故事推进\`(用户输入用法:大纲扩写;禁止扮演停笔)。缺任一就先补产物,不要说可以开玩。禁止在创作期写完整短文。开玩后程序只挂载这些产物:主世界出内容,转述出文风。
|
必须用 insert 写齐游玩上下文:\`设计.模仿范例\`、\`设计.模仿要点\`、\`设计.叙事指南与故事推进\`(用户输入用法:大纲扩写;禁止扮演停笔)。缺任一就先补产物,不要说可以开玩。禁止在创作期写完整短文。开玩后程序只挂载这些产物:主世界出内容,转述出文风。
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const DICTATE_TOOL_DEFINITIONS: ToolDefinition[] = [
|
|||||||
content: {
|
content: {
|
||||||
type: "string",
|
type: "string",
|
||||||
description:
|
description:
|
||||||
"落入该位置的正文。设计.正文组成=格式 JSON;设计.开场白=可读开场(Markdown)",
|
"落入该位置的正文。设计.正文组成=格式 JSON;设计.开场白=可读开场 Markdown,必须含字面 @玩家(用户角色名位,禁止写死姓名)",
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
type: "number",
|
type: "number",
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ export async function runDictateTurn(params: {
|
|||||||
systemPrompt?: string;
|
systemPrompt?: string;
|
||||||
recipeName?: string;
|
recipeName?: string;
|
||||||
recipeBrief?: string;
|
recipeBrief?: string;
|
||||||
|
/** modules catalog 的何时落盘块(formatModuleCatalogForDictate) */
|
||||||
|
moduleGuide?: string;
|
||||||
driver?: AgentDriver;
|
driver?: AgentDriver;
|
||||||
}): Promise<DictateTurnResult> {
|
}): Promise<DictateTurnResult> {
|
||||||
const driver = params.driver ?? createLocalLlmDriver(params.llm);
|
const driver = params.driver ?? createLocalLlmDriver(params.llm);
|
||||||
@@ -69,6 +71,7 @@ export async function runDictateTurn(params: {
|
|||||||
buildDictateSystemPrompt({
|
buildDictateSystemPrompt({
|
||||||
recipeName: params.recipeName,
|
recipeName: params.recipeName,
|
||||||
recipeBrief: params.recipeBrief,
|
recipeBrief: params.recipeBrief,
|
||||||
|
moduleGuide: params.moduleGuide,
|
||||||
});
|
});
|
||||||
const assembled = buildDictateMessages({
|
const assembled = buildDictateMessages({
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
|
|||||||
@@ -1332,6 +1332,65 @@ export function formatModuleCatalogForAgent(catalog: ModuleCatalog): string {
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Boss 直聘不经 DAG:这些能力由落档/开玩程序处理,创作对话一般不 insert */
|
||||||
|
const DICTATE_PROGRAM_SIDE_MODULE_IDS = new Set([
|
||||||
|
"worker-spec",
|
||||||
|
"context-order",
|
||||||
|
"refine",
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注入 Boss 直聘:能力何时落盘(meta when/when_not)+ insert 目标 tag。
|
||||||
|
* 不写 DAG / design-step 话术;不塞执行全文。
|
||||||
|
*/
|
||||||
|
export function formatModuleCatalogForDictate(catalog: ModuleCatalog): string {
|
||||||
|
const lines = catalog.modules.map((m) => {
|
||||||
|
const programSide = DICTATE_PROGRAM_SIDE_MODULE_IDS.has(m.id) || m.auto;
|
||||||
|
const flags = [
|
||||||
|
m.repeatable ? "〔可反复〕" : "",
|
||||||
|
m.closer ? "〔收口〕" : "",
|
||||||
|
programSide ? "〔落档程序〕" : "",
|
||||||
|
m.kind ? MODULE_NODE_KIND_FLAGS[m.kind] : "",
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("");
|
||||||
|
const land = dictateLandHint(m);
|
||||||
|
const parts: string[] = [
|
||||||
|
`- ${m.name}${flags}:${m.declaration}`,
|
||||||
|
` 落盘:${land}`,
|
||||||
|
];
|
||||||
|
if (m.when) parts.push(` 何时用:${indentMultiline(m.when, " ")}`);
|
||||||
|
if (m.when_not) parts.push(` 何时不用:${indentMultiline(m.when_not, " ")}`);
|
||||||
|
if (m.boundary) parts.push(` 边界:${indentMultiline(m.boundary, " ")}`);
|
||||||
|
return parts.join("\n");
|
||||||
|
});
|
||||||
|
return [
|
||||||
|
"【能力 · 何时落盘】",
|
||||||
|
"对照下方「何时用 / 何时不用 / 边界」与已有产物判定:条件成立才 insert(或 declare_*);含糊或未到时机 → 本轮不落、只短确认或问挡住的 1 点。",
|
||||||
|
"每轮通常只推进条件已成立的一刀;勿因用户一次说了很多就批量抢跑下游。",
|
||||||
|
"标〔落档程序〕的:创作对话一般不 insert,开玩/落档时由程序处理。",
|
||||||
|
"标〔收口〕的开场白:美学与必要上游齐后再写;未齐不硬塞。",
|
||||||
|
"不要把能力执行全文塞进聊天;规格只走 toolcall。",
|
||||||
|
lines.join("\n"),
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function dictateLandHint(m: ModuleCatalogEntry): string {
|
||||||
|
if (DICTATE_PROGRAM_SIDE_MODULE_IDS.has(m.id) || m.auto) {
|
||||||
|
return "落档/开玩程序处理;创作期一般不 insert";
|
||||||
|
}
|
||||||
|
if (m.id === "variable-design") {
|
||||||
|
return "declare_variable / declare_map;长文规则可另 insert 「设计.变量设计与更新规则」";
|
||||||
|
}
|
||||||
|
if (m.id === "opening-setup") {
|
||||||
|
return "insert 「设计.开场白」(Boss 直聘简化 tag;初值与开场同真相)";
|
||||||
|
}
|
||||||
|
if (m.artifact?.trim()) {
|
||||||
|
return `insert 「${m.artifact.trim()}」`;
|
||||||
|
}
|
||||||
|
return "按配方约定 insert 对应 设计.*";
|
||||||
|
}
|
||||||
|
|
||||||
/** 多行字段:首行接在标签后,续行缩进 */
|
/** 多行字段:首行接在标签后,续行缩进 */
|
||||||
function indentMultiline(text: string, indent: string): string {
|
function indentMultiline(text: string, indent: string): string {
|
||||||
const lines = text.split(/\r?\n/);
|
const lines = text.split(/\r?\n/);
|
||||||
|
|||||||
@@ -535,10 +535,11 @@ describe("creation-flow", () => {
|
|||||||
|
|
||||||
const rpg = dictate!.recipes.find((r) => r.id === "数据化跑团体验")!;
|
const rpg = dictate!.recipes.find((r) => r.id === "数据化跑团体验")!;
|
||||||
const rpgDetail = await loadRecipeDetail("dialogue/world-simulator", rpg);
|
const rpgDetail = await loadRecipeDetail("dialogue/world-simulator", rpg);
|
||||||
expect(rpgDetail.brief).toMatch(/数据化跑团|真值|映射/);
|
expect(rpgDetail.brief).toMatch(/美学|真值|映射/);
|
||||||
expect(rpgDetail.core).toMatch(/真值|declare_variable|数值/);
|
expect(rpgDetail.core).toMatch(/美学|真值|declare_variable|何时落盘/);
|
||||||
expect(formatDictateRecipeBrief(rpgDetail)).toContain("declare_variable");
|
expect(formatDictateRecipeBrief(rpgDetail)).toContain("declare_variable");
|
||||||
expect(formatDictateRecipeBrief(rpgDetail)).toContain("declare_map");
|
expect(formatDictateRecipeBrief(rpgDetail)).toContain("何时落盘");
|
||||||
|
expect(formatDictateRecipeBrief(rpgDetail)).toMatch(/美学纲领|体验核心/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("parses selected recipe ref", () => {
|
it("parses selected recipe ref", () => {
|
||||||
|
|||||||
+27
-1
@@ -8,6 +8,10 @@ import {
|
|||||||
isDictateModeValue,
|
isDictateModeValue,
|
||||||
sortDictateProducts,
|
sortDictateProducts,
|
||||||
} from "../src/dictate/index.js";
|
} from "../src/dictate/index.js";
|
||||||
|
import {
|
||||||
|
formatModuleCatalogForDictate,
|
||||||
|
loadModuleCatalog,
|
||||||
|
} from "../src/skills/creation-flow.js";
|
||||||
|
|
||||||
describe("dictate context", () => {
|
describe("dictate context", () => {
|
||||||
it("assembles full dialogue + products with almost no filtering", () => {
|
it("assembles full dialogue + products with almost no filtering", () => {
|
||||||
@@ -65,7 +69,10 @@ describe("dictate context", () => {
|
|||||||
it("system prompt requires insert toolcall and relative order", () => {
|
it("system prompt requires insert toolcall and relative order", () => {
|
||||||
const prompt = buildDictateSystemPrompt({
|
const prompt = buildDictateSystemPrompt({
|
||||||
recipeName: "数据化跑团体验",
|
recipeName: "数据化跑团体验",
|
||||||
recipeBrief: "设定 + 真值 + 分档映射\n怎么做:\n- declare_variable\n- declare_map",
|
recipeBrief:
|
||||||
|
"设定 + 真值 + 分档映射\n怎么做:\n- declare_variable\n- declare_map",
|
||||||
|
moduleGuide:
|
||||||
|
"【能力 · 何时落盘】\n- 美学纲领与交互范式:…\n 落盘:insert 「设计.美学纲领与交互范式」\n 何时用:还不能回答站位",
|
||||||
});
|
});
|
||||||
expect(prompt).toContain("设计.正文组成");
|
expect(prompt).toContain("设计.正文组成");
|
||||||
expect(prompt).toContain("设计.开场白");
|
expect(prompt).toContain("设计.开场白");
|
||||||
@@ -82,6 +89,25 @@ describe("dictate context", () => {
|
|||||||
expect(prompt).toContain("大纲扩写");
|
expect(prompt).toContain("大纲扩写");
|
||||||
expect(prompt).toContain("禁止扮演停笔");
|
expect(prompt).toContain("禁止扮演停笔");
|
||||||
expect(prompt).toContain("只挂载这些产物");
|
expect(prompt).toContain("只挂载这些产物");
|
||||||
|
expect(prompt).toContain("【能力 · 何时落盘】");
|
||||||
|
expect(prompt).toContain("设计.美学纲领与交互范式");
|
||||||
|
expect(prompt).toContain("每轮通常只推进一刀");
|
||||||
|
expect(prompt).toContain("勿因配方名叫数据化就抢跑 D");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats module catalog for dictate with land tags and when", async () => {
|
||||||
|
const catalog = await loadModuleCatalog("dialogue/world-simulator");
|
||||||
|
expect(catalog).toBeTruthy();
|
||||||
|
const block = formatModuleCatalogForDictate(catalog!);
|
||||||
|
expect(block).toContain("【能力 · 何时落盘】");
|
||||||
|
expect(block).toContain("美学纲领与交互范式");
|
||||||
|
expect(block).toContain("落盘:insert 「设计.美学纲领与交互范式」");
|
||||||
|
expect(block).toContain("何时用");
|
||||||
|
expect(block).toContain("何时不用");
|
||||||
|
expect(block).toContain("declare_variable");
|
||||||
|
expect(block).toContain("设计.开场白");
|
||||||
|
expect(block).toContain("〔落档程序〕");
|
||||||
|
expect(block).not.toContain("role=prototype");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sorts products by relative order: neg then 0 then pos", () => {
|
it("sorts products by relative order: neg then 0 then pos", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user