创作一轮改为先选题再填写,减少工具空转和重复覆盖。
上下文按规则、历史、产物分段拼装,便于吃提示缓存并核对费用。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+161
-29
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user