feat: 实现 PromptBuilder 段落构建器
This commit is contained in:
62
src/core/prompt-builder.ts
Normal file
62
src/core/prompt-builder.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import type { PromptSection, SectionKind } from "./prompt-sections";
|
||||||
|
|
||||||
|
export class PromptBuilder {
|
||||||
|
private sections: PromptSection[] = [];
|
||||||
|
|
||||||
|
head(text: string): this {
|
||||||
|
const existing = this.sections.findIndex((s) => s.kind === "head");
|
||||||
|
if (existing !== -1) {
|
||||||
|
this.sections[existing] = { kind: "head", content: text };
|
||||||
|
} else {
|
||||||
|
this.sections.push({ kind: "head", content: text });
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
context(text: string): this {
|
||||||
|
this.sections.push({ kind: "context", content: text });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt(text: string): this {
|
||||||
|
const existing = this.sections.findIndex((s) => s.kind === "prompt");
|
||||||
|
if (existing !== -1) {
|
||||||
|
this.sections[existing] = { kind: "prompt", content: text };
|
||||||
|
} else {
|
||||||
|
this.sections.push({ kind: "prompt", content: text });
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
guide(text: string): this {
|
||||||
|
this.sections.push({ kind: "guide", content: text });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
warn(text: string): this {
|
||||||
|
this.sections.push({ kind: "warn", content: text });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
build(): string {
|
||||||
|
const order: SectionKind[] = ["head", "context", "prompt", "guide", "warn"];
|
||||||
|
const groups: string[][] = order.map(() => []);
|
||||||
|
|
||||||
|
for (const section of this.sections) {
|
||||||
|
if (section.content.trim() === "") continue;
|
||||||
|
const idx = order.indexOf(section.kind);
|
||||||
|
if (idx !== -1) {
|
||||||
|
groups[idx].push(section.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rendered: string[] = [];
|
||||||
|
for (const group of groups) {
|
||||||
|
if (group.length > 0) {
|
||||||
|
rendered.push(group.join("\n\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rendered.join("\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
78
tests/core/prompt-builder.test.ts
Normal file
78
tests/core/prompt-builder.test.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { describe, it, expect } from "bun:test";
|
||||||
|
import { PromptBuilder } from "../../src/core/prompt-builder";
|
||||||
|
|
||||||
|
describe("PromptBuilder", () => {
|
||||||
|
it("空 builder build 返回空字符串", () => {
|
||||||
|
const builder = new PromptBuilder();
|
||||||
|
expect(builder.build()).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("单段输出", () => {
|
||||||
|
const result = new PromptBuilder().head("# 标题").build();
|
||||||
|
expect(result).toBe("# 标题");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("多段按序渲染", () => {
|
||||||
|
const result = new PromptBuilder()
|
||||||
|
.head("# 阶段")
|
||||||
|
.context("状态信息")
|
||||||
|
.prompt("核心引导")
|
||||||
|
.guide("操作指令")
|
||||||
|
.warn("警告信息")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const headPos = result.indexOf("# 阶段");
|
||||||
|
const contextPos = result.indexOf("状态信息");
|
||||||
|
const promptPos = result.indexOf("核心引导");
|
||||||
|
const guidePos = result.indexOf("操作指令");
|
||||||
|
const warnPos = result.indexOf("警告信息");
|
||||||
|
|
||||||
|
expect(headPos).toBeLessThan(contextPos);
|
||||||
|
expect(contextPos).toBeLessThan(promptPos);
|
||||||
|
expect(promptPos).toBeLessThan(guidePos);
|
||||||
|
expect(guidePos).toBeLessThan(warnPos);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("空段跳过", () => {
|
||||||
|
const result = new PromptBuilder().head("# 标题").prompt("内容").guide("指引").build();
|
||||||
|
expect(result).toContain("# 标题");
|
||||||
|
expect(result).toContain("内容");
|
||||||
|
expect(result).toContain("指引");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("同 kind 多次追加拼接", () => {
|
||||||
|
const result = new PromptBuilder()
|
||||||
|
.head("# 阶段")
|
||||||
|
.context("信息一")
|
||||||
|
.context("信息二")
|
||||||
|
.prompt("prompt")
|
||||||
|
.build();
|
||||||
|
expect(result).toContain("信息一\n\n信息二");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("head 重复设值覆盖", () => {
|
||||||
|
const result = new PromptBuilder().head("# 旧标题").head("# 新标题").build();
|
||||||
|
expect(result).toBe("# 新标题");
|
||||||
|
expect(result).not.toContain("旧标题");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prompt 重复设值覆盖", () => {
|
||||||
|
const result = new PromptBuilder().prompt("旧 prompt").prompt("新 prompt").build();
|
||||||
|
expect(result).toContain("新 prompt");
|
||||||
|
expect(result).not.toContain("旧 prompt");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("空字符串段被跳过", () => {
|
||||||
|
const result = new PromptBuilder().head("# 标题").context("").prompt("内容").build();
|
||||||
|
expect(result).toBe("# 标题\n\n内容");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("链式调用返回 this", () => {
|
||||||
|
const builder = new PromptBuilder();
|
||||||
|
expect(builder.head("x")).toBe(builder);
|
||||||
|
expect(builder.context("x")).toBe(builder);
|
||||||
|
expect(builder.prompt("x")).toBe(builder);
|
||||||
|
expect(builder.guide("x")).toBe(builder);
|
||||||
|
expect(builder.warn("x")).toBe(builder);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user