AI 知识图谱踩坑记录
客服知识库升级:用户问「怎么退款」,系统把带「退款」的文档全列出来,还得自己点进去找答案。关键词搜不理解上下文,「退款流程」和「退款失败」混为一谈;同一知识点散在多篇文档里,也没法自然追问。
预算和时间都卡着,团队熟 Python 和 Neo4j,最后选了图数据库方案而不是从零搭 RDF/OWL 那套。
需求到底是什么
项目背景是个客服系统的知识库升级。原有的系统就是个简单的关键词搜索,用户问"怎么退款",系统就把所有带"退款"两个字的文档都列出来,用户还得自己点进去看哪篇才是真正需要的。
这次要解决的问题有几个:
- 召回太宽泛。关键词搜索根本不理解上下文,“退款流程"和"退款失败"都被认为是相关结果。
- 答案不直接。用户想要的是答案,不是文档链接。
- 知识碎片化。同一个知识点散落在多个文档里,用户得自己拼凑。
- 不支持追问。用户问完一个问题后,没法自然地接着问下去。
需求说完了,但还有一个现实限制:预算有限,时间也很紧。不能搞个几百人的团队,也不能搞个大型分布式系统。
方案怎么选
摆在面前的方案有几个:
传统知识图谱: 用三元组 (实体, 关系, 实体) 保存知识,用 RDF/OWL 这种标准格式。优点是结构清晰、标准化程度高,缺点是构建成本高,非技术人员根本没法维护。
图数据库直接干: 用 Neo4j 之类的图数据库存关系,用 Cypher 查询。优点是查询灵活、可视化方便,缺点是要自己搞定数据抽取和映射。
向量 + 知识图谱结合: 用向量做语义召回,用知识图谱做关系推理。这是比较流行的路线,但复杂度也上去了。
考虑到项目的时间限制和团队技术栈,最后选了图数据库方案,具体是 Neo4j。主要原因是:
- 工具生态比较成熟,APOC 插件能处理不少复杂场景
- 社区资料多,遇到问题容易找答案
- 可视化比较友好,老板能看懂
构建流程怎么走
把知识图谱建起来,大概要经历这么几个步骤:
数据清洗:先把垃圾清理干净
原始文档质量参差不齐,有些是 Word 转 PDF 的乱码版,有些是不同部门写的重复内容。这一步不做干净,后面的实体抽取全是白费。
清洗重点有几个:
- 去重。用 MD5 去重只能处理完全相同的文件,还得用文本相似度检测找出那些"几乎一样"的。
- 格式统一。把不同格式转成统一的 Markdown,方便后续处理。
- 分段处理。长文档要拆成段落级别的 chunk,太大不好抽取,太小又丢失上下文。
这一步花了不少时间,但很值。之前跳过直接干抽取,结果后面修数据的时间比从头清洗还多。
实体抽取:把有用的东西挖出来
实体抽取其实就是从文本里找出"东西”,比如人名、地名、产品名、流程名等等。
可以自己训练模型,但考虑到时间成本,我们用了现成的 NER 工具。具体用了什么不重要,关键是效果要够用。
但模型不是万能的,有几个问题:
- 专业名词识别不准。客服系统里有很多内部术语,模型没见过。
- 同义词问题。“退款单"和"退款申请"其实是一个东西,但模型会当成两个。
- 边界模糊。有些实体前后缀很多,模型要么截太短要么截太长。
解决办法是搞了个白名单,把业务上明确的实体都列出来,模型抽取出来后再白名单过滤一遍。虽然有点土办法,但稳。
关系抽取:把东西连起来
有了实体还不行,得知道它们之间什么关系。比如"订单"和"退款"是"包含"关系,“用户"和"订单"是"发起"关系。
关系抽取比实体抽取难多了。模型容易把"相关关系"当成"特定关系”,比如看到两个词在同一个句子里就认为有关系。
我们用了一个相对简单的策略:
- 先定义一套关系模式。比如"谁 发起 了 什么订单”、“哪个订单 包含 哪个退款”。
- 用规则匹配 + 模型辅助的方式提取。规则保证覆盖核心场景,模型补漏。
- 人工抽检校正。这个没法省,实体关系错了,后面的应用全是错的。
图谱构建:存进去
前面几步都是数据处理,这一步才是真正把数据塞进 Neo4j。
建图的时候遇到一个问题:怎么处理多对多关系?比如一个订单可能有多个产品,一个产品也可能在多个订单里。如果直接用关系连,图会变得很复杂,查询性能也不好。
我们的方案是在中间加个"订单项"节点:
这样虽然多了节点,但查询逻辑清晰很多,性能也没受太大影响。
踩坑记录
这一节列几个印象比较深的坑。
坑一:节点爆炸
刚开始为了图省事,把每个文档里的每个词都当成候选节点。结果图里有几十万个节点,大部分都是没用的。
教训是:宁可少漏,不要多塞。白名单机制很关键,只保留业务上明确有用的实体。
坑二:关系类型爆炸
为了"精确描述",一开始定义了几十种关系类型。结果是:维护成本太高,新增一个场景就得想半天应该用哪个关系。
后来合并成了七八种核心关系,虽然精度下降了一点,但维护成本大幅降低,整体反而更实用。
坑三:查询性能陷阱
图数据库的查询很灵活,但也容易写出"看起来很对"但实际上很慢的查询。
比如有个查询要找"某个用户发起的所有退款订单",一开始写成了:
MATCH (u:User {id: '123'})-[:发起]->(o:Order)-[:包含]->(r:Refund)
RETURN r
这个查询看起来没问题,但在图很大的情况下会遍历大量无关节点。优化后改成:
MATCH (u:User {id: '123'})-[:发起]->(o:Order)
WHERE exists((o)-[:包含]->(:Refund))
MATCH (o)-[:包含]->(r:Refund)
RETURN r
加了 WHERE 过滤,先缩小范围再做连接,性能提升很明显。
坑四:数据一致性
图是动态更新的,但新增数据的时候很容易出现"孤节点"——只有关系没有实体,或者反过来。
解决办法是建图的时候强制检查:每个节点至少要有一个关系,每个关系两端的节点都要存在。虽然会漏掉一些边界情况,但避免了大部分问题。
最终效果
折腾了几个月,系统上线了。效果怎么样呢?
召回准确率提升了 30% 左右。用户问"退款要几天到账",系统直接给出"3-5 个工作日",而不是扔一堆包含"退款"和"到账"的文档。
支持追问。用户问完"退款要几天到账",可以接着问"周末算不算",系统能理解"周末"指的是"退款到账时间"的周末,而不是随便一个周末。
可视化有帮助。Neo4j 的可视化界面能展示实体之间的关系,产品经理用它梳理流程,客服用它快速查关联问题。
但也不是没有问题:
- 数据维护成本。新业务上线的时候,得有人维护实体和关系。不能指望模型自动搞定。
- 冷启动问题。新领域的文档一开始没有图谱,效果不如老领域。
- 边界情况处理。有些问题本身就很模糊,图谱也帮不上。
结语
把一个知识图谱从零搭起来,比想象中要花时间。理论上"只需三步"的事情,实际上要考虑数据质量、实体边界、关系类型、查询性能、维护成本等等问题。
但搭起来之后,确实比之前的搜索系统好用不少。用户不用自己拼凑答案,系统能给出相对直接的回复,也能处理一些追问场景。
最关键的是:图谱不是一次性工程,需要持续维护。业务在变,实体在变,关系在变,如果不管它,慢慢就变成一个没人用的"遗产系统"了。
所以,如果有人跟你说"搞个知识图谱吧,包治百病",你可以回一句:“这东西确实有用,但别指望它自己跑,得有人养。”
版权声明: 本文首发于 指尖魔法屋-AI 知识图谱踩坑记录(https://blog.thinkmoon.cn/post/263-ai-knowledge-graph-construction-application-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。