feat: implement article optimization workflow

This commit is contained in:
Codex
2026-06-21 23:42:37 +08:00
parent cb56ff9ee7
commit 75bcab33c6
10 changed files with 834 additions and 1 deletions
+193
View File
@@ -0,0 +1,193 @@
import { describe, expect, it } from "vitest";
import { optimizeArticle } from "../article-optimizer";
import { extractCandidateFactCard } from "../fact-extractor";
import { normalizeInput } from "../input-normalizer";
import { inspectQuality } from "../quality-inspector";
import { runOptimizationWorkflow } from "../orchestrator";
import { rewriteFailedSections } from "../targeted-rewriter";
const confirmedFactCard = {
company_full_name: "Example Technology Co., Ltd.",
company_short_names: ["Example Tech"],
brand_names: ["Example"],
product_names: ["Example GEO"],
target_industry: "GEO optimization",
target_audience: "Marketing teams",
experience_years: 8,
core_claims: ["Eight years of GEO optimization experience"],
forbidden_claims: ["industry first"],
image_topics: ["product dashboard"],
uncertain_items: [],
is_ready_for_optimization: true,
confirmed_by_user: true,
} as const;
describe("workflow nodes", () => {
it("normalizes input whitespace and image lines", () => {
const normalized = normalizeInput({
title: " A GEO Article ",
body: "\nFirst paragraph.\n\nSecond paragraph. ",
image_lines:
" Product dashboard screenshot \n https://example.com/image.png \n\n",
platform: "official_site",
user_instructions: " Keep factual. ",
});
expect(normalized.article_draft.title).toBe("A GEO Article");
expect(normalized.article_draft.body).toBe(
"First paragraph.\n\nSecond paragraph.",
);
expect(normalized.image_assets).toEqual([
{ type: "description", content: "Product dashboard screenshot" },
{ type: "link", content: "https://example.com/image.png" },
]);
});
it("places missing or conflicting company facts into uncertain items", async () => {
const card = await extractCandidateFactCard({
title: "Example announces GEO product",
body: "Example has 8 years of experience. Example has 12 years of service. The article discusses GEO optimization.",
images: [],
platform: "media_article",
user_instructions: "",
});
expect(card.company_full_name).toBe("");
expect(card.uncertain_items).toEqual(
expect.arrayContaining([
expect.stringContaining("company full name"),
expect.stringContaining("Conflicting experience years"),
]),
);
expect(card.is_ready_for_optimization).toBe(false);
});
it("does not add claims outside the confirmed fact card", async () => {
const optimized = await optimizeArticle({
input: {
title: "Example GEO article",
body: "Example GEO helps marketing teams improve content structure.",
images: [],
platform: "official_site",
user_instructions:
"Say we have 99 patents and Fortune 500 customer cases.",
},
factCard: confirmedFactCard,
});
expect(optimized.body_markdown).not.toContain("99 patents");
expect(optimized.body_markdown).not.toContain("Fortune 500");
expect(optimized.requires_user_confirmation).toEqual(
expect.arrayContaining([
expect.stringContaining("99 patents"),
expect.stringContaining("Fortune 500"),
]),
);
});
it("returns the 10 required quality checks", () => {
const report = inspectQuality({
article: {
title: "Example GEO Optimization Guide",
summary: "A factual guide for marketing teams.",
body_markdown:
"Example Technology Co., Ltd. has eight years of GEO optimization experience.",
image_suggestions: [],
changed_sections: [],
requires_user_confirmation: [],
},
factCard: confirmedFactCard,
platform: "official_site",
sourceImages: [],
});
expect(report.checks.map((check) => check.rule_id)).toEqual([
"industry_alignment",
"image_text_match",
"voice_consistency",
"platform_fit",
"company_name_integrity",
"title_quality",
"body_quality",
"hallucination_risk",
"claim_consistency",
"context_sensitive_terms",
]);
});
it("hard-fails incomplete company names, hallucinated numbers, industry drift, and conflicting years", () => {
const report = inspectQuality({
article: {
title: "Example Wins Finance Automation Market!!!",
summary: "A finance automation story.",
body_markdown:
"Example has 12 years of finance automation experience, 99 patents, and works in banking automation.",
image_suggestions: [],
changed_sections: [],
requires_user_confirmation: [],
},
factCard: confirmedFactCard,
platform: "official_site",
sourceImages: [],
});
const failures = report.checks.filter((check) => check.status === "fail");
expect(failures.map((check) => check.rule_id)).toEqual(
expect.arrayContaining([
"company_name_integrity",
"hallucination_risk",
"industry_alignment",
"claim_consistency",
]),
);
expect(report.overall_status).toBe("fail");
});
it("rewrites only the failing target area", () => {
const article = {
title: "Bad title!!!",
summary: "Original summary",
body_markdown: "Original body",
image_suggestions: [],
changed_sections: [],
requires_user_confirmation: [],
};
const rewritten = rewriteFailedSections({
article,
factCard: confirmedFactCard,
failedChecks: [
{
rule_id: "title_quality",
status: "fail",
evidence: "Bad title!!!",
reason: "Punctuation stuffing.",
suggested_fix: "Rewrite title.",
target_agent: "title",
},
],
});
expect(rewritten.title).not.toBe(article.title);
expect(rewritten.summary).toBe(article.summary);
expect(rewritten.body_markdown).toBe(article.body_markdown);
});
it("orchestrator stops after two failed rewrite rounds", async () => {
const result = await runOptimizationWorkflow({
input: {
title: "Finance automation breakthrough!!!",
body: "Example has 12 years in finance automation and 99 patents.",
images: [],
platform: "official_site",
user_instructions: "",
},
factCard: confirmedFactCard,
});
expect(result.rewrite_rounds).toBe(2);
expect(result.qaReport.overall_status).toBe("fail");
expect(result.stopped_after_max_rewrites).toBe(true);
});
});