最近一段时间,我一直在一个内部知识管理项目里折腾一套东西,最终落地成一套“RAG-Anything”风格的系统,专门解决一个很现实的问题:企业里大量的资料根本不只是文字,而是PDF手册、产品图片、维修视频、Excel参数表混在一起。传统RAG只吃文本,遇到图片里的仪表读数、视频里的操作步骤、表格里的型号对照,基本就废了。我踩了不少坑,也把一套完整的构建路径摸了出来,从多模态数据处理、知识图谱构建,到混合检索和问答生成,整个链路已经稳定跑在内部环境。今天把这套实战经验拆开揉碎说清楚,包括设计思路、关键实现、参数细节和那些文档里不会写的坑。
这套系统适合谁参考?如果你正在做企业知识库问答、智能客服、设备维修辅助、多模态资料检索,或者手头有一堆非结构化数据但不知道怎么让大模型真正“用起来”,这篇内容应该能帮你少走几个月弯路。本文涉及的技术栈以Python为主,图数据库用Neo4j,向量检索用Milvus,生成端接的是多模态大模型,整体思路不绑死某一家厂商,换个实现细节也能照搬。
1. 为什么传统RAG不够用:从文本检索到多模态图谱问答的演进
1.1 传统RAG在企业场景里的三个硬伤
先说说传统RAG的常见形态:把文档切成chunk,做embedding,存进向量库,用户提问时检索top-k片段,拼进prompt让大模型回答。这套流程在“纯文本Q&A”场景下没问题,但放到真实企业环境里,我很快就撞上了三个硬伤。
第一个硬伤是模态丢失。产品手册里一张接线图,可能比三页文字都重要;维修视频里一段操作演示,光靠文字描述根本还原不出细节;Excel里的参数对照表,一旦被切块就完全失去行列关系。传统RAG对这些内容基本无能为力,要么直接忽略,要么强行OCR成文字,结果字体、排版、公式全部乱掉。
第二个硬伤是关系断裂。企业里的知识是有结构的:“设备A”和“故障码B”之间有关联,“故障码B”又会对应“维修步骤C”,这些关系往往是跨文档的——可能在用户手册里提到故障码,在维修记录里才有对应操作,在培训视频里才有实际操作画面。纯向量检索返回的是碎片化片段,无法把这些跨文档的实体关系串成一条完整的证据链,大模型只能靠“脑补”来回答,幻觉率一下就上去了。
第三个硬伤是查询能力弱。向量检索擅长语义相似匹配,但不擅长精确的关系查询和统计聚合。比如“哪些设备在最近三个月更换过电机”这类问题,涉及实体关联、时间范围过滤和数据聚合,向量检索根本没法直接回答。
我实际碰到过一个典型场景:维修工程师问“电机过热报警后,除了查轴承还要检查什么”。这个问题如果走纯向量检索,召回的内容很散,答案往往张冠李戴或者漏掉关键步骤。后来我们把维修手册、故障树、视频操作脚本都结构化到知识图谱里,模型就能沿着“电机过热 → 关联故障模式 → 对应检查项 → 关联视频片段”的路径一路查下去,答案质量和可解释性完全是两个级别。
1.2 RAG-Anything的核心思路:多模态信息统一进图谱
我做的这套系统取名“RAG-Anything”,借鉴了Segment Anything那种“一个模型处理一切”的命名思路,核心目标只有一个——把任何格式的输入资料,都能转化为统一的知识表示,供检索和问答使用。
我这里说的“任何格式”,至少包括这几类:
- PDF、Word、Markdown等文档,包含文本、表格、图片、公式
- 扫描件和图片,需要OCR和版面分析
- 音视频文件,需要抽帧、ASR转写、关键场景识别
- 结构化数据,如Excel报表、CSV、JSON
统一的知识表示,则是两条线并行:一条线是传统的向量索引,把文本、图像描述、视频脚本等内容切成语义块,做embedding存向量库,负责“语义模糊召回”;另一条线是知识图谱,把抽取出来的实体、关系、属性存进Neo4j,负责“结构化精准查询”。
两条线在问答阶段做混合检索:先判断用户问题属于哪一类,再决定是以图谱查询为主、向量检索兜底,还是两者并行然后做融合重排。这样既保留了向量检索的灵活性,又补上了图谱查询的精确性。
我把这种架构叫做“双通道召回,一通道生成”,整体流程可以概括为:多模态解析 → 信息抽取 → 结构化存储 + 向量化索引 → 意图路由与混合检索 → 证据链组装 → 多模态大模型生成。
1.3 技术选型决策:为什么图谱用Neo4j而不是纯向量库
最开始我纠结过一个问题:是不是用纯向量库就够了,或者直接给向量检索外面套一层关系数据库的关联表?实测下来,在需要回答多跳关系型问题的场景,纯向量库的召回效果和可解释性都不太行。
举例来说,如果用户问“这台设备的振动异常可能导致哪几个子系统受影响”,在纯向量库里,你需要把“设备—振动—子系统”这层关系隐含在文本里才能召回。一旦资料没有完整描述这种多跳关系,检索就直接失效。而在知识图谱里,这类查询就是一次标准的多跳遍历,查询路径清清楚楚,每条证据都能溯源到原始来源。
Neo4j有几个特点是这个场景特别需要的:
- Cypher查询语法天然适合多跳关系遍历,写起来直观
- 节点和关系都可以带属性,方便做来源、置信度、时间戳标注
- 社区版免费可用,单机部署就能支撑百万级节点
- 生态成熟,Python驱动、向量索引、全文索引都有现成集成
至于向量库,我选了Milvus做独立向量存储,原因是当数据量上去之后,向量索引和图查询是两个完全不同的资源消耗模型,混在一个系统里容易互相拖累。生产环境里分开部署,扩展也更灵活。
2. 多模态数据统一处理与向量化:从文档、图片到视频的工程化实践
2.1 文档解析:PDF分栏、表格抽取和OCR的三座大山
文档解析是整个管线里最容易被低估的环节。我一开始直接用了最普通的PDF解析库,按页提取文本,结果惨不忍睹:双栏排版的文档文字顺序完全错乱,表格数据被拆成碎片,扫描件直接是空白。后来我把文档处理拆成三个子任务分别解决。
版面分析是第一步。我用PP-Structure做版面检测,能识别出标题、正文、表格、图片、页眉页脚等区域,然后按阅读顺序重新组织内容。这一步对双栏PDF尤其重要,它能帮你把“从左栏读完再到右栏”的逻辑顺序恢复出来,避免检索时出现语义跳转。
表格抽取是第二步,也是我踩坑最多的地方。一开始我把表格转成纯文本再切块,结果一行数据被拦腰截断,参数名和参数值分到了不同chunk里,检索完全对不上。后面我定了个原则:表格不切块,整表作为一个语义单元存入向量库,同时把表格内容解析成结构化的键值对,写入知识图谱作为实体的属性。这样既保住了表内关系,又能在图谱查询里用属性过滤。
OCR是第三步。扫描件、图片里的文字都需要OCR,我实测下来PP-OCR的识别率在干净扫描件上能达到95%以上,但遇到倾斜、模糊、表格线干扰的情况会掉到80%以下。我的处理经验是:先做图像预处理(灰度化、二值化、倾斜矫正),再送OCR,识别后对关键数字和型号做一次规则校验——比如设备编号、日期格式、电压电流数值,用正则和已知字典做二次纠错。
2.2 图片与视频:多模态大模型抽取语义描述
图片和视频不能直接进RAG管线,必须先转化为“可检索的语义内容”。最初我用过纯OCR的方案,只能提取图片里的文字,但完全无法理解图片内容——比如一张设备结构图,OCR只能拿到标注文字,拿不到“这个设备由哪几部分组成、每部分什么功能”的语义信息。后来我切换为多模态大模型,以Qwen-VL为主力模型,让模型输出结构化的图片描述。
我的图片处理流程是这样的:先做一次全局描述,让模型用一段话概述这张图的内容;再做一次领域定向抽取,按预设的schema提取图中的实体和关系。比如维修手册里的结构图,我会让模型输出“图中包含哪些零部件、各零部件之间的连接关系、图中出现的关键参数”。这样图片里的信息就变成了可入图谱的结构化知识。
视频处理复杂一些,需要考虑时间维度。我的做法是三步走:
第一步,抽帧。抽帧策略不采用固定间隔,而是按场景变化动态抽帧——计算相邻帧的直方图差异,差异超过阈值才保留,避免运动缓慢的场景抽出一堆重复帧。每帧记录时间戳。
第二步,对关键帧做多模态描述。同样用Qwen-VL输出每帧的语义描述,描述里带上时间戳信息。
第三步,语音轨ASR转写。用Paraformer或Whisper做语音识别,转成带时间戳的文本,再按语义切成句子。
最后把“某一时间段内的关键帧描述”和“同一时间段的ASR文本”拼接成一段视频结构化脚本,看起来就像这样:
[00:00:12-00:00:35] 操作者打开设备外壳,露出内部电路板 [00:00:12-00:00:35] 语音:先断开电源,等待五分钟让电容放电这段脚本同时写入向量库和知识图谱,视频片段作为“证据节点”,方便问答时直接定位到视频的哪一段。
2.3 Embedding模型选择与向量化细节
向量化环节,我针对不同内容类型用了不同的embedding策略。
文本内容我用BGE-M3,中英文混合场景效果比较稳,而且支持8192长度的输入,对长文档chunk更友好。图片内容我用CLIP提取图像特征,但这里有个关键决策:我不直接用CLIP向量做检索主通道,而是把“多模态大模型生成的图片描述文本”作为主索引对象,用BGE-M3做embedding,再用CLIP向量作为辅助通道。原因很简单:描述文本的语义空间和用户问句更接近,检索命中率更高;CLIP向量与文本向量存在模态对齐问题,单独用容易召回不相关结果。
实测下来,“文本描述为主,图像向量为辅”的混合策略,检索准确率比单用CLIP向量高出大概10到15个百分点。图像向量也不是没用,在做“以图搜图”或者“相似产品推荐”时会作为主通道。
Embedding维度统一也很重要。BGE-M3输出1024维,CLIP输出512维,如果混在同一个向量库,要么搭建两个collection分别存,要么做维度对齐。我的方案是分开存,Milvus里一个collection放文本向量,一个collection放图像向量,检索时按类型路由,最后在重排阶段合并结果。这样既避免维度对齐的信息损失,也能针对不同类型调整索引参数。
3. 知识图谱构建:从本体设计到Neo4j落库的完整过程
3.1 本体设计:先定实体、关系、属性,再谈抽取
知识图谱构建的第一步不是写代码抽取实体,而是先想清楚“这张图要存什么”。我在这上面走过弯路——一开始没有规划本体,让LLM自由抽取实体关系,结果图里出现了几十种五花八门的实体类型,关系命名也乱七八糟,后面写Cypher查询的时候根本没法通用。
后来我按业务场景重新设计了本体,对维修知识库这个场景,核心实体类型只有几类:
- 设备:物理设备或系统,如“空压机”“输送带”
- 组件:设备的组成部分,如“轴承”“电机”
- 故障:异常现象,如“过热”“异响”
- 故障模式:故障的具体表现,如“轴承磨损”“绕组短路”
- 操作:维修或检查步骤
- 文档:原始资料,如“用户手册.pdf”
- 视频片段:视频中的一段操作演示
- 参数:关键指标,如“额定电压”“报警阈值”
关系类型同样收敛到少量几种:
- 设备—包含—组件
- 组件—发生—故障
- 故障—对应—故障模式
- 故障模式—需要—操作
- 操作—参考—文档
- 操作—参考—视频片段
- 设备—关联参数—参数
属性设计上,每个节点都带source_doc(来源文档)、confidence(抽取置信度)、timestamp(入库时间),关系带时间戳和来源段落,方便追溯。
这个设计的好处是,所有下游查询都可以通过固定的关系路径来完成。比如“这个设备的故障处理步骤”,就是“设备 → 组件 → 故障 → 故障模式 → 操作”的路径遍历。如果我当初没做本体收敛,这里根本没法写通用查询。
3.2 实体抽取与关系抽取:LLM为主,规则兜底
实体和关系抽取,我用了“LLM为主、规则为辅”的双轨方案。
LLM抽取的实现方式是用Prompt约束模型输出JSON格式的实体和关系。我用的Prompt结构大概长这样:
extract_schema = """ 请从以下文本中抽取实体和关系,严格按JSON格式输出。 实体类型:设备(equipment)、组件(component)、故障(fault)、故障模式(fault_mode)、操作(operation)、参数(parameter) 关系类型:contains(包含)、occurs_in(发生在)、corresponds_to(对应)、requires(需要)、references(参考) 输出格式: { "entities": [ {"name": "电机", "type": "component", "attributes": {"source_doc": "xxx.pdf"}} ], "relations": [ {"from": "空压机", "to": "电机", "type": "contains", "attributes": {"source_doc": "xxx.pdf"}} ] } """为了稳定输出,我会在请求里开启JSON mode,并把temperature调到0.1以下,防止抽取结果每次都不一样。实际跑下来,主流大模型在清晰schema约束下,抽取准确率基本能到85%到90%。
规则抽取用在小而关键的领域,比如设备编号、故障码、日期、电压电流值这类格式明确的信息。规则抽取的准确率高,但覆盖低,所以我把它定位为“LLM抽取的校验和补充”。比如LLM抽出一个参数“电压380V”,规则模块会校验这个电压值是否在合理范围,设备编号是否符合编码规则。通过校验的才入库,没通过的标记为待确认。
实体消歧是另一个大坑。同一台设备在手册里叫“空压机”,在维修记录里叫“AIR-COMP-01”,在视频脚本里叫“螺杆机”,如果不做消歧,图谱里会出现三个不同节点,关系全部断裂。我的消歧方案是:抽取出的实体先做一次归一化,用图谱里已有实体的embedding做余弦相似度匹配,相似度超过0.9的直接合并到已有节点;0.7到0.9之间的人工确认;低于0.7的视为新实体。这个策略实施后,图谱里“重复实体”数量降低了大概六成。
3.3 Neo4j批量导入与索引管理
实体和关系抽取完之后,接下来的问题就是怎么把数据高效写入Neo4j。
小批量场景,几千个节点,直接跑Cypher写入就行,用Python驱动做事务批量提交,每500条一个事务。我的实测数据是,这种写法写入速度在每秒几百条级别,对于知识图谱初建来说完全够用。
数据量到了百万级节点,或者需要周期性全量重建,就走neo4j-admin import离线导入。这种模式要求先把数据导出成CSV,定义好节点文件和关系文件的列格式,然后用import命令批量加载。离线导入的吞吐量能到每秒几万条,但只能导入快照,不能跑增量更新。我的实践是:初始全量导入用离线模式,后续增量更新走在线事务写入。
索引方面有三个必须建的:
- 唯一性约束:比如对实体name建唯一约束,防重复写入
- 全文索引:对实体的name和描述字段建全文索引,支持模糊匹配
- 向量索引:Neo4j 5.x以后支持原生向量索引,可以存实体的embedding,但我自己的方案是实体embedding放Milvus,Neo4j只做图遍历和属性过滤,好处是两边职责清晰,避免图库被向量加载拖慢
重要提示:Neo4j 5.x和4.x在索引语法上有些差异,如果你用的是老版本,向量索引和全文索引的创建语句都要先查对应版本的文档,直接抄5.x的语法在4.x上会报错。
4. 混合检索与问答链路:意图路由、图谱遍历与证据组装
4.1 意图识别与检索路由:先判断问题类型,再决定检索策略
在检索之前,我加了一个“意图路由器”的模块——用LLM对用户问题做分类。这是整个问答链路里性价比最高的一个步骤,没有它,混合检索就会变成什么都查、什么都查不准。
我把企业知识库里的问题粗略分成四类:
- 事实型问题:问某个事实,比如“空压机的额定电压是多少”。这类问题适合走向量检索 + 属性查询
- 多跳关系型问题:问实体间的关系链,比如“电机过热会导致哪些故障”。这类问题必须走图谱多跳遍历
- 统计聚合型问题:问数量、排行、趋势,比如“哪种故障出现次数最多”。这类问题走Cypher聚合查询
- 开放生成型问题:问方案和建议,比如“怎样降低设备故障率”。这类问题需要综合多个来源,走混合检索 + 多轮综合分析
意图识别的实现不复杂,就是把问题发给LLM,让它输出一个JSON标签。我用的Prompt让它从预定义标签里选一个,附上简短理由。这一步不需要多强的模型,小参数模型也能做得很好,关键是标签体系要清晰。
路由之后,不同类型的问题走不同的检索链路,最后统一汇聚到生成模块。这样设计的好处是检索链路专项化,不至于让所有问题都走同一套重量级流程,也方便针对链路瓶颈单独优化。
4.2 图谱检索:Cypher模板生成与验证机制
一开始我尝试过让LLM直接根据用户问题生成Cypher查询,结果效果很差——模型经常生成语法正确但逻辑错误的查询,比如查错了关系方向,或者join了不该join的节点。对于企业应用来说,这种“看似正确实则错误”的查询比直接报错更危险,因为它会生成一个有误导性的答案。
我的解决方案是“预定义Cypher模板 + LLM参数填充”。针对每个业务问题类型,先手写并验证通过一组Cypher模板,LLM只需要从模板列表里选出匹配的模板,再填入实体名称和参数。比如多跳关系型问题的模板长这样:
// 查某设备的组件及其常见故障 MATCH (e:Equipment {name: $equipment_name})-[:CONTAINS]->(c:Component) OPTIONAL MATCH (c)-[:OCCURS_IN]->(f:Fault) WHERE f.name IS NOT NULL RETURN c.name AS component, collect(DISTINCT f.name) AS faults用户问“空压机有哪些常见故障”,LLM把equipment_name填成“空压机”,执行模板,就能返回结构化结果。这种方式的优势很明显:查询逻辑经过预验证,不会出错;参数从用户问题里抽取,即使抽错也不会导致查询语法崩溃;模板可复用,新增业务场景只要加模板即可。
在执行图谱查询时,我还设置了一些保护机制:
- 查询超时时间设为5秒,防止复杂递归查询拖垮数据库
- 多跳深度限制在3跳以内,超过深度的查询自动降级为多步简单查询
- 返回结果数限制:每个查询最多返回50条记录,防止结果集过大撑爆上下文
4.3 向量检索与重排:多路召回后的精细化打分
图谱通道负责精确查询,向量通道负责语义召回。向量检索这步我用的是Milvus,流程不复杂:用户问题先做意图分类和实体抽取,如果有明确实体名,就用实体名+问题整体一起做检索,扩大召回面。
向量检索的具体参数如下:
- top-k默认取20
- 相似度阈值0.45,低于阈值的候选丢弃
- 支持按元数据过滤,比如限定在“维修手册”或“视频脚本”类型中检索
多路召回之后,所有候选集合并去重,进入重排阶段。重排我用的方案是cross-encoder + 图谱逻辑规则打分。
Cross-encoder模型我选了bge-reranker-base,将用户问题和每个候选片段拼接后打分,得到语义相关性得分。图谱逻辑规则打分则根据候选与知识图谱的关联程度评分:候选片段中涉及的实体如果在图谱中与问题中的实体存在直接关联(一跳或两跳内),则获得额外加分。最终得分是语义分和图谱关系分的加权和,我实测下来权重设为0.7和0.3效果比较好。
这套重排机制对“证据链完整性”至关重要。比如用户问“电机过热应该检查什么”,一个直接提到“检查轴承”的文本片段,和另一个单独提到“电机过热”的片段,单纯语义打分差不多;但加了图谱关系分之后,与故障路径关联更紧密的片段会被排到前面,答案的准确性和完整性都明显提升。
4.4 上下文组装与生成:让大模型“有据可依”
检索完不算完,最后一步,也是最容易翻车的一步:怎么把检索结果组装成prompt,让大模型稳定生成答案。
我的prompt结构是分区块组装:
- 系统指令:设定角色、输出格式、回答边界,强调“只能依据给定的证据回答,严禁编造”
- 意图信息:用户的真实意图是什么
- 图谱证据链:从Neo4j查到的结构化作证,表现为“实体A → 关系B → 实体C”的路径形式
- 文本证据块:从向量库召回的片段,每个片段标注来源文档和时间戳
- 用户问题:最后才是用户的问题
上下文长度控制上,图谱证据链我限制在10条以内,文本证据块限制在5个片段以内,每个片段长度不超过500字。算下来,prompt里证据部分控制在3000字左右,给生成模型留足推理空间。
生成模型我接的是Qwen2.5-72B,在DeepSeek和Qwen之间对比过,最后选Qwen主要因为它对中文知识密集型问答的忠实度更高,而且支持流式输出,对长答案生成更稳定。生成时的temperature设为0.2,top_p设为0.9,避免随机性太强导致答案不稳定。
生成的答案里,我还会要求模型在关键结论后面标注引用来源,格式大致是:
根据《空压机维护手册》第3章,电机过热报警后的标准检查流程包括: 1. 检查轴承润滑情况(来源:空压机维护手册_第三章) 2. 测量绕组电阻(来源:故障诊断指南_视频片段00:01:23)这一步对生产落地非常重要,用户看到答案能直接追溯到原文,信任度完全不一样。
5. 系统评估与部署:从指标设计到性能调优的实战经验
5.1 评估体系设计:不能只看“答得对不对”
问答系统上线前,我搭了一套相对完整的评估机制。最初我只关注“人工评测回答正确率”,让业务人员对着几十个问题打标,既慢又主观,后来改用RAGAS框架加自建测试集。
RAGAS提供了三个核心维度:
- 忠实度(Faithfulness):答案是否严格基于给定证据,有没有幻觉
- 答案相关度(Answer Relevance):答案是否切合问题
- 上下文相关度(Context Relevance):检索到的证据是否相关、足够
我在这三个指标之外,又加了两个自建指标:
- 图谱覆盖度:正确答案涉及的关键实体和关系是否都已出现在图谱中
- 多跳准确率:针对预先设计好的多跳关系型问题,系统能否给出完整链路答案
测试集设计很关键。我从真实业务问题里整理了50个高频问题,覆盖5个维度:简单事实型、多跳关系型、统计聚合型、跨模态型、开放生成型。每个维度10个问题,固定下来做成回归集,每次改系统后跑一遍,看指标有没有回退。
跑下来最直观的发现是:加了知识图谱之后,多跳关系型问题的准确率从62%提升到84%,提升幅度最大;跨模态型问题从55%提升到78%,因为视频和图片信息终于能通过图谱节点被关联到;但简单事实型问题提升不大,甚至因为检索链路变复杂还引入了几个新的失败case。
5.2 部署中的性能优化:延迟、缓存与并发控制
系统上线初期,端到端问答延迟大概在8到10秒,对内部工具来说还能接受,但用户体感明显偏慢。我做了几轮优化,把平均延迟压到了3到4秒。
第一刀切在缓存上。相同或相似的问题,结果可以做缓存。我用的是语义缓存:新问题先和缓存里的历史问题算相似度,超过0.92直接返回历史答案,不再走完整检索链路。这个优化把常用问题的延迟直接压到400毫秒以内。
第二刀切在检索并行化上。图谱查询、向量检索、重排之间原本是串行链路,后来改成并行执行——意图分类完成后,图谱查询和向量检索同时发起,等两边结果都到了再进重排。整个流程从串行的两秒多压缩到并行的一秒出头。
第三刀切在GPU推理配置上。多模态模型的图片描述、抽取、生成都吃GPU。我的经验是:如果并发不高,模型用vLLM或者SGLang部署,打开continuous batching,吞吐能提升好几倍。生成端用流式输出,用户感受到的“首个token延迟”会大幅缩短,体感上快很多。
还有一个容易被忽略的点:Neo4j的连接池。默认配置下并发一高就会出现连接排队。我把连接池上限调到了50,每条查询超时设为5秒,并在业务侧做了信号量限流,保证极端情况下图库不被压垮。
5.3 常见问题排查实录:我在实战中踩过的五个坑
这一节记录了我在搭建过程中真实遇到、并且花了不少时间排查的问题,希望能帮你直接绕开。
问题一:OCR阶段的断字残词导致实体抽取失败
扫描版的PDF经过OCR后,经常出现“电 机过热”这种带空格断裂的文本,LLM抽取时会忽略掉这种不连贯的实体名。我的解决方式是:在OCR之后、实体抽取之前,加一个文本清洗步骤,把中英文之间的多余空格、断行导致的半句话拼接起来。同时用词表校验,设备名称如果和预置词表匹配度超过一定阈值,就按词表修正。
问题二:LLM抽取结果不稳定,同一条文本每次抽出来的实体不一样
这个坑几乎必然遇到。解决思路有两层:第一层,把temperature调到0.1以下,开启JSON schema约束,让模型输出结构固定;第二层,给抽取模块加“多数表决”——同一个文本片段跑三次抽取,取两两一致的实体和关系入库。这样抽取稳定性从70%左右提升到了90%以上。
问题三:图谱里出现大量重复实体,关系断链严重
这是没做实体消歧时的必然结果。我的解法是:入库前做实体归一化,通过embedding相似度加规则匹配,把同义实体合并。还有一个技巧是给每个实体保存一个“别名列表”,比如“空压机”的别名有“AIR-COMP-01”“螺杆空压机”,后续抽取新实体时先查别名列表,匹配上就直接复用已有节点。
问题四:用户问题里的实体名和图谱里不一致
用户不会严格按标准名称提问。他们可能说“那个会发热的设备”,而不是“电机过热”。我的方案是:在检索阶段先让LLM做一次“问题实体链接”,从问题里提取实体指称,再和图谱实体做模糊匹配。匹配同样用embedding相似度加别名列表,确保用户口语化的指称也能映射到图谱节点。
问题五:上下文太长导致生成质量下降
图谱证据、文本片段全部塞进prompt之后,经常冲到五六千字。结果就是大模型“迷失”在长上下文里,答案反而变差。我的实践经验是:严格控制证据量级,图谱路径最多10条,文本片段最多5个,每个片段压到500字以内;冗余重复的文本片段在重排阶段直接去重;如果证据确实不够,宁可让模型说“给定的资料中未找到明确信息”,也不要硬编。
5.4 实操心得:先把最小系统跑通,再谈扩大规模
最后分享几个直接影响成败的经验。
第一个心得是“先小后大”。不要一开始就想把所有类型的资料都接进来。我的做法是先选一个业务域——维修知识库,接入三种类型资料(PDF手册、故障图片、维修视频),跑通整个链路,指标达标后再横向扩展其他业务域。如果你一上来就搞全模态全家桶,光是排查哪一步出错就能把你耗到崩溃。
第二个心得是“图谱质量大于图谱规模”。一张有100万节点但质量参差不齐的图谱,不如一张有10万节点但实体清晰、关系准确的图谱。知识图谱问答的效果上限,很大程度上由图谱本身的准确性决定。我在项目里把“实体消歧准确率”和“关系抽取准确率”作为两个最高优先级指标,每周迭代抽查,比盲目加数据更重要。
第三个心得是“日志和观测要提前搭”。问答系统的链路很长:意图分类、图谱查询、向量检索、重排、生成,任何一个环节出问题,最终答案都会歪掉。如果你没有完整的链路追踪,排错会非常痛苦。我用LangSmith记录每一轮请求的完整调用链,从用户问题到最终答案,中间每步的输入输出都有日志,出了问题能直接定位到具体环节。
第四个心得是“让业务人员尽早介入测试”。技术人员觉得“答得不错”的问题,业务人员往往会提出完全不同的视角——他们关心答案能不能直接用于工作,证据是不是来自官方手册,格式是否方便复制到工单里。早点让他们实测,你才能早点调整答案格式和证据展示方式,避免系统做出来却没人用的尴尬。
这套系统从最初的想法到最后稳定运行,前后经历了大约三个月。回头看,最关键的转折点就是从“纯向量RAG”变成“向量+图谱混合检索”的那一步。如果让我给刚起步的人一个最具体的建议,那就是:先把你手头资料里的实体关系梳理清楚,设计一个最小但可靠的本体模型,再考虑接多模态、接大模型。图谱的地基打好了,上层的一切都会顺很多。