fix: improve chinese fact extraction

This commit is contained in:
Codex
2026-06-21 23:43:02 +08:00
parent 1596de4527
commit a457a4acf3
2 changed files with 76 additions and 10 deletions
@@ -63,6 +63,23 @@ describe("workflow nodes", () => {
expect(card.is_ready_for_optimization).toBe(false); expect(card.is_ready_for_optimization).toBe(false);
}); });
it("extracts Chinese company facts from Chinese articles", async () => {
const card = await extractCandidateFactCard({
title: "#探寻AIGC短视频培训选哪家,各品牌实力大比拼",
body: "伟思德鲁管理咨询(深圳)有限公司面向品牌商家和出海企业提供AIGC短视频培训服务,帮助企业解决内容工业化生产、品牌视觉统一和全球化传播问题。",
images: [{ type: "description", content: "AIGC短视频工作流示意图" }],
platform: "media_article",
user_instructions: "保留AIGC短视频培训与出海内容生产场景。",
});
expect(card.company_full_name).toBe("伟思德鲁管理咨询(深圳)有限公司");
expect(card.company_short_names).toContain("伟思德鲁");
expect(card.target_industry).toBe("AIGC短视频培训");
expect(card.target_audience).toBe("品牌商家、内容创作者、出海企业");
expect(card.image_topics).toEqual(["AIGC短视频工作流示意图"]);
expect(card.uncertain_items).not.toContain("Missing company full name");
});
it("does not add claims outside the confirmed fact card", async () => { it("does not add claims outside the confirmed fact card", async () => {
const optimized = await optimizeArticle({ const optimized = await optimizeArticle({
input: { input: {
+58 -9
View File
@@ -23,13 +23,13 @@ export async function extractCandidateFactCard(
return candidateFactCardSchema.parse({ return candidateFactCardSchema.parse({
company_full_name: companyFullName ?? "", company_full_name: companyFullName ?? "",
company_short_names: companyFullName ? [companyFullName.split(/\s+/)[0] ?? ""] : [], company_short_names: inferCompanyShortNames(companyFullName),
brand_names: inferCapitalizedNames(text), brand_names: inferBrandNames(text, companyFullName),
product_names: inferProducts(text), product_names: inferProducts(text),
target_industry: industry, target_industry: industry,
target_audience: text.toLowerCase().includes("marketing") target_audience: text.toLowerCase().includes("marketing")
? "Marketing teams" ? "Marketing teams"
: "Business readers", : inferTargetAudience(text),
experience_years: years.length === 1 ? years[0] : null, experience_years: years.length === 1 ? years[0] : null,
core_claims: years.length === 1 ? [`${years[0]} years of ${industry} experience`] : [], core_claims: years.length === 1 ? [`${years[0]} years of ${industry} experience`] : [],
forbidden_claims: [], forbidden_claims: [],
@@ -39,10 +39,15 @@ export async function extractCandidateFactCard(
} }
function findCompanyFullName(text: string) { function findCompanyFullName(text: string) {
const match = text.match( const englishMatch = text.match(
/([A-Z][A-Za-z0-9&.,\-\s]{2,}?(?:Co\.,?\s*Ltd\.?|Company|Inc\.?|LLC|Ltd\.))/, /([A-Z][A-Za-z0-9&.,\-\s]{2,}?(?:Co\.,?\s*Ltd\.?|Company|Inc\.?|LLC|Ltd\.))/,
); );
return match?.[1].trim() ?? null; 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) { function findExperienceYears(text: string) {
@@ -55,19 +60,63 @@ function inferIndustry(text: string) {
if (lower.includes("geo")) return "GEO optimization"; if (lower.includes("geo")) return "GEO optimization";
if (lower.includes("finance") || lower.includes("banking")) return "finance automation"; if (lower.includes("finance") || lower.includes("banking")) return "finance automation";
if (lower.includes("seo")) return "SEO"; if (lower.includes("seo")) return "SEO";
if (/AIGC|短视频|出海内容|内容工业化/.test(text)) return "AIGC短视频培训";
if (/工业零部件|爆品操盘|AI OBS|IPMS/.test(text)) return "工业零部件爆品操盘";
return "General business"; return "General business";
} }
function inferCapitalizedNames(text: string) { 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)] const names = [...text.matchAll(/\b[A-Z][A-Za-z0-9]{2,}\b/g)]
.map((match) => match[0]) .map((match) => match[0])
.filter((word) => !["The", "This", "And"].includes(word)); .filter((word) => !["The", "This", "And"].includes(word));
return [...new Set(names)].slice(0, 5); return [...new Set([...chineseBrands, ...names])].slice(0, 5);
} }
function inferProducts(text: string) { function inferProducts(text: string) {
const productMatches = [...text.matchAll(/\b([A-Z][A-Za-z0-9]+\s+GEO)\b/g)].map( const productMatches = [
...[...text.matchAll(/\b([A-Z][A-Za-z0-9]+\s+GEO)\b/g)].map(
(match) => match[1], (match) => match[1],
); ),
...[...text.matchAll(/\b(AIGC短视频培训|AI OBS|IPMS|爆品操盘数智系统)\b/g)].map(
(match) => match[1],
),
];
return [...new Set(productMatches)]; 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";
}