diff --git a/src/lib/llm/__tests__/prompts.test.ts b/src/lib/llm/__tests__/prompts.test.ts index 519e4fd..ee3fb8a 100644 --- a/src/lib/llm/__tests__/prompts.test.ts +++ b/src/lib/llm/__tests__/prompts.test.ts @@ -47,6 +47,14 @@ describe("LLM prompt builders", () => { expect(prompt).toContain("图片主题"); }); + it("fact extraction prompt explicitly forbids object items in string arrays", () => { + const prompt = `${FACT_EXTRACTOR_SYSTEM_PROMPT}\n${buildFactExtractorPrompt(articleInput)}`; + + expect(prompt).toContain("uncertain_items must be string[]"); + expect(prompt).toContain("core_claims must be string[]"); + expect(prompt).toContain("Do not return objects inside string arrays"); + }); + it("article optimizer prompt includes platform templates and Chinese output rules", () => { const prompt = `${ARTICLE_OPTIMIZER_SYSTEM_PROMPT}\n${buildArticleOptimizerPrompt(articleInput, factCard)}`; @@ -60,6 +68,14 @@ describe("LLM prompt builders", () => { expect(prompt).toContain("forbidden_claims"); }); + it("article optimizer prompt explicitly describes optional array shapes", () => { + const prompt = `${ARTICLE_OPTIMIZER_SYSTEM_PROMPT}\n${buildArticleOptimizerPrompt(articleInput, factCard)}`; + + expect(prompt).toContain("changed_sections must be string[]"); + expect(prompt).toContain("requires_user_confirmation must be string[]"); + expect(prompt).toContain("image_suggestions must be []"); + }); + it("quality inspector prompt encodes fail and warn standards for all risk gates", () => { const prompt = `${QUALITY_INSPECTOR_SYSTEM_PROMPT}\n${buildQualityInspectorPrompt({ article: { @@ -92,6 +108,35 @@ describe("LLM prompt builders", () => { expect(prompt).toContain("warn 标准"); }); + it("quality inspector prompt explicitly describes enum outputs", () => { + const prompt = `${QUALITY_INSPECTOR_SYSTEM_PROMPT}\n${buildQualityInspectorPrompt({ + article: { + title: "伟思德鲁 AIGC短视频培训", + summary: "官网文章摘要", + body_markdown: "正文", + image_suggestions: [], + changed_sections: [], + requires_user_confirmation: [], + }, + factCard, + platform: "official_site", + deterministicChecks: [ + { + rule_id: "title_quality", + status: "pass", + evidence: "标题自然", + reason: "本地规则通过", + suggested_fix: "", + target_agent: null, + }, + ], + })}`; + + expect(prompt).toContain("status must be one of pass, warn, fail"); + expect(prompt).toContain("rule_id must reuse the exact English rule_id"); + expect(prompt).toContain("target_agent must be title, body, fact_card, or null"); + }); + it("targeted rewrite prompt gives rule-specific repair actions", () => { const prompt = `${TARGETED_REWRITER_SYSTEM_PROMPT}\n${buildTargetedRewritePrompt({ article: { diff --git a/src/lib/llm/prompts.ts b/src/lib/llm/prompts.ts index 95d0dff..50c3bb8 100644 --- a/src/lib/llm/prompts.ts +++ b/src/lib/llm/prompts.ts @@ -25,6 +25,78 @@ const PLATFORM_GUIDANCE: Record = { "recommendation_list / 推荐榜单:推荐榜单口吻,必须解释推荐依据;不得制造榜单排名、奖项或第三方认证。", }; +const FACT_CARD_OUTPUT_CONTRACT = [ + "Output type contract:", + "- company_full_name, target_industry, target_audience must be strings.", + "- experience_years must be a number or null.", + "- company_short_names, brand_names, product_names, core_claims, forbidden_claims, image_topics, uncertain_items must be string[].", + "- core_claims must be string[].", + "- uncertain_items must be string[].", + "- Do not return objects inside string arrays; put the readable claim text directly in the array.", + "Example:", + JSON.stringify( + { + company_full_name: "示例科技有限公司", + company_short_names: ["示例科技"], + brand_names: ["示例品牌"], + product_names: ["GEO内容优化平台"], + target_industry: "GEO内容优化", + target_audience: "市场团队", + experience_years: null, + core_claims: ["提供GEO内容优化服务"], + forbidden_claims: ["行业第一"], + image_topics: ["产品后台截图"], + uncertain_items: ["客户案例缺少来源"], + }, + null, + 2, + ), +].join("\n"); + +const OPTIMIZED_ARTICLE_OUTPUT_CONTRACT = [ + "Output type contract:", + "- title, summary, body_markdown must be strings.", + "- image_suggestions must be [].", + "- changed_sections must be string[].", + "- requires_user_confirmation must be string[].", + "- Do not return objects inside string arrays; put the readable item text directly in the array.", + "Example:", + JSON.stringify( + { + title: "示例科技 GEO 内容优化方案", + summary: "围绕事实卡重写后的官网文章摘要。", + body_markdown: "## 服务能力\n示例科技有限公司提供GEO内容优化服务。", + image_suggestions: [], + changed_sections: ["title", "body"], + requires_user_confirmation: ["客户案例需要确认"], + }, + null, + 2, + ), +].join("\n"); + +const QA_OUTPUT_CONTRACT = [ + "Output type contract:", + "- Return { checks: QaCheck[] }.", + "- rule_id must reuse the exact English rule_id from deterministicChecks.", + "- status must be one of pass, warn, fail.", + "- evidence, reason, suggested_fix must be strings.", + "- target_agent must be title, body, fact_card, or null.", + "Example check:", + JSON.stringify( + { + rule_id: "title_quality", + status: "warn", + evidence: "标题偏营销化", + reason: "官网标题需要更克制", + suggested_fix: "改成事实型标题", + target_agent: "title", + }, + null, + 2, + ), +].join("\n"); + function formatPlatformGuidance(selected: PublishPlatform) { return [ "平台模板选项:", @@ -69,6 +141,8 @@ export function buildFactExtractorPrompt(input: ArticleInput) { "company_full_name, company_short_names, brand_names, product_names, target_industry, target_audience, experience_years, core_claims, forbidden_claims, image_topics, uncertain_items.", "Do not include confirmed_by_user.", "", + FACT_CARD_OUTPUT_CONTRACT, + "", "字段要求:", "- core_claims 只放原文明示、可作为后续优化硬约束的事实。", "- forbidden_claims 放行业第一、最强、知名客户、显著提升、保证转化等无法验证或高风险主张。", @@ -89,6 +163,8 @@ export function buildArticleOptimizerPrompt( "Return an OptimizedArticle JSON object with these exact keys:", "title, summary, body_markdown, image_suggestions, changed_sections, requires_user_confirmation.", "", + OPTIMIZED_ARTICLE_OUTPUT_CONTRACT, + "", formatPlatformGuidance(input.platform), "", "输出要求:", @@ -119,6 +195,7 @@ export function buildQualityInspectorPrompt(input: { "Return a JSON object with a checks array.", "Each check must include rule_id, status, evidence, reason, suggested_fix, and target_agent.", "Only use rule_id values already present in deterministicChecks.", + QA_OUTPUT_CONTRACT, "不得把 deterministic fail 降级。", "", formatPlatformGuidance(input.platform), @@ -146,6 +223,7 @@ export function buildTargetedRewritePrompt(input: { }) { return [ "Return an OptimizedArticle JSON object.", + OPTIMIZED_ARTICLE_OUTPUT_CONTRACT, "Rewrite only the fields needed for failedChecks.", "", "按 rule_id 执行修复动作:",