
做合同审查的朋友上周问我“这份PDF第三页是扫描盖章件第五页是张业务流程图我想直接让大模型回答‘这个流程里哪个环节最容易出问题’传统RAG根本答不上来”。我告诉他你撞上的正是多模态RAG要解决的问题——让LLM在图文混合文档上做智能问答不能只靠读文字必须真的“看见”。我最近半年在财报、发票、产品手册这些场景里把多模态RAG从零到一跑了一轮从解析、切分、检索到生成踩了不少坑这篇就把整个思路、选型和落地代码都拆开讲清楚给正要上这个方向的同路人做参考。1. 图文混合文档的检索难题为什么传统RAG一到图表就哑火1.1 传统RAG对图文的三种“失明”传统RAG的链路大家都熟文档加载器抽取纯文本按固定窗口切chunk套embedding模型做向量化检索时拿问题和所有chunk算相似度最后把Top-K文本块塞给LLM生成答案。这套流程在纯文本合同、纯文本网页、Markdown笔记上非常好用但遇到图文混合文档就集体破功。第一种失明发生在文档加载阶段。很多加载器对PDF只会抽文本层对扫描件直接输出空白对已经做进图片里的报表、流程图、票据抽取出来就是一堆无意义字符串。哪怕文件里带OCR层图层和文字层之间的坐标关系也经常丢失最终拿到的只是被压扁的纯HTML或纯文本。第二种失明是“空间信息的丢失”。表格、泳道图、组织架构图这类内容语义高度依赖二维排布。OCR后的文本虽然看起来是“第几行第几列”但如果没有bbox位置框信息这段文本就退化成一行行孤立文本。LLM看到“预算 200万 实际 180万”时无法判断是“超支”还是“结余”因为列头信息可能被切到另一个chunk里去了。第三种失明是大模型暴力读图的幻觉。有人想绕过RAG直接把整份PDF截图发给Qwen-VL或GPT-4o让多模态大模型直接回答。这在小文件上可行一旦文档超过几十页多模态模型的上下文窗口根本吃不消而且视觉注意力在大图上会明显衰退。更麻烦的是每次都把原图塞进模型无法针对“图表做问答”做精准召回答案既不稳定也没法溯源。1.2 最容易踩雷的文档类型我整理的图文混合文档最典型的有四类。第一类是财报和研报里面大量出现K线图、柱状图、饼图和业绩表格结论往往藏在图里而不是正文里。第二类是合同扫描件盖章、签名、手写批注都是关键证据文字层可能不存在。第三类是产品手册和维修工单装配图、流程图、接线图比纯文字说明更直观。第四类是发票、工单、体检报告这类半结构化票据既有表格框线又有印章、签字区域还经常歪斜模糊。这些文档有一个共同点如果只设置文本检索检索器根本不知道“这张图”和“这段文字”讲的是同一件事。用户问“去年第三季度哪个产品线收入下滑最快”答案图表在柱状图上索引里如果没有这个图的视觉嵌入哪怕正文反复出现“第三季度”也没法把图表这个关键证据召回。1.3 多模态RAG的定位不是“塞图给大模型”而是“建立视觉索引”所以多模态RAG要做的事情不是把多模态大模型当成万金油而是先对文档做视觉解析把文档拆成一个个带位置信息的“视觉-文本块”再对这些块做多模态对齐的向量化。检索时拿用户问题同时检索文本块和图像块最后把检索到的图像证据、结构化表格、OCR文本一起组织好交给生成端。一句话描述我的理解多模态RAG给LLM装上了一双可以定位、可以检索的“眼睛”而不是只给它嘴里塞一堆图片。这套思路下问题就从“模型看不看得懂图”变成了“索引建得好不好”这也是这篇文章要把绝大多数篇幅放在解析、切分、检索上的原因。2. 多模态RAG整体架构选型思路先搭框架再选零件2.1 五层处理链路一套能处理图文混合文档的多模态RAG系统我习惯拆成五层输入层、解析层、索引层、检索层、生成层。输入层处理PDF、DOCX、扫描件、图片的读取和格式归一化解析层做OCR、版面分析、表格结构识别、图像检测索引层把解析结果切成合理粒度做文本向量化和视觉向量化检索层负责混合召回、去重、重排生成层把检索证据拼成Prompt交给LLM生成答案。这五层没有哪层可以省。很多人跳步比如只做OCR不做版面分析或者只给图像做embedding不做结构化表格提取都会在后续某一个问题环节爆雷。我在项目一开始也想过“直接用GPT-4o做解析一步到位”但实验结果是解析不彻底后面所有环节都跟着错返工成本更高。2.2 文档解析层的选型解析层是整套系统的地基。我调研下来市面方案大概分成三档。轻量级方案是PaddleOCR加PP-Structure能完成OCR文字识别、表格还原、版面方向分类部署成本低对中文表格支持不错。完整级方案是Unstructured、MinerU这类开源文档解析库能直接把PDF转成带类型的元素列表比如Title、NarrativeText、Table、Image省去自己做版面模型。商业级方案有Azure Document Intelligence、百度文档解析等遇到复杂扫描件识别率稳定但需要考虑数据和成本。我个人的切换建议是如果文档里的表格规整、页面干净优先用PaddleOCR加PP-Structure因为可控性强。如果文档版式复杂比如论文、政务材料、数据报告MinerU这类工具能减少很多体力活。千万不要指望一个工具通吃实际项目里我最后是Unstructured做初分再用PaddleOCR做细粒度表格和印章区域补充识别。2.3 向量化模型怎么选向量化是“图文能否一起被搜索”的关键。用传统纯文本embedding模型去编码OCR文本再加一个视觉embedding模型去编码图片会面临两个向量空间不一致的问题检索时没法直接比相似度。所以我优先推荐使用多模态对齐embedding模型也就是把文本和图像编码到同一个向量空间里的模型。常用的有CLIP、SigLIP、Jina-CLIP以及做了文本增强的E5-V等。下面是我的简单对比模型支持模态典型向量维度特点CLIP ViT-B/32文本、图像512轻量、通用适合快速验证SigLIP文本、图像768-1152对比学习目标改进零样本效果好Jina-CLIP文本、图像768兼顾OCR文本和视觉细节E5-V文本、图像1024面向检索任务训练多模态语义对齐好如果资源充足也可以上ColPali这类基于视觉Transformer的文档检索模型它直接把页面渲染成图并做细粒度Token级相似度计算尤其适合复杂版面。ColPali的缺点是索引体积大、检索延迟高我一般把它作为重排层使用而不是首轮召回的主力。2.4 向量数据库和生成模型怎么配向量数据库我建议从Chroma和Qdrant起步。Chroma部署最简单适合几百页文档级别的DemoQdrant支持payload过滤可以按文档ID、页码、区块类型过滤适合生产环境。如果数据量到千万级再迁移到Milvus。没有必须用哪个关键看过滤能力和集群能力是否满足自己的数据规模。生成端有两种选择。方案A是多模态LLM比如Qwen-VL、GPT-4o、GLM-4V它们可以直接接收检索出来的图片块适合强视觉场景。方案B是纯文本LLM配合结构化文本把图片转成OCR文本加图像描述文本再喂给普通LLM适合不想引入多模态模型的场景。我在第5章会详细展开这两种方案的Prompt差异。3. 图文块切分与向量化索引质量决定问答上限3.1 不要按字符切要按版面切传统RAG喜欢按固定字符窗口切chunk比如512字符或1024字符再加个重叠。这在图文混合文档里是灾难。因为固定窗口会把一个表格拦腰切开把一张图的下半部分和另一段文字拼在一起语义彻底错乱。我的做法是先跑版面分析拿到每个元素的类型、文本内容和bbox再按照视觉逻辑来组装chunk。一个标准的chunk可以包含三种东西第一种是纯文本块比如段落、标题直接保留原文第二种是表格块既要保留表格识别出的Markdown或HTML结构也要把表格渲染图作为视觉块存一份第三种是图片块包含图片本身、图片所在页码、bbox、OCR出的图片内文字描述。每个chunk都记录document_id、page、bbox、modality这些字段后续过滤和溯源都靠它们。举一个例子。一份财报里有一张第四季度的营收柱状图文字正文说“第四季度同比增长9%”但用户可能问“哪个渠道的贡献最大”这个答案只藏在柱状图图例和柱子的高度里。如果不把图表区域单独切成图片块并关联OCR文字检索时就会漏掉它模型只能拿正文硬编。3.2 大图和长表格的独特处理大图直接做全局embedding效果通常很糟糕。我测试过一张由多个分面板组成的实验流程图整张图一个embedding进去query“控制变量组设置”召回时相关分数常常低于0.5因为图像整体语义被稀释了。解决办法是把大图切成子图用版面分析或目标检测识别图内的坐标分区切出大小合适的子图对每个子图单独嵌入。如果原图没有明显分区可以用滑动窗口切出重叠格子但要注意保留格子间的坐标关系。切分后还要建“父子文档关系”。检索命中的是子图生成时需要回传给大模型整张大图或父图否则模型看到的只是局部没法理解上下文。我一般会给每个子图一个parent_image_id检索到子图时返回父图的缩略图和坐标框Prompt里标注“这是下图左上角的局部”这样大模型既能看细节又有全局。长表格的处理逻辑类似。表格本身就是二维信息OCR成纯文本流会丢掉行列对应关系。现在解析工具基本都支持表格结构识别能输出HTML或Markdown。我建议切分时保留表格的Markdown文本作为文本索引同时把整个表格区域渲染成图片做视觉索引。生成阶段优先给模型看Markdown模型对结构化的理解更好如果表格非常复杂、Markdown丢失框线信息再给模型看渲染图。3.3 多模态嵌入的对齐问题紧接着遇到的就是向量对齐问题。如果文本块用bge-m3图片块用CLIP文本和图像chunk之间的相似度没有任何可比性因为两者不在同一向量空间。我把这个问题叫做“双空间陷阱”。解决思路有两种。第一种是选用CLIP类多模态模型文本和图像天然在同一空间检索时可以把query编码为文本向量和图像向量、文本向量统一比较。第二种是“文本化图像”先用Caption模型给每张图生成一段描述然后把描述作为图片chunk的文本索引检索时用纯文本向量库也能同时召回文本和图像代价是丢失视觉细节。我实际项目里是两条腿走路主线用SigLIP做多模态向量化保证图能直接召回同时对每一张图生成一段结构化Caption文本存成辅助字段。检索时先跑SigLIP向量召回再拿Caption文本走一遍BM25补召回。因为图像里如果只有“柱状图”“折线图”这种浅层描述Caption模型会漏掉具体数值必须靠视觉向量才能精确匹配。向量化后的存储结构也要提前设计。我的Collection设计大概是这样每条记录有id、modality、page、bbox、document_id、parent_id、text_content、image_path或image_url、embedding。检索时先按document_id过滤再对embedding做距离查询。有了modality字段就能在后期做带权重的混合检索。这里有一个很容易被忽视的点图像chunk的embedding尺寸可能和文本chunk不一致例如CLIP的图片向量和文本向量维度相同但如果纯文本模型维度不同存储时要注意按同一个模型统一编码。别把所有向量的维度写死在一个常量里未来换模型会非常痛苦。4. 图文联合检索混合召回与视觉重排的平衡4.1 多条召回通道的构建索引建好之后检索策略决定能不能把“对的那张图”捞出来。我常用的首轮召回有四个通道。第一是纯文本向量通道把用户query编码成文本向量去向量库检索所有文本chunk。第二是视觉向量通道如果query含有视觉属性例如“流程图中”“柱状图”“蓝色部分”可以直接用多模态模型把query编码成视觉向量去检索图像chunk。第三是关键词通道用BM25扫描chunk的文本字段把关键词匹配能力保留下来。第四是图文交叉通道把query和图片的Caption文本拼接后做向量检索用来捕捉Caption里隐含的语义。实际工程里并不是所有query都需要走四个通道。用户问“合同的签约日期是多少”这是纯文本检索用户问“第三页的流程图里有没有异常分支”这是视觉主导用户问“对比一下两个年度的营收结构”这既需要表格数据也需要图。所以我通常先让一个小的路由模型对query做意图判断判断它属于“文本型”“视觉型”还是“图文混合型”再决定召回通道的权重。第一次搭建时不建议直接上路由模型可以先用规则query中出现“图、表、流程、截图、第几页”就用视觉通道加权。4.2 合并与去重为什么不能简单取并集四个通道召回的结果会有大量重叠直接取并集会塞给重排层一堆冗余内容。我的做法是先按“document_id page chunk_id”做去重然后应用RRFReciprocal Rank Fusion合并多个榜单。RRF的核心思想是让在不同通道里排名都比较靠前的chunk获得更高总分而不是只看某一通道的相似度分。因为不同向量库、BM25的分数尺度不一样直接加权相加没有意义RRF用名次替代分数能避开标定权重的问题。实现RRF很简单给每个chunk在每个召回列表中一个名次r贡献分数是1/(k r)k通常取60。然后把所有通道贡献分数相加取Top-K。这个合并方法在实战中非常稳比手动调“向量0.7加BM25 0.3”要省心得多。我上线时对比过RRF的召回稳定性明显优于简单的分数融合。4.3 重排阶段视觉模型的二次确认首轮召回回来后往往还有不少“看起来相关但不精准”的chunk。这时候要上重排。如果是纯文本场景cross-encoder模型已经足够但在多模态文档里重排需要同时考虑文本相关性和视觉相关性。我的做法是二段式重排。第一段用文本cross-encoder给候选chunk的文本内容打分先淘汰掉文本完全不相关的。第二段对剩下来的图像chunk把query转成视觉向量和图像region embedding算相似度或者直接用一个小型多模态模型例如Qwen2.5-VL小参数版本对“用户问题图片”做一个相关性打分。注意这一步不能拿生产级的7B大模型跑所有候选太慢了。最合适的办法是用视觉embedding做过滤再把Top2的图喂给多模态小模型做精细判断。重排层还有一个人工兜底技巧调整检索结果中modality的多样性。比如用户明确问图表但Top-K全是文本块说明视觉召回权重太低。我会强制在最终进入生成的Top-K中至少保留1到2个图像chunk。这看起来粗暴但在人工评测时能避免系统“只会答文字、不答图表”的偏科问题。4.4 检索权重的调优实验权重调优最忌拍脑袋。我建议建一个小型评测集至少包含20个图文混合问答对人工标注每个问题对应的答案来源modality比如“来自表格”“来自流程图”“来自正文”。然后跑不同权重组合记录两个指标Recall10和MRR。我自己跑下来的初始推荐权重是文本向量0.4、视觉向量0.35、BM25 0.15、图文交叉通道0.1。但这不是固定值遇到图表密集的文档会把视觉向量提到0.5遇到扫描版合同BM25权重会下降因为OCR噪声太多关键词匹配价值有限。还有一种非常有用的调参方法是“负样本开关”。如果某一段时间用户问“图里的数据是多少”但模型总是答不到点上就去排查是图像chunk压根没召回还是召回了但排在了第20名如果是后者说明视觉通道权重偏低如果压根没召回那问题不在权重在索引切分和向量化质量。我见过太多人在权重上调来调去最后发现是切分的时候把图片和标题分离了。5. 生成端材料组织把检索结果变成LLM能用的“证据链”5.1 两种生成模式对比检索回来的证据如果不好好组织再好的模型也会混乱。先看生成端的两种模式。模式A是多模态LLM直接看图适合要回答涉及颜色、形状、空间关系的问题例如“流程图中哪个节点标红了”“两个柱状图里哪个更高”。模式B是纯文本LLM看结构化文本适合表格数字、关键条款这类重精度、轻视觉的问题例如“合同签字的日期是什么”“哪个产品线收入最高”。两者不是互斥关系我在成熟系统里会保留两个分支根据路由结果选择。能力需求模式A多模态LLM模式B纯文本LLM空间布局理解强弱精确数值计算中强如果表格结构化好上下文成本高图片Token贵低引溯源需额外结构化容易适合场景流程图、照片、票据盖章财报表格、合同条款5.2 Prompt模板把证据块排好顺序无论哪种模式Prompt里都要把证据块“编号化”。我踩过的坑是直接把检索结果拼在一起没有告知模型哪些是文本、哪些是图片、哪条来自第几页。结果模型会认为所有内容都是正文回答时张冠李戴。我现在的Prompt模板大致长这样你是一个文档问答助手。下面是从用户文档中检索到的若干证据块其中包含文本块和图像块。 请严格基于证据块回答。每个证据块开头有id、来源页码和类型引用时请用[id]。 如果证据块不足请直接回答“当前文档中没有找到对应信息”不要猜测。 证据块列表 [id1][page3][typetext] 第三季度营收为9.2亿元同比增长12%。 [id2][page4][typeimage] 图片内容三季度营收柱状图包含产品A、产品B、产品C三个系列的柱体。 图内OCR文字产品A 4.1亿产品B 3.0亿产品C 2.1亿。 [id3][page4][typetable] | 产品线 | 收入(亿元) | 同比 | | A | 4.1 | 15% | | B | 3.0 | 8% | 用户问题三季度哪个产品线增长最快 请结合证据块回答问题并列出你依据的证据编号。有了这个结构LLM就能分辨“文本只是导语具体数值要去看表格”这类关系。实际操作中还要再加一条指令图像证据只用文字描述不能还原数值时允许模型结合OCR文本计算但要在回答中说明“该数据来自图片OCR”。这能大幅降低幻觉。5.3 引溯源与“不知道”兜底图文混合问答的引用溯源也是用户体验核心。用户问“数据从哪来”答案要精确到第几页第几行。这要求检索层返回的chunk自带page和bbox生成时Prompt强制输出来源。我一般让模型用这种格式回答“根据第4页的表格(id3)第三季度增长最快的是产品A同比增长15%。”同时把证据块编号带出来。如果模型没有引用任何证据可以返回“该问题在文档中找不到依据”。兜底指令一定要写狠。多模态文档里特别容易出现“模型凭着图片caption里的只言片语就编数据”的情况。我在系统里加入了一个“置信开关”如果返回的答案中引用的证据块数量为0服务端自动拦截改成“未找到相关信息”。这个方法虽然简单但能把幻觉率降低一大截。后续如果要做LLM Agent也可以在这一层加上工具调用让模型决定是继续检索还是终止回答。6. 手搓一个最小多模态RAG系统关键代码逐段说明6.1 环境安装与数据准备下面是一个能跑通流程的最小实现我用的是Python和开源组件PaddleOCR做OCR和版面分析、open-clip-torch做多模态向量化、Chroma做向量库、Qwen-VL或OpenAI兼容接口做生成。先安装依赖。pip install paddleocr paddlepaddle open-clip-torch chromadb sentence-transformers PyMuPDF准备一份demo.pdf最好是包含一页正文、一页表格、一页流程图的文档。没有的话可以手动用PPT导出PDF来做测试。文档解析阶段我用PyMuPDF把PDF渲染成图像再用PaddleOCR的PP-Structure做版面分析。6.2 文档解析与chunk生成下面是解析PDF并生成图文chunk的最小代码。import fitz from paddleocr import PPStructure import numpy as np doc fitz.open(demo.pdf) ocr_engine PPStructure(show_logFalse, langzh) chunks [] for page_idx, page in enumerate(doc, start1): pix page.get_pixmap(dpi150) img np.frombuffer(pix.samples, dtypenp.uint8).reshape(pix.height, pix.width, pix.n) result ocr_engine(img) for line in result: block_type line[type] # text, table, figure, title bbox line[bbox] if block_type in (text, title): chunk { id: fp{page_idx}-{block_type}-{len(chunks)}, modality: text, page: page_idx, bbox: bbox, text: line[res][text], } chunks.append(chunk) elif block_type table: html line[res][html] chunk { id: fp{page_idx}-table-{len(chunks)}, modality: table, page: page_idx, bbox: bbox, text: html_to_markdown(html), # 自己实现HTML到Markdown转换 } chunks.append(chunk) elif block_type figure: # 保存图片并OCR图中文字 img_path foutput/figure_p{page_idx}_{len(chunks)}.jpg # 这里用bbox裁剪原图后保存 ocr_text run_ocr_on_cropped_image(img, bbox) chunk { id: fp{page_idx}-fig-{len(chunks)}, modality: image, page: page_idx, bbox: bbox, image_path: img_path, ocr_text: ocr_text, } chunks.append(chunk)代码里最关键的一步是给每个chunk打modality标签和page/bbox元数据。后续检索时看到image类型就知道要用视觉向量去匹配。6.3 图文向量化与检索向量化阶段我用open-clip把文本块、表格Markdown文本、图片统一编码到同一个向量空间。import open_clip import torch from chromadb import Client model, _, preprocess open_clip.create_model_and_transforms(ViT-B-32, pretrainedlaion2b_s34b_b79k) tokenizer open_clip.get_tokenizer(ViT-B-32) def embed_text(text): tokens tokenizer([text]) with torch.no_grad(): return model.encode_text(tokens).squeeze(0).numpy() def embed_image(image_path): from PIL import Image img preprocess(Image.open(image_path)).unsqueeze(0) with torch.no_grad(): return model.encode_image(img).squeeze(0).numpy() client Client() collection client.get_or_create_collection(multimodal_rag) for c in chunks: if c[modality] image: emb embed_image(c[image_path]) text_rep c[ocr_text] else: emb embed_text(c[text]) text_rep c[text] collection.add( ids[c[id]], embeddings[emb.tolist()], documents[text_rep], metadatas[{page: c[page], modality: c[modality], bbox: c[bbox]}] )检索时用户query先走文本向量检索再加一个可选视觉通道。为了简化这里只展示向量检索合并RRF。def search(query, k10): q_vec embed_text(query) res collection.query(query_embeddings[q_vec.tolist()], n_resultsk) return list(zip(res[ids][0], res[distances][0], res[metadatas][0]))生成层调用多模态模型的接口把检索到的图像和文本块按第5章的Prompt模板拼好。如果检索到的chunk里含图片字段就传给多模态模型的image_url参数如果只用纯文本LLM就先把图片转成“图片描述OCR文本”的文本块。6.4 跑通后的最小验证拿一个问题“第二页的表格中方案A的预算占比是多少”跑一遍。解析层会把表格转成Markdown检索层会召回该表格块生成层会给出“方案A预算占比约为28%依据第2页表格(id...)”的回答。如果文档图片中的表格识别不出来说明PP-Structure表格还原参数要调或者考虑换成商业文档解析API。这一步跑通后再往系统加父图关联、RRF融合、重排模型就非常顺手了。7. 实测中的翻车现场与补救经验7.1 表格识别乱序OCR拿到的不是“表格”是“文本流”我第一次拿复杂合同做测试时PP-Structure把表格识别成了一大段文本行顺序完全是乱的。排查发现表格线太浅导致单元格合并检测失败模型把整行拼成一串。补救措施有两条一是对扫描件先做图像预处理增强对比度和边缘二是放弃纯OCR结果改用表格结构识别模型输出HTML后再解析。后来的经验是表格块永远不要用普通OCR文本做索引一定要走“表格结构还原”流程否则数值问答一定翻车。7.2 大图切碎后向量之间“各说各话”有一张产品架构图包含网络层、服务层、数据层三个大区域我按滑动窗口切成九宫格之后每个格子的向量互相独立。用户问“数据层包含哪些服务”它召回的是数据层区域那块但同样检索“整个架构图的顶层设计”得分就不高。原因在于子图坐标完全丢失了上下文依赖。最后我建了父子图索引每个子图不仅存自身向量还记录父图ID、父图整体向量以及子图之间的相对位置。检索时一旦子图命中就把父图一并带入生成效果立刻好起来。7.3 生成端“没看见图还硬答”Prompt兜底太重要这是我最常遇到的问题。即便是多模态模型当上下文里图片太多时也会偷懒只看文本描述然后编造一个表格里根本没有的数字。我加了两层防护。第一层是在Prompt开头写死“只能依据证据块中的内容回答禁止根据常识补全”。第二层是在服务端做“证据检查”答案生成后解析其中的引用编号如果引用编号为空就强制要求模型重新回答或者返回“未找到”。严格跑下来这种做法比换更大的模型更省成本。7.4 性能与增量维护别等文档多了再去拆索引一套多模态RAG跑在真实业务里性能问题比算法问题更早暴露。OCR和视觉embedding都比较重实测单页扫描件从解析到向量化大约需要1到3秒所以必须做页面级缓存。文档更新时不要整库重嵌而是按document_id增量删除旧chunk再重新解析更新页。Chroma、Qdrant都支持基于metadata的删除这是多模态RAG落地的硬需求。另外视觉向量占存储很大如果文档量大建议把图片存储和向量存储分离向量库里只保存向量的ID和元数据避免每次检索都拉一遍图片二进制。我自己把整套流程在财报问答场景里跑了一个月最大的感触不是模型选得多牛而是解析和切分的工程细节决定了问答上限。如果你也在做类似的图文混合文档智能问答建议从第三、四章入手先把索引质量打磨好模型反而是最后要考虑的变量。等整套链路稳定了再往多轮对话、Agent工具调用、自动意图路由这些方向延伸会顺畅得多。