最近我花了两周时间把一个基于 Agent 的问答系统从“只接 3 个数据源”扩到了 5000 数据源的直接调用实测效果确实很猛。不是加了几个 API 那么简单而是把 Agent 的边界从“会说”真正拉到了“会做”它能根据用户一句话实时去正确的数据源里取数、算数、比对、返回结论。这个过程中踩了不少坑也把整个调用链路从设计到实现完整走了一遍。这篇文章就把我这次的完整思路和实操过程整理出来包括数据源适配层怎么设计、工具声明怎么写、权限和缓存怎么处理、以及最常见的报错和排查方法。对 Agent 开发有兴趣、或者正在做多数据源接入的朋友这篇文章应该能帮你省下不少试错时间。1. 为什么 Agent 必须“直接调用”数据源而不是靠预设接口先说结论Agent 和传统 API 集成的本质区别在于它把“调用动作”从工程师写死变成了模型自己决策。这个区别看起来不大实际用起来天上地下。1.1 传统 API 调用和 Agent 调用的本质差异传统的接口集成开发流程是这样的业务提需求后端写接口前端对接页面接口参数固定、返回结构固定。用户能看到什么取决于工程师预先把哪个按钮绑定了哪个接口整个过程是“人编排、机器执行”。Agent 调用数据源不一样。你把一个数据源描述给 Agent说清楚这个数据源是干什么的、有哪些参数、返回什么结构Agent 就能在对话过程中自己判断“用户这句话应该去查这个数据源”然后自动填参数、发请求、解析返回结果最后把结果组织成自然语言回复。举个例子传统方案里用户想查“最近 7 天华东区的销售总额”前端会有一个固定报表页面指向一个写死的 SQL 或接口。Agent 方案里用户就随便说一句“华东最近卖得怎么样”Agent 会自己拆出时间范围最近 7 天、地区维度华东、指标销售额然后调用销售数据源参数自动填好结果自动汇总。这里最关键的一点是Agent 不是按预设路径调用而是按“语义理解”调用。所以数据源数量越少Agent 的优势越小数据源越多传统方式越难维护Agent 的优势就越明显——这就是 5000 数据源场景下Agent 能“猛”起来的根本原因。1.2 5000 数据源意味着什么不是数量问题是复杂度问题5000 个数据源如果你用传统方式做接口映射哪怕每个数据源只写一个调用函数代码量也会膨胀到不可维护。真实情况下一个数据源往往有多种查询维度比如一个销售库可以按地区、按产品、按时间、按客户类型四种方式查那就是 2 万个参数组合。更麻烦的是数据源的异构性。我这次接的 5000 数据源里面大概分布是这样的40% 是 REST API返回 JSON但接口风格完全不统一有的用 GET 传参有的用 POST 塞 JSON body25% 是内部数据库走 JDBC/ODBC需要拼 SQL15% 是文件类数据源比如 Excel、CSV、Parquet分布在各种 NAS 和对象存储上20% 是内部系统的业务接口比如 ERP、CRM、工单系统有各自的鉴权体系数据类型不一样调用方式不一样返回结构也不一样。如果每个数据源都单独写一段逻辑让 Agent 去学模型根本记不住上下文窗口也不够用。所以面对 5000 数据源核心问题不是“怎么调”而是“怎么统一”。这也是我整个项目里最花精力的一块设计一个中间的适配层让 Agent 只面对一套统一的调用协议而把 5000 个数据源的差异全部消化在适配层内部。提示如果你正在做 Agent 接入数据源不管你现在是 3 个数据源还是 30 个第一时间先把适配层设计好别等到 100 个的时候再重构。我就是前期图省事直接硬编码后期重构花了两倍时间才改完。1.3 这个方案适合谁、能解决什么问题这套方案最适用的场景有三类第一类是内部数据中台各种数据散落在不同部门的数据系统里业务人员想查数只能找数据工程师提工单Agent 接了数据源之后可以直接用自然语言查数。第二类是智能客服和运维助手客服系统后端挂了订单库、物流库、商品库、售后库Agent 根据用户问题实时决定查哪个库。第三类是个人知识库和效率工具本地文件、云盘、网页书签、笔记系统全都可以作为数据源接入Agent 相当于有了一个“外挂大脑”而且是实时读写的。不适合的场景也有如果你的数据源只有一两个且调用模式完全固定那用传统接口反而更简单。Agent 的优势在多不在快场景单一的时候引入 Agent 是给自己找麻烦。2. 核心机制拆解Agent 如何从“听懂”到“查到”这一节我拆一下 Agent 调用数据源的完整内部流程。理解了这个流程你才能知道配置什么、优化什么、排查什么。2.1 工具调用Function Calling的原理现在主流的大模型GPT 系列、 Claude 系列、以及 DeepSeek 等国产模型都支持一个能力叫 Function Calling中文一般叫函数调用或工具调用。它的核心思想是模型除了生成文字还能生成一个“结构化指令”。这个指令告诉你“我要调用某个工具参数是这些”。你的程序收到指令后执行真实的函数把结果返回给模型模型再组织语言回复用户。流程是这样的用户在对话窗口问“查一下上个月石家庄的订单量”你的程序把这句话发给大模型同时附上你预先定义好的全部工具声明大模型理解了用户意图在工具列表里找到“查订单量”这个工具自动填好参数城市石家庄时间上月大模型返回的不是自然语言而是 JSON 格式的调用指令比如{tool: query_order_count, params: {city: 石家庄, time_range: last_month}}你的程序解析这段 JSON真的去数据库查询拿到结果程序把查询结果拼上用户原话再次发给大模型大模型基于真实结果组织成一句带结论的话“石家庄上个月的订单量是 24587 单”关键在这一步的理解工具声明是告诉模型“有什么工具可用”但模型并不知道具体数据长什么样。真正让用户看到可靠答案的是第 6 步把真实查询结果喂回给模型。2.2 数据源在 Agent 眼中的“身份”工具 Schema5000 个数据源接入后Agent 怎么知道该选哪个答案是靠工具声明Tool Schema。每个数据源在 Agent 的配置里都对应一份 JSON Schema包含名称name比如query_sales_data描述description说明这个工具是干什么的、适合回答什么问题参数声明parameters每个参数的名字、类型、是否必填、含义模型就是靠“读描述”来决定用户这句话该选哪个工具。所以描述写得清不清楚直接决定调用的准确性。这一步很考验功底我后面会专门细讲。2.3 从发出请求到拿到数据的完整链路在数据源规模上来之后完整链路比我上面写的 7 步要长一些第一步意图识别与工具选择。模型根据用户输入从 5000 个工具声明里选出一批候选工具。注意不是只选一个复杂问题可能涉及多个数据源联查。第二步参数提取与格式转换。模型从用户自然语言里抽取参数比如时间、地点、产品名然后转换成工具规定的格式。这里常见的问题是单位不统一比如用户说“上周”模型的工具声明里要求传start_date和end_date需要模型自己计算日期。第三步适配层鉴权与请求分发。每次调用都有一次鉴权校验然后根据工具配置的路由信息把请求转发到目标数据源。第四步数据源执行查询。这里可能是查数据库、调 HTTP 接口、读文件。第五步结果转换与截断。把数据源的原始返回转换成模型友好的文本格式。这个转换看似简单实际很关键不能把全部数据塞给模型超出上下文窗口会被截断太小又可能丢失关键信息。一般我会做一个结果摘要函数自动对大数据量做聚合统计。第六步模型生成最终回复。模型拿到查询结果后生成完整的自然语言回复附带必要的提醒比如数据截止时间。2.4 多数据源场景下的“路由决策”怎么做5000 个数据源如果每次都把所有工具声明塞给模型上下文窗口直接爆掉。所以必须做路由筛选。我实测出来的方法是分两级路由第一级类别筛选。给每个数据源打标签比如“销售数据”“库存数据”“客户数据”“物流数据”。先用一个小的分类模型或规则引擎根据用户输入判断属于哪个类别只把该类别的工具声明发给大模型。第二级关键词行为加权。同一个类别下面用关键词匹配做粗筛再用近期的调用频次加权。比如一个用户经常查“华南区销售”那华南区相关的数据源权重自动提高下次查询优先排在前面。这套两级路由下来5000 个工具通常能被筛选到 20 个左右再发给模型。模型做决策的准确率会高很多响应速度也快不少。3. 实操部分设计统一数据源适配层这节是最核心的实操内容。我会从适配层的数据模型设计讲起一直到代码结构的组织方式。3.1 统一调用协议四个字段打天下既然 5000 个数据源的实现各不相同Agent 层面就不能直接操作它们必须抽象出一套中间协议。我最终定稿的协议非常简单核心只有四个字段字段含义示例source_id数据源唯一标识erp_order_dbaction操作类型query/aggregate/list_fieldsparams查询参数{city: 石家庄, date_from: 2025-01-01}timeout超时时间毫秒5000无论底层数据源是什么Agent 永远只发这四个字段。这四个字段怎么映射到底层具体的 API 调用、SQL 拼接和文件读取全部收在适配层内部处理。选择这个协议是经过考量的。source_id负责寻址action负责区分查询类型params负责控制细节timeout负责保护。再多加字段意义不大反而增加模型理解的负担。保持简单是适配层设计的核心原则。3.2 数据源注册机制一行代码加一个新源新接入一个数据源不能在代码里硬编码一个 if 分支。我做了一个注册表配置在数据库表或者 YAML 文件里每行代表一个数据源。注册表的核心字段- source_id: erp_order_db type: mysql category: sales connection: host: 10.0.12.34 port: 3306 database: order_center table: order_records description: 订单数据库包含所有订单明细按订单号、客户ID、商品SKU、数量、金额、创建时间等字段记录 parameters: - name: customer_id type: string required: false description: 客户编号精确匹配 - name: start_time type: datetime required: false description: 起始时间格式YYYY-MM-DD - name: end_time type: datetime required: false description: 结束时间格式YYYY-MM-DD auth: type: internal credential_key: erp_mysql_readonly新增数据源时只需要在这个 YAML 里追加一段配置然后执行一个注册脚本适配层就会自动加载这个数据源把工具声明注册到 Agent 的工具列表里。整个过程不需要动任何 Agent 业务代码也不需要重新部署。一个数据源对应一次调用可以设定为定时同步也可以实时直连。实时直连对于数据库类源时效性最好但要注意连接数上限。文件类源建议定时同步到统一查询引擎避免 Agent 请求时现场读大文件。3.3 适配器接口一个抽象类撑起所有类型的接入适配层的核心是定义一个适配器抽象类所有具体实现都是它的子类class DataSourceAdapter(ABC): abstractmethod def parse_config(self, config: dict) - dict: 解析注册表里的配置做格式校验和默认值填充 pass abstractmethod def query(self, source_id: str, params: dict) - QueryResult: 执行查询返回统一结构 pass abstractmethod def test_connection(self, config: dict) - bool: 测试连接可用性接入时自动调用 pass具体的子类根据类型各自实现比如class MySQLAdapter(DataSourceAdapter): def query(self, source_id, params): # 把统一参数转成 SQL # 执行查询 # 把结果封装成 QueryResult pass class RESTAPIAdapter(DataSourceAdapter): def query(self, source_id, params): # 把统一参数映射到 URL 和 body # 调用 requests.post # 解析 JSON 并封装 pass class ExcelFileAdapter(DataSourceAdapter): def query(self, source_id, params): # 读取本地或远程文件 # 按条件过滤 # 返回结果 pass这个抽象类的价值在于无论新增什么类型的数据源实现者只需要关心“这个新源怎么查”不需要关心 Agent 怎么用。Agent 侧的代码保持零修改。3.4 查询结果统一转换模型友好比精确更重要数据源返回的结果是千奇百怪的MySQL 返回的是列表嵌套字典REST API 返回的是 JSON 报文文件读取出来是表格结构。但模型对输入有偏好人类可读的文本比纯 JSON 更友好紧凑的摘要比长表格更友好。所以适配层里我做了一个结果处理器把各种格式统一成模型友好的纯文本。规则有三条第一转成带字段名的文本。比如查询订单结果订单记录 1. 订单号 SO23001客户ID C1001商品SKU SKU-A01数量 10金额 2500元下单时间 2025-03-01 14:23 2. 订单号 SO23002客户ID C1002商品SKU SKU-B07数量 2金额 800元下单时间 2025-03-01 15:01第二数量大的时候自动聚合。我设了一个阈值超过 20 条记录就自动按维度汇总而不是全量输出订单记录共 245 条按日期汇总 - 03-0145 单合计 125000 元 - 03-0267 单合计 183500 元 - 03-03133 单合计 276200 元第三数值类型自动格式化。金额加上千分位日期加上时区标识布尔值转成是/否避免模型对纯数字的误读。这个结果转换层花了我不少时间但效果显著。改造前模型经常“看错”数据改造后准确率高了很多。注意结果转换时一定要把“数据是什么时间点的”这个信息带上。比如查库存如果数据是昨天的快照你不写清楚Agent 会直接告诉用户“当前库存”那是误导。4. 执行过程让 Agent 真正从 5000 数据源里找到答案适配层搭好之后就到了最关键的环节怎么让 Agent 在 5000 个工具里精准找到用户要的那个数据源。这一节我直接展示我的做法、配置和参数选择的理由。4.1 工具描述编写Agent 选不选得对就看描述怎么写数据源接入后Agent 靠工具描述来了解每个数据源。1000 个数据源接好了如果描述写得模棱两可模型调用起来就是“凭感觉瞎点”效果不会好。我在实操后总结了一个工具描述公式这个工具用途核心字段适用问题示例一个正面案例工具名query_channel_daily_metrics 描述查询渠道广告投放的每日数据包含曝光、点击、消耗、转化率、CPA。适合回答这个渠道最近投放效果如何曝光和点击怎么样CPA 是多少这类问题。 参数 - channel_id渠道ID必填 - start_date开始日期必填格式YYYY-MM-DD - end_date结束日期必填格式YYYY-MM-DD一个反面案例工具名get_data 描述获取指标数据后者模型根本不知道什么时候该用经常会漏选或者滥用。所以我要求所有新增数据源在注册时描述必须写清楚“什么时候用”和“适合回答什么问题”。描述里还有个技巧用“否定句”来约束。比如此工具仅返回已确认的订单数据不包含未支付订单。如果要查询未支付订单请使用另一个工具 query_unpaid_orders。模型对否定信息的理解往往比肯定信息更可靠加一句约束能显著减少误调用的概率。4.2 参数自动抽取与格式化用户说的自然语言和工具要求的参数格式通常是两层皮。比如用户说“这个月”工具要求传2025-03-01到2025-03-31用户说“石家庄市”数据库存的是sjz用户说“昨天”模型得自己计算具体日期。解决这个问题的思路是在参数声明里给出明确的格式说明和枚举范围尽量缩小模型的猜测空间。比如日期参数我这样写{ name: start_date, type: string, description: 起始日期格式YYYY-MM-DD必须是最近90天内的日期, example: 2025-03-01 }加上了example字段之后模型填参数的准确率提升非常明显。因为模型见过大量代码和文档语料它天然对“示例”更敏感。再比如枚举值的参数我可以把可选项直接写在描述里{ name: region, type: string, description: 地区编码。可选值north北方区、east华东区、south华南区、west西北区, required: true }这样模型输出参数时就能在枚举范围内选不会输出一个“北方”去数据库里查不到。4.3 上下文窗口优化5000 个工具不能全塞给模型目前主流模型单次调用能传入的上下文令牌数通常在几千到十万不等但工具声明要占用大量的上下文空间。5000 个工具每个工具声明平均 200 个 token全塞进去就是 100 万 token根本不可能。我采用的方案是动态路由分级前面提过的两级路由展开讲一下。第一级大类过滤。把工具按业务域划分比如销售域、库存域、财务域、客服域、物流域。给每个域打上标签然后根据用户输入的关键词匹配到具体域。这一步能把候选集从 5000 缩减到 500 以内而且用规则引擎就能完成不需要动用模型。第二级语义召回。在选定域内用嵌入embedding模型给每个工具的描述生成向量用户在提问时也生成向量通过向量相似度召回最相关的工具。这一步把候选集从 500 缩减到 20 左右。第三级最终决策。把召回出来的 20 个工具的完整声明发给大模型让模型从中选择最匹配的一个或多个。这个三级路由的方案是我在 5000 数据源场景下反复调整后的最终方案。之前采用过全量发送上下文直接爆掉也试过全靠关键词硬匹配准确率只有 60% 多效果都不行。现在这套实测工具选对率能到 90% 左右响应延迟也能控制在可接受范围内。4.4 缓存与高效查询策略5000 个数据源接进来如果不做缓存一个常见问题就会找上门用户连续两次问同样的问题你要打两次真实数据源。对于实时性要求不高的查询这非常浪费资源也会影响响应速度。我实践的缓存策略分三层第一层结果缓存。针对纯查询类数据源如果用户在 60 秒内发出相同参数的查询直接返回上一次的结果。用 Redis 做键值存储key 为数据源 ID 参数组合的哈希值。第二层预取缓存。针对高频访问的数据源比如每日销售报表数据源每天早上自动执行一次查询把结果写入缓存。用户晚些时候问“今天的销量怎么样”直接命中预取数据。第三层汇总缓存。用户问数据时超过一半的情况是在问“总计、平均、最大值”这类统计信息。我在适配层加了一个“统计预聚合”的逻辑把数据源里的明细数据提前按天/按类目聚合好。查询统计类问题时直接查聚合表而不是扫明细表。这三层缓存配合下来数据源的真实负载降低了很多。刚接入时5000 个数据源同时在线数据库连接池被占满经常超时。加了缓存之后连接池只需要支撑一小部分高频请求剩下的全走缓存。5. 多数据源接入中的常见问题与排错实录这个环节如果不是亲历很难提前意识到。我尽量把常见的问题和解决办法完整列出来如果你也在做 Agent 数据源接入别踩同样的坑。5.1 工具误调用模型选了错的数据源表现用户问“库存周转率”Agent 去查了“库存明细查询”而不是“库存周转率分析数据源”。返回的结果不是用户要的但 Agent 依然自信地编了一段结论。原因分析工具描述写得不够清晰。两个数据源都带“库存”关键词模型无法区分。解决办法在描述里明确区分“明细数据”和“分析指标”比如“此工具返回库存明细按SKU列出当前库存和最近入库时间不适合做趋势分析”给工具名称加业务前缀比如stock_detail_query和stock_turnover_analysis模型对名称的语义关联比描述更强上线前对高频问题进行“虚拟调用测试”看看模型实际选择了哪个工具及早发现混淆5.2 参数错传数据格式对不上表现用户说“从一月到三月”Agent 调用工具时传了参数start_date2025-03-01、end_date2025-01-31时间范围反了查出来是空。原因分析模型对“从 A 到 B”这样的自然语言表达偶尔会搞反左右参数。另外日期范围太大比如用户说“今年”但工具限定了 90 天模型也会出错。解决办法适配层加参数校验逻辑在后端做范围检查if end_date start_date: 自动交换在参数描述上明确“start_date 必须早于或等于 end_date”当查询结果为空时适配层返回提示信息“查询结果为空请检查参数是否正确或者数据源内是否含有该时间范围的数据”而不是简单返回空列表这样 Agent 收到反馈还可以进行二次纠正5.3 返回结果超长导致上下文截断表现用户查一个区间内的订单明细适配层返回了 5000 条记录模型在处理时直接截断导致最后生成的总结只基于前几百条数据数据完全不准确。原因分析适配层没有做结果截断和聚合处理把全量数据都塞给了模型。解决办法适配层设一个上限默认最多返回 20 条明细超出部分自动进行分组汇总如果用户明确要明细可以设置参数limit100但明确在结果前缀标注“仅显示前100条明细如需全部请联系数据管理员导出”对于需要真实全量的场景接入一个数据导出能力而不是让模型读全量数据5.4 调用超时与数据源抖动表现Agent 在查询某个数据源时长时间无响应导致整个对话卡死用户体验极差。原因分析数据源本身不稳定可能是网络慢、数据库锁表、接口负载过高。Agent 的对话机制和真实数据源之间有天然的时空差距不能像传统 API 那样长时间挂起。解决办法每个适配器都必须在查询时设置超时我默认设为 5 秒超过后直接返回超时错误超时后 Agent 会自动向用户解释“该数据源暂时不可用请稍后再试”而不是一直等待或编造数据适配层对数据源做健康检查连续 3 次超时的数据源自动标记为“暂不可用”后续请求不再路由过去直到恢复探测通过5.5 鉴权与权限隔离问题表现Agent 能查到数据但用户本来不应该有权限查看这些数据。数据安全在真实业务场景是红线。原因分析只做了 Agent 到数据源的鉴权没有做“用户到 Agent”的权限过滤。解决办法在适配层增加用户上下文Agent 发起查询时必须携带当前用户 ID适配层根据用户 ID 查权限表过滤掉无权访问的数据源敏感字段还要做列级脱敏比如手机号、身份证号即使查到了也要在结果转换层打码重要数据源的权限问题必须在 Agent 接入数据源之前就考虑。等上线之后再补权限风险非常大因为模型可能在回复里泄露更多细节。5.6 常见问题速查表现象直接原因处理建议Agent 回答“该数据源不存在”source_id 配置错误或未注册检查注册表配置确认 source_id 唯一且已执行注册脚本查询结果全部为空参数格式不对或时间范围反了适配层加参数校验自动交换反向的时间范围响应速度特别慢没有启用缓存每次都在查真实库开启结果缓存和预聚合缓存模型重复调用同一工具工具描述没有写“约束条件”在描述末尾增加“什么情况下不要用此工具”数据源连接数耗尽大量 Agent 并发请求未做连接池控制在适配器内部统一使用连接池限制最大并发5.7 调优技巧从模型反馈里反推工具描述改进这套系统上线后我建立了一个日志分析流程。每次 Agent 调用数据源我都会记录这么一组信息用户原话模型选择的工具实际应该选择的工具人工标注参数填得是否准确最终回复是否准确然后每周我会分析一批错误样本找出错误集中在哪些环节。我遇到过两种情况很有代表性一种情况是模型经常把“销售订单查询”和“销售发票查询”搞混。我在描述里明确区分了“订单是客户下单时的记录”“发票是财务开票后的记录”并强调“若用户提到发票、开票、财务应选择后者”。另一种情况是“新增客户数”这类口径问题。分析师可能要看“注册新客户”销售要看“下单新客户”指标定义完全不同。我在两个工具描述里都加了口径定义同时在上层配置了一个“指标字典”把常用指标和对应的工具绑定模型调用的准确率提升非常明显。这套“日志-复盘-改描述”的流程持续滚动了两周工具选对率从我刚开始接时候的 75% 提升到了 90% 以上效果非常稳定。6. 从 5000 数据源接入往前想Agent 规模化的下一步这个项目做到现在我最大的体会就是Agent 接数据源这件事真正厉害的不是“能接”而是“接完之后还能保持稳定、可解释、可维护”。顺着这个方向我还在持续做一些扩展一是把数据血缘也纳入 Agent 的认知范围。比如用户问“为什么这个订单没有发货”Agent 不仅能查到订单状态和库存信息还能把上下游的数据串起来告诉你“订单已支付但商品 SKU-B07 缺货所以未发货”。让 Agent 在一次调用里关联多个数据源这是比单源查询更有价值的能力。二是把数据源接入做成自助式。业务方想接一个新数据源我打算做成一个向导式页面上传配置文件、填好描述和参数声明系统自动完成数据源注册和工具声明生成。这样业务方不需要懂 Agent 的内部机制也能自己接入一个小数据源。三是把 Agent 调用数据源的行为再上一层“决策可视化”。我准备把每次调用的链路做成一个可回放的记录用户问了什么、Agent 选了哪几个工具、每个工具返回了多少数据、最后怎么生成的回复全部可追踪。这样出了问题不仅开发人员能排查业务人员也能理解“为什么 Agent 给出这个答案”。我个人在实际操作中最深的体会是Agent 接数据源技术难点从来不在“调通 API”上而在“把工具描述写好、把适配层做干净、把权限和缓存想清楚”这些容易被忽视的环节上。每一步看似琐碎连起来就是整个系统的下限。最后再分享一个小技巧每次新增一个数据源之前先不要急着写代码。花十分钟把工具描述写出来然后用一个固定的测试问题集去跑一遍线上的 Agent看看模型能不能每次都选对。这个前置检查做了之后后面上线出问题的概率能少七成。我在实践中发现提前五分钟做这个动作比事后排查一个小时更划算而且数据源接入量越大这个动作越值钱。
阅读完成 · 觉得有帮助?