feat: use llm for article optimization
This commit is contained in:
@@ -15,8 +15,25 @@ vi.mock("../../llm/client", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
import { extractCandidateFactCard } from "../fact-extractor";
|
import { extractCandidateFactCard } from "../fact-extractor";
|
||||||
|
import { optimizeArticle } from "../article-optimizer";
|
||||||
|
|
||||||
describe("LLM workflow integration", () => {
|
describe("LLM workflow integration", () => {
|
||||||
|
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: ["8 years of GEO optimization experience"],
|
||||||
|
forbidden_claims: ["industry first"],
|
||||||
|
image_topics: ["dashboard"],
|
||||||
|
uncertain_items: [],
|
||||||
|
is_ready_for_optimization: true,
|
||||||
|
confirmed_by_user: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
llmMocks.generateValidatedJson.mockReset();
|
llmMocks.generateValidatedJson.mockReset();
|
||||||
});
|
});
|
||||||
@@ -64,4 +81,50 @@ describe("LLM workflow integration", () => {
|
|||||||
expect(card.company_full_name).toBe("Fallback Technology Co., Ltd.");
|
expect(card.company_full_name).toBe("Fallback Technology Co., Ltd.");
|
||||||
expect(card.experience_years).toBe(8);
|
expect(card.experience_years).toBe(8);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses LLM output for article optimization when valid", async () => {
|
||||||
|
llmMocks.generateValidatedJson.mockResolvedValueOnce({
|
||||||
|
title: "LLM Optimized GEO Article",
|
||||||
|
summary: "LLM summary constrained by the fact card.",
|
||||||
|
body_markdown: "## LLM Body\nExample Technology Co., Ltd. keeps claims factual.",
|
||||||
|
image_suggestions: [{ source: "image_1", suggestion: "Use dashboard." }],
|
||||||
|
changed_sections: ["title", "body"],
|
||||||
|
requires_user_confirmation: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const article = await optimizeArticle({
|
||||||
|
input: {
|
||||||
|
title: "Original",
|
||||||
|
body: "Example Technology Co., Ltd. has 8 years of GEO optimization experience.",
|
||||||
|
images: [{ type: "description", content: "dashboard" }],
|
||||||
|
platform: "official_site",
|
||||||
|
user_instructions: "",
|
||||||
|
},
|
||||||
|
factCard: confirmedFactCard,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(article.title).toBe("LLM Optimized GEO Article");
|
||||||
|
expect(article.body_markdown).toContain("LLM Body");
|
||||||
|
expect(llmMocks.generateValidatedJson).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to deterministic article optimization when LLM returns null", async () => {
|
||||||
|
llmMocks.generateValidatedJson.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
|
const article = await optimizeArticle({
|
||||||
|
input: {
|
||||||
|
title: "Original",
|
||||||
|
body: "Example Technology Co., Ltd. has 8 years of GEO optimization experience.",
|
||||||
|
images: [{ type: "description", content: "dashboard" }],
|
||||||
|
platform: "official_site",
|
||||||
|
user_instructions: "Say we have 99 patents.",
|
||||||
|
},
|
||||||
|
factCard: confirmedFactCard,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(article.title).toContain("GEO optimization Guide");
|
||||||
|
expect(article.requires_user_confirmation).toContain(
|
||||||
|
"Unsupported requested claim: 99 patents",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import type {
|
|||||||
OptimizedArticle,
|
OptimizedArticle,
|
||||||
} from "../domain/types";
|
} from "../domain/types";
|
||||||
import { optimizedArticleSchema } from "../domain/validation";
|
import { optimizedArticleSchema } from "../domain/validation";
|
||||||
|
import { generateValidatedJson } from "../llm/client";
|
||||||
|
import {
|
||||||
|
ARTICLE_OPTIMIZER_SYSTEM_PROMPT,
|
||||||
|
buildArticleOptimizerPrompt,
|
||||||
|
} from "../llm/prompts";
|
||||||
|
|
||||||
export interface OptimizeArticleInput {
|
export interface OptimizeArticleInput {
|
||||||
input: ArticleInput;
|
input: ArticleInput;
|
||||||
@@ -14,6 +19,20 @@ export async function optimizeArticle({
|
|||||||
input,
|
input,
|
||||||
factCard,
|
factCard,
|
||||||
}: OptimizeArticleInput): Promise<OptimizedArticle> {
|
}: OptimizeArticleInput): Promise<OptimizedArticle> {
|
||||||
|
const llmArticle = await generateValidatedJson({
|
||||||
|
schema: optimizedArticleSchema,
|
||||||
|
system: ARTICLE_OPTIMIZER_SYSTEM_PROMPT,
|
||||||
|
prompt: buildArticleOptimizerPrompt(input, factCard),
|
||||||
|
temperature: 0.2,
|
||||||
|
});
|
||||||
|
|
||||||
|
return llmArticle ?? optimizeArticleFallback({ input, factCard });
|
||||||
|
}
|
||||||
|
|
||||||
|
function optimizeArticleFallback({
|
||||||
|
input,
|
||||||
|
factCard,
|
||||||
|
}: OptimizeArticleInput): OptimizedArticle {
|
||||||
const unsupported = findUnsupportedInstructionClaims(
|
const unsupported = findUnsupportedInstructionClaims(
|
||||||
input.user_instructions,
|
input.user_instructions,
|
||||||
factCard,
|
factCard,
|
||||||
|
|||||||
Reference in New Issue
Block a user