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 { 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<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 uncertainItems: string[] = [];
|
||||
const companyFullName = findCompanyFullName(text);
|
||||
|
||||
Reference in New Issue
Block a user