十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent落地必读:问数项目基础设施五层选型与避坑实践

AI Agent落地必读:问数项目基础设施五层选型与避坑实践 这期是LCODER问数项目智能体搭建系列的第二篇上一篇拆完了业务需求这一篇开始动真格进入基础设施搭建。很多人一听基础设施就以为是买机器、装数据库其实在AI Agent项目里基础设施的第一层是模型接入第二层是编排引擎第三层是工具与数据通道第四层才是存储和可观测性。这四层顺序搞反后面改起来非常痛。我写这篇的定位很明确适合正在搭第一个AI Agent项目、或者已经跑通Demo但不知道怎么工程化的开发者。问数项目说到底就是让Agent听懂自然语言去数据库或指标平台把数据查出来、算清楚、讲明白它的基础设施选型会直接决定后面开发阶段的效率。下面这些内容都是我在LCODER里实际落地过程中验证过的做法包括选型理由、最低可用配置、以及踩过的坑。1. 问数智能体的地基清单先想清楚要搭什么1.1 一条问数请求的真实流向在动手搭任何组件之前先跟着一条真实请求走一遍比看一百篇架构文章都管用。拿LCODER问数项目来说帮我查一下上个月华东区各产品线的销售额Top5这句话到达系统后实际的路径是这样的用户输入经过API网关进入编排引擎编排引擎先做意图识别判断这是一次数据查询请求。然后Agent会根据问题提取关键条件时间范围上个月、地区华东区、维度产品线、指标销售额去元数据服务里找到对应的表和字段描述。接着生成SQL或者调用指标平台的接口执行查询拿到结果后再由大模型把结果组织成自然语言回复。这条链路上每一环都不是调一次大模型接口就能搞定的它需要模型接入层提供稳定的调用能力需要编排层管理状态流转需要工具层把外部数据源安全地暴露给Agent需要存储层记录多轮对话的上下文还需要可观测层把每次调用的完整轨迹留下来。基础设施搭建本质上就是在为这条链路铺路。1.2 五条基础设施线的职责边界我把问数Agent的基础设施拆成五条线每条线的职责和常见组件如下基础设施线核心职责常见组件模型接入层管理LLM的Key、模型路由、上下文长度、成本核算模型网关、OpenAI兼容代理编排层管理Agent状态机、任务规划、多Agent协作、重试策略LangGraph、Spring AI Multi-Agent、自研流程引擎工具层把外部系统能力以标准接口暴露给Agent处理鉴权、参数校验、结果约束MCP Server、Function Calling注册表存储层保存会话、短期记忆、长期偏好、向量知识库Redis、PostgreSQLpgvector、Milvus可观测层记录每次推理的完整轨迹、Token消耗、工具调用结果Langfuse、LangSmith、自研日志链路这五条线不是等重的。工具层和编排层决定Agent能不能干成事模型接入层和存储层决定干得稳不稳可观测层决定出了问题能不能找到原因。基础设施搭建如果时间紧优先级也是这个顺序。1.3 先搭到哪里算够用新手最容易犯的错误是一上来就铺一个大而全的架构Kafka、K8s、微服务全套上齐结果Agent本身还没跑通光运维就耗掉一半精力。我的建议是基础设施搭建阶段的目标只有一个让Agent在本地和测试环境稳定跑通一条完整的问数链路。具体来说够用的标准就三条模型调用有统一的入口和计量编排流程能跑通并能断点重跑数据查询是安全可控的。至于高并发、多租户、自动扩缩容那都是之后的事基础设施阶段不需要提前焦虑。2. 模型层为什么我坚持在LLM前面加一层网关2.1 直接对接官方SDK的三个问题刚开始做Demo的时候我也图省事直接在代码里用官方SDK调模型。但项目要正式往工程化走问题马上暴露出来。第一个问题是模型切换成本高。问数项目里不同任务对模型的要求不一样意图识别用快一点的模型SQL生成用能力强的模型最终回答生成又要兼顾成本和效果。如果代码里到处硬编码SDK每次换模型都要改代码重新部署。第二个问题是Key管理混乱。SDK直接写在业务代码里Key散落在各个服务中一旦泄漏就是直接的经济损失。我自己就见过同事把Key提交到Git仓库的事那感觉就像是把家门钥匙贴在大门口。第三个问题是没法统一计量。问数项目上线后一定要知道每个用户、每个会话花了多少钱直接对接SDK的话这部分统计全靠手工拼根本撑不住。2.2 用OpenAI兼容接口统一接入所以我在LCODER里的做法是在业务代码和各家模型之间加一层模型网关业务侧只认一个统一的OpenAI兼容接口由网关负责把请求转发到不同模型。这层网关可以是开源的LiteLLM、one-api这类方案也可以自己用FastAPI写一个轻量代理。核心配置大概是这样的model_list: - model_name: sql-gen litellm_params: model: openai/gpt-4o api_key: ${OPENAI_KEY} - model_name: sql-gen-fast litellm_params: model: zhipu/glm-4 api_key: ${ZHIPU_KEY} - model_name: intent-fast litellm_params: model: qwen/qwen-plus api_key: ${DASHSCOPE_KEY}这样业务代码里只写modelsql-gen或modelintent-fast这些别名实际后端是谁完全由网关控制。想切换模型、想加一个新模型改配置文件就行不用碰业务代码。网关本身是OpenAI兼容协议意味着任何会调OpenAI接口的SDK都能直接对接省去了一堆适配工作。2.3 上下文预算与成本核算模型网关还有一个隐藏价值——方便做上下文预算。问数项目的每次请求实际进入模型的Token构成比很多人想象的要复杂系统提示词大约1200 Token用来约束Agent的角色和行为动态检索到的表结构描述约800 Token工具定义约900 Token多轮对话历史约1500 Token模型输出约600 Token。这样单次调用就是5000 Token量级一次完整问数流程如果涉及意图识别、SQL生成、结果汇总三次模型调用总消耗就在15000 Token上下。有了网关统一计量之后我建议在网关层做两件事一是给每个模型设置上下文上限超过就触发截断策略而不是硬塞进去二是按用户维度记录Token消耗这样项目上线后能够回答一个用户一个月要花多少钱这种逃不掉的问题。基础设置阶段就把这两个能力加上后面做成本优化时才有的放矢。3. 编排层选型LangGraph、Spring AI Multi-Agent还是自研3.1 三种方案的适用边界编排层是整个Agent基础设施里最容易被低估的一层。很多人的第一反应是不就是把Prompt发出去再拿回来吗但问数项目一旦涉及多步工具调用、失败重试、多Agent分工没有编排框架就会写成一堆难以维护的if else。市面上主流的三条路各有适用场景。LangGraph是纯Python生态的图状态机框架把Agent的执行流程建模成一张图节点是操作边是状态转移条件。它的优势是表达力强循环、分支、并行都能表达适合流程复杂且团队熟悉Python的场景。缺点是学习曲线不低刚上手时容易把图画得过于复杂。Spring AI Multi-Agent适合Java技术栈团队尤其是已经在Spring Boot体系里的项目。它能和Spring原生组件无缝整合配置化程度高但灵活性相比LangGraph弱一些强绑定Java生态。自研编排引擎的诱惑力在于想要什么就自己写什么但代价是状态管理、重试、分布式追踪这些都要自己造轮子。我有段时间想自己写后来算了一下成本光是把状态持久化和断点恢复做好就够喝一壶了。3.2 我在LCODER里的选择与理由LCODER最终选的是LangGraph核心原因有两个。第一问数场景天然适合图结构。一次完整查询不是线性的它很可能出现SQL生成→执行报错→根据错误信息重新生成SQL→再执行这种循环还可能并行去查表结构、查指标定义、查历史相似问题。这些用有向图来表达最自然状态流转清晰出了问题也能看到到底卡在哪个节点。第二团队后续要在Agent里加很多自定义逻辑比如敏感字段拦截、SQL安全审计、结果集大小限制这些既不是纯前端也不是纯后端更适合在编排节点里做。LangGraph允许我在每个节点前后插入自定义处理器这比Spring AI那种偏配置化的方式灵活得多。选型这件事没有绝对答案我的建议是先看团队技术栈再看项目复杂度。如果一个问数项目只是简单的单轮问答单次查库那直接用Function Calling就够了完全不需要编排框架。一旦涉及多轮、多工具、容错再上编排层不迟。3.3 问数图的最低可用配置用LangGraph搭问数Agent一张最简图至少要有5类节点意图识别节点、元信息提取节点、查询执行节点、结果校验与重试节点、回答生成节点。意图识别节点判断用户是想查数、想改口径还是闲聊元信息提取节点从问题中抽取出时间、地区、指标、维度这些结构化信息查询执行节点负责调用工具层去取数结果校验节点判断查询结果是否合理不合理就打回重试回答生成节点负责把结构化结果转成自然语言。这里的核心设计是State状态对象它贯穿所有节点定义好State就是定义好了整个Agent的内存。我的建议是最低限度包含四个字段current_intent存当前意图extracted_params存抽取出来的查询参数tool_results存每次工具调用的结果error_history存重试过程中的错误信息。千万别把大段的历史消息也塞进State里否则每次Token消耗都会爆炸切出去用专门的上下文管理策略。4. MCP与工具层让Agent真正摸到数据4.1 工具注册表与MCP的定位问数Agent如果只会聊天不会查数那只是个玩具。让Agent真正拿到数据靠的是工具层。早期的做法是把函数直接注册成Function CallingAgent按JSON Schema调用。这个方案对单体应用够用但项目一复杂就有问题每加一个数据源就要改一遍Agent代码工具之间也容易互相干扰。MCPModel Context Protocol就是来解决这个问题的。它把工具、资源、提示词统一成标准协议Agent通过MCP Client去发现和调用远程的能力。简单说MCP工具层对于Agent就像USB接口对于电脑——只要设备支持USB标准插上就能用不需要针对每个设备单独定做接口。在LCODER里我把数据查询能力都包在MCP Server里Agent本身不需要知道数据源是MySQL还是ClickHouse它只需要知道有一个叫query_data的工具可以执行查询。4.2 问数项目需要暴露哪些工具一个问数Agent的MCP Server我建议至少暴露四类工具。第一类是元数据查询工具比如list_tables和describe_table让Agent知道有哪些表、每张表有哪些字段、注释是什么。第二类是数据查询工具就是execute_query负责执行SQL并返回结果。第三类是指标计算工具对接指标平台让Agent能按口径计算指标而不是自己瞎写SQL。第四类是结果处理工具比如格式化输出或者生成图表数据。工具定义的精细程度直接影响Agent的表现。一个典型的工具Schema长这样{ name: execute_query, description: 执行只读SQL查询返回最多1000行结果禁止执行INSERT、UPDATE、DELETE、DDL语句, inputSchema: { type: object, properties: { sql: { type: string, description: 完整的只读SQL语句必须以SELECT开头 }, limit: { type: integer, description: 返回行数上限默认500最大1000, default: 500 } }, required: [sql] } }别看这只是几十行JSON它是在用规则约束模型的行为边界。描述里写明必须只读最大行数这些约束比在Prompt里反复强调一百遍你不要做危险操作管用得多。4.3 数据源连接与SQL执行的安全边界工具层最容易被忽视的地方是安全边界。问数项目让Agent直接操作数据库听起来很酷但如果不设防一条帮我把用户表删了这样的恶意输入就足够让整家公司哭。安全边界至少要覆盖四层数据库账号权限、单次查询资源上限、SQL语法白名单、敏感字段脱敏。数据库账号必须用只读账号这是硬底线单次查询要限制返回行数和执行时间SQL要经过解析器做语法校验非SELECT语句直接拦截敏感字段比如手机号、身份证号查询时要自动脱敏。我实际用的一个配置示例database: user: agent_readonly password: ${READONLY_PASSWORD} readonly: true # 强制只读连接 max_rows: 1000 # 单次查询最大返回行数 statement_timeout: 30s # 单条SQL最大执行时间 allowed_tables: - dim_product - dim_region - fact_sales denied_columns: - user.phone - user.id_card这些配置在基础设施阶段就必须做进去而不是等上线前才补。因为Agent的查询是动态生成的你永远没法预料模型会生成出什么样奇怪的SQL唯一可靠的方法就是在这层做硬约束。5. 存储层会话、记忆与向量库的搭建顺序5.1 会话态与记忆的拆分问数项目的存储层经常被简化成随便找个Redis存一下聊天记录但实际没那么简单。Agent的状态至少分两类会话态和记忆。会话态是当前这轮对话的上下文包括用户说了什么、Agent查了什么、结果是什么记忆则是跨越会话的长期信息比如某个用户总是关注华东区的销售数据或者某张表的口径调整过。我建议把两者分开存储会话态放Redis用TTL控制生命周期方便快速读写记忆放持久化数据库经过用户授权后再写入。千万不要把全部历史消息都塞进每次模型请求里那不只是浪费Token的问题还会因为上下文过长导致模型迷失重点。5.2 向量库选型pgvector还是Milvus问数项目里的向量库核心作用是存业务知识包括指标口径说明、表字段的业务含义、常见问题的问答对等。Agent在生成SQL之前先去向量库检索相关的知识片段拼进Prompt里准确率会有明显提升。选型上如果项目的数据量在百万级以内并且团队已经用了PostgreSQL我非常推荐直接用pgvector。它就是一个PostgreSQL插件不需要额外维护一套Milvus集群用SQL就能做向量检索运维成本低很多。只有当数据量到千万级、并发检索很高的时候才值得引入Milvus这类专用向量数据库。初始化pgvector很简单CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE knowledge_chunks ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, metadata JSONB, embedding VECTOR(1024), created_at TIMESTAMP DEFAULT now() ); CREATE INDEX ON knowledge_chunks USING hnsw (embedding vector_cosine_ops);我用HNSW索引检索速度实测下来很稳百万级数据单次查询在几十毫秒左右对Agent场景来说完全够用。5.3 记忆写入与召回的具体做法记忆不是把对话丢进数据库就完事关键是写入和召回的策略。写入方面我的做法是每个会话结束后异步地把对话中出现的业务实体和检索到的知识片段提取出来写入知识库并带上metadata比如用户ID、会话ID、时间戳。这里有个容易踩的坑如果全量写入知识库里全是噪音召回时反而干扰判断。所以写入前要加一道过滤只保留置信度高、有明确业务含义的片段比如华东区销售额同比增长10%这种过滤掉你好谢谢这类寒暄。召回方面一次问数请求开始时先用用户的问题向量去知识库检索TopK个相关片段再把片段拼进系统提示词或上下文里。K值我建议取3到5太小信息不够太大容易堆噪音。召回的时机要卡在SQL生成之前这样模型在写SQL时已经知道了口径和字段含义比生成完再补救效果好得多。6. 可观测性Agent排错的第一依赖6.1 单次请求为什么会产生一整个调用树传统接口的日志很好写一个requestId从入口打到出口就行。但Agent项目的排错之所以难是因为一次用户请求会引发多次模型调用、多次工具调用还可能因为重试出现分支。如果日志是平的你根本看不出来SQL执行失败到底是哪一步引起的。我在LCODER里选型时比较过Langfuse和LangSmith最后因为数据要留在自己手里选择了Langfuse自托管。不过无论用哪个核心思路是一样的把一次完整请求的所有调用组织成一棵调用树。6.2 最小可用的Trace字段设计如果你不想引入额外组件自己写一套最小可用的追踪也不难。关键是要给每个调用定义好span跨度每个span记录以下字段字段说明示例值trace_id整个请求的唯一标识8f3a...c01span_id单次调用的标识8f3a...a12parent_span_id父调用标识用于串联调用树8f3a...a01span_type调用类型llm_call / tool_call / node_execmodel使用的模型或工具名sql-gen / execute_queryprompt_tokens输入Token数4200completion_tokens输出Token数680latency_ms调用耗时2340status调用状态success / errorerror_info错误信息SQL语法错误落地方式很简单在编排层的入口生成trace_id每进入一个节点就生成一个新的span_id并把父节点记下来LLM调用和工具调用都打上同样的标识。排查问题时按trace_id搜出整棵树一眼就能看到哪一步耗时最长、哪一步出错、重试了几次。6.3 本地联调先让回归用例说话基础设施阶段的另一个容易被忽视的工作是建立一组回归测试用例。我做问数项目时维护了一个测试集里面有30条典型的问数请求覆盖正常查询、缺条件追问、模糊指标识别、恶意SQL注入等场景。每改一次Prompt或模型配置就跑一遍这30条用例对比每条用例的Trace记录看输出有没有明显退化。这个习惯帮我省了很多时间尤其是在切换模型、调整工具描述的时候单靠一两条手工测试根本发现不了系统性退化。7. 基础设施阶段最常踩的坑7.1 权限边界能查不等于能改第一个大坑是把Agent当成普通数据库客户端来配权限。Agent不是人它不会判断这条SQL会不会影响线上而且模型偶尔会生成意料之外的语句。必须用只读账号、限制执行时间、过滤非SELECT语句、脱敏敏感字段这四件事少一件都可能出事。我在前文已经给了配置示例这里再强调一遍安全配置要在基础设施阶段就做不要等项目跑起来再补。7.2 重试幂等与重复扣费Agent的重试机制和传统接口不太一样。网络超时、模型限流、工具执行失败都会触发重试。但如果没有幂等设计一次超时后的重试可能导致两条SQL被执行两次或者模型被重复调用扣两次费。现在主流大模型的接口都支持在请求头里传一个唯一请求IDGateway层可以做去重。工具调用层面我建议在MCP Server里对查询类工具做只读天然幂等的约束写操作一律不允许同时给每次工具调用生成一个request_id如果同一个request_id重试直接返回上一次的结果而不是重新执行。7.3 上下文截断要防止引用了不存在的内容这是很隐蔽的一个坑。对话历史超过上下文窗口后很多人的第一反应是简单截断只保留最后几轮。但问数场景里用户可能在第三轮说过就按这个口径如果截断把这句关键信息丢掉模型只能瞎猜。更严重的是如果截断发生在引用关系之间模型引用了被截断掉的内容生成的回答就是无源之水。我的建议是按引用优先级截断而不是按时间顺序截断查询参数、指标口径、表结构这些被后续节点引用的信息优先级最高优先保留寒暄、重复确认这类低优先级先丢弃。同时每次截断后要在系统提示词里更新一个当前上下文已省略的信息清单让模型知道自己看不到什么。7.4 并发限流不只是保护模型也是保护数据库最后一个容易忽视的问题是限流。问数Agent的并发请求不只是打到模型API还会打到业务数据库。如果不做限流一个用户连续发几个问题Agent可能瞬间生成一堆SQL打到数据库直接把线上查询压垮。我的做法是在网关和工具层各做一层限流网关层按用户维度和API维度限流保护模型账号不被封工具层按数据源维度限流保护数据库不被压垮。限流参数要根据实际压测调整不要套默认值因为问数项目的特点是查询耗时不稳定一条简单SQL几十毫秒一条复杂SQL可能几秒峰值并发往往集中在少数复杂查询上。基础设施搭建这部分我个人的建议是节奏放慢一点每一层都先跑通再往下走。模型网关配好之后先手动验证几个模型切换编排图画好之后先把一条最简单的查询链路跑通再加分支MCP工具层做好之后先用真实数据库测试安全边界有没有漏洞。基础设施阶段省下的每一分钟都会在后面开发Agent具体功能时报以加倍的时间债。
返回列表