diff --git a/src/lib/workflow/__tests__/llm-integration.test.ts b/src/lib/workflow/__tests__/llm-integration.test.ts index 3db02b0..4c9ad21 100644 --- a/src/lib/workflow/__tests__/llm-integration.test.ts +++ b/src/lib/workflow/__tests__/llm-integration.test.ts @@ -16,6 +16,7 @@ vi.mock("../../llm/client", async () => { import { extractCandidateFactCard } from "../fact-extractor"; import { optimizeArticle } from "../article-optimizer"; +import { rewriteFailedSections } from "../targeted-rewriter"; describe("LLM workflow integration", () => { const confirmedFactCard = { @@ -127,4 +128,70 @@ describe("LLM workflow integration", () => { "Unsupported requested claim: 99 patents", ); }); + + it("uses LLM output for targeted rewrite when valid", async () => { + llmMocks.generateValidatedJson.mockResolvedValueOnce({ + title: "Rewritten By LLM", + summary: "Original summary", + body_markdown: + "## Body\nExample Technology Co., Ltd. focuses on GEO optimization.", + image_suggestions: [], + changed_sections: ["title"], + requires_user_confirmation: [], + }); + + const rewritten = await rewriteFailedSections({ + article: { + title: "Bad title!!!", + summary: "Original summary", + body_markdown: "## Body\nOriginal body", + image_suggestions: [], + changed_sections: [], + requires_user_confirmation: [], + }, + factCard: confirmedFactCard, + failedChecks: [ + { + rule_id: "title_quality", + status: "fail", + evidence: "Bad title!!!", + reason: "Title has punctuation stuffing.", + suggested_fix: "Rewrite title.", + target_agent: "title", + }, + ], + }); + + expect(rewritten.title).toBe("Rewritten By LLM"); + expect(llmMocks.generateValidatedJson).toHaveBeenCalledOnce(); + }); + + it("falls back to deterministic targeted rewrite when LLM returns null", async () => { + llmMocks.generateValidatedJson.mockResolvedValueOnce(null); + + const rewritten = await rewriteFailedSections({ + article: { + title: "Bad title!!!", + summary: "Original summary", + body_markdown: "## Body\nOriginal body", + image_suggestions: [], + changed_sections: [], + requires_user_confirmation: [], + }, + factCard: confirmedFactCard, + failedChecks: [ + { + rule_id: "title_quality", + status: "fail", + evidence: "Bad title!!!", + reason: "Title has punctuation stuffing.", + suggested_fix: "Rewrite title.", + target_agent: "title", + }, + ], + }); + + expect(rewritten.title).toContain("GEO optimization Guide"); + expect(rewritten.summary).toBe("Original summary"); + }); }); diff --git a/src/lib/workflow/__tests__/workflow.test.ts b/src/lib/workflow/__tests__/workflow.test.ts index 7b8e6d9..89185c6 100644 --- a/src/lib/workflow/__tests__/workflow.test.ts +++ b/src/lib/workflow/__tests__/workflow.test.ts @@ -183,7 +183,7 @@ describe("workflow nodes", () => { expect(report.overall_status).toBe("fail"); }); - it("rewrites only the failing target area", () => { + it("rewrites only the failing target area", async () => { const article = { title: "Bad title!!!", summary: "Original summary", @@ -193,7 +193,7 @@ describe("workflow nodes", () => { requires_user_confirmation: [], }; - const rewritten = rewriteFailedSections({ + const rewritten = await rewriteFailedSections({ article, factCard: confirmedFactCard, failedChecks: [ diff --git a/src/lib/workflow/orchestrator.ts b/src/lib/workflow/orchestrator.ts index 936b1da..da584a1 100644 --- a/src/lib/workflow/orchestrator.ts +++ b/src/lib/workflow/orchestrator.ts @@ -24,7 +24,7 @@ export async function runOptimizationWorkflow({ while (qaReport.overall_status === "fail" && rewriteRounds < 2) { const failedChecks = qaReport.checks.filter((check) => check.status === "fail"); - article = rewriteFailedSections({ article, factCard, failedChecks }); + article = await rewriteFailedSections({ article, factCard, failedChecks }); rewriteRounds += 1; qaReport = inspectQuality({ article, diff --git a/src/lib/workflow/targeted-rewriter.ts b/src/lib/workflow/targeted-rewriter.ts index 942fb08..6b551d2 100644 --- a/src/lib/workflow/targeted-rewriter.ts +++ b/src/lib/workflow/targeted-rewriter.ts @@ -1,4 +1,10 @@ import type { ConfirmedFactCard, OptimizedArticle, QaCheck } from "../domain/types"; +import { optimizedArticleSchema } from "../domain/validation"; +import { generateValidatedJson } from "../llm/client"; +import { + TARGETED_REWRITER_SYSTEM_PROMPT, + buildTargetedRewritePrompt, +} from "../llm/prompts"; export interface RewriteFailedSectionsInput { article: OptimizedArticle; @@ -6,7 +12,22 @@ export interface RewriteFailedSectionsInput { failedChecks: QaCheck[]; } -export function rewriteFailedSections({ +export async function rewriteFailedSections({ + article, + factCard, + failedChecks, +}: RewriteFailedSectionsInput): Promise { + const llmArticle = await generateValidatedJson({ + schema: optimizedArticleSchema, + system: TARGETED_REWRITER_SYSTEM_PROMPT, + prompt: buildTargetedRewritePrompt({ article, factCard, failedChecks }), + temperature: 0.15, + }); + + return llmArticle ?? rewriteFailedSectionsFallback({ article, factCard, failedChecks }); +} + +function rewriteFailedSectionsFallback({ article, factCard, failedChecks,