大规模生产级 RAG:每日处理 10,000+ 条列表的经验教训
我为一家招聘网站构建了一个 RAG 流水线。它在测试环境中运行良好,但在真实负载下却显得力不从心。每天处理数千条职位列表,不仅仅需要一个优秀的向量数据库,你还必须了解系统会在哪里崩溃。
以下是我在分块 (chunking)、嵌入 (embeddings)、成本和可观测性方面的经验教训。
- 不要凭直觉决定分块策略
大多数教程将分块视为一个简单的设置。但在生产环境中,你的策略决定了准确性和成本。
我针对职位列表测试了三种方法:
- 固定大小分块 (Fixed-size chunks):这种方法失败了。它们会在随机点切断“职位要求”和“福利待遇”等章节,从而导致检索结果产生噪音。
- 语义分块 (Semantic chunking):效果稍好,但不够稳定。有些分块太长,有些则太短。
- 带重叠的递归字符切分 (Recursive character splitting with overlap):效果最好。我根据换行符和句子进行切分。我使用了 400 个 token 的大小,并设置了 50 个 token 的重叠。这确保了跨越两个分块的句子能够保持连贯。
专业建议:在分块之前先进行数据归一化。来自 Greenhouse 或 Lever 等不同来源的数据格式各异。先清洗文本,让你的分块器看到一致的结构。
- 嵌入 (Embeddings):成本与准确性的权衡
我对比测试了通过 Ollama 运行的 Llama 3.1 和 OpenAI 的 text-embedding-3-small。 本地模型虽然免费,但在处理“股权激励 (equity compensation)”等领域特定术语时表现挣扎,产生的结果带有噪音。 OpenAI 的成本更高,但能提供准确的匹配。我最终选择了 OpenAI,因为检索质量差会导致后续 LLM 调用产生更高的成本。
为了节省时间,我采用了批量请求。我一次调用最多发送 100 个分块。这降低了延迟,并保持了流水线的高效运行。
- 向量数据库的权衡
我在原型设计阶段使用了 Pinecone,因为它搭建速度很快。然而,随着规模扩大,成本变得过高。
我切换到了 PostgreSQL 中的 pgvector。
- 设置起来更费功夫。
- 节省了大量的资金。
- 提供了事务一致性。 由于嵌入数据与职位数据存储在同一个数据库中,你拥有了单一的事实来源 (source of truth),无需同步两个不同的系统。
- 控制 LLM 成本
使用 GPT-4o 为每条列表评分非常昂贵。我采用了三种策略来降低成本:
- OpenAI Batch API:我会在夜间处理评分任务,这可以获得大幅折扣。
- 缓存 (Caching):我为重复出现的候选人档案缓存结果。
- 模型分层 (Model tiering):对于“销售代表 (Sales Representative)”等常见职位,我使用 GPT-4o-mini。只有在精度至关重要的利基 (niche) 职位上,我才会使用 GPT-4o。
- 先构建可观测性
我的流水线曾经发生过“静默失败”。格式错误的数据导致产生了空分块,而系统在没有报错的情况下直接跳过了它们。
我通过添加带有关联 ID (correlation ID) 的结构化日志解决了这个问题。这让我能够追踪单个列表从摄取到评分的全过程。我终于能看清是哪些数据源导致了失败。
最深刻的教训是:大多数问题源于混乱的数据,而非 AI 本身。先修好你的数据管道 (data plumbing)。
可选学习社区:https://t.me/GyaanSetuAi
