feat: use llm for fact extraction
This commit is contained in:
@@ -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<typeof import("../../llm/client")>(
|
||||||
|
"../../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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,9 +1,25 @@
|
|||||||
import type { ArticleInput, CandidateFactCard } from "../domain/types";
|
import type { ArticleInput, CandidateFactCard } from "../domain/types";
|
||||||
import { candidateFactCardSchema } from "../domain/validation";
|
import { candidateFactCardSchema } from "../domain/validation";
|
||||||
|
import { generateValidatedJson } from "../llm/client";
|
||||||
|
import {
|
||||||
|
FACT_EXTRACTOR_SYSTEM_PROMPT,
|
||||||
|
buildFactExtractorPrompt,
|
||||||
|
} from "../llm/prompts";
|
||||||
|
|
||||||
export async function extractCandidateFactCard(
|
export async function extractCandidateFactCard(
|
||||||
input: ArticleInput,
|
input: ArticleInput,
|
||||||
): Promise<CandidateFactCard> {
|
): Promise<CandidateFactCard> {
|
||||||
|
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 text = `${input.title}\n${input.body}`;
|
||||||
const uncertainItems: string[] = [];
|
const uncertainItems: string[] = [];
|
||||||
const companyFullName = findCompanyFullName(text);
|
const companyFullName = findCompanyFullName(text);
|
||||||
|
|||||||
Reference in New Issue
Block a user