十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

图RAG烹饪问答系统:Neo4j+Milvus双路召回实践

图RAG烹饪问答系统:Neo4j+Milvus双路召回实践 你有没有遇到过这种情况想做一道菜网上搜了一堆菜谱但每个菜谱都默认你有某种食材或调料而你想问的是“能不能不放花生”“有没有替代猪肉的办法”“宫保鸡丁和鱼香肉丝都用什么技法”这类需要把菜谱拆开、跨菜谱比较的问题。普通搜索返回的是整篇文章LLM直接回答又容易一本正经地胡说八道。我去年一直在琢磨怎么把菜谱变成可查询、可推理的结构化知识最后做了这个“图 RAG 烹饪问答系统”底层用 Neo4j 存菜谱知识图谱用 Milvus 做语义向量召回再把两者的结果组装成上下文丢给 LLM 生成答案。这个项目已经跑通了一个可演示的版本思路也可以迁移到其他垂直领域的知识问答。这篇文章是完整的工程实践记录涉及数据建模、环境安装、代码实现和排坑过程适合正在做 RAG 应用、或者想了解怎么把图数据库和向量数据库结合在一起使用的开发者参考。1. 为什么烹饪问答需要“图向量”双路召回1.1 菜谱天然是图结构菜谱不是一段纯文本它是一张关系网。一道菜包含多种食材、多种调料、多个步骤和技法食材之间有替代关系比如“花生”可以被“腰果”替代但“酱油”很难被“盐”替代菜肴属于某个菜系又和另一道菜共用同样的技法。这种多对多的关系用关系型数据库硬存也行但查询“哪些菜不需要用油”“糖和蜂蜜能不能互换”就会写出一大堆 JOIN而且越查越复杂。图数据库就是为这种关系网络准备的Neo4j 里的节点和关系能直接表达“A 是 B 的替代品”“C 使用了 D 技法”查询起来非常自然。早期我想用纯向量数据库做这个问答因为菜谱文本可以先向量化再检索实现简单。但很快发现一个问题向量检索擅长的是“语义相似”它知道“宫保鸡丁”和“鸡丁炒花生米”在语义上有点像但它不知道鸡丁和花生米之间是否存在某种依赖关系也无法推理“去掉花生米之后这道菜的结构还成立吗”。图数据库恰恰补上了这块短板——它能把菜谱中的实体和关系暴露给后续的生成步骤让 LLM 不只是“读到”一段文本而是“看到”一个可理解、可验证的知识结构。1.2 纯向量检索的短板与图 RAG 的互补逻辑我一开始只用了向量检索效果怎么说呢问它“鱼香肉丝有没有不放猪肉的做法”它能搜到一些鱼香肉丝菜谱但给出来的答案就是把菜谱原封不动贴出来没有任何替代建议因为它根本没检索到“猪肉”这个实体在食材关系网中的位置。后来我在检索结果里强行塞入实体关系效果立刻变好。这让我意识到RAG 的上下文不一定是“一堆文本块”还可以是“一个从知识图谱里提取出来的子图”。这种把图结构作为上下文的一部分喂给 LLM 的做法就是所谓图 RAG。具体来说这个系统的双路召回逻辑是先用 Milvus 做语义粗筛从所有菜谱中找到与用户问题最相关的 Top-K 个菜谱然后把这几个候选菜谱在 Neo4j 里对应的节点捞出来沿着关系向外扩展一跳拿到关联的食材、技法、替代关系最后把“候选菜谱文本 图扩展出的关系三元组”合并成结构化的上下文交给 LLM 生成回答。向量负责“广度”图负责“深度”LLM 负责“组织语言和推理”。1.3 系统整体架构与数据流概览整个系统由四个模块组成菜谱数据处理、Neo4j 图谱、Milvus 向量库、LLM 服务编排。处理流程如下收集菜谱原始文本我用的是开源中文菜谱数据集大约 4000 道菜。用规则模板 简单命名实体识别从菜谱中抽取出菜名、食材列表、调料列表、步骤、技法、口味标签。将抽取结果节点化写入 Neo4j菜谱节点、食材节点、技法节点、口味节点以及它们之间的关系。同时把菜谱的核心信息拼接成一段文本例如“菜名鱼香肉丝食材猪里脊、木耳、胡萝卜…技法炒口味鱼香……”向量化后写入 Milvus。用户提问时先用 LLM 做一层查询理解提取食材/菜名等实体然后用这些实体直接到 Neo4j 查相关图谱同时把原始问题送入 Milvus 进行向量检索。把图谱查询结果和向量召回结果合并构造 prompt调用 LLM 生成最终答案。流程图我故意不画文字描述可能更直观。整个系统用 Python 编写Neo4j 和 Milvus 都是独立服务LLM 可以接 OpenAI 兼容接口也可以接本地部署的模型我后来为了省钱换成了 Ollama 跑的 Qwen。下面从建模开始逐层拆解。2. 知识图谱建模用 Neo4j 把菜谱变成可以推理的图2.1 菜谱数据获取与清洗数据是项目的起点。我用了一个开源的中文菜谱数据集包含菜名、原料列表、步骤、菜系等字段。原始数据质量参差不齐最大的问题有三个同一食材有不同写法“马铃薯”和“土豆”“花生米”和“花生仁”调料和食材混在一个字段里步骤是长文本很难直接拆出技法。我的处理思路是先做词表归一化把常见同义词映射到一个标准名称再根据一个预置的食材调料词典把原料字段分割成结构化列表。这个词典我维护了大概 800 个常见条目虽然不能覆盖全部食材但覆盖了训练数据集里 90% 以上的情况。清洗后的数据格式大致是{ name: 鱼香肉丝, cuisine: 川菜, ingredients: [猪里脊, 木耳, 胡萝卜, 冬笋, 泡椒, 葱姜蒜], seasonings: [酱油, 醋, 糖, 盐, 淀粉], steps: [里脊切丝加盐、淀粉腌制备用, 木耳、胡萝卜、冬笋切丝, 热锅凉油下肉丝滑熟, 爆香泡椒、葱姜蒜加入配菜翻炒, 调入酱油、醋、糖勾芡出锅], techniques: [炒, 滑炒, 勾芡], flavors: [鱼香, 酸甜, 微辣] }清洗这一步容易被忽略但它是整个项目的地基。如果食材没有归一化图里的同一个食材可能会拆成好几个节点替代关系就会断裂。我后来特意加了一个合并步骤把 Neo4j 中同名的食材节点合并这样关系才能聚拢。2.2 节点与关系设计Recipe、Ingredient、Technique、Flavor、替代关系知识图谱没有唯一正确的设计但有一个原则节点要代表“会被反复查询的实体”关系要代表“会被反复提问的语义”。我的图谱包含以下节点类型Recipe菜谱属性有 name、cuisine、description。Ingredient食材/调料属性有 name、category食材还是调料。Technique技法属性有 name例如炒、蒸、炸、烤。Flavor口味标签属性有 name例如鱼香、麻辣、清淡。DietaryTag膳食标签属性有 name例如素食、清真、无麸质。这个节点对替代推荐特别有用。关系类型及含义关系起点终点含义CONTAINSRecipeIngredient菜谱包含该食材USES_TECHNIQUERecipeTechnique菜谱使用了某种技法HAS_FLAVORRecipeFlavor菜谱具有某种口味CAN_SUBSTITUTEIngredientIngredient两种食材在特定场景下可以互换RELATED_RECIPERecipeRecipe共用至少两种食材的菜谱相似CAN_SUBSTITUTE关系是图谱里最有价值的部分。我从两个渠道构建一部分是人工整理的常见替代规则比如花生替代腰果、猪油替代植物油、白糖替代冰糖另一部分是从“共用食材和技法”的关系中推导候选替代关系再人工过滤。最终得到约 600 条替代边不多但足够覆盖常见问题。2.3 Cypher 导入从 CSV 批量创建图谱数据清洗完成后我用 Cypher 语句把结构化数据导入 Neo4j。这里不推荐逐条执行 CREATE 语句数据量大时太慢。我直接导 CSV 文件。步骤如下把菜谱、食材、技法、口味分别导出为 CSV 文件。把菜谱-食材关系、菜谱-技法关系等导出为关系 CSV 文件。用LOAD CSV WITH HEADERS FROM file:///recipes.csv AS row逐行创建节点。为了加速创建关系先为节点创建唯一约束例如CREATE CONSTRAINT recipe_name_unique FOR (r:Recipe) REQUIRE r.name IS UNIQUE。核心导入语句示例// 创建食材节点 LOAD CSV WITH HEADERS FROM file:///ingredients.csv AS row MERGE (i:Ingredient {name: row.name}) SET i.category row.category; // 创建菜谱节点 LOAD CSV WITH HEADERS FROM file:///recipes.csv AS row MERGE (r:Recipe {name: row.name}) SET r.cuisine row.cuisine, r.description row.description; // 创建关系 LOAD CSV WITH HEADERS FROM file:///recipe_ingredient.csv AS row MATCH (r:Recipe {name: row.recipe_name}) MATCH (i:Ingredient {name: row.ingredient_name}) MERGE (r)-[:CONTAINS]-(i);这里有个注意点CSV 导入时如果数据里有中文逗号或换行符容易导致解析错位。我的解决办法是导出 CSV 时统一用\t作为分隔符然后在LOAD CSV里指定FIELDTERMINATOR \t。另外源数据里食材名称可能带前后空格导入前要清洗否则MATCH匹配不上。2.4 Neo4j 安装与配置的坑Windows 桌面版、APOC 插件Neo4j 的安装值得一提因为不同系统差别很大。我在 Windows 上用的是 Neo4j Desktop。下载安装后需要创建一个本地数据库设置密码。常见问题有两个一是初始密码要求比较复杂容易记不住建议直接存到密码管理器里二是导入 CSV 时提示“Couldnt load the external resource”这是因为默认导入目录是数据库的import文件夹不是任意路径。把 CSV 文件放到数据库目录/import/下就能解决。另一个绕不开的坑是 APOC 插件。我原本想用apoc.load.json解析数据但 Neo4j Desktop 里启用 APOC 需要手动把插件 jar 包放进plugins目录并且在配置文件里开启。不同 Neo4j 版本对 APOC 版本要求不同如果对不上启动会报错。我的建议是Noe4j 5.x 用对应的 APOC 5.x 版本不要图新去官方 GitHub 的 Releases 页面找匹配版本。如果你和我一样只是简单导入 CSV其实不用 APOC核心 Cypher 就能完成。所以 APOC 不是必需品遇到问题可以跳过。安好之后我推荐在浏览器打开http://localhost:7474用 Neo4j Browser 检查图谱。写几个 Cypher 查询看看节点有没有建立关系。我第一次导入后发现很多菜谱节点没有连上食材排查后发现是食材名称里多了一个空格用TRIM函数清理后就正常了。这种小问题很耗时间但踩过坑之后就好了。3. 语义向量库用 Milvus 承载菜谱的语义检索3.1 文本切分与向量化模型选择图谱负责确定性关系向量库负责把菜谱文本的整体语义纳入检索。我的做法是把每道菜生成一段“菜谱摘要文本”格式为菜名鱼香肉丝菜系川菜食材猪里脊、木耳、胡萝卜、冬笋技法炒、滑炒、勾芡口味鱼香、酸甜这段文本直接送入 embedding 模型生成向量。不需要做复杂的分块因为每道菜的信息量不大一段文本足以表达核心语义。如果某道菜步骤特别多我可以把步骤也拼在后面但实验下来对召回结果影响不大。模型方面我尝试过text2vec-large-chinese和bge-large-zh-v1.5最终选了后者。原因是英文中文混合场景下 bge 更稳且向量维度是 1024Milvus 支持起来没有压力。如果你用 OpenAI 的 embedding 接口也行但中文语义检索效果未必比本地模型好而且有网络和费用考量。3.2 Milvus 安装非 Docker 方式与集合设计Milvus 是个分布式向量数据库常规推荐用 Docker 启动但如果你在 Windows 上不想装 Docker其实也有办法。Milvus Lite 是一个可以嵌入到 Python 应用中的轻量版本适合开发测试。不过我的项目用的是 Milvus 2.3 standalone 的 Windows 非 Docker 安装方式这里必须说清楚官方本身不支持 Windows 直接运行 Milvus 服务但可以通过 WSL2 运行或者使用 Docker Desktop 的 Linux 容器。如果不用 Docker我的做法是在一台固定机器上用 Linux 虚拟机装 Milvus standalone然后本地 Python 通过局域网访问。WSL2 的安装方式相对简单很多教程写“Milvus Windows 安装非 Docker”实际都是在 WSL2 里跑。你可以把下面的命令在 WSL2 的 Ubuntu 里执行。# 下载 milvus standalone 安装脚本 wget https://github.com/milvus-io/milvus/releases/download/v2.3.4/milvus-standalone-docker-compose.yml # 编辑 docker-compose.yml把映射端口改成自己需要的 # 启动 docker compose up -d这本质上还是 Docker 容器但省掉了安装 Docker Desktop 的步骤。如果你连 Docker 都不想要还有一个 Milvus Lite 方案pip install milvus-lite然后直接用默认本地存储跑。我的建议是开发阶段用 Milvus Lite部署阶段再用真正的 Milvus 服务。两者 API 几乎一致切换成本很小。Milvus 集合设计比较简单。我创建了一个recipe_vectors集合字段如下字段名类型说明idINT64主键我用自增整数recipe_nameVARCHAR(255)菜谱名称textVARCHAR(2048)摘要文本embeddingFLOAT_VECTOR(1024)向量内容建集合时要注意索引参数。我用的是IVF_FLAT索引nlist128。查询参数设nprobe16。说实话数据量只有几千条用FLAT索引暴力搜索也很快但为了测试更大的数据集还是建了 IVF 索引。3.3 Attu 管理界面版本匹配问题Milvus 自带命令行没有可视化界面很多人会装 Attu 这个 GUI 工具来查看数据。这里有个高频坑Attu 和 Milvus 的版本必须匹配。比如 Milvus 2.3.x 的客户端就不能用太老的 Attu 连接我一开始装了 Attu 2.3 的早前版本连接时总是报错“Unsupported media type”后来升级到 v2.4 才正常。建议安装前先去 Attu 的 GitHub Releases 页面看一下说明确认支持你的 Milvus 版本。另外连接地址要写对如果 Milvus 跑在 WSL2 里你在 Windows 浏览器里访问的是localhost:8000但在代码里连接 Milvus 时要用 WSL2 的 IP通常可以通过ifconfig查到不能直接用 localhost否则连接会被拒绝。3.4 写入与查询向量——近邻搜索参数我用pymilvus完成写入和查询。写入的核心代码from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType connections.connect(aliasdefault, hostlocalhost, port19530) fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue), FieldSchema(namerecipe_name, dtypeDataType.VARCHAR, max_length255), FieldSchema(nametext, dtypeDataType.VARCHAR, max_length2048), FieldSchema(nameembedding, dtypeDataType.FLOAT_VECTOR, dim1024), ] schema CollectionSchema(fields, descriptionrecipe semantic vectors) collection Collection(recipe_vectors, schema) collection.create_index(embedding, {index_type: IVF_FLAT, metric_type: COSINE, params: {nlist: 128}})查询时我把用户问题向量化后调用collection.searchsearch_params {metric_type: COSINE, params: {nprobe: 16}} results collection.search( data[question_vector], anns_fieldembedding, paramsearch_params, limit5, output_fields[recipe_name, text] )这里有个小经验距离度量我用COSINE而不是L2。因为文本向量的模长和菜谱长度有一定关系余弦相似度更能反映语义方向的一致。如果发现召回结果里混入一些奇怪菜谱可以调大nprobe或limit再结合图谱过滤。4. 检索增强编排怎么把图和向量结果喂给 LLM4.1 两阶段召回向量粗筛 图扩展精排回到最初的痛点用户问“鱼香肉丝有没有不放猪肉的做法”如果只做向量召回系统会返回鱼香肉丝的菜谱文本但不会返回“可以用鸡胸肉替代猪里脊”的结论。必须让图数据库参与进来。我的编排流程是先用 LLM 对问题进行实体识别提取出菜名和食材名。例如这个问题会提取出{ recipes: [鱼香肉丝], ingredients: [猪肉] }。然后走两路召回向量召回计算问题向量在 Milvus 里返回 Top-5 菜谱名比如[鱼香肉丝, 青椒肉丝, 木须肉]。图谱召回拿着实体去 Neo4j 查先找鱼香肉丝节点再查它包含哪些食材重点查 “猪肉” 这个食材节点有哪些替代关系同时查共享“炒”技法或相近口味的其他菜谱。两路召回结果取并集。如果向量召回中的菜谱在图谱里不存在就以图谱为准如果图谱里有关系但向量没召回也没关系图谱数据会直接进入上下文。4.2 上下文组装如何形成 LLM 可读的 JSON这是整个系统最巧妙的部分。LLM 不能直接读图所以我把图查询结果序列化成三元组列表并和向量召回的文本拼在一起。一个典型的上文结构如下{ question: 鱼香肉丝有没有不放猪肉的做法, vector_recall: [ {recipe_name: 鱼香肉丝, text: 菜名鱼香肉丝食材猪里脊、木耳、胡萝卜...}, {recipe_name: 青椒肉丝, text: 菜名青椒肉丝食材猪里脊、青椒...} ], graph_facts: [ {type: contains, from: 鱼香肉丝, to: 猪里脊}, {type: contains, from: 鱼香肉丝, to: 木耳}, {type: can_substitute, from: 猪里脊, to: 鸡胸肉, constraint: 口感略相似调味不变}, {type: can_substitute, from: 猪肉, to: 牛肉, constraint: 适合不喜欢猪肉脂肪的情况}, {type: technique, from: 鱼香肉丝, to: 滑炒} ] }我把这些 JSON 直接拼到 prompt 里要求 LLM 优先使用 graph_facts 中的关系做推理不要凭空编造食材替代。这样做的好处是让生成过程有据可循回答“能替代”或“不能替代”时能引用图谱里的关系给出理由。4.3 调用 LLM 生成答案OpenAI 兼容接口 提示词模板LLM 调用我用的是 OpenAI 兼容接口这样可以方便地切换不同后端。下面的代码演示了如何构造请求import json from openai import OpenAI client OpenAI(base_urlhttp://localhost:11434/v1, api_keynone) def generate_answer(context: dict) - str: sys_prompt 你是资深中餐大厨。请根据提供的食谱知识和食材关系用简洁明确的语言回答问题。如果图谱中给出了替代关系请直接引用。如果没有明确的替代关系请说明食谱中没有明确记录替代方案。不要编造关系。 user_prompt f问题{context[question]}\n\n检索信息\n{json.dumps(context, ensure_asciiFalse)}\n\n请根据以上信息回答。 resp client.chat.completions.create( modelqwen2.5:7b, messages[{role: system, content: sys_prompt}, {role: user, content: user_prompt}], temperature0.3 ) return resp.choices[0].message.content我后来把模型切成 Qwen 2.5 7B 跑在 Ollama 上回答质量虽然比 GPT-4 略逊色但速度够快且离线可用。如果需要更好的推理能力可以换成更大的模型或 GPT-4 系列不过 prompt 里的 graph_facts 要写得足够清晰否则大模型也会忽略。4.4 一个完整问题的检索链路代码演示我把整个检索和生成过程封装成一个answer()函数def answer(question: str): # 1. 用LLM做实体识别 entities extract_entities(question) # 2. 向量召回 vec_docs vector_search(question, top_k5) # 3. 图谱召回 graph_facts graph_search(entities) # 4. 合并上下文 context {question: question, vector_recall: vec_docs, graph_facts: graph_facts} # 5. 调用LLM生成答案 final generate_answer(context) return finalextract_entities也是调用 LLM用一个简单的提示词让它输出 JSON。vector_search就是上一节提到的 Milvussearch。graph_search则是几个 Cypher 查询的组合核心代码// 根据菜谱名找食材 MATCH (r:Recipe {name: $recipe_name})-[:CONTAINS]-(i:Ingredient) RETURN i.name // 查食材的替代关系 MATCH (a:Ingredient {name: $ingredient_name})-[:CAN_SUBSTITUTE]-(b:Ingredient) RETURN a.name, b.name // 查共享食材的相似菜谱用共现食材数排序 MATCH (r:Recipe {name: $recipe_name})-[:CONTAINS]-(i:Ingredient)-[:CONTAINS]-(other:Recipe) WHERE other r WITH other, count(i) AS shared ORDER BY shared DESC LIMIT 3 RETURN other.name, shared这些查询在 Python 中用neo4j驱动执行返回的 Record 转成字典列表。要点是一个问题可能涉及多个食材和菜谱因此每个实体都要查一次然后把结果合并注意去重。5. 实测效果与排坑记录从“答非所问”到“懂行老饕”5.1 测试问题与检索结果对比系统跑通后我做了几组测试下面用表格列出典型问题和效果对比。问题纯向量 RAG 的回答图 向量 RAG 的回答鱼香肉丝有没有不放猪肉的做法给出了鱼香肉丝的完整菜谱让用户自己做取舍直接指出可用鸡胸肉或牛肉替代猪里脊并说明图谱中有明确替代关系同时推荐了青椒肉丝作为类似选择的参考宫保鸡丁可以不放花生吗介绍宫保鸡丁的做法建议把花生去掉提示花生在宫保鸡丁中主要提供酥脆口感如果没有花生可以用腰果或杏仁替代并说明这是基于食材替代关系得出的结论哪些菜适合蒸返回了几个包含“蒸”字的菜谱片段从图谱中直接检索到标签为“蒸”的技法节点关联的菜谱列表并额外推荐了“粉蒸肉”“蒸鱼”等经典菜可以看出图结构带来的增量信息不是“另一个文本块”而是推理路径。LLM 的回答不再是罗列而是“判断依据”。5.2 关键坑一实体对齐与同义词扩展图 RAG 最容易被忽视的坑是实体对齐。如果我提取出的食材是“猪里脊”而图谱中的食材叫“里脊肉”查询就匹配不到。我做了三层处理第一层在实体识别时提示 LLM 尽量输出标准名称第二层在 Neo4j 查询时对每个实体名生成一个同义词集合例如通过apoc.text.phonetic或自定义同义词表第三层也是最简单的在提取实体后先做一次模糊匹配用 Neo4j 的CONTAINS或 Levenshtein 相似度找到标准名。我实际用了近似匹配MATCH (i:Ingredient) WHERE i.name CONTAINS $keyword OR $keyword CONTAINS i.name RETURN DISTINCT i.name LIMIT 5但这会产生很多噪声所以最终方案是维护一个同义词映射字典例如{鸡胸肉: [鸡脯肉, 鸡胸], 里脊肉: [猪里脊, 里脊]}。识别出的实体先查字典没有的话再走模糊匹配。这个字典随着测试中发现的漏配逐渐扩充。5.3 关键坑二Neo4j 连接池与并发查询当查询逻辑变复杂Neo4j 驱动的连接管理也会出问题。我之前每个请求都新创建一个GraphDatabase.driver()很快报“Too many open files”。正确做法是全局维护一个 driver 实例让它内部管理连接池from neo4j import GraphDatabase driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, password)) # 在应用启动时初始化不要每次查询都调用 driver() 构造器查询时用driver.session()会自动从池中获取连接用完释放。但要注意如果某个查询耗时较长应该将 session 内的事务尽量简短。我把一个复杂的图谱查询拆成多个独立小查询用多线程并发执行总耗时反而更低。5.4 关键坑三Milvus 无 Docker 安装后的服务状态管理我最初用 Milvus Lite 开发后面换到独立的 Milvus 服务后遇到最折腾的问题是服务启动依赖 etcd。Milvus standalone 的 Docker Compose 会同时启动 etcd、minio 和 milvus 三个容器哪个挂了都会起不来。用docker compose logs milvus查看日志时最常见的错误是 etcd 连接超时。我的解决方式是在启动 Milvus 之前先确认 etcd 容器健康或者在 docker-compose.yml 里增加健康检查和 restart 策略。如果你在 Windows 下用 WSL2注意要把 WSL2 本机内存分配调大否则 Milvus 启动时容易 OOM。还有一个跟部署相关的点pymilvus客户端和服务端版本要匹配。我客户端从 2.2 升到 2.3 后旧代码里connections.connect没变但返回的结果集类型变了output_fields字段名称大小写也略微不同。遇到问题后去 GitHub Issues 一搜发现很多人遇到同样的问题。所以固定依赖版本非常重要别一有新版本就升级。6. 还能怎么玩图 RAG 在其它领域的扩展思路烹饪问答只是图 RAG 的一个切入点。这套“知识图谱建立关系结构 向量库建立语义索引 LLM 做生成推理”的组合可以迁移到很多场景。比如医疗领域的“用药禁忌查询”药物之间的相互作用可以用图关系表示症状描述和药物说明书文本可以向量化问“高血压患者能不能吃布洛芬”LLM 就能从图中找到药物节点之间的禁忌关系而不是靠猜。又比如工业维修问答设备故障代码、零件依赖关系、历史维修记录正好是图结构向量召回相关故障描述图扩展出可能的原因和零件关系生成答案时自然带上维修步骤。我在迁移到这个系统时最大的体会不是写代码而是想清楚“哪些知识必须用图表达哪些知识用向量表达”。如果关系是显式的、可枚举的例如“A 替代 B”“A 属于 B”放图数据库如果关系是模糊的、语义的例如“这个菜谱看起来像另一道菜”“这段描述和用户问题相关”放向量数据库。两者不是替代关系而是分工。这套项目的代码我放在 GitHub 上了有需要的朋友可以留言交流。最后分享一个小技巧测试图 RAG 系统时不要只问你准备好的问题试着让朋友随便说一句“我想吃酸甜口的但没有鸡肉”看系统能不能正确处理“酸甜”口味和“鸡肉”缺失这两个约束。如果这个都能回答得像样说明你的图谱和提示词工程真的过关了。
返回列表