基于Langchain多智能体的数据检索与可视化系统实战

基于Langchain多智能体的数据检索与可视化系统实战 简介本资源是一个基于LangChain框架构建的多智能体数据检索与可视化系统实现方案面向Python中级开发者、AI工程实践者及数据分析方向学习者解决多源异构数据协同检索、自然语言交互式查询与动态可视化呈现的一体化技术落地问题。压缩包共14个文件含4个核心Python脚本如main.py主流程、graph.py图谱构建、display.py可视化渲染、8张系统界面与架构示意图PNG以及requirements.txt依赖清单和README.md使用说明整体大小5.68MB结构清晰、开箱即用。已有58人下载学习可直接运行并快速理解多智能体分工协作机制、LangChain链式调用设计、Streamlit动态交互图表集成等关键技术点配套图像涵盖系统架构图、检索流程图、可视化效果截图及模块关系图便于对照代码理解整体设计逻辑与实现细节。 很多做数据的朋友问过我同一个问题数据报表工具那么多为什么还要自己搭一套基于Langchain多智能体的检索与可视化系统我的回答通常是——因为市面上的工具解决不了“我问一句它给我一张图”这件事尤其是当业务方的问题横跨多张表、多种口径甚至问题本身都带着歧义时。这套系统的核心是把“理解问题、写SQL、查数据、画图表”拆成多个Agent协作完成而不是让一个Agent包打天下。这篇文章会完整复盘我如何用Langchain搭建多智能体数据检索与可视化系统包括架构设计、核心代码思路、部署细节以及我在实际使用中踩过的坑。适合正在做数据问答、指标平台、报表自动化的开发者参考也适合想搞懂多智能体到底怎么落地的朋友。1. 为什么“查数出图”这件事单Agent搞不定1.1 单Agent方案的三个典型失败场景我最早做的版本非常朴素一个Agent把数据库Schema、图表规范、业务口径全部塞进同一个Prompt让它一次完成“理解问题-生成SQL-查询-返回图表”。表面上看很省事实际跑起来却频繁翻车而且翻车的模式很有规律。第一个典型问题是角色冲突。同一个模型在同一段上下文里既要扮演“严谨的数据工程师”去推断SQL逻辑又要扮演“懂审美的可视化设计师”去选择图表类型。这两个角色对语言的敏感度完全不同。要求它严格时它连“退货率”这种业务指标都不敢推断要求它灵活时它在SQL里开始自作主张地加减字段。最后的结果是SQL生成质量被图表的开放要求拖累两边都做不好。第二个问题是上下文膨胀。系统Prompt里塞了十几张表的建表语句、五六个业务口径、三四条图表规范每次请求的Token消耗非常惊人。更麻烦的是对话一旦多轮历史消息和系统提示词混在一起模型很容易遗忘早期的约束条件回答开始前言不搭后语。第三个问题是最致命的——没有兜底机制。单Agent生成的SQL如果错了没有任何环节能拦截直接送进数据库执行。运气好时返回一个报错运气不好时返回一个看似正常、实际口径完全错误的数而业务方往往发现不了。1.2 一次实测对比单Agent vs 多Agent我拿同一批测试问题做过对比一共50条覆盖“趋势查询、对比查询、占比查询、明细查询”四类场景。单Agent方案的SQL生成正确率在58%左右而且一旦涉及多表关联正确率直线下降到40%以下。后来拆成多Agent由意图路由、SQL生成、审查、可视化四个角色分工协作同样的50条问题SQL首轮正确率提升到76%加入审查反馈重写后最终正确率稳定在88%左右。这个提升并不神秘。多智能体的本质不是用了更聪明的模型而是把复杂任务拆成了多个可以分别校验的简单环节。每个Agent的Prompt更短、目标更单一模型不需要在同一个上下文里切换角色输出自然更稳定。而且审查Agent会把“挑错”这个动作显式建出来让错误在进入数据库之前就被拦截一轮。2. 系统架构四个Agent如何分工协作2.1 项目目录与角色划分我的工程结构比较清晰项目名叫langchain-mas-visual目录如下langchain-mas-visual/ ├── app.py # Flask入口 ├── agents/ │ ├── router_agent.py # 意图路由Agent │ ├── query_agent.py # SQL生成与执行Agent │ ├── review_agent.py # 审查Agent │ └── visual_agent.py # 可视化Agent ├── core/ │ ├── llm_factory.py # LLM实例管理 │ ├── schema_loader.py # 元数据加载与裁剪 │ ├── memory.py # 会话记忆 │ └── data_pipeline.py # 查询结果统一封装 ├── templates/ │ └── index.html # 前端页面 └── static/ ├── echarts.min.js └── app.js四个Agent的职责非常明确。Router Agent负责判断用户问题类型是数据查询还是普通闲聊顺便提取时间范围、地区等关键维度Query Agent负责根据Router传过来的结构化意图结合Schema生成SQL并执行查询Review Agent是质检员用独立的视角检查SQL或查询结果是否有问题发现问题就附上修改建议丢回给Query AgentVisual Agent负责分析查询结果的数据特征生成图表配置。2.2 协作流程不止是链式调用很多文章讲到多智能体就喜欢画一张“A做完给BB做完给C”的串行图。我的实践是这套系统虽然整体是串行主线但中间存在一个非常重要的反馈回路也就是Query Agent和Review Agent之间的“正反博弈”。以用户问“华东区上个月退货率走势”为例整个过程是这样的Router Agent先判断这是一个数据查询请求并输出结构化参数区域华东、时间上月、指标退货率、意图趋势。Query Agent拿到参数后从Schema里只加载相关表生成一条SQL例如SELECT month, return_rate FROM order_summary WHERE region华东 AND month ...。SQL不会立刻执行而是先交给Review Agent。Review Agent会从业务口径审查退货率的口径是什么分子分母有没有算反时间条件是否合理如果发现SQL里退货率字段是比率值但查询结果需要的是分子分母两个字段时Review Agent会给出意见例如“退货率需要单独计算请改为同时查询退货订单数和总订单数”。Query Agent根据意见重写SQL再次交给Review。除非双方达成一致否则这个回合不会结束。SQL通过后才会执行查询结果交给Visual Agent判断用折线图还是柱状图生成ECharts配置由前端渲染。这条协作链路的核心在于“生成-审查-修正-执行-呈现”的闭环而不是单向的传递。实测中这个博弈机制能把SQL正确率从76%拉高到88%效果显著。2.3 状态传递统一数据结构的价值多智能体系统里最容易被忽略的问题是状态传递。我见过不少实现两个Agent之间靠字符串拼接传递信息比如把Prompt后面追加一段“这是上一个Agent生成的内容xxx”这种方式既浪费Token又容易丢字段。我的做法是定义一个统一的中间数据结构QueryResult所有Agent都往里填内容dataclass class QueryResult: question: str # 原始用户问题 intent: str # 路由结果 sql: str # 最终执行的SQL columns: list # 列名列表 data: list # 查询结果 visual_type: str # 推荐图表类型 error: str # 错误信息Router只负责填intent和questionQuery Agent只负责填sql、columns、dataVisual Agent只读columns和data来生成图表。这样做的好处是任何一个环节出问题都可以通过打印这个对象快速定位每个Agent的输入输出都有稳定的协议后面的Agent不会收到一堆格式混乱的中间产物。3. 数据检索链路从中文问句到可执行SQL3.1 Schema加载不是把建表语句全塞给模型数据检索系统的命脉是Schema设计。直接把所有表的CREATE TABLE语句丢给大模型是最偷懒也最蠢的做法。一来Token消耗大二来无关表会干扰模型判断。比如用户明明问的是订单结果Schema里有用户表、商品表、库存表模型就可能猜错关联关系。我采用的方案是两层元数据过滤。第一层是表清单只包含表名、表注释、行量级第二层是字段详情只在确定了候选表之后才加载。Router Agent先根据表清单判断可能涉及哪几张表Query Agent再加载这些表的详细字段。字段详情也不是简单罗列而是一个结构化的元数据字典table_schema { table_name: order_detail, table_comment: 订单明细表, columns: [ {name: order_id, type: string, comment: 订单号}, {name: region, type: string, comment: 大区枚举值华东/华南/华北/西部}, {name: return_flag, type: int, comment: 是否退货1为退货0为正常}, {name: order_amount, type: decimal, comment: 订单金额单位元}, {name: order_time, type: datetime, comment: 下单时间} ] }“region的注释里写明枚举值”这个细节非常重要。如果没有枚举值说明模型可能把“华东区”推断成模糊匹配或者写错条件。枚举值注释能显著降低条件过滤的出错率。3.2 Query Agent的Prompt设计Query Agent的System Prompt我反复调了很多版最终固定为四个部分角色定义、Schema片段、输出格式、执行约束。角色定义很简单但有效“你是一名资深数据分析师只负责根据用户问题生成SQL不要回答其他问题。”这个“只负责”三个字能大幅减少模型跑偏的概率。输出格式我强制要求JSON结构化包含sql、explanation、potential_issue三个字段。explanation是给Review Agent看的执行逻辑说明potential_issue是Query Agent自己觉得可能有歧义或风险的点。这个设计最初是为了方便调试后来发现它还起到了“自我反思”的作用——模型在写SQL时就会下意识检查自己哪里可能出错。3.3 Review Agent的博弈机制Review Agent是整个系统的质量闸门。它的System Prompt刻意写得“刁钻”“你是一名严谨的审计员请挑出SQL中的错误、口径歧义、性能隐患和潜在风险。如果发现任何问题返回修改建议只有确认无误才返回通过。”实际运行时Query Agent生成的SQL和explanation会一起传给Review Agent。Review Agent会重点检查三个方向业务口径退货率有没有该除的地方没除百分比字段有没有当绝对值用时间范围是否和问题匹配比如“上月”是否被正确翻译成月初到月末。语法与性能多表关联是否缺少条件是否可能出现笛卡尔积有没有该加LIMIT却没加的查询。隐藏假设用户没提但模型默认添加的过滤条件是否合理。如果Review Agent不通过它会返回一段结构化意见包括问题类型、问题说明、修改建议。Query Agent会带着这段意见重新修改SQL然后再次送审。我设置最大博弈轮数为2超过两轮则采用Review Agent的意见作为兜底避免无限循环导致延迟过高。3.4 执行安全不敢省掉的三道防线数据检索系统最怕的不是模型答错而是模型生成的SQL在数据库里“闯祸”。我在执行层做了三道防线。第一道是数据库账号权限。系统使用的数据库账号只有SELECT权限没有INSERT、UPDATE、DELETE、DDL。这是底层防线也是最可靠的一层。第二道是SQL静态检查。在SQL发送给数据库前用规则过滤拦截可疑语句包括注释符、分号拼接、INTO OUTFILE、SLEEP()等危险模式。这一步不是用来替代权限管控的而是为了尽早拦截、早点告警。第三道是查询兜底。所有查询自动加上LIMIT 2000防止一次取出几十万行数据把内存打爆同时设置超时时间执行超过10秒就取消任务并提示用户“查询量过大请缩小时间范围或增加筛选条件”。3.5 查询结果的缓存策略数据分析场景中很多用户会反复查询相同的问题比如“本月每天的订单量”这类固定报表。我在系统里增加了基于LRU的缓存层缓存Key是“规范化后的SQL参数”。短时间内的重复查询直接命中缓存不再调用LLM既省了Token又降低了延迟。4. 可视化呈现如何让数据自动“选对图”4.1 图表类型推荐不是玄学是规则Visual Agent是我认为整套系统里最容易被低估的部分。很多人以为可视化就是套一个模板实际不然。图表选型错了数据再准确也白搭。比如展示时间序列趋势用饼图读者根本看不出走势。我的Visual Agent不做复杂的“审美推理”而是基于数据特征和查询意图的规则化判断查询意图是“趋势”且结果包含时间字段用折线图。查询意图是“对比”结果里有分类字段和一个数值字段用柱状图。查询意图是“占比”结果里有分类字段且数值总量接近100%用饼图。查询意图是“明细”或“排行”结果行数小于50直接用表格行数超过50再考虑用柱状图TopN。这些规则被写进Visual Agent的System Prompt模型只需要根据意图和结果数据的列类型做选择几乎不会出错。4.2 ECharts配置让模型生成配置的坑一开始我尝试让LLM直接输出完整的ECharts option对象事实证明这是灾难。ECharts的配置项非常灵活同一个含义可以用多种写法实现模型经常输出冗余字段或错误属性前端渲染出来是一张错乱的图。后来我改成“模板注入局部生成”策略。前端页面写死ECharts option的骨架只把xAxis.data、series.data、title.text、series.type这几个字段留空让Visual Agent以“填写卡片”的方式补充这些内容。模型不需要理解ECharts全部配置体系只需学会“把数据填进这4个字段里”出错率大幅下降。4.3 前端页面与交互前端我用一个极简的Jinja2模板页面布局是左侧对话区、右侧图表区。用户输入问题后前端通过fetch向后端发起请求后端返回JSON包含SQL、数据、推荐图表类型、ECharts配置。前端拿到配置后直接调用echarts.setOption渲染。这里有一个值得注意的交互细节后端接口如果等到全部Agent跑完才返回用户体验会很差因为一次完整查询可能需要10秒以上。我最终采用了流式响应的思路后端在“路由完成”“SQL生成完成”“审查通过”“查询成功”“图表生成完成”这几个节点分别向前端推送进度信息。前端在图表区显示阶段状态用户能实时看到系统“正在做什么”等待的焦虑感会大幅降低。Flask自带的流式响应Response(generate(), mimetypetext/event-stream)足以支撑这个需求。5. 工程化落地Flask承接Langchain多Agent的部署实践5.1 技术选型为什么是Flask这套系统本质上是一个内部数据问答平台用户量不大并发要求不高但迭代速度快。选Flask而不是FastAPI的原因很简单团队熟悉、生态成熟、模板渲染方便。FastAPI的异步性能优势在这个场景下发挥不出来反而会增加团队的学习成本。如果你要做的系统面向公网、并发较高建议直接上FastAPI。但如果是企业内部的取数工具Flask完全够用不需要为了“潮流”而引入额外复杂度。5.2 会话记忆让追问不再失忆多Agent系统的记忆管理比单Agent更复杂。因为每个Agent都可能需要访问历史会话但记忆又不能全部塞给所有Agent否则Token消耗无法接受。我采用的方案是分层记忆。Router Agent持有完整的对话摘要用来理解用户追问中的指代信息比如“把上个月也加进来”Query Agent只持有最近一轮的QueryResult因为SQL生成只需要当前问题相关的上下文历史SQL对它来说反而是干扰Visual Agent不持有对话记忆只根据当前查询结果出图。对话摘要用LLM自动生成每次对话结束后把本轮的关键信息压缩成一两句话追加到长期记忆里。这样既保留了指代消解所需的信息又不会让上下文无限膨胀。5.3 并发控制与LLM限流接LLM的API时遇到一个很现实的问题并发超限。团队只是几个人内测但多Agent串行调用会让每个请求产生三四次LLM调用同时来几个请求就触发了限流。我的解决方案是一个简单的内存限流器用信号量控制同时进行的LLM调用数量超过阈值则排队等待。对于内部系统来说这比引入消息队列和任务队列简单得多。还有一个容易忽略的点延迟。一次完整的多Agent流程需要多次LLM调用每个Agent的模型参数不同。我做了差异化配置Router Agent用轻量模型Query Agent和Review Agent用更强的模型Visual Agent用中等模型。这样既不憋屈“看路”的环节也不浪费钱在简单任务上。5.4 配置管理与密钥保护LLM的API Key、数据库连接串绝对不能硬编码在代码里。我在项目根目录放一个.env文件用环境变量的方式注入。.env不进Git仓库团队内部分发时只给.env.example模板。数据库连接串还有一个额外的建议不要给超级管理员账号单独创建一个只读账号既是为了安全也是为了让开发环境与生产环境隔离。6. 实测中的坑、教训与下一步取舍6.1 高频踩坑记录这里整理几个我在实测中遇到的高频问题每一个都真实影响了最终的系统稳定性。问题现象根因解决方案SQL里时间条件总写错Schema中的时间字段是datetime模型默认按date处理在字段注释中显式标注“类型为datetime需处理时分秒”多表关联出现重复数据模型搞不清楚一对多关系导致JOIN结果膨胀在表清单中补充表间关系说明Review Agent误报过多审查Prompt太严格总把正确SQL当错误打回调整Review Agent的判定标准只有确定性错误才打回图表数据轴顺序乱查询结果没有排序导致折线图乱序在Query Agent Prompt中要求按时间字段排序用户追问“刚才那个”失忆记忆只保存在单次会话中刷新页面即丢失增加基于会话ID的Redis持久化6.2 关于Langchain和LangGraph的取舍很多人在多智能体项目里会纠结要不要上LangGraph。我的经验是如果你的协作流程相对固定比如“路由-生成-审查-执行-可视化”这个链路基本不变用Langchain的Agent 自定义工作流已经足够。LangGraph更适合流程不固定、需要动态编排、分支很多、循环很深的场景。当前项目里我用的其实是Langchain表达式语言LCEL来组合流程配合手动实现的上面的博弈循环。如果未来要支持更复杂的多轮修复、并行查询多个数据源、按条件动态选择处理路径我再考虑把底层换到LangGraph。6.3 成本与Token优化多Agent系统最大的隐性成本是Token消耗。每轮查询平均要消耗8000到15000个Token其中很大一部分被Query Agent和Review Agent的博弈消耗掉。我用三个手段控制成本。第一是缓存重复查询直接命中不调用LLM。第二是Schema裁剪只加载和当前问题相关的表字段大幅缩短Prompt长度。第三是模型分级简单的SQL查询用中等模型生成复杂查询才动用更强的模型。经过这三项优化单次查询的Token成本降低了约40%。6.4 后续扩展的可能方向这套系统目前的形态还比较基础。如果继续演进我会优先考虑三个方向。一是接入更多数据源类型目前只支持MySQL后续可以对接ClickHouse、PostgreSQL甚至API数据源。二是指标口径管理平台化把常用指标的口径定义从Prompt中抽离出来放到配置中心统一管理。三是引入更细粒度的权限控制不同用户只能查询自己有权限的数据范围。做这个系统的过程中我最深的体会是多智能体的价值不在于“看起来很酷”而在于它能真正拆分复杂度让每个环节可验证、可干预、可优化。数据检索这件事恰恰是复杂度极高、错误代价极大、又非常适合分角色协作的典型场景。最后再分享一条经验不要一开始就追求全自动先用多Agent把流程跑通再把每个角色的能力逐步增强这样系统的稳定性和可维护性都会好很多。本文还有配套的精品资源点击获取