From 803533a7e07162a4499d381261deb92434e1dbb8 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Thu, 11 Jun 2026 16:30:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20PromptBuilder=20?= =?UTF-8?q?=E6=AE=B5=E8=90=BD=E6=9E=84=E5=BB=BA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/prompt-builder.ts | 62 ++++++++++++++++++++++++ tests/core/prompt-builder.test.ts | 78 +++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 src/core/prompt-builder.ts create mode 100644 tests/core/prompt-builder.test.ts diff --git a/src/core/prompt-builder.ts b/src/core/prompt-builder.ts new file mode 100644 index 0000000..da2b9ca --- /dev/null +++ b/src/core/prompt-builder.ts @@ -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"); + } +} diff --git a/tests/core/prompt-builder.test.ts b/tests/core/prompt-builder.test.ts new file mode 100644 index 0000000..e21eff2 --- /dev/null +++ b/tests/core/prompt-builder.test.ts @@ -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); + }); +});