我们用AI丰富了28,840条记录:批量数据处理的经验教训
上季度,我们承接了一个听起来很简单的项目:用AI生成的描述、分类和元数据丰富28,840条产品记录。客户有一个庞大的电商目录正在迁移到无头CMS,每一条记录都需要更好的内容。随之而来的是一堂关于向AI API投入数万条记录时一切可能出错——和做对——的实践课程。
这不是一份理论指南。我将为你详细介绍我们构建的实际架构、我们支付的确切成本、我们遇到的故障模式,以及拯救我们的模式。如果你正在考虑为自己的项目进行AI批量内容丰富,这应该能为你节省几周的痛苦探索。
目录
- 为什么我们选择AI丰富而不是手工工作
- 架构:我们如何构建管道
- 选择Claude API进行批量处理
- 大规模提示词工程
- 速率限制、重试和不被禁用的艺术
- 质量控制:人在环的现实
- 真实成本分解
- 我们做错了什么
- 下次我们会做什么不同的事
- 常见问题
为什么我们选择AI丰富而不是手工工作
数学计算是残酷的。我们的客户有28,840条产品记录——每条记录都需要重写描述(150-300字),三个SEO友好的分类标签,元描述,以及从非结构化文本中提取的结构化属性。按照人类文案编辑每条记录保守估计8分钟,那就是3,845个小时的工作。以每小时$35计算,你看的是$134,575和大约6个月的小型团队时间。
我们在11天内以API成本低于$3,200、加上约80小时的工程和QA时间完成了AI丰富。即使考虑我们的开发时间,总成本也大约是手工方法的十分之一。
但这里有个没人告诉你的事:困难部分不在于调用API。而是它周围的一切。数据清理、提示词调优、质量验证、错误处理,以及让你质疑职业选择的不可避免的边界情况。
架构:我们如何构建管道
我们将丰富管道构建为Node.js应用程序,这对我们在Next.js开发和TypeScript方面的专业知识很有意义。以下是高级架构:
源CSV → 解析器 → 批处理队列 → Claude API → 响应验证器 → 输出存储 → QA仪表板
数据层
我们使用SQLite作为本地处理数据库。听起来很无趣,对吧?但对于这样的批处理,它是完美的。无需管理服务器,事务速度快,你可以轻松查询结果。每条记录都获得一个状态列,跟踪其进度:
interface EnrichmentRecord {
id: string;
original_title: string;
original_description: string;
raw_attributes: string;
status: 'pending' | 'processing' | 'completed' | 'failed' | 'needs_review';
enriched_description: string | null;
enriched_categories: string[] | null;
enriched_meta: string | null;
structured_attributes: Record<string, string> | null;
attempts: number;
last_error: string | null;
token_usage: number;
created_at: string;
updated_at: string;
}
队列系统
我们使用由Redis支持的BullMQ实现了一个简单的作业队列。每个作业代表一条单一的记录丰富。我们配置了:
- 并发性:5个并行工作者(稍后将讨论为什么是这个数字)
- 最大重试:每条记录3次
- 退避:指数级,从30秒开始
- 作业超时:60秒
const enrichmentQueue = new Queue('enrichment', {
connection: redisConnection,
defaultJobOptions: {
attempts: 3,
backoff: {
type: 'exponential',
delay: 30000,
},
timeout: 60000,
removeOnComplete: false, // 保留用于审计
},
});
处理工作者
每个工作者都拉取一条记录,构建提示词,调用Claude的API,验证响应结构,并将结果写回。如果响应与我们期望的JSON架构不匹配,它进入needs_review桶,而不是无声地破坏我们的数据集。
选择Claude API进行批量处理
在确定Claude(具体是Claude 3.5 Sonnet,以及后来用于更简单任务的Claude 3.5 Haiku)之前,我们评估了三个选项:
| 特性 | Claude 3.5 Sonnet | GPT-4o | Gemini 1.5 Pro |
|---|---|---|---|
| 输入成本(每100万令牌) | $3.00 | $2.50 | $1.25 |
| 输出成本(每100万令牌) | $15.00 | $10.00 | $5.00 |
| 速率限制(RPM,第2层) | 1,000 | 500 | 360 |
| JSON模式可靠性 | 优秀 | 良好 | 不一致 |
| 结构化输出质量 | 同类最佳 | 非常好 | 良好 |
| 批处理API折扣 | 50% | 50% | 无 |
截至2025年第一季度的价格。检查当前价格——这些会频繁变化。
我们选择Claude有几个原因。首先,在我们500条记录的测试运行中,它对结构化输出的指令遵循明显优于替代方案。当你处理将近29K条记录时,即使是2%的格式合规性提高也能为你节省数百项手动更正。其次,Anthropic的批处理API为非时间敏感的工作提供了50%的折扣,这使经济学更加有利。
说实话,GPT-4o也会很好。在这个规模上,差异更多地涉及速率限制和定价,而不是原始质量。但Claude与JSON输出的一致性是决定因素。
为什么我们同时使用Sonnet和Haiku
这是一个为我们节省了大约40% API成本的技巧:我们没有对所有内容使用相同的模型。产品描述需要Sonnet的质量。但类别分类和属性提取?Haiku以很小的一部分成本就能处理得很好。
我们将丰富分为两个通过:
- 通过1(Haiku):类别分类、属性提取、基本元数据——$0.25/100万输入、$1.25/100万输出
- 通过2(Sonnet):描述重写、元描述、SEO内容——$3.00/100万输入、$15.00/100万输出
大规模提示词工程
这是大多数教程失败的地方。他们展示一个单一的提示词并称之为完成。当你通过相同的提示词模板运行28,840条记录时,微小的缺陷会被放大成巨大的问题。
提示词模板
经过大约15次迭代(是的,我们在git中跟踪了它们),这是有效的大致结构:
const buildPrompt = (record: SourceRecord): string => `
You are enriching product data for an e-commerce catalog. Generate the following for the product below:
1. A product description (150-300 words, second person, benefit-focused)
2. Exactly 3 category tags from this allowed list: ${CATEGORY_LIST}
3. A meta description (120-155 characters)
4. Structured attributes as key-value pairs
Rules:
- Do NOT invent features not present in the source data
- If information is ambiguous, use the "uncertain" flag
- Match the brand's tone: professional but approachable
- Description must be unique -- do not repeat the title verbatim in the first sentence
Respond ONLY with valid JSON matching this schema:
${JSON_SCHEMA}
Source product data:
Title: ${record.title}
Existing description: ${record.description}
Raw attributes: ${record.attributes}
Price: ${record.price}
Brand: ${record.brand}
`;
大规模提示词的经验教训
对输出格式要极其具体。 我们在每个请求中都包含了完整的JSON架构。是的,它增加了令牌。不,不要跳过它。我们唯一一次尝试仅依赖系统指令时,我们的格式合规性从97%下降到81%。
约束输出词汇。 对于分类标签,我们提供了一个明确的允许列表。开放式的分类产生了我们测试批中847个唯一的类别。约束版本?恰好是我们想要的42个。
添加对幻觉的保护栏。 产品偶尔会发芽他们没有的功能。添加"不发明源数据中不存在的功能"将幻觉属性减少了约70%。添加uncertain标志捕获了大多数剩余情况。
温度比你想的更重要。 我们最终确定为0.3。比那更低,相似产品的描述就会变得重复。更高,我们就开始进行与品牌声音不匹配的创意写作。
速率限制、重试和不被禁用的艺术
这一部分实际上应该称为"占用最多工程时间的部分"。Anthropic的速率限制有据可查,但在持续负载下的行为与你从阅读文档中预期的不同。
我们的速率限制策略
在第2层(你花费$40+后获得),Claude给你每分钟1,000个请求和每分钟80,000个令牌。听起来很慷慨,直到你意识到我们的平均请求约为1,200个输入令牌和800个输出令牌。这意味着我们在遇到令牌限制之前的实际限制约为40个并发请求。
我们运行了5个并发工作者,每个处理一条记录,请求之间有200ms的延迟。这给我们大约每分钟15-20个请求——远低于RPM限制,舒适地在令牌预算内。
const rateLimiter = new Bottleneck({
maxConcurrent: 5,
minTime: 200, // ms之间的请求
reservoir: 900, // 每分钟请求数(留有缓冲区)
reservoirRefreshAmount: 900,
reservoirRefreshInterval: 60 * 1000,
});
为什么这么保守? 因为点击速率限制会导致级联故障。一个429响应触发重试,这会添加到队列,这会增加并发压力。我们在第一次真正运行的第3小时时以困难的方式学到了这一点,当时激进的设置导致重试风暴,有效地使管道停滞了45分钟。
批处理API替代方案
在项目的中途,我们部分切换到Anthropic的批处理API。不是进行个别请求,而是上传一个请求的JSONL文件并在24小时内获得结果。权衡:节省50%的成本,但你失去实时反馈。
我们对第1通过(Haiku分类)使用了批处理API,对第2通过(Sonnet描述)使用了实时API。这种混合方法对我们来说是最佳点——对昂贵创意工作的快速反馈,对商品分类的批处理经济学。
质量控制:人在环的现实
任何告诉你AI丰富完全自动化的人要么是在撒谎,要么还没有大规模做过。我们构建了一个QA流程,能够及早发现问题并防止垃圾进入生产。
自动化验证
每个API响应在接受前都经过验证:
const validateEnrichment = (result: EnrichmentResult): ValidationOutcome => {
const issues: string[] = [];
// 长度检查
if (result.description.length < 400 || result.description.length > 2000) {
issues.push('description_length');
}
// 类别验证
const invalidCats = result.categories.filter(c => !ALLOWED_CATEGORIES.includes(c));
if (invalidCats.length > 0) issues.push('invalid_categories');
// 元描述长度
if (result.meta.length > 160) issues.push('meta_too_long');
// 幻觉信号
const hallucination_phrases = ['I think', 'probably', 'might be', 'as an AI'];
if (hallucination_phrases.some(p => result.description.includes(p))) {
issues.push('possible_hallucination');
}
// 重复检测(模糊匹配已处理的记录)
if (isDuplicateDescription(result.description)) {
issues.push('duplicate_content');
}
return {
valid: issues.length === 0,
issues,
needsReview: issues.length > 0 && issues.length < 3,
rejected: issues.length >= 3,
};
};
手动审查采样
我们对所有处理的记录中的5%(大约1,440条)进行了采样进行手动审查。我们的QA团队根据准确性、品牌声音和完整性对每条进行了评分。以下是我们实际审查的数字:
| 指标 | 分数 |
|---|---|
| 事实准确性 | 94.2% |
| 品牌声音匹配 | 87.6% |
| 格式合规性 | 97.1% |
| 类别准确性 | 91.8% |
| 需要修订的记录 | 8.3% |
| 完全拒绝的记录 | 1.9% |
那个8.3%的修订需求是重要的背景。这意味着大约2,400条记录需要人工编辑。仍然远少于手动写所有28,840条——但不是零。为它预算。
真实成本分解
透明时间到了。以下是我们实际花费的:
| 成本类别 | 金额 |
|---|---|
| Claude 3.5 Haiku(通过1 - 批处理API) | $312 |
| Claude 3.5 Sonnet(通过2 - 实时) | $2,147 |
| 失败/重试请求(约6%开销) | $189 |
| Redis托管(2周) | $15 |
| 工程时间(80小时 × $150) | $12,000 |
| QA审查时间(40小时 × $45) | $1,800 |
| 总计 | $16,463 |
| 仅API成本 | $2,648 |
将其与完全手工工作的$134,575估计进行比较。即使包括所有工程和QA时间,我们也只占手工成本的约12%。下次我们运行类似项目时,工程成本降至接近零。
每条记录的API成本最终为约$0.092。在十分钱以下,用于AI丰富。这是让高管坐起来注意的数字。
我们做错了什么
低估数据清理
在将源数据发送给Claude之前,我们花了3天来清理源数据。记录有HTML实体、Unicode垃圾、截断的描述和错误列中的字段。垃圾进,垃圾出不仅仅是一句老话——它是批量AI处理的基本法则。
没有从第一天开始使用批处理API
通过实时API运行第1通过之前发现批处理API的成本大约为一半,我们额外花费了约$400。在开始之前,阅读完整的文档。全部。
重复检测不足
我们最初的重复检测太天真了——简单的字符串匹配。Claude会为相似产品生成结构相同但使用略微不同单词的描述。我们不得不实现语义相似性检查(使用嵌入)来捕获这些,这增加了一天的工作。
JSON解析失败
大约2.4%的响应返回格式不正确的JSON。有时是尾部逗号,有时是产品描述中未转义的引号。我们应该从一开始就实现一个更宽容的JSON解析器,而不是将这些视为硬故障。
// 我们从第一天开始应该做的
const parseResponse = (raw: string): EnrichmentResult | null => {
try {
return JSON.parse(raw);
} catch {
// 尝试从markdown代码块中提取JSON
const jsonMatch = raw.match(/```json?\n?([\s\S]*?)\n?```/);
if (jsonMatch) {
try { return JSON.parse(jsonMatch[1]); } catch { /* 继续 */ }
}
// 最后的手段是尝试jsonrepair库
try { return JSON.parse(jsonrepair(raw)); } catch { return null; }
}
};
下次我们会做什么不同的事
在提交完整运行之前从1,000条记录的试点开始。我们做了500条,这不足以表面所有边界情况。
从一开始就使用结构化输出。 Anthropic现在支持带有定义的架构的工具使用,这消除了大多数JSON解析问题。我们在中途迁移到这个,希望我们从一开始就开始了。
先构建QA仪表板。 我们在问题出现后被动地构建了它。从第一天开始使用它会在第一个100条记录而不是前2,000条记录中发现问题。
投资于更好的去重嵌入。 我们会从一开始就使用专用嵌入模型(如
text-embedding-3-small)进行语义重复检测。考虑混合模型路由。 一些记录很简单(带有基本属性的t恤),一些很复杂(带有数十个规格的电子产品)。将简单记录路由到Haiku,复杂的路由到Sonnet——甚至对于描述——会额外节省API成本的20-30%。
如果你正在规划类似的项目并想跳过痛苦的部分,我们已经为这种工作构建了可重用的管道,作为我们无头CMS开发实践的一部分。很乐意分享更多细节。
常见问题
用AI丰富28,000+条记录需要多长时间? 我们的实际处理时间约为11天,包括管道开发、测试、处理和QA审查。API处理本身(发送请求和获取响应)花费了大约48小时的连续运行。如果你完全使用批处理API,期望处理需要24-48小时,加上3-5天的工程和QA。
AI内容丰富的每条记录成本是多少? 使用Claude 3.5 Sonnet和Haiku的组合,我们的API成本约为每条记录$0.092,用于生成产品描述、类别、元描述和结构化属性。你的成本可能会根据输入/输出长度和你选择的模型而变化。批处理API处理的成本大约减半。
对于批量数据丰富,Claude还是GPT-4更好? 两者都很好。我们选择Claude 3.5 Sonnet是因为在我们的测试中其JSON格式合规性优越(97.1%对GPT-4o的约94%)。但是,GPT-4o在输出令牌的成本上略便宜。如果你的丰富主要是分类而不是内容生成,差异可以忽略不计。在提交之前,用500条记录测试两者。
进行数千次API调用时如何处理速率限制? 使用Bottleneck等速率限制库,设置保守的并发(5-10个并行请求),为重试实现指数退避,并在发布的速率限制下留下10-15%的缓冲区。对于非时间敏感的工作,Anthropic的批处理API完全避免了速率限制问题,成本低50%。
多少百分比的AI丰富记录需要人工审查? 在我们的项目中,8.3%的记录需要某种形式的人工编辑,1.9%被完全拒绝并手动重写。你的数字将取决于数据质量、提示词工程和可接受的质量阈值。计划5-15%的人工干预作为现实的基线。
AI批量丰富能处理多种语言吗? 是的,但质量因语言而异。Claude和GPT-4能很好地处理主要的欧洲语言,但对于不太常见的语言,准确性会下降。我们建议为每种语言运行单独的提示词模板,并让本地使用者参与QA采样。期望非英语内容的人工审查百分比大约翻倍。
如何防止AI在产品数据中的幻觉? 三层:提示词指令明确禁止虚构功能、用于模糊数据的"uncertain"标志,以及自动化验证比较丰富的属性与源数据。我们还使用了语义相似性评分来标记与原始产品信息的描述偏差太大。这将幻觉属性减少了约70%。
值得构建自定义管道还是应该使用现有工具? 对于少于1,000条记录,Clay、Bardeen或甚至结构良好的Google Sheets + Apps Script设置都可以工作。超过这个,自定义管道会很快自我支付。对重试逻辑、质量验证和成本优化的控制,这是自定义解决方案提供的,在规模上至关重要。我们的管道大约2,000行TypeScript——不琐碎,但也不是一个巨大的项目。如果你想让我们为你的用例构建一个,请检查我们的定价页面。