diff --git a/src/lib/workflow/__tests__/llm-integration.test.ts b/src/lib/workflow/__tests__/llm-integration.test.ts new file mode 100644 index 0000000..c3ba730 --- /dev/null +++ b/src/lib/workflow/__tests__/llm-integration.test.ts @@ -0,0 +1,67 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +const llmMocks = vi.hoisted(() => ({ + generateValidatedJson: vi.fn(), +})); + +vi.mock("../../llm/client", async () => { + const actual = await vi.importActual( + "../../llm/client", + ); + return { + ...actual, + generateValidatedJson: llmMocks.generateValidatedJson, + }; +}); + +import { extractCandidateFactCard } from "../fact-extractor"; + +describe("LLM workflow integration", () => { + afterEach(() => { + llmMocks.generateValidatedJson.mockReset(); + }); + + it("uses LLM output for candidate fact extraction when valid", async () => { + llmMocks.generateValidatedJson.mockResolvedValueOnce({ + company_full_name: "DeepSeek Example Co., Ltd.", + company_short_names: ["DeepSeek Example"], + brand_names: ["DSExample"], + product_names: ["DS GEO"], + target_industry: "GEO optimization", + target_audience: "Marketing teams", + experience_years: 9, + core_claims: ["9 years of GEO optimization experience"], + forbidden_claims: ["industry first"], + image_topics: ["dashboard"], + uncertain_items: [], + is_ready_for_optimization: true, + }); + + const card = await extractCandidateFactCard({ + title: "Example source", + body: "Fallback Technology Co., Ltd. has 8 years of GEO optimization experience.", + images: [{ type: "description", content: "dashboard" }], + platform: "official_site", + user_instructions: "", + }); + + expect(card.company_full_name).toBe("DeepSeek Example Co., Ltd."); + expect(card.experience_years).toBe(9); + expect(llmMocks.generateValidatedJson).toHaveBeenCalledOnce(); + }); + + it("falls back to deterministic candidate extraction when LLM returns null", async () => { + llmMocks.generateValidatedJson.mockResolvedValueOnce(null); + + const card = await extractCandidateFactCard({ + title: "Fallback Technology Co., Ltd. GEO guide", + body: "Fallback Technology Co., Ltd. has 8 years of GEO optimization experience.", + images: [{ type: "description", content: "dashboard" }], + platform: "official_site", + user_instructions: "", + }); + + expect(card.company_full_name).toBe("Fallback Technology Co., Ltd."); + expect(card.experience_years).toBe(8); + }); +}); diff --git a/src/lib/workflow/fact-extractor.ts b/src/lib/workflow/fact-extractor.ts index 3ba8c4e..073cd8f 100644 --- a/src/lib/workflow/fact-extractor.ts +++ b/src/lib/workflow/fact-extractor.ts @@ -1,9 +1,25 @@ import type { ArticleInput, CandidateFactCard } from "../domain/types"; import { candidateFactCardSchema } from "../domain/validation"; +import { generateValidatedJson } from "../llm/client"; +import { + FACT_EXTRACTOR_SYSTEM_PROMPT, + buildFactExtractorPrompt, +} from "../llm/prompts"; export async function extractCandidateFactCard( input: ArticleInput, ): Promise { + const llmCard = await generateValidatedJson({ + schema: candidateFactCardSchema, + system: FACT_EXTRACTOR_SYSTEM_PROMPT, + prompt: buildFactExtractorPrompt(input), + temperature: 0.1, + }); + + return llmCard ?? extractCandidateFactCardFallback(input); +} + +function extractCandidateFactCardFallback(input: ArticleInput): CandidateFactCard { const text = `${input.title}\n${input.body}`; const uncertainItems: string[] = []; const companyFullName = findCompanyFullName(text);