多智能体+知识图谱+向量检索:企业级复杂问答系统架构实战
简介:面向科研新手、硕博研究生与青年科研工作者的《Gemini科研手册指南》PDF,系统讲解借助Gemini完成从选题生成、文献检索、精读分析、综述撰写到科研绘图、数据可视化及汇报PPT制作的全流程,突出“研究方案先行”“方案可修订”与结构化输出,并结合多智能体协作、知识图谱与检索增强生成等前沿方向,构建可追溯、可复用、可迭代的科研路径。资源仅1个PDF文件,压缩包约12.15MB,内含大量可直接复用的提示词公式与操作示例,覆盖学术热点与产业趋势交叉分析、高可信文献检索、多篇论文横向对比、机制图与数据图智能生成,也支持与Canvas、Nano Banana等智能体协同。已有232人学习浏览,适合希望提升科研效率、规范研究流程、增强成果可视化表达的跨学科研究者;通过“描述即生成”的交互方式,可快速掌握从文本指令到高质量科研产出的闭环方法。
1. 企业级复杂问答为什么扛不住:把《Gemini科研手册指南》拆开看
把《Gemini科研手册指南》和多智能体、知识图谱、检索增强生成放在一起看,要解决的是同一个问题:企业级复杂问答靠单一大模型加向量检索扛不住多跳推理。售后场景里,“这台设备的故障记录和我上次投诉的备件是不是同一批次”要跨工单、设备、客户三个数据域做实体对齐和关系推理,传统RAG只是让答案看起来有依据,并没有真正把关系算出来。
这套方案把生成过程拆成多个专职智能体,用知识图谱承载实体与关系事实,用Gemini这类多模态大模型做任务规划、工具调用与最终生成,再通过企业级系统架构把权限、并发、成本兜住。适合做企业内部知识助手、智能问数、售后智能体的人:架构师看系统拆分,后端看代码骨架,算法看检索与抽取的坑。
2. 多智能体管线的角色拆分:Planner、Retriever、Graph Agent、Verifier各管什么
2.1 职责边界与交接协议:为什么角色之间只传JSON
我一般会把多智能体协同拆成四个角色。Planner负责把用户的复杂问题拆成子任务列表,每个子任务标注检索域——是去文档里找语义片段,还是去知识图谱里查关系事实。Retriever只做向量召回,Graph Agent只做图谱查询,Verifier最后用图谱事实核验草稿答案。四个角色之间不传自然语言段落,统一传结构化JSON。
这样做的好处是每一跳都可观测、可重试。比如用户问“华新精密的CNC-08主轴异响,处理人是不是李工”,Planner会拆成三个子任务:查客户与设备归属、查故障记录、查工单处理人。每个子任务的输入输出都是JSON,中间任何一步失败都能定位。如果角色之间互相传大段文字,多智能体很快会陷入模型自我对话,上下文爆炸,这是多智能体代码编排最常见的失控点。
交接协议长这样:
{ "task_id": "t2", "query": "CNC-08最近一次主轴异响对应的工单处理人", "domain": "graph", "deps": ["t1"], "constraints": {"time_range": "2024-01-01_2024-12-31"} }Planner把这类子任务塞进state,retriever和graph_agent才能并行执行。domain字段是路由判据,比每次都让模型自己猜该查什么可靠得多。deps表示依赖关系,满足拓扑排序后才能进入生成阶段。
参数这里要固定下来:Planner和Graph Agent的temperature建议0.1,让输出结构稳定;LLM调用超时30秒;Retriever的top_k在8到12之间,相似度低于0.25的向量片段直接丢弃。这些数值不是拍脑袋,跑过一轮业务评测集就会发现,top_k超过12之后召回的边际收益明显下降,而token成本还在涨。
2.2 用LangGraph把多智能体协同编排跑起来:state与条件边
编排框架的选择上,常见做法是用LangGraph。它把每个Agent建模成图里的节点,状态在节点之间显式传递,天然适合“拆解—并行检索—生成—校验”这个流程。先看最小可用的代码骨架:
from typing import TypedDict from langgraph.graph import StateGraph, END class QnAState(TypedDict): question: str history: str tasks: list # planner 输出的子任务列表 chunks: list # retriever 的向量召回结果 facts: list # graph agent 返回的图谱事实 draft: str # generator 生成的草稿 verdict: dict # verifier 的核验结果 retries: int # 当前重试次数 def planner(state: QnAState) -> dict: # 调用 Gemini function calling,拆分子任务,返回 {"tasks": [...]} return {"tasks": plan_llm(state["question"], state["history"])} def retriever(state: QnAState) -> dict: # 遍历 tasks,对 domain=="vector" 的任务做向量召回 return {"chunks": vector_batch_search(state["tasks"])} def graph_agent(state: QnAState) -> dict: # 遍历 tasks,实体链接 -> 生成 Cypher -> 查询 Neo4j return {"facts": graph_batch_query(state["tasks"])} def generator(state: QnAState) -> dict: # 把 chunks + facts 拼进提示词,生成草稿 return {"draft": generate_answer(state)} def verifier(state: QnAState) -> dict: # 从草稿抽断言,与 facts 比对,返回 {"verdict": {...}} return {"verdict": verify_draft(state["draft"], state["facts"])} graph = StateGraph(QnAState) graph.add_node("planner", planner) graph.add_node("retriever", retriever) graph.add_node("graph_agent", graph_agent) graph.add_node("generator", generator) graph.add_node("verifier", verifier) graph.set_entry_point("planner") graph.add_edge("planner", "retriever") graph.add_edge("planner", "graph_agent") graph.add_edge("retriever", "generator") graph.add_edge("graph_agent", "generator") graph.add_edge("generator", "verifier") graph.add_conditional_edges( "verifier", lambda s: "generator" if (not s["verdict"]["passed"] and s["retries"] < 2) else END, {"generator": "generator", END: END} ) app = graph.compile()逻辑说明:planner之后retriever和graph_agent是两条并行边,LangGraph会用线程池同时跑;generator等两边结果都到齐后再触发。verifier返回失败且重试次数小于2时,回到generator做局部修订,否则直接结束。
参数说明:重试上限写死在条件边里,我一般设2次,超过就返回“部分结果+待人工复核”,避免死循环。每个节点的超时不要用全局默认值——规划类任务30秒,图谱查询3秒,向量检索1秒,分开设置才能定位瓶颈。这里还有一个容易忽略的点:LangGraph的并行是共享事件循环还是单独线程池,取决于节点声明为sync还是async,生产环境建议把retriever和graph_agent写成async节点,避免耗时操作阻塞整个编排。
2.3 为什么选Gemini做路由与生成:function calling与多模态输入
这个组合里,Gemini承担的是大脑角色:Planner的拆解、Graph Agent的实体链接与Cypher生成、Generator的最终回答,本质都是大模型调用,只不过提示词和输出约束各不相同。
选择Gemini而不是本地微调模型,一般是看中三件事。一是function calling成熟,直接把子任务列表的JSON Schema注册成工具,模型返回结构化参数,比让模型“按格式输出”稳定得多;二是超长上下文,企业问答经常要带合同条款、设备手册段落,百万级窗口让这类内容可以直接进上下文,不用提前做太多裁剪;三是多模态,2026年前后的多模态大模型已经能直接读设备照片、车间截图、Excel表格截图,这些输入在纯文本LLM里完全处理不了,而企业真实问题里带着图片问设备的案例比比皆是。
接入方式上,常见做法是用公司API网关统一接入官方接口,网关负责密钥管理、每用户QPS限制、日志审计。这里提醒一句:不要把密钥下发到前端或客户端,所有调用都走服务端网关,否则日志一查一个泄露。选型时还要考虑降级路径,一旦主模型限流,网关要能快速切换到备用模型,这个在架构设计阶段就要留好接口。
3. 企业级系统架构设计:六层划分与双通道检索
3.1 接入层到数据层:权限为什么要下沉到检索层
企业级系统架构设计和普通Demo最大的区别是边界。Demo可以一个进程把检索生成都串起来,企业级系统必须分层,因为权限、并发、监控、成本都要在特定层兜住。我一般按六层拆:接入层、对话管理层、编排层、检索层、生成层、数据层。
下面这张表把每层的关键职责和故障影响说清楚:
| 层 | 关键组件 | 主要职责 | 故障影响 |
|---|---|---|---|
| 接入层 | API网关 | 统一认证、限流、路由 | 过载直接表现为超时 |
| 对话管理层 | 会话服务 | 用户身份、权限上下文、历史摘要 | 权限错乱比答错更严重 |
| 编排层 | LangGraph | 多智能体调度、重试、超时 | 死循环拖垮整个请求 |
| 检索层 | 向量库+Neo4j | 双通道召回,权限过滤 | 召回空导致生成胡编 |
| 生成层 | Gemini+提示模板 | 草稿生成、格式约束 | 高延迟、高成本 |
| 数据层 | 文档库、图谱、ACL | 索引、图存储、权限表 | 数据不一致直接答错 |
权限是这六层里最容易被忽略的企业级问题。复杂问答的价值恰恰在于能把多个数据域关联起来,但能关联不等于有权限看。做法是把权限过滤下沉到检索层:生成层只拿到用户有权访问的chunks和facts,不能靠提示词让模型自觉遵守权限。图谱侧要按实体类型和关系类型做ACL过滤,向量侧按文档标签过滤。
我见过一个反面案例:答案生成正确,但把另一个部门的内部合同摘要带了出来。问题不在模型,而在生成层能拿到全部召回结果,权限过滤放在了前端展示。所以权限必须物理隔离在检索层,生成层永远接触不到无权数据。
3.2 向量召回与图谱查询并行:双通道的时序与参数
检索层的核心设计是双通道并行。并行两个字很重要。早期版本我把图谱查询放前置,实体链接失败率接近一半,整体延迟多出2秒;后来改成两条通道同时发起,图谱通道失败时自动等待向量通道,整体P95才降下来。
时序上,Planner把子任务拆完后,retriever处理vector域,graph_agent处理graph域,两路互不阻塞。需要控制的是汇总等待时间:
import asyncio async def dual_channel_search(state): vector_task = asyncio.create_task(vector_search(state["tasks"])) graph_task = asyncio.create_task(graph_query(state["tasks"])) chunks, facts = await asyncio.wait_for( asyncio.gather(vector_task, graph_task, return_exceptions=True), timeout=5 ) return chunks, facts逻辑说明:asyncio.gather让两路并发执行,wait_for的5秒是最长等待窗口,超时则返回已拿到的一路结果,另一路标记为超时。这样即使图谱通道卡住,请求也不会跟着卡死。
参数上,我一般把双通道参数统一放在一个配置里,方便调优:
| 参数 | 建议值 | 说明 |
|---|---|---|
| 向量top_k | 8~12 | 语义片段召回数量 |
| score_threshold | 0.25~0.35 | 低于阈值直接丢弃 |
| 实体链接置信度阈值 | 0.80 | 低于阈值放弃图谱查询 |
| Cypher查询超时 | 3秒 | 长路径查询必须带LIMIT |
| 双通道汇总超时 | 5秒 | 并行等待上限 |
| 校验重试上限 | 2次 | 超过返回“待人工复核” |
3.3 会话上下文管理与成本控制:滑动窗口与限流参数
会话上下文是另一个和成本强相关的设计点。企业问答场景用户习惯连续追问,但不可能把整段历史都传给Gemini,token烧得太快。做法是滑动窗口压缩:历史被压缩成一段摘要,加上最近3轮原文,一起作为history传给Planner。摘要生成用轻量模型异步做,不让用户请求等在那个环节。
成本控制分三层。第一层是按用户限流,普通用户每分钟10次,VIP 30次,防止单用户占满预算;第二层是按会话设置token上限,超限自动降级到轻量模型;第三层是按部门按天核算token消耗,达到阈值后在网关直接降级到备用模型,业务代码无感知。
这三层都在网关和调度层实现,不要在应用代码里写死。我见过一个翻车案例:限流写在应用里,线上调参要发版,限流参数半年没人动过,高峰期全靠网关硬扛。正确的做法是网关配置热更新,应用层只关心业务逻辑。
4. 知识图谱构建与查询:从工单文档到Cypher检索
4.1 用Gemini从非结构化文档批量抽取三元组:提示词与JSON输出
知识图谱构建的第一步不是画图模型,而是把非结构化文档转成三元组。企业里最典型的数据源是工单、故障记录、设备台账、客户合同。常见做法是用大模型批量离线抽取,而不是问答时在线抽取——在线抽取每问一次就跑一轮LLM,成本和延迟都不可控;离线抽好后入库,在线只查Cypher。
抽取请求长这样,以Gemini的generateContent接口为例:
curl -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent" \ -H "Content-Type: application/json" \ -d '{ "contents": [{ "parts": [{"text": "你是知识图谱抽取器。从运维工单中抽取(实体、关系、实体)三元组,实体限定在设备、客户、故障类型、工单、处理人。只输出合法JSON数组,不要解释。工单内容:【华新精密的CNC-08在2024年5月报主轴异响,维修更换主轴轴承,耗时4小时,处理人工号10023,工单号WO-2024-0113】"}] }], "generationConfig": { "temperature": 0.1, "responseMimeType": "application/json" } }'逻辑说明:把抽取任务限定在预定义的实体类型里,比让模型自由发挥稳定得多。temperature设0.1,事实抽取要确定性;responseMimeType强制JSON输出,省去解析自然语言的麻烦。接口地址以官方最新文档为准,模型型号也要按账户实际开通的列表替换。
抽取结果示例:
[ {"head": "华新精密", "relation": "拥有设备", "tail": "CNC-08", "domain": "customer_device"}, {"head": "CNC-08", "relation": "发生故障", "tail": "主轴异响", "domain": "device_fault"}, {"head": "CNC-08", "relation": "关联工单", "tail": "WO-2024-0113", "domain": "device_workorder"}, {"head": "WO-2024-0113", "relation": "处理人", "tail": "10023", "domain": "workorder_staff"} ]拿到原始三元组后要先做实体链接再接图谱:同一实体的不同叫法,比如“CNC-08”和“八号机床”,需要先对齐到同一个实体ID。批量抽取的并发控制在4到8路,带指数退避重试,不然很容易触发模型接口限流。工业知识图谱场景里实体密度高、同义词多,这一步不做,后面查询全是空结果。
4.2 Neo4j图模型设计:实体类型、关系与Cypher约束
图谱存储我一般选Neo4j,模型设计围绕查询模式展开。以设备售后为例,最小可用的图模型是五类实体、四类关系:
| 实体类型 | 关键属性 | 对应索引 |
|---|---|---|
| Customer | customer_id, name | customer_id唯一约束 |
| Device | device_id, model, status | device_id唯一约束 |
| FaultType | fault_code, name | fault_code唯一索引 |
| WorkOrder | wo_id, created_at, status | wo_id唯一约束 |
| Staff | staff_id, name | staff_id唯一约束 |
关系方面,Customer有HAS_DEVICE指向Device,Device有HAS_FAULT指向FaultType,WorkOrder有RELATES_TO指向Device,WorkOrder有HANDLED_BY指向Staff。Cypher约束建好后,查询才能走索引:
CREATE CONSTRAINT customer_id IF NOT EXISTS FOR (c:Customer) REQUIRE c.customer_id IS UNIQUE; CREATE CONSTRAINT device_id IF NOT EXISTS FOR (d:Device) REQUIRE d.device_id IS UNIQUE; CREATE CONSTRAINT wo_id IF NOT EXISTS FOR (w:WorkOrder) REQUIRE w.wo_id IS UNIQUE;常见查询是“某客户名下所有在修设备的故障分布”:
MATCH (c:Customer)-[:HAS_DEVICE]->(d:Device) WHERE c.name = $name AND d.status = 'maintaining' OPTIONAL MATCH (d)-[:HAS_FAULT]->(f:FaultType) RETURN f.name AS fault_type, count(d) AS device_count ORDER BY device_count DESC LIMIT 10逻辑说明:参数化查询避免Cypher注入风险;OPTIONAL MATCH保证没有故障记录的设备不会被JOIN过滤掉;LIMIT限制返回行数,避免长路径扫描拖慢整体。这里的$name由Graph Agent的实体链接结果填充,不是用户原话。
4.3 图谱查询与RAG的混合召回:RRF融合与降级策略
双通道的召回结果最后要并进生成提示词。图谱结果和向量结果性质不同,向量结果是“可能相关”的文档切片,图谱结果是“确定成立”的关系事实,混在一起时我一般用RRF做融合,避免两种分数直接相加的量纲问题:
def rrf(ranked_lists, k=60): scores = {} for ranked in ranked_lists: for rank, item in enumerate(ranked): doc_id = item["id"] scores[doc_id] = scores.get(doc_id, 0) + 1.0 / (k + rank) return sorted(scores.items(), key=lambda x: x[1], reverse=True)逻辑说明:k是平滑参数,k越小排名靠前的项权重越大;k=60是常见默认值,可以根据结果稳定性调整。实体链接置信度高的图谱结果,我会在排序后给一个保底加分,因为图谱事实的确定性高于向量片段。
降级策略上,我坚持一条原则:图谱通道失败时,整个检索不会失败,只会退化为纯向量RAG,同时在生成提示词里注明“当前缺少图谱证据,请给出带不确定性的回答”。千万不要在图谱查询为空时让模型硬编事实。这个降级逻辑要写在编排层的条件边里,而不是靠模型自觉。
5. 企业级落地避坑记录:四个高频问题从现象到解决
5.1 多Agent循环不收敛:工作记忆设计错了
现象:一个简单问题,verifier挑出一点小毛病就整篇打回generator,generator改完又引入新的表述偏差,verifier再打回,来回四五轮,token消耗翻倍,答案反而和第一版本不同。
原因:工作记忆没有隔离。verifier把整篇草稿传给generator,generator是在修正一篇自己生成的长文,模型容易被自己过去的输出带偏。这是多智能体协同最容易踩的坑。
解决:verifier只返回“缺失事实列表”,而不是返回草稿全文。generator收到的修订指令是“补充以下事实:CNC-08主轴异响由工单WO-2024-0113处理”,草稿原文只允许局部修改。同时把重试上限设成2,超过就直接输出“部分结果+待人工复核”。这条血泪经验是:多智能体不是轮次越多越聪明,要尽快收敛。
5.2 图谱查询大面积空结果:实体链接没做别名对齐
现象:图谱查询耗时很低,但命中率惨淡。用户问“华新的CNC-08现在什么状态”,Cypher查不到任何东西,只能靠向量通道兜底,图谱的价值没发挥出来。
原因:用户说的是简称“华新”,图谱里存的是全称“华新精密有限公司”;或者实体链接模型把“CNC-08”和“八号机床”当成两个实体。实体链接置信度只有0.55,低于0.80的阈值,Graph Agent直接放弃了。
解决:先建实体词典再做抽取。别名表是必须的,把“华新”“华新精密”“华新精密有限公司”映射到同一个customer_id。实体链接阶段要返回候选列表,不只是返回最高分。阈值从0.80降到0.75可以,别低于0.70,否则误链会把不相关事实拽进来。空结果时强制走向量通道,并给出一句兜底话术。
5.3 Gemini接口超时与限流:网关熔断与降级
现象:上线压测时P95延迟从8秒飙到25秒,日志里全是模型接口限流错误,单个请求重试了三四次,把等待队列彻底堵死。
原因:所有并发请求都打同一个密钥,网关没做限流;LLM调用超时设成了全局30秒,重试逻辑是“失败就重试”,在限流状态下等于火上浇油。
解决:在API网关做三层控制。第一层每用户QPS限制,普通5 QPS,VIP 20 QPS;第二层LLM调用分类型设超时,路由类15秒、生成类60秒;第三层全局熔断,连续失败率超20%就切换到备用模型。重试最多1次且必须幂等。密钥绝不能出现在前端或客户端代码里,服务端日志也要脱敏。
5.4 评测指标好看但业务不买账:基准集和业务集落差太大
现象:在公开问答基准上F1不错,一到真实工单就露馅。业务反馈“答案读起来对,但没说到点子上”,比如把“设备是否在保内”答成“设备在2024年5月报修过”。
原因:公开基准的推理方式和企业问法差别太大。企业真实问题是口语化、带错别字、跨数据域、高度依赖业务口径的,公共基准考的策略性推理覆盖不到“在保状态怎么判定”这种业务规则。
解决:自建业务评测集,从真实工单和聊天记录里抽200到300条,标好标准答案和可接受答案,离线跑评估;再抽10%流量在线灰度,同用户池对比新旧版本的问题解决率。打分上,用LLM-as-judge按忠实性、完整性、安全性三个维度打分,同时监控“拒答率”——模型对没依据或没权限的问题说“不知道”,比硬答重要得多。
6. 验证这套架构值不值得上:成本核算与灰度评估
多智能体+知识图谱不是免费午餐,它比单Agent RAG多消耗的token是实打实的。以我的经验,完整问答链路里planning约占总token的15%,verifier每回退一轮多耗约10%到15%。所以上不上这套架构,先把三个数字测出来:graph_hit_rate(图谱通道命中率)、verifier_retry_rate(校验打回率)、avg_tokens_per_query(单问答平均token数)。
离线阶段,业务评测集先跑两轮:第一轮单Agent RAG,第二轮多智能体+图谱,对比忠实性得分和拒答率。得分差小于5分,说明这个业务场景图谱价值有限,不如先回头把文档切片的召回质量做好。在线灰度阶段,抽10%流量到新版本,观察两个指标:P95延迟和“用户是否结束对话”的二次反馈率。延迟涨幅超过40%就要回头优化图谱查询的超时和并行度。
成本核算可以按千次问答做一张表,把模型调用、向量库、图数据库的消耗分开记:
| 指标 | 单Agent RAG | 多智能体+图谱 |
|---|---|---|
| avg_tokens_per_query | 约2500 | 约4000 |
| graph_hit_rate | 不适用 | 70%以上 |
| verifier_retry_rate | 不适用 | 低于15% |
| P95延迟 | 约5秒 | 约8秒 |
这里的数值是常见量级,不是固定值。如果graph_hit_rate上不去,先回头查实体词典和别名表,而不是急着调提示词。多智能体系统的价值用一句话概括:用更多token换更多确定性。值不值得,把上面三个数字跑出来再下结论。
最后说一个我的习惯。我早期做这套系统时,把图谱通道放前置,实体链接失败率接近一半,平均延迟多出2秒,差点把这个方向砍掉。后来改成双通道并行、图谱失败自动降级到向量RAG,graph_hit_rate从45%爬到70%以上,系统才算真正稳定下来。现在的教训是:每接入一个Agent,先问它“失败时谁兜底”,答不出来就不要上。希望帮到你。
本文还有配套的精品资源,点击获取