1. 项目缘起:当LLM遇上“多模态”与“知识库”
最近在折腾AI应用开发的朋友,估计没少被“多模态”和“Agent”这两个词刷屏。大模型本身已经很强了,但让它“看”懂图片、“听”懂声音,再结合一个庞大的知识库(比如Wiki)来回答问题,这个组合拳打出来,才是真正能落地的生产力工具。我手头正好有个项目,姑且称之为“多模态LLM Wiki Skill”,核心目标就是打造一个能理解图片、文本,并能从结构化知识库中精准检索信息的智能体(Skill)。
这听起来像是把Claude、GPT-4V的能力和本地知识库缝合起来,但实际做起来,远不是调个API那么简单。你会遇到一连串问题:多模态信息怎么统一“喂”给模型?海量Wiki文档如何高效检索,而不是让LLM去“硬背”?不同的LLM提供商(OpenAI、Anthropic、开源模型)接口各异,如何设计一个通用的处理流程?更别提整个链路的速度、成本控制和错误处理了。市面上有LangChain、Dify这类框架,它们提供了拼图,但如何根据你的具体场景(比如内部知识库问答、客服机器人、内容审核)选出最合适的组件并搭建成稳定可用的Skill,才是真正的挑战。
我花了相当一段时间,从模型选型、知识库构建、流程编排到前后端集成,踩了不少坑,也总结出一套相对可行的架构和实操细节。这篇文章,我就把这个“多模态LLM Wiki Skill”从想法到实现的完整过程拆解给你看,重点不是罗列代码,而是分享在技术选型、架构设计以及实际部署中那些文档里不会写的“为什么”和“怎么办”。
2. 核心架构设计:拆解“多模态”与“知识库”的协同流水线
一个健壮的“多模态LLM Wiki Skill”不能是一个黑盒,它应该是一条清晰、可观测、可调试的流水线。直接上最终我们采用的架构图可能太抽象,我先从最核心的三个问题出发,带你理解每个环节的设计考量。
2.1 多模态输入的“统一表示”问题
用户可能上传一张产品截图,附带一句“这个零件在哪份手册里?”,或者直接丢来一份包含图表和文字的PDF。第一步,也是最大的难点,是如何将这些异构信息转化为LLM能够一致理解的“语言”。
方案选择与原因: 我们放弃了早期尝试的“分别处理再拼接”方案(例如,用CV模型描述图片生成文本,再和用户文本拼接)。这种方案信息损耗严重,一张复杂架构图,CV模型生成的描述可能丢失关键细节。最终,我们选择了依赖原生支持多模态的LLM API,如GPT-4V、Claude 3(Opus/Sonnet)的视觉能力,或开源的LLaVA、Qwen-VL系列。这些模型在训练时就将图像像素和文本token在同一个表示空间中对齐,理解能力有质的飞跃。
具体实现流程:
- 输入网关:接收用户请求,解析其中的多媒体内容。如果是文件(图片、PDF),则进行预处理。对于PDF,使用
PyMuPDF或pdfplumber提取文字和图片,将每一页或每个图片单元视为一个独立的多模态信息块。 - 格式标准化:将图片转换为Base64编码的字符串,或直接提供可访问的URL(对于云存储)。文本部分保持原样。构造符合目标LLM API要求的消息格式。例如,对于OpenAI的GPT-4V,消息列表(messages)中,一个内容块(content)可以是文本和图片的混合数组。
# 示例:构造GPT-4V API请求的消息体 messages = [ { "role": "user", "content": [ {"type": "text", "text": "请根据这张图表和下面的说明,回答我的问题。"}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}}, {"type": "text", "text": "说明:该图表展示了系统架构...\n我的问题是:组件A的输出流向哪里?"} ] } ] - 上下文管理:多模态内容通常很“胖”,一张高清图编码后token数可能上千。必须谨慎管理上下文窗口。我们的策略是:在输入网关层就对图片进行有损压缩(如调整分辨率至模型推荐尺寸,如1024x1024),并在系统提示(System Prompt)中明确要求模型优先关注与问题相关的视觉元素。
注意:不同模型的多模态输入格式和支持能力天差地别。Claude 3对图片数量、格式有更严格限制,而一些开源模型可能需要离线预处理将图片转为特征向量。选型前务必仔细阅读最新文档。
2.2 知识库(Wiki)的“高效检索”问题
让LLM直接“阅读”成千上万的Wiki页面来回答问题,既不现实(上下文长度限制、成本极高),也不可靠(模型会胡编乱造)。因此,检索增强生成(RAG)是必由之路。但传统的文本RAG遇到多模态查询就抓瞎了。
我们的混合检索方案:
- 文本向量库(核心):使用
Sentence Transformers(如all-MiniLM-L6-v2)或OpenAI的text-embedding-3系列模型,将Wiki页面的纯文本内容(经过清洗和分块)转化为向量,存入ChromaDB或Qdrant这类向量数据库。这是处理纯文本问题的主力。 - 多模态向量库(增强):对于Wiki中包含大量图片、图表、截图的页面,仅用文本描述其内容是不够的。我们额外使用多模态嵌入模型,如OpenAI的
CLIP或Salesforce的BLIP,将图片和对应的上下文文本一起编码成向量。这样,当用户上传一张图片并问“类似这个界面的功能在哪篇文档?”时,系统可以通过多模态向量进行相似性检索,找到包含类似视觉元素的Wiki页面。 - 检索器与重排序:
- 并行检索:用户查询到达后,同时使用文本嵌入模型对查询文本进行向量化,并使用多模态嵌入模型对查询中的图片(如果有)进行向量化。然后并行地在文本向量库和多模态向量库中检索最相关的片段(Top-K)。
- 结果融合与重排序:将两组检索结果合并。这里简单的分数平均可能不行,因为文本和图片检索的分数尺度不同。我们采用加权分数或学习排序(Learning to Rank)的轻量级方法。例如,如果查询明显是视觉导向的(如“这个错误弹窗什么意思?”),则给多模态检索结果更高的权重。最后,可以使用一个交叉编码器(Cross-Encoder,如
ms-marco-MiniLM-L-6-v2)对融合后的候选片段进行精排,选出与查询最相关的几个片段作为上下文。
2.3 智能体(Skill)的“流程编排”问题
有了多模态理解和知识检索,还需要一个“大脑”来协调整个流程:何时检索?检索到什么程度?如何组织回答?这就是Skill或Agent的工作流。
基于LangGraph的有限状态机设计: 我们放弃了简单的线性链(LangChain Expression Language),因为实际交互中需要条件判断和循环。例如,初次检索结果不理想时,需要让模型自我判断并重新生成检索查询。我们使用LangGraph来构建一个清晰的工作流。
节点设计:
route_query:路由节点。分析用户输入,判断意图是“纯知识问答”、“需视觉理解”还是“流程操作”。根据意图决定下一步是调用retrieve还是直接进入generate。retrieve:检索节点。调用上一节描述的混合检索流程,从知识库获取相关上下文。generate:生成节点。将用户问题、检索到的上下文、历史对话(如果有)整合,构造最终提示词,调用选定的多模态LLM生成回答。grade_documents:评估节点。一个关键但常被忽略的环节。让LLM(通常用小模型,如GPT-3.5-Turbo)评估检索到的文档是否真正回答了问题。如果评估结果“不相关”,则流转到rewrite_query节点。rewrite_query:改写节点。让LLM根据对话历史和之前不理想的检索结果,重新构思一个更精准的检索查询,然后跳回retrieve节点。
边与流转:通过条件判断(
conditional_edge)连接节点。例如,从grade_documents出来,如果评估为“相关”,则流向generate;如果“不相关”,则流向rewrite_query。这样就形成了一个带反馈循环的、能自我优化的检索-生成流程。
这个架构的优势在于可观测性和可调试性。每个节点的输入输出都可以被记录和检查,当回答不准时,你可以快速定位是检索出了问题,还是生成提示词没写好,或者是路由判断错误。
3. 关键技术选型与踩坑实录
架构清晰了,具体用什么工具来实现?这里没有银弹,只有权衡。我分享下我们的选型逻辑和遇到的真实问题。
3.1 多模态LLM:云端巨头 vs. 本地开源
这是最大的成本和技术决策点。
云端API(GPT-4V, Claude 3):
- 优点:效果最好,开箱即用,无需担心算力。特别是对于复杂图表、流程图的理解,目前开源模型仍有差距。API的并发、速率限制管理相对省心。
- 缺点:成本和数据隐私。多模态调用比纯文本贵一个数量级。图片按token计费,高分辨率图片成本飙升。敏感的企业Wiki内容传出外部API存在合规风险。
- 踩坑点:
429 Too Many Requests错误是常客。必须实现健壮的退避重试机制和请求队列。我们用了tenacity库进行指数退避重试,并为不同优先级的请求设计了队列。另外,Claude API对消息格式和图片大小有严格限制,预处理代码需要特别适配。
本地开源模型(LLaVA, Qwen-VL-Chat):
- 优点:数据完全私有,长期成本可能更低,可定制化微调。
- 缺点:硬件门槛高(需要GPU,且显存要求大),推理速度慢,效果可能不稳定。部署和优化(如使用vLLM、TGI加速)需要深厚的工程能力。
- 我们的折中方案:对于内部测试、非实时或对效果要求稍低的场景,使用量化后的Qwen-VL模型在A10/A100上部署。对于面向客户、要求高准确性的核心场景,仍然使用GPT-4V。同时,我们密切关注MoE(混合专家)架构的开源多模态模型,如最新的
DeepSeek-VL,它们在效果和效率的平衡上展现了潜力。
3.2 知识库构建:从原始Wiki到向量存储
你的Wiki可能是Confluence、飞书Wiki、甚至是一堆Markdown文件。如何把它们变成RAG可用的知识库?
数据抓取与清洗:
- 工具:对于Confluence/飞书,使用官方API或
confluence2md这类工具。避免简单爬虫,容易触发风控。 - 清洗:去除HTML/Markdown标签、导航栏、页眉页脚等无关内容。但要保留图片的alt文本和链接,这是多模态检索的关键元数据。我们写了一系列正则和基于
BeautifulSoup的规则来处理。 - 分块(Chunking):这是RAG效果的决定性因素之一。不要简单按固定字符数切分。
- 策略:采用递归式分块,优先按标题(
#,##)分割,再按段落、列表分割。确保每个块语义相对完整。 - 重叠:块与块之间设置10%-20%的重叠,防止答案被切碎。
- 特殊内容:对于表格,我们使用
tabulate库将其转换为结构化的文本描述(如“下表展示了...第一列是...第二列是...”),并作为独立块。代码块也单独处理。
- 策略:采用递归式分块,优先按标题(
- 工具:对于Confluence/飞书,使用官方API或
向量化模型选择:
- 文本嵌入:初期使用
all-MiniLM-L6-v2,平衡速度和效果。后期对中文Wiki内容,切换到了BAAI/bge-large-zh-v1.5,检索精度有明显提升。关键点:嵌入模型必须与检索时的查询嵌入模型保持一致。 - 多模态嵌入:实验了OpenAI的
CLIP(通过transformers库)和BLIP-2。CLIP更通用稳定,BLIP-2生成的描述更自然但更慢。我们最终选择CLIP-ViT-B-32,因为它有成熟的社区支持,且能将图片和文本映射到同一空间,方便做图文互搜。
- 文本嵌入:初期使用
向量数据库:我们对比了
ChromaDB(轻量、简单)、Qdrant(性能强、功能多)和Weaviate(自带向量化模块)。对于中等规模(百万级向量)的Wiki,ChromaDB的持久化模式完全够用,且集成到LangChain极其简单。如果追求分布式和高性能,Qdrant是更好的选择。
3.3 编排框架:LangChain/LangGraph vs. 自研
为什么选择LangGraph而不是裸调用API或自研框架?
- LangChain/LangGraph的优势:它提供了大量现成的组件(文档加载器、文本分割器、检索器、各种工具集成),能极大减少样板代码。
LangGraph的状态图模型非常直观地描述了Agent的工作流,调试工具(如LangSmith)能可视化整个执行轨迹,这对排查复杂问题至关重要。 - 遇到的麻烦:LangChain版本迭代快,有时会有Breaking Changes。某些高级定制需求需要深入理解其抽象层,可能会觉得“臃肿”。我们的策略是“轻量使用,核心逻辑自控”。我们主要用它的
Document Loaders、Text Splitters和LangGraph的图定义能力。对于检索链、提示词模板等核心逻辑,我们倾向于自己编写,以获得更精细的控制和更好的性能。 - 关于Dify、FastAPI等:Dify是一个优秀的低代码AI应用平台,它的Workflow可视化编辑器很棒。但对于我们这个深度定制、需要复杂混合检索和多模态路由的项目,Dify的抽象层有时会限制手脚。我们最终用
FastAPI构建了核心的API服务,内部调用基于LangGraph编排的AI工作流,这样前后端分离更清晰,也便于独立扩展。
4. 实战部署与性能优化
把原型跑通只是第一步,要让Skill真正可用,必须过部署和性能这一关。
4.1 异步处理与流式响应
用户上传一张大图,进行多模态理解+知识检索+生成,整个链路可能耗时10秒以上。让用户干等是不可接受的。
- 异步化:我们将整个Skill pipeline设计为完全异步。使用
FastAPI的async/await,以及celery或dramatiq处理后台任务。用户请求提交后,立即返回一个任务ID,前端通过WebSocket或轮询获取进度和结果。 - 流式响应(SSE):对于最终的文字生成部分,我们启用LLM API的流式输出(如OpenAI的
stream=True),并通过Server-Sent Events (SSE) 推送到前端。这让用户能实时看到答案一个字一个字出现,体验提升巨大。对于Claude等也支持流式响应的模型,同理。
4.2 缓存与成本控制
多模态调用昂贵,重复问题频繁检索知识库也无必要。
- 语义缓存:我们引入了
GPTCache。它的原理是将用户查询和检索到的文档片段进行向量化,并在缓存中查找语义相似的过往查询。如果找到,且相似度超过阈值(如0.9),则直接返回缓存的结果,跳过LLM调用和检索。这对常见问题(如“公司年假政策是什么?”)效果极好,能降低超过40%的API调用。 - 分级存储与检索:不是所有查询都需要动用多模态LLM和混合检索。我们在路由节点(
route_query)做了更细的分流:- 简单、事实型问题(如“XX项目的负责人是谁?”):直接走传统的文本RAG,使用更便宜的文本嵌入模型和小型文本生成模型(如
gpt-3.5-turbo)。 - 需要视觉理解的问题:才进入完整的多模态混合检索+大模型流程。
- 这样可以有效平衡效果和成本。
- 简单、事实型问题(如“XX项目的负责人是谁?”):直接走传统的文本RAG,使用更便宜的文本嵌入模型和小型文本生成模型(如
4.3 监控、评估与持续迭代
没有监控的AI系统就是黑盒。
- 链路追踪:我们集成了
LangSmith,它自动记录每个LangChain/LangGraph组件的输入输出、耗时和token使用量。当用户反馈答案错误时,我们可以根据trace_id快速复现整个决策过程,看是哪个环节出了问题。 - 评估指标:
- 检索相关度:定期抽样查询,人工或用小模型(
gpt-4o-mini)评估检索到的文档是否相关。 - 生成质量:使用
RAGAS等框架自动评估答案的忠实度(是否基于检索内容)、答案相关度和完整性。 - 延迟与成本:监控每个请求的端到端延迟、各阶段耗时、以及API调用成本(折算成token或金额)。
- 检索相关度:定期抽样查询,人工或用小模型(
- 知识库更新:Wiki是活的。我们建立了两种更新机制:
- 定时全量重建:每周日凌晨,自动触发知识库的完整抓取、清洗、向量化重建流程。
- 增量更新:监听Wiki平台的Webhook(如飞书Wiki的更新事件),一旦有页面变更,立即触发该页面的重新处理和向量更新。这需要向量数据库支持按ID更新或删除旧向量。
5. 避坑指南:那些我们曾掉进去的“坑”
最后,分享几个让我们头疼不已的典型问题,希望你能绕开。
5.1 多模态LLM的“幻觉”与提示词工程
即使提供了图片和检索到的文档,多模态LLM依然会“睁眼说瞎话”,编造文档中不存在的内容。
- 根因:提示词没有强制模型“严格依据”。模型倾向于综合所有知识(包括其内部训练知识)来生成流畅答案。
- 解决方案:设计强约束的系统提示词。例如:
“你是一个严谨的助手,必须严格依据用户提供的‘参考文档’和‘图片信息’来回答问题。参考文档是你能使用的唯一文本信息来源,图片信息是你能使用的唯一视觉信息来源。如果答案无法从这些提供的信息中100%确定,你必须明确回答‘根据提供的信息,无法确定该问题的答案’,并指出信息缺失在哪里。绝对不允许编造、推断或使用外部知识。”
- 在消息中,将检索到的文档明确标记为“参考文档”,将图片描述明确标记为“图片信息”,与用户问题清晰分隔。
- 采用思维链(CoT)要求:在最终答案前,让模型先输出它的推理过程,例如“步骤1:从参考文档第X段,我找到...;步骤2:从图片中,我看到...;因此,答案是...”。这不仅能提高答案准确性,也便于我们调试。
5.2 混合检索中的“权重失衡”
文本检索和多模态检索的结果分数如何融合?初期我们简单按检索来源平均,导致视觉查询被大量文本片段淹没。
- 解决方案:实现基于查询类型的动态权重。
- 在路由节点,用一个轻量级文本分类模型(或基于规则的关键词匹配)判断查询的“视觉强度”。例如,包含“图片”、“截图”、“图表”、“红色框”等词,或检测到上传了图片,则判定为高视觉强度。
- 根据视觉强度,为多模态检索结果的分数赋予一个乘数因子(如0.7到1.5)。视觉强度高,则提高多模态检索结果的权重。
- 最终合并排序时,使用加权后的分数。
5.3 长上下文与Token消耗的博弈
为了回答一个复杂问题,我们可能检索出5个长文档片段,加上高清图片,上下文轻松超过8000token。这不仅成本高,而且模型对远处信息的关注度会下降。
- 策略:
- 摘要式检索:在存入向量库前,先让一个小模型(如
gpt-3.5-turbo-16k)对长文档块生成一个简洁的摘要(100-200字),同时存储摘要向量和原文块。检索时,先用摘要向量进行初筛,召回相关文档后,再将对应的原文块(而非摘要)送入最终生成阶段。这大大减少了检索阶段的噪声和计算量。 - 渐进式细化:对于极其复杂的问题,采用多轮对话式检索。第一轮先用一个宽泛的查询检索出相关主题的文档;根据模型的初步回答或用户的进一步提问,在第二轮生成一个更精准的查询,进行深度检索。这模仿了人类研究问题的过程,比一次性塞入所有上下文更有效。
- 摘要式检索:在存入向量库前,先让一个小模型(如
构建一个“多模态LLM Wiki Skill”是一个典型的系统工程,它考验的不仅是对某项技术的掌握,更是对问题拆解、架构权衡和持续迭代的综合能力。从效果惊艳的原型,到稳定、高效、可控的生产级服务,中间有很长的路要走。希望我们的这些实践和踩坑经验,能为你点亮几盏路灯。这条路还在快速演进,新的模型、新的框架层出不穷,但把握住“多模态理解”、“精准检索”、“流程编排”和“可观测迭代”这几个核心环节,你就能搭建出适应自己业务需求的智能知识助手。