LCODER AI Agent实战:自然语言查数系统架构全拆解

LCODER AI Agent实战:自然语言查数系统架构全拆解 我上周刚把问数项目的第一个版本跑通从零搭了一套基于LCODER的AI Agent智能体架构。问数项目说白了就是让业务人员用自然语言直接查数据库比如上个月华东区销售额前10的品类是什么系统自动完成意图理解、SQL生成、查询执行、结果解读一整条链路。这篇文章是LCODER之AI Agent开发实战系列的第一篇重点拆解项目架构设计——为什么这么分层、核心模块怎么划分、Agent的运行逻辑是什么、技术选型背后的考量。适合正在做AI Agent开发、或者准备把大模型落地到数据查询场景的工程师参考这篇文章会给你一份可以直接抄作业的架构蓝图。1. 项目背景与核心需求问数这件事到底难在哪1.1 为什么需要问数项目传统的数据查询路径是业务提需求→数仓写SQL→出报表→业务看图。这个链路的问题在于慢一个简单的取数需求排队下来至少半天碰上口径来回确认拖两天也很正常。问数项目想做的事情就是把这个链路压缩成一句自然语言提问让AI Agent自动完成从语义理解到结果返回的过程。但把所有环节串起来远比想象中复杂。最初我尝试过用纯Prompt工程来做让大模型直接输出SQL再执行返回结果效果很不稳定。问题主要出在几个地方大模型不了解库表结构导致字段名编造、长长的WHERE条件容易漏条件、多轮对话的上下文容易丢失、执行结果没法自动做可视化。这也是我决定切换到Agent架构的核心原因——问数不是一个单一的文本生成任务而是一个需要感知、规划、行动、反思的完整智能体任务。1.2 目标场景与核心能力边界在动手设计架构之前先明确问数项目的场景边界否则很容易做成一个什么都想干但什么都干不好的系统。我圈定的核心场景是面向内部业务人员的经营分析查询不面向C端用户支持单表查询和简单多表关联复杂ETL逻辑不在Agent能力范围内查询结果返回后Agent需要给出一段自然语言的业务解读支持多轮对话用户可以逐层追问能力边界也画得很清楚Agent不做数据加工和清洗不在SQL里做复杂窗口函数不做自助建模。边界画清楚之后架构设计才不会跑偏也方便后续做能力迭代。整体设计思路是能用Agent解决的就用Agent不能让Agent做的一律挡住。比如SQL生成交给Agent但SQL的安全性校验必须走规则引擎表结构识别交给Agent但字段语义映射必须走元数据缓存。2. 整体架构设计LCODER问数项目智能体的四层结构2.1 分层架构与核心模块划分问数项目的整体架构分为四层接入层、Agent核心层、工具层、数据与基础服务层。每一层解决一类问题层与层之间只通过标准接口通信。接入层负责对接外部使用方包括Web端对话界面、企业IM机器人和OpenAPI三种方式。这里有一个设计决策接入层只做协议转换和会话管理不承载任何业务逻辑。所有请求统一封装成标准Message格式进入Agent核心层的消息队列。Agent核心层是整个架构的中枢也是LCODER这个Agent框架重点解决的问题。它包含六个核心模块意图识别模块判断用户提问属于查数、追问、闲聊还是报表生成任务规划模块将用户目标拆解为可执行的子任务序列工具调度模块根据子任务类型选择合适的工具并传递参数上下文管理模块维护多轮对话的记忆支持指代消解Schema感知模块向大模型提供准确的库表结构信息结果解析模块将查询结果转化为自然语言解读和可视化方案工具层是Agent可以调用的能力集合包括SQL执行工具、元数据查询工具、图表生成工具、数据字典工具等。每个工具都遵循统一的输入输出协议。数据与基础服务层包括MySQL、Redis、向量数据库、对象存储和模型网关。模型网关在这里是个关键设计统一封装了与不同大模型的交互方式。2.2 技术选型与选型理由技术选型是架构设计里最容易被低估的环节。很多团队一上来就选了最热门的框架结果项目做到一半发现某层不合适推翻重来。我在LCODER问数项目里的核心选型是Python 3.11 FastAPI作为服务框架LangGraph负责Agent的编排MySQL和向量数据库搭配存储元数据Redis做缓存和会话管理模型网关兼容主流的闭源和开源大模型。先解释为什么用LangGraph而不是LangChain。LangChain的问题在于抽象层级太高当Agent的流程比较复杂时流程控制能力会很弱。LangGraph提供了图结构的编排方式可以把意图识别、SQL生成、SQL校验、结果解读这些节点定义成一张有向图每个节点是一个可编程的步骤节点之间通过状态对象传递数据这对问数这类多步骤强交互的场景非常合适。数据库选MySQL而不是PG主要是历史原因数仓已经跑在MySQL上。向量数据库选的是开源的Milvus存表结构信息、字段描述和查询历史的Embedding。用向量检索而不是直接查元数据表是因为用户在提问时的表述经常和表名/字段名不一致向量检索能做语义匹配。比如用户问销量实际字段名是sale_cnt光靠字符串匹配是匹配不上的。2.3 为什么不用一个巨型Prompt解决问题这里分享一个我踩过坑之后的反思。在项目初期我试过把系统提示词写得极其详细把数据库所有表结构、字段注释、业务口径全部塞进一个Prompt里期望大模型看完就能查。实际效果很差。原因主要有三个。第一是上下文窗口有限表结构一多几千个字段灌进去Token成本高且有效信息被稀释。第二是大模型的注意力机制决定了它对Prompt中间位置的敏感度低于开头和结尾埋在一大堆表结构中最关键的表反而不容易被注意到。第三是字段口径常有冲突比如销售额在订单表是去掉退款后的净额在报表表是含退款的总金额同一个词在不同表里语义完全不同一个巨型Prompt没法处理这种细粒度差异。所以架构上必须拆开表结构不靠人工写死在Prompt里而是按需通过Schema感知模块检索出来动态注入Prompt。这就是Agent架构相比纯Prompt工程的核心优势——具备感知能力能够动态决定要看什么信息。3. Agent核心运行逻辑智能体是怎么想的3.1 从用户提问到Agent决策的完整链路要理解Agent的运行方式首先得跳出大模型问答的思维框架。Agent不是简单地输入问题→输出答案而是感知状态→规划动作→调用工具→更新状态→继续决策的循环。以上个月华东区销售额前10的品类这个问题为例Agent的处理链路是这样的接入层收到用户消息带上会话ID进入Agent核心层意图识别节点判断这是一次数据查询请求类型为查数Schema感知节点从元数据库检索出与销售额华东区品类相关的表和字段SQL生成节点基于检索到的Schema信息和用户问题生成SQL候选SQL校验节点对生成的SQL做安全性检查包括是否只读、是否有LIMIT、是否有明显语法错误SQL执行工具执行查询返回结果集结果解析节点根据结果集生成自然语言解读同时判断是否需要生成图表最终回复返回给用户同时把本轮对话写入上下文管理器这个过程看起来是线性的但在LangGraph里是图结构节点之间有分支和跳转。比如第5步SQL校验如果发现生成的SQL有问题会直接回到第4步让大模型重新生成而不是直接报错给用户。3.2 LLM在问数链路中的三个关键角色同一个大模型在问数项目里实际上承担了三种不同的角色这也是Agent架构与传统Chatbot最不一样的地方。第一个角色是意图理解和任务规划器。这个环节大模型需要判断用户的意图类型并决定需要调用哪些工具。这里我用的是Function Calling机制让大模型从预设的工具列表中选择而不是让它自由发挥。比如用户说帮我看看这个数据意图识别模块需要结合上下文判断这个数据指的是上轮查询的结果集还是需要重新发起一次查询。第二个角色是SQL生成器。这是问数项目里最核心也是最难的部分。这里的大模型实际上在做受约束的生成——约束来自两个方面一是Schema感知模块提供的表结构信息二是SQL规范里的业务口径定义。我测试过不下十种Prompt模板最终发现效果最好的是先解释再生成模式要求大模型先说明自己理解了这个表是干什么的、用户想问的是什么然后再生成SQL。这个中间推理步骤对提升准确率帮助很大。第三个角色是结果解读器。查询返回的是结构化数据用户要的是通俗易懂的结论。这个环节大模型需要把数据转化为文字描述比如销售额排名第一的是休闲食品达到2300万环比增长12%。这个角色对上下文的要求很高因为它需要引用上轮生成的SQL和结果集来做分析。3.3 需要几个Agent单Agent还是多Agent架构设计时我面临一个选择是一个Agent从头干到尾还是拆成多个各司其职的子Agent。这个取舍在LangGraph里尤其重要因为图编排可以很灵活地组合节点。我的最终方案是单Agent核心 多专家节点。也就是从用户视角看是一个Agent在对话但从架构视角看内部有多个专家节点承担不同职责。核心Agent负责任务调度和上下文维护专家节点专注于特定任务。为什么不用多Agent独立部署呢我实测下来多Agent通信的开销和复杂度远超收益。每个Agent都有独立的上下文和记忆当A Agent的输出需要传给B Agent时序列化/反序列化是有信息损失的。更头疼的是问题定位多Agent场景下你很难追踪到底是哪个Agent给出了错误结果。单Agent核心加多专家节点的模式既保留了任务分工的优势又避免了多Agent通信的复杂度。专家的上下文由核心Agent统一管理相当于是一个大脑指挥多双手而不是多个大脑各自决策。4. 关键链路设计从自然语言到查询结果的落地细节4.1 完整问数流程的状态管理与数据流在LangGraph里状态对象是整个图流转的中枢神经。每个节点执行完毕后都要把结果写入状态对象下一个节点从状态里读取需要的数据。这条数据链路设计得好不好直接影响系统的稳定性和可调试性。我这里定义了一个基础状态对象包含以下核心字段session_id: 会话标识用于区分不同用户和对话user_query: 原始用户问题clarified_query: 经过指代消解后的问题把上个月这个品类这种指代替换为具体内容intent: 意图识别结果retrieved_schema: Schema感知模块检索到的表结构信息generated_sql: 生成的SQLsql_validated: SQL校验结果query_result: SQL执行返回的结果集final_response: 最终拼装好的回复内容上下文存储包含历史问题的压缩摘要和最近几轮的完整记录每个节点只负责读取自己需要的字段在结束时更新自己的输出字段。这样做的好处是支持链路追踪——任何一轮对话都能从日志里完整复盘状态对象的变化过程定位问题非常高效。4.2 Schema感知让大模型读懂数据库的关键机制Schema感知是问数项目里最容易被忽视但影响最大的模块。很多团队做问数项目效果差追根溯源就是Schema感知没做好大模型拿到的表结构信息要么不够、要么不准确。我这里的Schema感知模块采用离线索引 在线检索的设计。离线阶段把数据库里每张表的表名、字段名、字段注释、字段类型、枚举值、表之间的主外键关系全部抽取出来做一个清洗和向量化写入向量数据库。同时业务口径文档也被向量化保存比如销售额订单金额-退款金额这条口径定义会被关联到订单表和退款表的相关字段上。在线检索阶段当用户发起查询时Schema感知模块把用户问题做向量化在向量数据库里做相似度检索筛出最相关的表结构信息限制在15个字段以内。检索条件同时包含表名/字段名的文本匹配和向量语义匹配双路召回再融合排序。这套机制最重要的价值是把大模型需要知道的信息从全量缩小到与本次问题相关的信息既是效果保障也是成本控制手段。实测下来加上Schema感知之后SQL生成准确率从52%提升到了81%。4.3 SQL生成与校验的双保险机制SQL生成是问数项目的核心但再强的模型也会犯错所以安全兜底和准确性校验绝对不能省。我这里的做法是生成和校验双保险。生成阶段我要求大模型输出SQL的同时输出一段自解释。为什么一定要自解释因为这段文字可以强制大模型想清楚再写。我实测过加入自解释之后SQL的语法错误率下降了30%以上。同时生成阶段会给定几个约束只能SELECT不能多语句必须有LIMIT子句表名和字段名必须来自提供的Schema列表。校验阶段做了三层规则校验加一层语义校验。规则校验包括SQL语句是否以SELECT开头、是否包含INSERT/UPDATE/DELETE/DROP/ALTER等危险关键字、LIMIT子句是否存在且值是否合理、表名字段名是否在Schema白名单内。语义校验交给LLM再做一次审查把最终生成的SQL和用户问题、Schema信息一起发给模型让模型判断这个SQL和用户的问题是否一致、有没有明显的逻辑矛盾。这里给一个参考的校验结果处理方式规则校验不通过的直接打回重新生成重新生成两次仍不通过的返回给用户抱歉暂不支持该查询语义校验不通过的会提示大模型修正。这些规则都写入配置文件方便后续调整不写死在代码里。4.4 多轮对话的上下文管理与指代消解问数项目的多轮对话比普通客服机器人复杂得多因为每一轮都可能产生新的SQL和结果集上下文里同时存在语义信息和数据结构信息。我的上下文管理分两层。短期记忆存最近5轮的完整对话记录、每轮生成的SQL和执行结果长期记忆存用户在整个会话中的核心关注点、常用查询维度、经常用到的业务口径做摘要后存入Redis。指代消解是另一件很麻烦的事。用户说上个月到底是哪个月这个品类指的是前一轮结果里的哪个品类我的做法是在每次处理新问题前先让大模型结合历史上下文做一次问题改写把指代词替换为具体内容。改写后的问题才进入Schema感知和SQL生成流程。实测这个前置步骤对多轮追问的准确率提升非常明显从63%提高到了比首轮查询只低5个百分点的水平。5. 工程化落地代码结构、环境准备与开发顺序5.1 项目工程结构是怎么组织的架构如果只停留在文档里那就不叫落地。下面是这个问数项目第一版的核心代码结构实际项目的结构会多一些模块但骨架就是这个。每个目录的职责在代码里通过命名和注释做了明确区分避免后续越写越乱。lcoder_agent/ ├── api/ # 接入层HTTP接口、WS接口、IM机器人适配 │ ├── routes/ │ ├── schemas/ # 请求/响应模型 │ └── middleware/ ├── agent/ # Agent核心层 │ ├── graph/ # LangGraph状态图定义 │ ├── nodes/ # 每个图节点的具体实现 │ │ ├── intent_node.py │ │ ├── schema_node.py │ │ ├── sql_gen_node.py │ │ ├── sql_check_node.py │ │ ├── exec_node.py │ │ └── response_node.py │ ├── memory/ # 上下文管理、指代消解 │ └── state.py # 状态对象定义 ├── tools/ # 工具层 │ ├── sql_executor.py │ ├── schema_retriever.py │ ├── chart_generator.py │ └── registry.py # 工具注册中心 ├── meta/ # Schema离线索引构建 │ ├── extractor.py │ ├── vectorizer.py │ └── indexer.py ├── llm/ # 模型网关封装 │ ├── base.py │ ├── factory.py │ └── providers/ ├── config/ # 配置文件 └── tests/ # 单元测试和回归测试5.2 开发顺序建议先跑通一条最简链路一开始别想着把完整架构都实现出来那样会陷入无尽的调试。我给出的建议是先跑通一条最简链路用户输入→意图识别→SQL生成→SQL执行→结果返回。这五个节点串起来能跑以后再逐一把Schema感知、SQL校验、上下文管理、结果解读这些模块加进去。开发顺序可以按依赖关系分四步走。第一步是初始化LangGraph状态图定义好状态对象把最简单的几个节点挂上去用假数据跑通整个图。第二步接入Schema感知模块建好离线索引管道把向量数据库打通这样才能在SQL生成时动态注入Schema信息。第三步做SQL校验和重试机制这是保障安全的关键必须尽早补上。第四步加上下文管理和多轮对话增强。每一步都有明确的验证标准。第一步的标准是任意一段写死的SQL能返回结果第二步的标准是同一句自然语言查询5次生成SQL的准确率能达到80%以上第三步的标准是危险SQL能被拦截且正常SQL不受影响第四步的标准是连续三轮追问能正确指代历史结果。5.3 环境准备与依赖清单环境依赖的坑比想象中多这里梳理一份第一版实际用到的依赖清单省得大家在装环境时踩重复的坑。Python环境建议用3.11太旧的版本对LangGraph和Pydantic的兼容性都不太好。核心依赖包括langgraph、langchain-core、fastapi、uvicorn、pydantic、sqlalchemy、pymysql、redis、openai、milvus-python。模型方面第一版建议直接用主流的闭源API开发效率最高等流程跑通了再考虑替换成私有化部署的开源模型。数据库账号建议单独创建一个只读账号给Agent用这和后面要讲的安全机制是配套的。向量数据库用Milvus的话注意先在本地起一个Standalone模式的实例等联调环境再切集群模式。5.4 配置管理的设计要点问数项目的配置项并不少而且同类配置的形态差异很大。我拆成了三类配置来管理避免把所有东西堆在一个文件里。第一类是模型配置包括API地址、密钥、模型名称、温度参数、max_tokens等。第二类是工具配置包括数据库连接信息、Redis连接、向量数据库连接、SQL执行超时时间、LIMIT默认值。第三类是Agent行为配置包括意图类型列表、SQL校验规则、重试次数、上下文轮数等。模型配置和工具配置用YAML文件管理Agent行为配置因为经常调整放到配置中心里支持热更新。有一个实用的经验SQL生成相关的Prompt不要写在代码文件里单独维护一个prompt目录用版本管理。每次Prompt改动都留档方便回溯是哪次Prompt改动影响了效果。提示SQL执行超时时间建议设为10秒超过直接终止并提示用户查询超时。数据库层面还要再设一个statement级别的超时双保险防止慢SQL拖垮连接池。6. 常见问题与排查实录这些坑我替你们踩过了6.1 问题排查速查表开发过程中遇到的高频问题整理成速查表遇到类似问题可以直接对照排查。现象大概率原因排查思路SQL生成总用错字段Schema感知注入的表结构不完整查看检索到的字段列表确认是否匹配用户问题生成的SQL语法正确但执行超时缺少LIMIT约束或表连接条件缺失检查SQL校验规则加LIMIT强制约束多轮对话答非所问指代消解失败查看clarified_query确认指代词是否被正确替换查询结果正确但解读错误结果解读Prompt缺少数据上下文检查最终生成解读时是否传入了完整结果集同一问题不同结果大模型温度参数偏高调低temperature必要时改为0.1Agent流程卡住不执行下一步LangGraph状态对象字段更新异常打开状态追踪日志检查各节点输出6.2 关于SQL安全的设计底线问数项目直接操作数据库安全设计必须放在第一位。我第一版就确定了几条原则后续不管是加功能还是改架构都不会突破这层底线。数据库账号用只读账号从根源上杜绝写操作。SQL校验在应用层做一层规则拦截危险关键字直接打回。但规则拦截不是全部数据库账号层面的只读是最底层的保证这两者不能互相替代。所有查询强制加LIMIT默认100行防止业务人员一条SQL拉千万行数据。查询打标用户ID、会话ID、生成的SQL、执行耗时全部写入日志方便做审计回溯。6.3 现阶段的心得与后续架构演进方向问数项目第一版跑通之后我对AI Agent开发最大的感受是架构的价值不在用上最新框架而在把复杂问题拆解成可维护的模块每个模块负责一小块出了问题能快速定位修补。LandGraph这套图编排的方式在问数这个场景下真的比传统Agent框架顺手得多。之前用纯LangChain的时候每个节点的输入输出全靠模型自己控制排错经常要靠猜。换成LangGraph之后每个节点是明确的Python函数状态流转清清楚楚调试的时候直接打印状态对象就能看到全部信息。接下来的演进方向我打算在几个方面继续深入一是Schema感知维度继续扩展增加数据血缘信息让Agent能理解这个字段是从哪张表汇总出来的二是结果可视化能力增强不只是生成图表而是能根据数据特征自动选择最合适的图表类型三是引入反馈闭环把用户的点赞点踩数据收集起来作为Prompt优化和Schema权重调整的依据。最后分享一个小技巧问数项目的Prompt测试一定要做成自动化。我维护了一个覆盖50多个典型问题的测试集每次改Prompt都跑一遍回归用SQL生成是否准确、结果是否有重大偏差做自动判定。别信感觉变好了这种主观判断AI项目的效果评估必须量化。这个系列后续会继续更新下一篇文章重点讲Schema感知模块的实现细节包括离线索引管道怎么搭建、向量检索的排序策略怎么调优到时候再聊。