
从零搭建 LangChain RAG 项目的 30 天成长日记每日一个工程决策30 天前我从零开始用 LangChain 搭建一个内部知识库 RAG 系统。这不是 demo是要给 50 个同事日常使用的生产系统。每天我做一个工程决策记录下来。现在回头看这些决策里有一半是错的但正是在错误中迭代项目才从能跑变成了能用。我把这 30 天的关键决策整理出来每个阶段讲核心决策和踩过的坑。一、深度引言与场景痛点很多人卡在第一周——demo 跑通了就觉得自己做完了。实际上从 demo 到 production 的时间比例大约是 1:3。第一周是 20% 的工作量后面三周是 80% 的工程化工作。二、底层机制与原理深度剖析第 1 天决定不用 LangChain 的高层抽象。LangChain 的VectorstoreIndexCreator一行代码就能搭 RAG但我决定只用 LangChain 的基础组件Document Loader、Text Splitter、Embeddings 封装。Chain 和 Agent 自己写。原因高层抽象太黑盒出了 bug 没法定位。后来的经验证明这个决策是正确的——项目里最复杂的 bug 都在自定义逻辑里LangChain 的基础组件一次都没出过问题。第 3 天选择 Markdown 分块而不是固定长度分块。知识库文档是 Markdown 格式。用CharacterTextSplitter按 1000 字符切分会切断标题和代码块。改用了MarkdownHeaderTextSplitter按#、##、###层级切分。这个改动让检索召回率提升了 15%。第 5 天放弃 Pinecone选择 Chroma 做 PoCRedis 做生产。Pinecone 的免费额度只能存 10 万条向量不够用。PoC 阶段用 Chroma本地文件存储零配置生产阶段换成 Redis Stack Server。切换成本很低因为 LangChain 的 VectorStore 接口统一。第 7 天第一版评测结果惨不忍睹。用 30 个内部问题做了首轮评测。召回率 45%、答案满意度 40%。最大问题是文档里有很多缩写词和专业术语Embedding 模型的语义理解和实际需求之间存在巨大的语义鸿沟。三、生产级代码实现第 8 天引入了 Query 改写层。用户输入的问题经常不完整。那个报错怎么处理——哪个报错加了 Query 改写用 LLM 把用户问题改写成完整的检索语句。召回率从 45% 提升到 65%。第 10 天混合检索的关键词通道上线。纯向量检索漏掉了大量精确匹配错误码、API 名。加了 BM25 通道用 RRF 融合。混合检索让技术类查询的召回率从 65% 提升到 82%。第 12 天遇到中文分词问题。BM25 用的默认分词器把数据库连接池切成了数据/库/连接/池。换了 jieba 分词器后关键词检索准确率提升了 20%。LangChain 默认不处理中文分词这是国内 RAG 项目的一个隐蔽陷阱。第 14 天响应延迟 P95 超过 8 秒。加上了所有检索优化后简单查询 2 秒、复杂查询 8 秒。延迟大头在 LLM 生成占 70%。做了三件事换成 streaming 输出、限制 max_tokens512、加 query 缓存。P95 降到 4 秒。四、边界分析与架构权衡第 16 天建立了自动化评测 Pipeline。写了一个评测脚本每次 push 自动跑 50 个测试用例输出召回率、忠实度、答案相关性。设定阈值三项都 0.7 的 PR 不允许合并。第 18 天发现 RAG 评测的假阳性问题。有的答案看起来相关实际上引用了错误文档。加了引用溯源检查答案的每个关键声明必须有至少一个检索文档作为支撑。LLM 做裁判自动标记无依据声明。第 19 天重新设计了文档结构。原来的文档是散装的没有元数据。加上了文档标题、更新时间、作者、标签。检索时可以按标签过滤也让 LLM 知道每条信息的时效性。第 21 天评测分数 85%准备上线。经过两周优化自动化评测的总体分数从 40% 提升到 85%。可以上线了但还需要解决监控问题。五、总结第 23 天加上成本追踪。每次查询记录 Token 消耗和费用。发现 QA 类问题平均消耗 $0.02分析类平均 $0.08。设置了日预算上限 $10超预算自动降级到简化回答模式。第 25 天灰度发布给 10 个用户。3 天里收集了 200 条真实查询。发现了一个 RAG 评测集里不存在的问题类型用户经常问XXX 和 YYY 有什么区别。评测集里没有对比类问题这是评测体系覆盖不全的典型教训。第 26 天紧急补充了对比类评测用例。从真实日志里捞了 20 条对比类查询补进评测集。对比类问题的忠实度比事实类低 15%因为需要综合多篇文档的信息。调整了 Prompt要求 LLM 在回答对比问题时显式标注每一条信息的来源文档。第 28 天发现并修复缓存穿透。高频查询怎么连接数据库每天被问 50 次每次都走完整 RAG 链路。加上了查询级缓存命中率 35%延迟 50ms。第 30 天全量上线开启监控。50 个用户全量使用。P95 延迟 3.5 秒用户满意度 82%。设立了三项 SLO可用性 99%、P95 延迟 5 秒、用户满意度 75%。六、从 30 天日志提炼的工程决策框架import asyncio from datetime import datetime, timedelta from dataclasses import dataclass from typing import Any import logging logger logging.getLogger(__name__) dataclass class ProjectPhase: name: str start_day: int end_day: int goals: list[str] pitfalls: list[str] class RAGProjectChecklist: def __init__(self): self.phases [ ProjectPhase( 验证, 1, 7, goals[跑通基础链路, 评估技术可行性, 选型对比], pitfalls[ 过早引入复杂架构, 评测集太少, 忽略中文分词, ], ), ProjectPhase( 工程化, 8, 14, goals[接入真实数据, 处理边界情况, 延迟达标], pitfalls[ Query改写过度工程化, 混合检索调参玄学, 缓存设计不到位, ], ), ProjectPhase( 质量攻坚, 15, 21, goals[评测体系自动化, 召回率80%, 忠实度80%], pitfalls[ 评测集覆盖不全, 假阳性答案, 忽视对比类问题, ], ), ProjectPhase( 生产化, 22, 30, goals[监控告警, 灰度发布, 成本控制], pitfalls[ 没有成本追踪, 没有降级策略, 过早全量发布, ], ), ] def generate_report(self, current_day: int) - dict: current_phase None for p in self.phases: if p.start_day current_day p.end_day: current_phase p break if not current_phase: return {status: 已完成所有阶段} progress ( (current_day - current_phase.start_day 1) / (current_phase.end_day - current_phase.start_day 1) * 100 ) return { day: current_day, phase: current_phase.name, progress: f{progress:.0f}%, goals: current_phase.goals, watch_out_for: current_phase.pitfalls, }七、总结30 天从零到生产最大体会三条第一周不要追求完美架构。用最简单的方案跑通链路拿到真实的坯结果比完美的架构更重要。评测体系要早建。没有评测的优化是随机游走。第一周搭完基础链路第二周就要有评测。生产化的投入是工程化的两倍。监控、告警、灰度、降级、成本——这些东西让系统从能用变成可靠。别跳过。资料说明本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论不应视为行业事实。可参考 0730 资料来源索引并在发布前将具体来源贴到对应断言之后。