fix: surface llm workflow errors

This commit is contained in:
Codex
2026-06-21 23:43:03 +08:00
parent 4bb5ca5eb0
commit f1dac15400
12 changed files with 286 additions and 500 deletions
+1 -121
View File
@@ -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";
}