ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

问数项目智能体架构设计:基于LangGraph与MCP的工程化实践

2026/9/12 14:12:11 拓冰建站 浏览量
问数项目智能体架构设计:基于LangGraph与MCP的工程化实践 1. 项目概述问数项目到底要解决什么问题当我把“问数项目”四个字发给团队的时候很多人的第一反应是这不就是把原来的报表查询加个ChatGPT入口吗真实做起来才知道这个认知差得不是一点半点。传统BI看数是“人找数”——你打开报表筛选维度点击导出整个流程是面向固定页面和固定维度的而“问数”是“数找人”——用户用一句自然语言描述诉求系统自己去理解指标、定位表、生成查询、返回答案。这个转变表面上是交互方式变了骨子里是整个架构模式从“查询系统”变成了“智能体应用”。这里要结合LCODER的背景说一句。LCODER本身是一个面向AI辅助编程和Agent开发的实践社区我们在做的这个问数项目智能体是拿LCODER体系里沉淀的编排思想、多Agent协作模板来落地的一个业务示例。目的很直接为公司内部的运营、产品、财务同学提供一个“用大白话查数据”的入口减少他们等排期、提工单、看文档的时间损耗。从业务价值上看这个项目最值得做的事情不是对线一个聊天机器人而是把企业内部的数据使用门槛打下来。过去一个非技术同学想取数要先搞清楚数据库表结构、指标口径、SQL语法这个过程通常要两三天Agent化之后这些复杂度全部转移到工程侧业务同学只需要描述“我想看最近30天华东区的新增用户数按天维度”剩下全部交给Agent处理。当然说理想很容易落地才是关键。问数项目看起来是个“自然语言转SQL”的经典题目真正难的是两件事第一Agent要理解企业内部复杂的指标口径和表关系不是拿通用大模型一套就完事第二Agent必须能被工程体系管理、监控、容错不能是一个在黑盒里自己乱跑的东西。这两点直接决定了我们在架构选型和实现路径上的所有取舍。2. 整体架构设计不是“聊天机器人”而是“有手有脑的智能体”定位明确了我们再来看架构。问数项目智能体的架构设计核心要回答一个问题Agent应该以什么形态存在于企业内部我把它拆成了四层交互层、编排层、工具层、数据服务层。每一层单独看都不复杂但把它们串成一个完整链路需要想清楚数据怎么流动、控制权怎么交接、异常怎么兜底。2.1 第一层交互层与意图澄清交互层最容易理解就是用户直接接触的那一层。在问数项目里入口形态可以是一个IM机器人飞书、钉钉、企微也可以是一个Web对话页面。交互层的核心不只是聊天窗口而是把用户的模糊诉求“翻译”成结构化的查询意图。这里有个容易被忽视的细节交互层必须保留“澄清机制”。用户说“看一下销售情况”的时候Agent不应该直接去猜表而应该回问一句“您指的是哪个业务线时间范围是”。这个澄清不是靠工程写死的流程而是通过提示词约束模型在意图置信度不足时主动发问。我们在LCODER的模板里把这类交互封装成“意图确认节点”效果比让模型自由发挥稳定得多。以“看一下销售情况”这句话为例不同角色对“销售情况”的理解完全不同。运营想看的是订单量和转化率财务想看的是回款和毛利销售负责人想看的是区域排名和环比增速。如果Agent不问清楚就直接去查数大概率给出一个看似正确但没人真正想看的答案。我们的做法是当意图识别节点对指标和维度的置信度低于0.6时强制进入澄清子流程用选择题或填空式的追问引导用户补齐信息而不是放任模型继续向下游执行。2.2 第二层编排层与图结构状态机编排层是整个Agent的“大脑”也是架构设计的重点。传统的“输入-处理-输出”串行流程根本跑不动问数这种复杂场景因为你没法预估用户会提出什么问题、需要几步才能完成。我们采用的是基于图结构的编排方式把整个任务拆成“意图识别→查询计划生成→工具调用→结果校验→回复生成”这样一个有向图中间允许有分支、循环和回退。这套思路最成熟的开源载体是LangGraph它把Agent的每一次状态变换都变成图里的一个节点节点之间通过条件边流转这样既保留了ReAct模式的灵活性又能通过图结构对流程做显式的控制。为什么要选图编排而不是纯粹的“让大模型自由发挥”说白了可观测性和可控性。自由发挥的Agent在demo里惊艳在生产环境里就是定时炸弹。图编排允许我们在任意节点记录输入输出、注入人类审批、设置循环上限这些东西在架构评审的时候是必须拿得出手的。另外图结构的另一个好处是支持“子图嵌套”——我们可以把“指标查询”做成一个独立子图然后在更上层的主图里引用它这样多Agent协作、流程复用、单点测试都变得可行。2.3 第三层工具层与MCP接入工具层是Agent的“手脚”。问数项目里最核心的工具是两类一类是元数据查询工具获取表结构、字段注释、指标口径另一类是数据查询工具执行SQL返回结果。此外还有指标推荐工具、图表生成工具、权限校验工具等等。这里强调一个架构设计上的原则工具接口的粒度宁可细一点不要粗。经验是单个工具只做一件事参数尽量少工具描述写清楚“什么时候用、怎么用”这样模型才容易正确选择。把一堆功能揉进一个大而全的“执行SQL”工具里看似简化了接口实际上是在考验模型的工具选择能力踩坑概率极高。工具层的协议选型我们最终采用了MCP标准。MCP把工具以标准化server的形式对外暴露任何支持MCP的客户端都可以直接调用。在这个项目中我们把元数据查询工具和数据查询服务封装成两个独立的MCP server编排层通过标准协议发现和调用。这个设计的长期价值在于将来如果想把同样的工具能力开放给其他Agent或者第三方系统不需要重新开发一套接口直接用MCP client去连就行。2.4 第四层数据服务层与语义层隔离数据服务层负责接住工具层发过来的查询请求把SQL执行、数据权限校验、数据脱敏这些能力全部收敛在这一层。这么设计的好处是Agent编排层可以完全不用关心底层数据存在哪、用什么查询引擎只需要知道这个数据服务接不接。在实际落地时我们接的是一套统一指标服务SQL不是直接打到业务库里而是打到指标语义层由语义层负责把指标名翻译成物理SQL。这层抽象是问数项目能够“多业务线复用”的关键。如果你直接让Agent生成SQL去打业务库第一轮可能还行等表结构一改、业务字段一调整Agent生成的SQL基本全废维护成本会立刻失控。这里要特别说明数据安全的下沉策略。最开始我们试图在提示词里约束模型“只查询用户有权限的数据”实际效果非常差。后来把权限校验全部下沉到数据服务层SQL执行前由服务端统一注入数据权限过滤条件不依赖模型自觉。这个改动是问数项目安全性的关键转折点——权限是硬规则必须由代码保证而不是靠提示词约束。3. 技术选型与工具链解析架构层面有了蓝图技术选型就变成了最现实的博弈。市面上Agent编排框架多得吓人选型必须结合团队实际情况。这里把我们在问数项目中经过对比验证的核心选型思路逐一讲透包括LangGraph、Spring AI Multi-Agent和MCP协议以及它们各自在项目里的生态位。3.1 LangGraph把Agent流程变成可控的图LangGraph在Agent项目里这么受欢迎核心原因是它把“图状态机”和“大模型调用”优雅地结合在一起。它允许你定义状态对象每个节点返回状态增量边可以是固定的也可以是条件的。翻译成人话就是你可以非常精确地控制Agent每一步的行为哪些步骤必须串行、哪些可以并行、出错之后怎么回退。在问数项目里LangGraph带来的直接收益是“流程可测试性”。你可以不看模型实际生成了什么只通过图结构的转移路径来判断运行是否正常。比如用户问“本月华东销售”正确路径应该是“意图识别→指标推荐→SQL生成→执行→回答”如果日志显示走错了分支问题定位会非常快。另一个实际收益是断点恢复机制。LangGraph原生支持checkpointer可以把每一步的中间状态持久化到数据库。这个能力在做长耗时查询时特别好用——Agent调完SQL执行工具发现要跑30秒没必要让用户干等先把状态存下来异步恢复继续生成回复即可。3.2 Spring AI Multi-AgentJava团队的务实切入点如果团队是Java技术栈不打算引入Python服务Spring AI的Multi-Agent模块值得认真考虑。它提供了一套基于Java的Agent抽象支持ChatClient、Tool Calling、多Agent协作等功能。好处是能和现有的Spring Boot微服务体系无缝整合Maven引一下依赖就能用。不过有一说一Spring AI在Agent编排的成熟度上还比不上LangGraph。它的定位更像是一个“快速起步工具箱”适合中小规模应用、编排逻辑相对简单的场景。如果你需要细粒度的条件路由、复杂的状态回溯、长时间运行的任务状态持久化Java生态里目前的方案还是偏早期。我们团队最终的选择是LangGraph为核心编排层Spring Boot作为外围接口服务这算是取两者之长的折中方案。这个组合的具体形态是这样的用户的HTTP请求先到Spring Boot网关网关做认证鉴权、限流、会话管理然后把请求转发给Python侧的LangGraph编排服务。编排服务跑完Agent流程把结果返回给Spring Boot再回给前端。Python负责“智能”Java负责“接入”各干各擅长的事。如果将来有大并发需求Python侧做了个无状态化设计可以通过消息队列横向扩容。3.3 MCP协议工具接入的“统一语言”MCPModel Context Protocol是AI Agent开发绕不开的一个词。它的核心价值是标准化了“大模型如何调用外部工具”这个接口协议。在没有MCP之前每个Agent框架都要自己定义一套工具调用标准换个框架工具层基本要重写有了MCP之后工具以标准化的server形式对外暴露任何支持MCP的客户端都可以直接调用。在问数项目的架构里我们把数据查询工具、元数据工具封装成了标准MCP server编排层通过MCP协议去发现和调用工具。这里要给大家一个实用建议不要一开始就追求把所有工具都变成MCP标准第一版直接把核心的数据查询工具做成MCP其他工具先用本地函数封装等跑通了再逐步迁移。过度设计在Agent项目里是常态但架构演进是渐进式的一上来就铺太大的摊子维护成本会把自己压垮。4. 实操环节搭建问数Agent的最小可用架构这一节走一遍实际的搭建过程目标不是完整的生产系统而是一个能跑通的“最小骨架”——意图识别、查询计划、工具调用、结果回复这四件事都能动起来。整个实现基于LangGraph和Python代码结构尽量精简方便后续扩展。4.1 定义Agent的状态与数据流用LangGraph开发第一步永远是定义State。问数项目的State建议至少包含这些字段messages多轮对话的消息列表大模型上下文的基础intent当前识别到的用户意图query_plan拆解后的查询计划包括时间范围、维度、指标列表tool_calls本轮需要调用的工具记录intermediate_results工具返回的中间结果final_answer最终返回给用户的内容State的设计决定了Agent能力的边界这一步不要着急写代码。建议先把业务流程里所有可能出现的“中间产物”列出来再去看哪些需要进State哪些可以只在节点内部消化。State里塞太多临时变量会让整个图变得极难调试。用TypedDict定义即可加上totalFalse允许字段可选这样在节点执行顺序上有更大的灵活性。from typing import TypedDict, Optional class QueryPlan(TypedDict): metrics: list[str] dimensions: list[str] time_range: str filters: dict class AgentState(TypedDict, totalFalse): messages: list[dict] intent: str query_plan: QueryPlan tool_calls: list[dict] intermediate_results: list[dict] final_answer: str4.2 实现意图识别与查询计划生成节点意图识别用大模型做绝不复杂准备好系统提示词告诉模型用户的问题属于哪几种意图查数据、看趋势、做对比、问口径、闲聊让它输出JSON格式的结果。代码层面就是一个函数调用一次LLM解析返回值。这里有一个实用的技巧把大模型的输出格式约束为JSON Schema并要求模型严格按Schema输出解析代码几乎不需要做异常处理。INTENT_PROMPT 你是一个问数系统的意图识别模块。请判断用户诉求属于哪一种意图 - query_data: 需要查询具体数据 - compare: 需要对比两个或多个维度的数据 - trend: 需要查看趋势变化 - ask_metric: 想了解指标口径或定义 - chitchat: 与查数无关的闲聊 输出JSON: {intent: 意图类型, confidence: 0-1之间的分数, reason: 判断理由} def recognize_intent(state: AgentState) - AgentState: response llm.chat([ {role: system, content: INTENT_PROMPT}, *state[messages] ]) result json.loads(response.content) state[intent] result[intent] return state查询计划生成节点稍微复杂一点。它需要把“本月华东销售”拆成“时间范围本月区域华东指标销售额”同时要映射到对应的表和字段。这块依赖的是一套指标字典通过少量示例和描述传给模型做few-shot让模型学会“指标名→表字段”的映射规律。第一次跑的时候准确率可能只有六成别慌这是正常现象后面通过工具调用返回的元数据反馈去迭代提示词。4.3 工具注册与ReAct循环搭建LangGraph里工具调用最常见的方式是ToolNode。把数据查询工具注册成ToolNode节点然后在大模型节点和ToolNode之间连一条条件边模型判断需要调用工具就走ToolNode调用结束回到模型节点继续生成。这个循环就是ReAct模式的落地形态——Reason推理和Act行动交替进行。from langgraph.graph import StateGraph, END from langgraph.prebuilt import ToolNode, tools_condition graph StateGraph(AgentState) graph.add_node(intent, recognize_intent) graph.add_node(plan, generate_query_plan) graph.add_node(llm, call_model) graph.add_node(tools, ToolNode([query_data_tool, get_metadata_tool])) graph.set_entry_point(intent) graph.add_edge(intent, plan) graph.add_edge(plan, llm) graph.add_conditional_edges(llm, tools_condition, {tools: tools, END: END}) graph.add_edge(tools, llm)这里注意tools_condition是LangGraph预置的条件函数它会检查模型最后一条消息是否包含tool_calls字段有就走工具节点没有就结束。实际项目中还需要在llm节点后面加一个max_iterations检查器防止Agent无限循环。我给这个项目设置的默认上限是5轮工具调用超过强制结束并提示用户拆解问题。4.4 处理工具返回结果的大小问题工具调用有一个必须提前踩的坑工具的返回结果尤其是SQL查询结果可能会非常大。如果你把上万行结果直接塞回给模型当作上下文一次对话的Token就可能烧掉几十万。我们的做法是分两层处理结果大于一定行数时先做聚合摘要或者只返回前N行加进度提示需要完整数据时走下载链路不经过模型上下文。方案一在数据查询工具内部做行数限制默认只返回前50行同时附带total_rows字段告诉模型总行数。方案二如果是超大结果集数据查询工具直接把结果写入临时存储对象存储或临时表返回给模型的是一个下载链接而不是数据本身。这样既节省Token又能保证数据的完整性。这两个方案配合使用效果最好。日常的“本月销售额是多少”这类问题走方案一就够了用户需要“导出一份所有订单明细”的时候走方案二。实现的细节是数据查询工具接收一个参数output_typedetail返回全部数据summary返回聚合摘要根据参数决定处理逻辑。4.5 接入大模型与提示词工程模型本身的选择问数场景推荐优先考虑带较强SQL能力和工具调用能力的模型当前市面上的主流大模型基本都达标。真正拉开差距的是提示词怎么写。在写问数Agent提示词时的一个核心原则给模型“边界感”。系统提示词里明确告诉模型哪些能做、哪些不能做、不确定时怎么处理以及必须遵守的输出格式。比万能的提示词更管用的是给模型几个“标准样例”让它照着样例的风格来回答。SYSTEM_PROMPT 你是一个企业数据分析助手负责把用户的自然语言问题转换为数据查询并以通俗的语言回答。 你可以使用的工具 - query_data(sql): 执行数据查询返回查询结果 - get_metadata(table): 获取表结构和字段说明 规则 1. 必须基于工具返回的真实数据回答不允许编造数字。 2. 如果用户问题含糊不清必须先澄清不要猜测。 3. 查询结果只返回前50行或聚合摘要数据较多时提示用户是否有更细的筛选条件。 4. 用户问到你不知道的指标时先调用get_metadata查看表结构再决定怎么查询。 5. 回答风格先用一句话概括结论再列出关键数据最后给出必要的解读。 示例 用户问上月华东区的销售额是多少 你的回答上月2025年3月华东区销售额为 1,235 万元环比增长 8.2%。 注意提示词中明确要求模型“必须基于工具返回的真实数据回答”这一条能很大程度扼杀模型的幻觉冲动。我见过不少问数项目翻车查的根本原因都是模型在数据缺失时自己“脑补”了一个数字用户拿去开会汇报后果非常严重。5. 实践踩坑记五大高频问题与排查方法单看每一步都不算难难的是系统串起来跑之后各种隐藏问题就冒出来了。这一节记录问数项目里最典型的高频问题和排查经验每一类问题都对应一套可复用的解决方法。5.1 大模型幻觉导致SQL与业务口径不一致最经典的翻车现场用户问“本月销售额”模型生成的SQL是按订单金额汇总但业务口径其实是按回款金额。这类问题的根因是模型没有正确理解指标口径。解决思路是在工具层增加“指标解释查询”——模型在执行SQL之前先调用元数据工具获取相关指标的详细定义如果定义模糊就向用户确认。另外在指标字典里加上了别名和反例显著降低了口径误判的概率。这个问题的排查通常很隐蔽因为光看SQL很“对”语法正确、表名正确、字段存在但算出来的数和业务口径对不上。我们在日志里加了semantic_anchor字段记录模型使用的指标ID和业务侧维护的指标口径库做交叉验证一旦发现指标ID匹配异常就发出告警由专人介入确认。5.2 Agent死循环或调用链过长一旦工具调用链超过三步模型就开始“绕圈”在同一个工具上来回调用最终耗尽预算。这里不要指望模型自己收敛架构层面必须给硬约束。我们在LangGraph里设置了最大迭代次数超过就强制结束并回复“这个问题太复杂请尝试拆分成多个问题”同时加上了“如果同一工具连续调用超过2次触发人类审批”。这些硬性护栏在demo中可能显得多余但生产环境是救命的。排查这类问题时重点看两个指标平均Agent循环深度、单次任务Token消耗分布。如果发现某类用户问题的循环深度普遍偏高说明查询计划生成节点对任务的拆解不合理需要优化提示词。如果是偶发的“卡死”问题多半是工具的返回格式让模型理解困难需要在工具描述里补充更详细的格式说明。5.3 工具调用链过长导致Token耗尽这是问数项目上线初期最让人头疼的问题。用户问一个稍微复杂点的问题Agent可能要调4-5次工具每次工具返回可能带上千行样本数据一轮跑下来Token消耗是正常值的10倍以上。优化手段有三板斧第一工具返回结果精简再精简能返回聚合值就不返回明细第二把多轮工具调用合并成一次批量调用比如同时查销售额和订单量这种可以并行执行的动作放进同一个工具请求里处理第三引入缓存对相同或高度相似的查询结果做短期缓存TTL设置为5-10分钟减少重复计算。5.4 权限控制如何嵌入Agent问数项目会对不同角色开放不同的数据范围。一开始我们做了最粗暴的方案在提示词里告诉模型“这个用户只能看华东数据”。结果模型根本不买账有时候会把条件忘掉。后来把权限校验下沉到工具层SQL执行前由数据服务统一注入数据权限过滤条件而不是依赖模型记住。这样即使模型生成了全量SQL最后执行出来的也只会是用户有权看到的数据。这个改动是权限安全的关键转折点。具体实现是让用户认证模块在通过认证后在请求上下文中注入一个permissions对象包含用户角色和可见数据域。数据查询工具执行前调用权限过滤组件自动把SQL改写为带权限条件的版本原始SQL是select count(*) from orders where date 2025-01-01改写后变成select count(*) from orders where date 2025-01-01 and region in (华东, 华南)。整个过程Agent无感知也不用担心模型“忘记”加权限条件。5.5 元数据过期导致Agent“瞎猜”问数项目另一个坑是元数据不同步。业务侧改了字段名、删了表Agent拿到的还是旧元数据生成的SQL自然是错的。应对办法是给元数据服务加缓存失效机制业务方在更新表结构后调用接口主动刷新元数据缓存同时每天早上定时同步一次。另外当数据查询工具因为字段不存在报错时不要只是把异常信息丢给模型而是自动触发一次元数据刷新再让模型根据最新元数据重新生成SQL。6. 下一步规划与实践总结问数项目智能体的第一期遮“项目架构”核心要传达的始终是那个观点做Agent项目难度不在“智能”而在“工程”。这期文章的篇幅主要集中在架构设计的逻辑和技术选型的权衡上因为架构没定清楚后面一切开发都是在沙滩上盖楼。在问数项目里我们靠LangGraph把流程变成了可控的图靠MCP把工具变成了可复用的标准服务靠数据服务层的语义隔离把权限和数据安全牢牢攥在工程侧这几件事做到了Agent本身“聪明不聪明”反而成了次要问题。后续这个系列我计划继续往下拆按“项目架构→指标语义层→多Agent协作→生产可观测性”的顺序推进。第二篇重点写指标语义层怎么设计包括指标字典的结构、口径识别的方法、以及Agent如何利用语义层提高SQL生成的正确率第三篇写多Agent协作如何把查询、分析、图表生成拆成独立Agent以及它们怎么通过编排层协同工作第四篇写生产环境的可观测性与评估体系比如怎么给Agent的每一次查询打分、用哪些指标评价一个Agent系统的健康度。最后说一个我自己测试时候的体验问数Agent这类项目demo里跑通一个漂亮案例一点都不难难的是把它放在真实业务流里跑一个月、跑三个月还能保持稳定和可信。这就要求我们在架构阶段多留一些“监控眼”和“逃生通道”——让我再选一次的话我会把可观测性设计提前到第一天就纳入架构而不是等出了问题再去补。这个教训希望看这篇文章的朋友们能直接带走。