最近好几个朋友跟我聊 AI 项目,聊到最后都会落到同一个困惑上:模型选型、Prompt 调优、算法指标都折腾过一轮了,产品还是做不“厚”,每次新需求都要在原地重盖地基。我自己的体会是,问题往往不出在模型层,而出在项目开场时压根没搭骨架——更准确地说,没搭“可学习的知识底座”。
这几个月我陆陆续续帮几个项目做过骨架梳理,也从零搭过一两个 AI 功能模块,最大的心得是:AI 产品和传统 CRUD 应用的差别,不在于用了什么模型,而在于它的能力必须随着使用持续进化。这个进化能不能发生,靠的不是模型有多聪明,而是项目骨架里有没有一条从数据到知识、从知识到能力、从使用反馈再回到知识的完整通路。这篇文章就是想把这条通路拆开讲清楚,适合刚接手 AI 模块的开发者、想把 Demo 变成正式系统的负责人,以及被知识库频繁更新搞得焦头烂额的团队参考。
1. 知识底座和普通数据仓库最大的区别:它会生长
1.1 先分清楚“数据”和“知识”这两层
很多团队启动 AI 项目时第一个决策就是把所有东西塞进数据库,理由是“反正模型都是吃数据长大的”。但真到落地你就会发现,数据仓库解决的是“系统里存了什么”,知识底座解决的是“AI 该怎么理解和使用这些信息”。这两个东西用同一套结构装,迟早出问题。
传统业务系统里,数据仓库是给人和报表看的,表结构稳定、字段枚举清晰、按业务维度组织,查询讲究精确命中。知识底座不一样,它服务的对象是模型和检索器,必须容忍语义多样性、支持非结构化文本、允许数据质量模糊,还要能不断追加修正。项目骨架如果一开始就把这两层混在一起,后面所有做检索、做反馈回流的人都得跟这张混合表较劲。
我参与过一个内部问答系统的改造,最初的实现是把客服记录直接存在业务库里,模型每次回答都临时全表扫。单表单表跑得动,因为数据量小;三个月后数据翻了几倍,检索慢是一回事,更麻烦的是没有任何语料分级和版本概念,运营同学想修正一条错误回答,得靠 DBA 手工改数据库记录,改完也不知道会影响哪部分用户。这就是典型的“用数据仓库的姿势管理知识资产”。
1.2 “会生长”到底是什么意思
知识底座的“生长”体现在三个层面。第一层是数量增长:新的对话记录、新的产品文档、新的用户反馈不断汇入。第二层是质量提升:同一条知识从粗粝的原始文本,经过清洗、标注、人工修正,慢慢变成可靠的标准答案。第三层是结构演化:一开始只有零散问答对,后来有了分类目录,再后来有了知识版本和淘汰机制。
普通数据仓库关心的是“现在有什么”,知识底座关心的是“当下最该用什么”。同样一条业务规则,上线第一天和上线第一百天,在知识底座里的存在形态应该是不一样的——第一天它可能只是文档里的一句话,第一百天它可能已经被拆解成结构化事实、被多条用户反馈验证过、被打上了高置信度标签。能支持这种形态变化的骨架,才配叫可学习的知识底座。
1.3 项目骨架的第一个设计动作
所以项目骨架的第一个设计动作不是建目录,而是画一条“进化路径”:原始语料从哪进来,经过哪些加工变成知识,模型如何消费知识,用户反馈如何重新进入语料池,人工审核插在哪个环节。这条路径想清楚了,目录结构和模块边界自然就出来了。
我习惯把这条路径分成四段:采集、加工、服务、回流。采集面向原始素材,加工负责清洗和结构化,服务面向模型和用户查询,回流收集使用中的反馈信号。后面的章节我会按这个顺序逐段展开,先讲加工链,再讲知识表示和检索,最后讲反馈闭环和落地骨架。
2. 语料加工链路:从业务垃圾到可学习矿藏的核心工序
2.1 先找到那些被低估的隐形语料
很多人一想到“知识来源”,第一反应是产品文档和 Wiki,但这远远不够。真正让 AI 产品变得好用的知识,往往散落在客服聊天记录、工单系统、用户留言、历史报障、操作日志、老员工的工作笔记里。这些素材一开始看着不够“规整”,但信息密度往往比正式文档高得多——因为它们是真实问题逼出来的表达方式。
我的习惯是先做一次语料盘点,输出一张来源清单,标注每个来源的体量、更新频率、结构化程度和信息可信度。这一步不用做得很重,但要真实走一遍,因为后面加工链的优先级完全取决于这张清单。比如某一次盘下来,发现 80% 的高质量答案藏在 5000 条历史工单里,那加工链的预算就重点投在工单清洗上,而不是花时间美化产品文档。
2.2 加工链的四道核心工序
第一道是清理。原始语料里充满噪音:HTML 标签、回复前缀、座席签名、脱敏信息、重复字符。清理的目标不是把文本变漂亮,而是保住语义完整性的前提下把噪音干掉。这里有个容易犯的错误:清洗规则写得太激进,把昵称、产品别名、口语特有说法也一并删了,导致后续语义检索时连“用户到底怎么称呼这个功能”都识别不出来。
第二道是归一化。统一单位、统一称谓、统一时间格式,把“今天下午”“马上”“3 个工作日内”这类相对表达转成可计算或可排序的形式。归一化的深度取决于业务需要,不要为了统一而统一,工程上建议只处理影响检索和答案生成的字段。
第三道是分块。对非结构化文本按语义边界切块,切的好不好直接决定向量检索的上限。我的经验是优先按文档自身的结构走:标题、段落、列表项、表格说明都是天然断点,一个块尽量只承载一个主题,块与块之间不要塞太多上下文。切得太碎会丢语境,切得太粗会引入噪音,块长度通常落在几十到几百词之间。
第四道是增强与去重。同一件事在不同文档里可能有不同说法,需要通过别名表、同义词映射甚至人工审核把它们关联起来;完全重复的块直接删除,近似重复的块保留高置信度版本,其余归档到待处理区。这一步决定了知识底座有没有冗余噪音,也决定了后面反馈修正时会不会出现“改了一处、漏了十处”的情况。
2.3 给每条语料发一张“身份证”
语料从原始素材变成可学习知识的过程中,有一件事最容易被忽略:血缘追踪。每条加工后的知识块,都应该能回溯到它的原始出处、经过哪条清洗规则、由谁或哪个脚本负责修改、什么时候被写进知识库、被哪些模型版本消费过。
没有血缘的知识底座,上线初期看不出问题,三个月后一定出事。某次线上回答突然变差,业务方来质问,你翻遍代码也找不到原因,最后才发现是半年前某次清洗规则更新误伤了某类高频问题。有血缘之后,这类排查可以从“大海捞针”变成“顺着血缘链逐段定位”。我在骨架里专门留了一张血缘元数据表,记录知识块 ID、来源文档 ID、加工脚本版本、入库时间和下线时间,这张表本身算不上知识,但它决定了整个底座可不可信、可不可维护。
3. 知识表示是个策略问题,不是存储姿势问题
3.1 表示方式决定能力边界
每个做 RAG 相关项目的人都会面临一个诱惑:把所有知识一股脑向量化,然后用语义检索一把梭。向量化确实强大,但它不是银弹。知识用什么形态表示,直接决定了后面模型能用这些知识做什么。
举个很简单的例子。假设用户问“这个产品支不支持批量导入”,如果知识是以结构化事实存的,你能拿到精确的“支持/不支持/有条件支持”字段,再叠加条件判断准确回答;如果全变成向量文本,你在相似度上赌概率,遇到两个说法接近但含义相反的块,很可能翻车。反过来,用户问“我导入了半天还是失败,是不是文件格式有问题”,这种开放式的求助,最优匹配靠的是语义召回而不是字段枚举,结构化数据反而不够用。
3.2 三种知识形态的对照
| 形态 | 适合的知识类型 | 优势 | 短板 |
|---|---|---|---|
| 结构化记录 | 可枚举事实、规则条款、参数配置 | 精确、可计算、易审计 | 无法应对开放表达 |
| 非结构化文本 | 流程说明、经验总结、案例描述 | 信息密度高、保留上下文 | 检索质量依赖分块和清洗 |
| 向量化表示 | 语义模糊的诉求、开放域匹配 | 召回强、容忍表达差异 | 可解释性差、需要重排 |
我见过不少团队只做其中一列,然后抱怨效果不理想。其实更合理的做法是三层混合:先按业务把知识分成“事实类”和“经验类”两大类,事实类尽量抽成结构化字段,经验类保留为文本块,再在检索时统一映射到向量空间做语义召回。这个方案听起来比单列复杂,但架构上其实只是多了一层转换,换来的是精确性和召回率的双重保障。
3.3 分层建模的具体做法
具体落地上,我是这样分的。第一层是事实知识层,用表格或者键值对存放产品参数、政策条款、支持范围这类有标准答案的内容,更新时走严格的字段校验和审批。第二层是经验知识层,存放 FAQ、历史工单里的解法、操作流程说明,保留完整文本和元数据,检索时可以按分类、时间、来源过滤。第三层是语义索引层,把前两层都映射成向量,用于语义召回和排序。
三层之间的数据流是单向的:事实层和经验层是“权威源”,语义索引层是从它们生成的派生数据。这带来一个好处——当需要修正一条知识时,你改的是权威源,向量索引只是派生品,可以定期重建,不会出现“索引里改了、原文档没改”的脏数据。
4. 检索接口的设计,决定了知识底座的“可插拔性”
4.1 模型和知识底座之间只有一条窄路
知识底座建得再好,最终都要通过一个检索接口跟模型对话。这个接口设计得怎么样,直接决定知识底座能不能被多个模型、多个功能模块共享,也决定了以后换模型时知识底座要不要跟着重写。
我见过的最差做法,是业务代码里到处直接查数据库、直接调向量库,索引结构调整一次,上层代码跟着改一遍。合理的方法是收敛出一个统一的检索服务接口,上层只跟它打交道,屏蔽底层向量库、关键词检索引擎和重排逻辑的差异。这样当底层向量库要更换、索引分片策略要调整,或者从单路召回改成多路召回时,上层模型和业务完全无感。
4.2 接口契约长什么样
一个实用的检索接口,输入除了用户 query 之外,还应该带上几个控制参数:返回条数、业务域过滤条件、时间范围、最低置信度阈值。返回结构建议统一包含知识块内容、来源标识、置信度分数、命中片段和元数据(如更新时间、业务分类)。这些字段看似简单,但缺了它们,上层根本无法做去重、溯源和答案引用。
# 检索接口的简化示意 def search( query: str, biz_domain: str = "", top_k: int = 5, min_score: float = 0.3, time_range: tuple = None ) -> List[SearchResult]: # 1. 关键词召回:走倒排索引,精确匹配专有名词 kw_results = keyword_search(query, domain=biz_domain) # 2. 向量召回:走语义索引,召回改写、口语化表达 vec_results = vector_search(query, domain=biz_domain) # 3. 融合打分:加权合并两路结果 merged = merge_results(kw_results, vec_results, weights=(0.5, 0.5)) # 4. 重排:用 rrf 或简单排序护栏,保证结果稳定 ranked = rerank(merged.elements, query) # 5. 按阈值剪裁并返回 return [r for r in ranked if r.score >= min_score][:top_k]4.3 召回是两步,不是一步
很多团队做 RAG 时把“检索”当成一个动作,其实检索内部至少拆成召回和重排两步。召回阶段追求高覆盖率,宁可多返回一些噪声,也要把正确答案包进来;重排阶段追求高准确率,从召回结果里把最相关、最可信的排到前面。
召回阶段的常用组合是关键词召回加向量召回:关键词负责专有名词和精确条款,向量负责语义相似和同义改写。融合的时候可以简单加权,但更稳的做法是先用关键词召回的强势信号做硬过滤,再用向量分数调整顺序。重排阶段再菜也可以先做规则重排:官方文档优先、更新时间新优先、高置信度优先;条件允许后可以在候选集合上跑一个轻量排序模型。
我一直建议把金标准测试集塞进骨架的第一版。所谓金标准测试集,就是挑出几百个有代表性的历史问题,每条手工标注好最匹配的知识块 ID。每次改检索链路、调打分权重,就把这个测试集跑一遍,看召回率和排序有没有劣化。很多团队没有这个习惯,改检索和改代码一样靠感觉,上线后才发现某个高频问题被召回结果悄悄挤掉了。有金标准集在,回归测试就有了锚点。
4.4 检索可观测性别省
检索接口上线后,至少要记录三类日志:query 原文、返回结果 ID 列表、用户最终有没有采用某个结果(点击/采纳/复制)。这些日志既用于线上指标监控,也是后面搭建反馈闭环的数据基础。日志格式建议在骨架阶段就定好,别等上线了再补,因为复盘需要的字段往往只在第一时间能准确采到。
5. 把“学习”做进项目里:反馈回流、批次更新与版本治理
5.1 学习不是模型自动变聪明,而是流程在变聪明
每次听到“让 AI 自动学习”这种说法我都想泼冷水。现实里,可学习的知识底座靠的是一套朴素的流程:感知信号、沉淀语料、加工复核、更新上线。模型本身当然可以参与其中,但决定学习效果的是这条流程能不能稳定跑起来。
感知环节是最容易被忽略的。用户点了个赞、点了踩、复制了回答、追着问了一句“不对吧”,这些信号如果没被采集系统接住,知识底座就只能靠人工手动维护。我在骨架设计里都会加一个 feedback collector 组件,在业务侧埋点,把用户行为统一转化成标准事件,写入待处理队列。这个组件代码量不大,但它决定了学习闭环的“第一公里”通不通。
5.2 哪些反馈信号值得回收
不是所有反馈都值得进入知识底座,信号质量参差不齐。我按价值排序:
- 用户的显式修正:用户直接在回答下方编辑正确内容,或运营同学手动补充标准答案,这是最高价值的信号,可以直接进入语料加工链。
- 显式评价:点赞、点踩、评分,带有较强的倾向性,但需要结合内容看是表达不满还是表达认同。
- 行为信号:复制回答、展开详情、继续追问,这类行为只能间接推断回答有没有用,信噪比低,要谨慎使用。
- 会话级信号:整个会话兜兜转转没有解决用户问题,可能说明召回或答案出了问题。
我的建议是:前两类信号进“直接学习通道”,后两类进“诊断通道”。直接学习通道改变知识底座内容,诊断通道只生成改进建议,由人工或定期任务决定是否采纳。两条通道分开,能避免被噪声直接污染权威源。
5.3 批次更新与版本治理
知识底座更新最怕三件事:实时写入导致线上效果抖动、批量导入导致旧知识被误覆盖、更新后无法回滚。对应三条原则我几乎每次都会强调:异步化、批量化、版本化。
反馈事件先进入待处理队列,由消费任务定期批量汇入语料池,而不是每次反馈都马上改索引。加工完成的新知识块先落到候选区,跑一遍金标准集,确认没有劣化再发布。每次发布都生成一个版本快照,线上遇到大面积效果回退时,可以一键切回上一个版本。这套机制看起来重,但它保证的是:知识底座能在无人盯着的情况下持续演进,同时随时留有后悔药。
6. 一个可参考的最小骨架:某智能问答助手的目录与装配流程
6.1 从零搭一个“某智能问答助手”
概念讲再多,不如一套目录和流程来得直接。我用一个虚构的“某智能问答助手”项目举例,它的知识底座需要覆盖产品 FAQ 和历史客服工单。骨架的目录设计如下:
ai_qa_assistant/ ├── knowledge_base/ │ ├── sources/ # 原始素材,只进不出 │ │ ├── faq_docs/ │ │ └── ticket_export/ │ ├── cleaned/ # 清洗后的规范语料 │ ├── facts.db # 结构化事实层 │ ├── chunks/ # 经验知识层的文本块 │ └── vector_index/ # 派生向量索引,可随时重建 ├── collector/ # 反馈信号采集组件 │ ├── events_queued/ # 待处理队列 │ └── feedback_archive/ # 已处理反馈归档 ├── processor/ # 语料加工流水线 │ ├── clean.py │ ├── normalize.py │ ├── chunk.py │ └── enrich.py ├── retriever/ # 统一检索服务 │ ├── api.py │ ├── keyword_search.py │ ├── vector_search.py │ └── rerank.py ├── evaluator/ # 金标准测试集 │ └── golden_questions.json └── scripts/ ├── ingest.py ├── rebuild_index.py └── rollback.py这个骨架的关键点在于:sources 目录只进不出,保证原始素材不被加工逻辑污染;cleaned、chunks、facts.db 是加工后的权威源;vector_index 是派生数据,可以删了重建;evaluator 里的金标准集独立于任何模块,专门用于回归验证。
6.2 首次启动的六步流程
第一步,把 FAQ 文档和历史工单导入 sources 目录,尽量保持原始格式,不做任何加工。第二步,运行加工流水线,把材料清洗、归一化、抽出结构化事实,同时按语义边界切块。第三步,运行脚本把事实层和文本块统一映射进向量索引。第四步,在 evaluator 里写入两百条带标准答案的金标准问题。第五步,启动检索服务,用金标准集跑一遍,看召回率和排序情况。第六步,启动反馈采集组件,把埋点事件接入待处理队列,闭环就通了。
这套流程第一次跑可能一两天就能搭完,但它的价值在于:之后任何一轮知识更新都走同一套流水线,不会因为某次临时改库把数据搞乱。我自己的习惯是先把流水线跑稳,再考虑加模型、加界面,骨架永远先于功能落地。
6.3 一条工单从进入到被回答的旅程
假设有一句用户反馈“我导入文件一直失败,提示格式错误”,这条工单进入 sources 后,清洗脚本去掉座席通话前缀,归一化把“一直失败”这种口语保留下来,切块后落入经验知识层。这时用户再问类似问题,检索服务先做关键词召回和向量召回,融合打分后把这条工单对应的知识块排到前面,模型引用它生成回答,并附上来源标识。回答被点赞或点踩后,反馈事件进入 collector,如果被点踩,后续会转人工复核,复核结论再作为新语料进入加工链。这就是一个完整的知识底座循环。
7. 骨架落地之后,我再也不想踩的四个坑
7.1 骨架比功能还大,成了表演工程
最常踩的坑是开工第一天就把目录建得巨细无比,分布式队列、多租户、权限中心全上了,结果业务功能一行没写。骨架的价值是支撑演进,不是炫技。我现在的经验是:先保证单机、单库、单流水线能跑通闭环,再根据真实瓶颈去加复杂度。过早引入微服务和复杂的中间件,只会让团队把时间耗在基础设施上。
7.2 默认把所有知识一股脑向量化
第二种坑是对向量化的盲目信任。结构化的规则,比如“仅企业版支持批量导入”,一旦只有向量文本形式,精确回答就变得不可控。我会在骨架里专门留结构化事实层,哪怕一开始字段很少,也保留这个通道,因为以后它们一定会用上。
7.3 血缘缺失,三个月后成了无头悬案
第三种坑是没有跟踪每块知识从哪里来、经过谁加工、被谁消费。没有血缘,线上回答出问题的时候,你只能靠人肉翻日志;有血缘,一两小时内就能定位到是清洗脚本、索引重建还是反馈误入的问题。血缘表在骨架阶段加几乎零成本,后补则要重建整个追溯体系。
7.4 线下测试集和线上反馈口径对不上
最后一种坑是金标准集只标注文档 ID,线上反馈却按用户体验打分,两者衡量的是完全不同的东西。金标准集测的是“检索有没有把正确知识块召回来”,线上反馈测的是“用户回答有没有解决他的问题”,中间隔着生成模型和表达方式,差距会被放大。骨架里应该同时保留检索集和答案质量集,分别归因,才不会出现“检索指标看似很好,用户依然不满意”的局面。
这几个月做下来,我越来越确信一件事:AI 产品能走多远,不取决于你用多新的模型,而是取决于项目骨架有没有把“可学习”当成一等公民来设计。知识底座不是一次性建好的仓库,而是一条要一直转下去的流水线。尽早把这条流水线的雏形搭出来,哪怕开始丑陋一点,也比后期翻修成本低得多。如果你也正在搭自己的 AI 项目骨架,我的建议很简单:先画进化路径,再建目录结构,最后再填功能。顺序对了,很多事情会自动顺起来。