fix: surface llm workflow errors
This commit is contained in:
@@ -9,131 +9,11 @@ import {
|
||||
export async function extractCandidateFactCard(
|
||||
input: ArticleInput,
|
||||
): Promise<CandidateFactCard> {
|
||||
const llmCard = await generateValidatedJson({
|
||||
return generateValidatedJson({
|
||||
schema: candidateFactCardSchema,
|
||||
system: FACT_EXTRACTOR_SYSTEM_PROMPT,
|
||||
prompt: buildFactExtractorPrompt(input),
|
||||
temperature: 0.1,
|
||||
task: "fact_extractor",
|
||||
});
|
||||
|
||||
return llmCard ?? extractCandidateFactCardFallback(input);
|
||||
}
|
||||
|
||||
function extractCandidateFactCardFallback(input: ArticleInput): CandidateFactCard {
|
||||
const text = `${input.title}\n${input.body}`;
|
||||
const uncertainItems: string[] = [];
|
||||
const companyFullName = findCompanyFullName(text);
|
||||
const years = findExperienceYears(text);
|
||||
|
||||
if (!companyFullName) {
|
||||
uncertainItems.push("Missing company full name");
|
||||
}
|
||||
if (years.length > 1) {
|
||||
uncertainItems.push(`Conflicting experience years: ${years.join(", ")}`);
|
||||
}
|
||||
if (input.images.length === 0) {
|
||||
uncertainItems.push("Image description is missing");
|
||||
}
|
||||
|
||||
const industry = inferIndustry(text);
|
||||
|
||||
return candidateFactCardSchema.parse({
|
||||
company_full_name: companyFullName ?? "",
|
||||
company_short_names: inferCompanyShortNames(companyFullName),
|
||||
brand_names: inferBrandNames(text, companyFullName),
|
||||
product_names: inferProducts(text),
|
||||
target_industry: industry,
|
||||
target_audience: text.toLowerCase().includes("marketing")
|
||||
? "Marketing teams"
|
||||
: inferTargetAudience(text),
|
||||
experience_years: years.length === 1 ? years[0] : null,
|
||||
core_claims: years.length === 1 ? [`${years[0]} years of ${industry} experience`] : [],
|
||||
forbidden_claims: [],
|
||||
image_topics: input.images.map((image) => image.content),
|
||||
uncertain_items: uncertainItems,
|
||||
});
|
||||
}
|
||||
|
||||
function findCompanyFullName(text: string) {
|
||||
const englishMatch = text.match(
|
||||
/([A-Z][A-Za-z0-9&.,\-\s]{2,}?(?:Co\.,?\s*Ltd\.?|Company|Inc\.?|LLC|Ltd\.))/,
|
||||
);
|
||||
if (englishMatch?.[1]) return englishMatch[1].trim();
|
||||
|
||||
const chineseMatch = text.match(
|
||||
/([\u4e00-\u9fa5A-Za-z0-9()()]{2,40}?(?:股份有限公司|有限公司|集团|公司))/,
|
||||
);
|
||||
return chineseMatch?.[1].trim() ?? null;
|
||||
}
|
||||
|
||||
function findExperienceYears(text: string) {
|
||||
const matches = [...text.matchAll(/\b(\d{1,3})\s*(?:years?|年)\b/gi)];
|
||||
return [...new Set(matches.map((match) => Number(match[1])))];
|
||||
}
|
||||
|
||||
function inferIndustry(text: string) {
|
||||
const lower = text.toLowerCase();
|
||||
if (lower.includes("geo")) return "GEO optimization";
|
||||
if (lower.includes("finance") || lower.includes("banking")) return "finance automation";
|
||||
if (lower.includes("seo")) return "SEO";
|
||||
if (/AIGC|短视频|出海内容|内容工业化/.test(text)) return "AIGC短视频培训";
|
||||
if (/工业零部件|爆品操盘|AI OBS|IPMS/.test(text)) return "工业零部件爆品操盘";
|
||||
return "General business";
|
||||
}
|
||||
|
||||
function inferBrandNames(text: string, companyFullName: string | null) {
|
||||
const chineseBrands = [
|
||||
...new Set(
|
||||
[
|
||||
companyFullName ? inferChineseShortName(companyFullName) : "",
|
||||
...[...text.matchAll(/\b(AIGC|AI OBS|IPMS|GEO|SEO)\b/g)].map(
|
||||
(match) => match[1],
|
||||
),
|
||||
].filter(Boolean),
|
||||
),
|
||||
];
|
||||
const names = [...text.matchAll(/\b[A-Z][A-Za-z0-9]{2,}\b/g)]
|
||||
.map((match) => match[0])
|
||||
.filter((word) => !["The", "This", "And"].includes(word));
|
||||
return [...new Set([...chineseBrands, ...names])].slice(0, 5);
|
||||
}
|
||||
|
||||
function inferProducts(text: string) {
|
||||
const productMatches = [
|
||||
...[...text.matchAll(/\b([A-Z][A-Za-z0-9]+\s+GEO)\b/g)].map(
|
||||
(match) => match[1],
|
||||
),
|
||||
...[...text.matchAll(/\b(AIGC短视频培训|AI OBS|IPMS|爆品操盘数智系统)\b/g)].map(
|
||||
(match) => match[1],
|
||||
),
|
||||
];
|
||||
return [...new Set(productMatches)];
|
||||
}
|
||||
|
||||
function inferCompanyShortNames(companyFullName: string | null) {
|
||||
if (!companyFullName) return [];
|
||||
if (/[\u4e00-\u9fa5]/.test(companyFullName)) {
|
||||
const legalShortName = inferChineseShortName(companyFullName);
|
||||
const brandShortName = legalShortName.replace(/管理咨询$/, "");
|
||||
return [...new Set([brandShortName, legalShortName].filter(Boolean))];
|
||||
}
|
||||
return [companyFullName.split(/\s+/)[0] ?? ""].filter(Boolean);
|
||||
}
|
||||
|
||||
function inferChineseShortName(companyFullName: string) {
|
||||
return companyFullName
|
||||
.replace(/[((].*?[))]/g, "")
|
||||
.replace(/股份有限公司|有限公司|集团|公司/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function inferTargetAudience(text: string) {
|
||||
if (/品牌商家|内容创作者|出海企业/.test(text)) {
|
||||
return "品牌商家、内容创作者、出海企业";
|
||||
}
|
||||
if (/工业企业|工业零部件制造商|采购/.test(text)) {
|
||||
return "工业企业、采购团队、工业零部件制造商";
|
||||
}
|
||||
return "Business readers";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user