创作一轮改为先选题再填写,减少工具空转和重复覆盖。

上下文按规则、历史、产物分段拼装,便于吃提示缓存并核对费用。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-07 23:48:20 +08:00
co-authored by Cursor
parent d0a48f9431
commit 1cc4ae557a
20 changed files with 2660 additions and 123 deletions
+161 -29
View File
@@ -6,42 +6,140 @@ import {
type DictateProduct,
} from "./types.js";
export type DictateContextPhase = "select" | "fill" | "reply";
/** 五段字数,只给用量统计,不另存正文。 */
export type DictateSegmentChars = {
rules: number;
history: number;
products: number;
injected: number;
tail: number;
};
export type DictateBuiltContext = {
messages: ChatMessage[];
segmentChars: DictateSegmentChars;
};
/**
* 拼给模型的上下文:系统提示 + 全部产物(按相对序)+ 全量对话。
* 清空对话后,只剩产物块,产物即浓缩上下文。
* 创作请求的块顺序:固定规则、更早的对话、全部产物、本轮注入、末尾。
* 选题不带产物正文和本轮注入。回复步只带本轮刚落盘产物与末句用户话。
* 更早对话只追加,最后一轮用户输入放在末尾。
*/
export function buildDictateContext(params: {
systemPrompt: string;
products: DictateProduct[];
dialogue: DictateChatTurn[];
phase?: DictateContextPhase;
injected?: string;
tail?: string;
}): DictateBuiltContext {
const phase = params.phase ?? "fill";
if (phase === "reply") {
return buildReplyContext(params);
}
const { earlier, lastUser } = splitEarlierDialogue(params.dialogue);
const history = formatEarlierDialogue(earlier);
const products =
phase === "fill" ? formatProducts(sortDictateProducts(params.products)) : "";
const injected = phase === "fill" ? params.injected?.trim() ?? "" : "";
const tail = joinBlocks([params.tail?.trim() ?? "", lastUser ? `最后一轮用户输入\n${lastUser}` : ""]);
const fillDuty =
phase === "fill"
? "## 本步收尾\n\n写完本轮节点表后,同一次请求必须调用 reply_user。只 insert 不算完成。"
: "";
const sections = [
fillDuty,
history,
products ? `## 当前产物(已写入黑板,按相对序;对话清空后仍保留)\n\n${products}` : "",
injected
? `## 本轮注入\n\n写完下列节点后,同一次请求必须调用 reply_user。\n\n本轮节点表\n\n${injected}`
: "",
tail ? `## 本轮\n\n${tail}` : "",
].filter(Boolean);
const user = sections.join("\n\n");
return {
messages: [
{ role: "system", content: params.systemPrompt },
{ role: "user", content: user },
],
segmentChars: {
rules: params.systemPrompt.length,
history: history.length,
products: products.length,
injected: injected.length,
tail: tail.length,
},
};
}
/** 回复兜底:只给本轮产物 + 末句,不带全黑板与节点规格。 */
function buildReplyContext(params: {
systemPrompt: string;
products: DictateProduct[];
dialogue: DictateChatTurn[];
tail?: string;
}): DictateBuiltContext {
const { lastUser } = splitEarlierDialogue(params.dialogue);
const products = formatRoundProducts(params.products);
const tail = joinBlocks([
params.tail?.trim() ?? "",
lastUser ? `最后一轮用户输入\n${lastUser}` : "",
]);
const sections = [
products ? `## 本轮刚落盘\n\n${products}` : "## 本轮刚落盘\n\n(无正文产物;按删除与用户原话回复)",
tail ? `## 本轮\n\n${tail}` : "",
].filter(Boolean);
const user = sections.join("\n\n");
return {
messages: [
{ role: "system", content: params.systemPrompt },
{ role: "user", content: user },
],
segmentChars: {
rules: params.systemPrompt.length,
history: 0,
products: products.length,
injected: 0,
tail: tail.length,
},
};
}
function formatRoundProducts(products: DictateProduct[]): string {
if (products.length === 0) return "";
return sortDictateProducts(products)
.map((product) => {
const score = formatSelfScore(product);
return [`### ${product.tag}`, score, product.content].filter(Boolean).join("\n");
})
.join("\n\n");
}
function formatSelfScore(product: DictateProduct): string {
const dims = product.selfScore?.dims;
if (!dims?.length) return "";
const lines = dims.map((dim) => {
const gap = dim.gap?.trim() ? `;缺口:${dim.gap.trim()}` : "";
const options =
dim.options?.length ? `;选项:${dim.options.map((item) => item.trim()).filter(Boolean).join(" / ")}` : "";
return `- ${dim.name}:${dim.score}${gap}${options}`;
});
return `self_score:\n${lines.join("\n")}`;
}
export function buildDictateMessages(params: {
systemPrompt: string;
products: DictateProduct[];
dialogue: DictateChatTurn[];
phase?: DictateContextPhase;
injected?: string;
tail?: string;
}): ChatMessage[] {
const parts: string[] = [];
const products = sortDictateProducts(params.products);
if (products.length > 0) {
parts.push("## 当前产物(已写入黑板,按相对序;对话清空后仍保留)");
for (const p of products) {
const ord = effectiveDictateOrder(p);
parts.push(`### ${p.tag} 〔order=${ord}〕\n${p.content}`);
}
} else {
parts.push("## 当前产物\n(尚无)");
}
parts.push("## 对话(全量)");
if (params.dialogue.length === 0) {
parts.push("(尚无对话;等用户开口)");
} else {
for (const turn of params.dialogue) {
const who = turn.role === "user" ? "用户" : "助手";
parts.push(`【${who}】\n${turn.text}`);
}
}
return [
{ role: "system", content: params.systemPrompt },
{ role: "user", content: parts.join("\n\n") },
];
return buildDictateContext(params).messages;
}
/** 从会话消息里抽出转述式对话(用户输入 + 助手回复) */
@@ -62,3 +160,37 @@ export function extractDictateDialogue(
}
return out;
}
function splitEarlierDialogue(dialogue: DictateChatTurn[]): {
earlier: DictateChatTurn[];
lastUser: string;
} {
const last = dialogue.at(-1);
if (last?.role === "user") {
return { earlier: dialogue.slice(0, -1), lastUser: last.text.trim() };
}
return { earlier: dialogue, lastUser: "" };
}
function formatEarlierDialogue(dialogue: DictateChatTurn[]): string {
if (dialogue.length === 0) return "## 更早的对话\n\n(尚无更早对话)";
const lines = dialogue.map((turn) => {
const who = turn.role === "user" ? "用户" : "助手";
return `【${who}】\n${turn.text}`;
});
return `## 更早的对话\n\n${lines.join("\n\n")}`;
}
function formatProducts(products: DictateProduct[]): string {
if (products.length === 0) return "(尚无)";
return products
.map((product) => {
const ord = effectiveDictateOrder(product);
return `### ${product.tag} 〔order=${ord}〕\n${product.content}`;
})
.join("\n\n");
}
function joinBlocks(blocks: string[]): string {
return blocks.map((block) => block.trim()).filter(Boolean).join("\n\n");
}