1. 从一个真实的技术选型困境说起
去年下半年我接手了一个 AI Data Agent 项目,目标很明确:让业务人员用自然语言就能完成数据查询、报表生成、异常归因这一整套分析流程。项目启动会上,团队里两拨人吵得不可开交。一拨主张直接上通用 Agent Runtime 加 Plugin 的组合,理由是灵活、可控、不绑定任何厂商;另一拨坚持用现成的 Agent Framework,理由是开发快、生态全、少造轮子。这个争论持续了将近两周,最后我们决定两条路都做原型验证,用数据说话。
这篇文章就是那次选型过程的完整复盘。我会把通用 Agent Runtime + Plugin 和 Agent Framework 这两条路线的核心差异、适用边界、实操细节、踩过的坑全部摊开来讲。如果你正在做 AI Data Agent、智能客服、自动化运维助手这类项目,或者你只是单纯在纠结 Agent 技术选型该往哪个方向走,这篇内容应该能帮你省下不少试错时间。
先说结论性的判断:这两条路线不是非此即彼的关系,而是对应了不同的项目阶段和团队能力模型。选错了不会立刻死,但会在三到六个月后集中爆发问题——要么是扩展性撞墙,要么是维护成本失控。下面我从架构本质开始拆。
2. 两条路线的架构本质与核心差异
2.1 通用 Agent Runtime + Plugin 到底是什么
通用 Agent Runtime 本质上是一个执行引擎。它负责管理 Agent 的生命周期、调度任务、维护上下文状态、处理工具调用请求,但它本身不包含任何具体的业务能力。所有的业务能力都通过 Plugin 的形式挂载进来。
你可以把它理解成一台电脑的操作系统:Runtime 是内核,Plugin 是驱动程序。内核提供进程调度、内存管理、IO 这些基础能力,但具体能干什么取决于你装了什么驱动。这种设计的好处是极致的解耦——Runtime 可以独立升级,Plugin 可以按需插拔,不同团队可以并行开发不同的 Plugin 而互不干扰。
在 AI Data Agent 的场景里,这意味着什么呢?数据源连接是一个 Plugin,SQL 生成是一个 Plugin,图表渲染是一个 Plugin,异常检测算法是一个 Plugin。每个 Plugin 只需要实现 Runtime 定义好的接口规范,注册进去就能被 Agent 调用。Runtime 不关心你用的是 MySQL 还是 ClickHouse,不关心你画的是折线图还是热力图,它只负责在正确的时机把正确的请求路由到正确的 Plugin。
这种架构的核心优势在于可控性。每一个 Plugin 的输入输出都是显式定义的,调用链路是可追踪的,出了问题能精确定位到是哪个环节。对于数据类应用来说,这一点极其重要——数据查询的准确性、权限控制、审计日志,这些都不能含糊。
2.2 Agent Framework 的定位与能力边界
Agent Framework 则是一个全栈式解决方案。它通常包含了 Runtime 的能力,同时预置了大量的工具集成、记忆管理、规划策略、多 Agent 协作机制。你拿到手就是一个能跑的东西,只需要配置一下 LLM 的 API Key,写几个工具函数,就能搭出一个能用的 Agent。
Framework 的设计哲学是约定优于配置。它替你做了大量的技术决策:用什么方式管理对话历史、怎么拆分任务、如何选择工具、失败后怎么重试。这些决策在大多数场景下是合理的,能帮你快速跑通流程。
但问题也恰恰出在这里。当你的业务逻辑足够复杂、对性能和可控性有极致要求时,Framework 的"黑盒"部分就会变成障碍。比如你想自定义一个任务规划策略,Framework 可能只暴露了有限的配置项,你没法深入到它的规划器内部去改逻辑。再比如你想对某个工具调用做精细的权限控制,Framework 的抽象层可能让你找不到合适的切入点。
2.3 核心差异对照表
| 维度 | 通用 Agent Runtime + Plugin | Agent Framework |
|---|---|---|
| 架构定位 | 执行引擎,能力靠 Plugin 扩展 | 全栈方案,开箱即用 |
| 开发速度 | 前期慢,需要定义接口和规范 | 前期快,配置即可运行 |
| 可控性 | 极高,每个环节可定制 | 中等,受限于框架抽象层 |
| 扩展性 | 强,Plugin 可独立开发部署 | 受框架设计约束 |
| 维护成本 | 前期高,后期低(解耦好) | 前期低,后期可能高(耦合深) |
| 团队要求 | 需要架构设计能力 | 需要熟悉框架用法 |
| 适用阶段 | 中大型项目、长期迭代 | 原型验证、中小项目 |
| 技术锁定 | 低,Runtime 可替换 | 高,迁移成本大 |
这张表是我在实际项目中反复验证后总结的,但我要强调一点:没有绝对的好坏,只有匹配与否。接下来我详细拆解每个维度的深层逻辑。
3. AI Data Agent 场景下的需求拆解
3.1 数据类 Agent 的特殊性在哪里
AI Data Agent 和通用的聊天 Agent 有本质区别。聊天 Agent 的核心是生成流畅的文本,偶尔调用一两个工具查查天气、搜搜网页。而 Data Agent 的核心是准确执行数据操作,文本生成只是交互的外壳。
具体来说,Data Agent 面临几个独特挑战。第一是数据准确性要求极高。用户问"上个月华东区的销售额是多少",Agent 生成的 SQL 必须精确无误,字段名、过滤条件、聚合方式都不能错。一旦出错,用户看到的就是错误的数据,这比不回答还糟糕。
第二是权限控制复杂。不同用户能访问的数据范围不同,有的只能看自己部门的数据,有的能看全公司的。Agent 在执行查询前必须做权限校验,而且这个校验要贯穿到每一个数据操作环节。
第三是多步骤推理链长。一个完整的分析任务可能包含:理解用户意图、确定数据源、生成查询语句、执行查询、处理结果、生成图表、撰写分析结论。每一步都可能出错,每一步都需要可观测、可干预。
第四是性能敏感。数据查询本身可能耗时数秒甚至数十秒,Agent 的调度开销不能成为瓶颈。如果 Framework 在每次工具调用时都做大量的上下文序列化和反序列化,累积起来就是可观的延迟。
3.2 哪些能力必须自建,哪些可以复用
基于上面的分析,我在项目中做了一个能力分层:
必须自建的核心能力包括:数据源连接池管理、SQL 安全校验与权限过滤、查询结果缓存策略、敏感数据脱敏、审计日志记录。这些直接关系到数据安全和合规,不能依赖第三方框架的实现。
可以复用现成方案的能力包括:LLM 调用封装、对话历史管理、基础的提示词模板、通用的重试与降级逻辑。这些是通用能力,Framework 通常做得比自建更好。
需要权衡的能力包括:任务规划策略、工具选择逻辑、多轮对话状态管理。这些能力 Framework 有现成实现,但可能需要定制。如果定制需求超过 30%,自建的性价比就更高。
这个分层思路直接影响了我们的选型决策:核心数据能力自建,通用能力复用,中间地带根据定制深度决定。
3.3 一个典型的 Data Agent 任务流
让我用一个具体例子说明 Data Agent 的完整任务流,这样后面的技术选型分析才有落脚点。
用户输入:"帮我看看最近三个月哪些产品的退货率超过了 5%,按退货率从高到低排个序。"
这个请求看似简单,实际执行链路是这样的:
- 意图识别:判断这是一个数据查询+分析任务,不是闲聊
- 实体抽取:时间范围(最近三个月)、指标(退货率)、阈值(5%)、排序方式(降序)
- 数据源定位:退货率数据在哪个库、哪张表、哪些字段
- 权限校验:当前用户有没有权限访问退货数据
- SQL 生成:构造查询语句,注意时间函数、除法精度、NULL 处理
- SQL 校验:语法检查、危险操作拦截(比如防止全表扫描)
- 查询执行:连接数据库执行,处理超时和异常
- 结果处理:格式化数据,计算派生指标
- 可视化:选择合适的图表类型渲染
- 结论生成:用自然语言总结发现
这 10 个步骤里,第 3、4、5、6、7 步是 Data Agent 特有的,也是技术选型时最需要关注的。通用 Framework 在这些环节往往只能提供基础的函数调用能力,具体的校验逻辑、权限模型、错误处理都需要自己实现。
4. 通用 Agent Runtime + Plugin 的实操落地
4.1 Runtime 的核心接口设计
如果你决定走 Runtime + Plugin 路线,第一件事是定义好 Runtime 和 Plugin 之间的接口契约。这个契约设计得好不好,直接决定了后续扩展的顺畅程度。
我在项目中定义的 Plugin 接口包含四个核心方法:
class BasePlugin: def name(self) -> str: """插件唯一标识""" pass def description(self) -> str: """插件功能描述,供 LLM 理解何时调用""" pass def parameters_schema(self) -> dict: """参数 JSON Schema,用于校验和生成提示词""" pass def execute(self, params: dict, context: ExecutionContext) -> PluginResult: """实际执行逻辑""" pass这四个方法看似简单,但每个都有讲究。name必须全局唯一且语义清晰,因为 LLM 在选择工具时依赖这个名字。description的写法直接影响 LLM 的工具选择准确率,我后面会专门讲怎么写好它。parameters_schema用 JSON Schema 定义,既用于参数校验,也用于自动生成给 LLM 的工具描述。execute方法接收一个ExecutionContext,里面包含了用户身份、会话状态、超时配置等运行时信息。
ExecutionContext的设计是关键。它需要传递足够多的上下文让 Plugin 做出正确决策,但又不能太重导致每次调用都有性能开销。我的做法是把上下文分成两层:不可变层(用户 ID、租户 ID、权限令牌)和可变层(会话历史、中间结果缓存)。不可变层在会话创建时确定,可变层按需加载。
4.2 Plugin 的注册与发现机制
Plugin 怎么被 Runtime 发现?最简单的做法是启动时扫描指定目录下的所有 Plugin 模块,自动注册。但生产环境需要考虑更多:Plugin 的版本管理、热加载、灰度发布、故障隔离。
我采用的是注册中心 + 本地缓存的方案。每个 Plugin 打包成独立的 Python 包,通过入口点(entry point)声明自己。Runtime 启动时从注册中心拉取 Plugin 列表,按需加载。这样做的额外好处是支持多版本共存——A 租户用 Plugin v1,B 租户用 v2,互不影响。
# setup.py 中声明入口点 entry_points={ 'data_agent.plugins': [ 'sql_query = plugins.sql_query:SQLQueryPlugin', 'chart_render = plugins.chart_render:ChartRenderPlugin', ] }热加载的实现要小心。Python 的模块导入机制对重复导入有缓存,直接 reload 可能导致状态不一致。我的做法是每个 Plugin 在独立进程中运行,通过 IPC 通信。这样虽然增加了一点通信开销,但换来了彻底的故障隔离——一个 Plugin 崩溃不会拖垮整个 Runtime。
4.3 工具描述怎么写才能让 LLM 选对
这是我在实践中踩坑最多的地方。Plugin 的description不是写给人看的文档,是写给 LLM 看的"使用说明书"。写得好,LLM 选工具的准确率能从 70% 提升到 95% 以上。
几个关键原则。第一,明确使用场景和边界。不要写"执行 SQL 查询",要写"当用户需要从结构化数据库中查询数据时使用。不适用于非结构化文本检索,不适用于实时流数据"。第二,说明前置条件。比如"调用前需要确保已通过数据源连接插件建立了连接"。第三,给出参数示例。LLM 对示例的敏感度远高于抽象描述。
我整理了一个对比表,展示优化前后的效果差异:
| 描述写法 | LLM 选择准确率 | 典型错误 |
|---|---|---|
| "查询数据" | 62% | 该用图表插件时选了查询插件 |
| "从数据库查询结构化数据,返回结果集" | 78% | 参数格式错误 |
| "从已连接的关系型数据库执行 SELECT 查询。输入:SQL 语句字符串。输出:JSON 格式结果集。仅用于读取,禁止写操作。示例:{'sql': 'SELECT * FROM sales LIMIT 10'}" | 94% | 偶尔在复杂嵌套查询时出错 |
差距非常明显。写工具描述这件事,值得花时间反复打磨。
4.4 状态管理与上下文传递
Runtime 需要维护 Agent 的执行状态。在 Data Agent 场景里,状态包括:当前对话历史、已执行的操作序列、中间结果、待确认的决策点。
我的设计是把状态分成会话级和任务级。会话级状态跨多轮对话保持,比如用户之前查过什么数据、偏好什么图表类型。任务级状态只在单次任务执行期间存在,任务结束就销毁。
状态存储用 Redis 做热存储,PostgreSQL 做冷存储。热存储保存最近 30 分钟的活跃会话,冷存储归档历史会话用于审计和分析。这个分层让高频访问的状态读取延迟控制在毫秒级,同时保证了数据的持久性。
上下文传递有个容易忽略的坑:上下文膨胀。如果每轮对话都把完整历史塞进提示词,token 消耗会快速增长,而且 LLM 的注意力会被无关信息稀释。我的做法是做上下文压缩——保留最近 3 轮完整对话,更早的对话压缩成摘要。摘要由 LLM 生成,保留关键实体和决策点。
5. Agent Framework 的选型与集成实践
5.1 主流 Framework 的能力对比
市面上的 Agent Framework 不少,我实际深度使用过三四个,各有侧重。选型时不能只看功能列表,要看架构理念是否匹配你的场景。
有的 Framework 强在多 Agent 协作,内置了角色定义、消息传递、协商机制,适合做复杂的模拟和博弈场景。有的强在工具生态,预置了几百个工具集成,适合快速搭建通用助手。有的强在可观测性,提供了完整的调用链追踪和调试面板,适合对可维护性要求高的团队。
对于 AI Data Agent,我重点关注三个能力:工具调用的可靠性(能不能稳定地按格式输出参数)、状态管理的灵活性(能不能自定义状态结构)、错误处理的细粒度(能不能针对不同错误类型做不同处理)。
5.2 用 Framework 快速搭建 Data Agent 原型
假设你选定了某个 Framework,搭建一个 Data Agent 原型的步骤大致如下。
第一步,定义工具函数。把数据查询、图表生成、统计分析这些能力包装成 Framework 要求的工具格式。大多数 Framework 支持从函数签名和 docstring 自动生成工具描述,这能省不少事,但自动生成的描述质量参差不齐,关键工具还是建议手写描述。
第二步,配置 Agent 的规划策略。Framework 通常提供几种预设策略:ReAct(推理+行动交替)、Plan-and-Execute(先规划再执行)、Reflexion(带反思的迭代)。Data Agent 场景我推荐 Plan-and-Execute,因为数据分析任务通常步骤明确,先规划能避免 LLM 在执行过程中跑偏。
第三步,接入记忆模块。Framework 一般自带对话记忆,但 Data Agent 需要的是结构化记忆——记住用户查过哪些表、用过哪些过滤条件、偏好什么时间粒度。这需要自定义记忆结构,Framework 的默认实现往往不够用。
第四步,配置可观测性。把 Framework 的调用链追踪接入你的监控系统,记录每次 LLM 调用的输入输出、token 消耗、延迟。这些数据是后续优化的基础。
5.3 Framework 的定制化改造边界
用 Framework 最怕的是"改不动"。当你的需求超出框架设计范围时,你有三个选择:绕过框架自己实现、给框架提 PR、换框架。
我的经验是,定制化改造要控制在框架代码量的 20% 以内。超过这个比例,升级框架版本时的合并冲突会让你痛不欲生。如果确实需要深度定制,不如把那部分能力抽出来自建,通过适配器模式和框架对接。
具体到 Data Agent,我建议自建的部分包括:SQL 生成与校验、权限过滤、结果脱敏。这些逻辑和框架的核心抽象关系不大,自建反而更清晰。而对话管理、工具调度、重试逻辑这些,用框架的现成实现就好。
5.4 从 Framework 迁移到自建 Runtime 的信号
什么时候该考虑从 Framework 迁移到自建 Runtime?我总结了几个明确的信号:
- 性能瓶颈出现在框架层:profiling 显示大量时间花在框架的序列化、上下文组装上,而不是实际的 LLM 调用或数据查询
- 定制需求频繁撞墙:每个月都有两三次因为框架不支持某个需求而被迫绕路
- 升级恐惧症:团队不敢升级框架版本,因为改动太大,测试成本太高
- 多租户隔离需求:框架的全局状态设计导致租户间无法彻底隔离
出现两个以上信号,就该认真评估迁移了。迁移不是重写,而是渐进式替换——先把最痛的那部分能力抽出来自建,通过适配器让框架继续调用,逐步扩大自建范围,直到框架只剩下一层薄薄的壳。
6. 混合架构:我的最终选择与理由
6.1 为什么纯路线都走不通
经过两个月的原型验证,我的结论是:纯 Runtime + Plugin 和纯 Framework 都不适合中大型 Data Agent 项目。
纯 Runtime + Plugin 的问题在于前期投入太大。你需要自己实现对话管理、工具调度、重试逻辑、可观测性,这些通用能力重复造轮子,而且很难做得比成熟框架好。项目前三个月基本都在搭基础设施,业务价值交付慢,团队压力大。
纯 Framework 的问题在于后期扩展受限。当业务逻辑复杂到一定程度,框架的抽象层就成了障碍。你想优化某个环节的性能,发现改不动;你想实现一个特殊的权限模型,发现框架不支持;你想做精细的成本控制,发现框架的计费粒度太粗。
6.2 混合架构的分层设计
最终我采用的方案是分层混合:
底层用自建 Runtime,负责 Plugin 管理、状态存储、权限校验、审计日志。这部分是 Data Agent 的核心资产,必须完全可控。
中层用 Framework 的通用能力,通过适配器接入。对话管理、LLM 调用封装、基础的工具调度用框架的现成实现,省去重复开发。
上层用 Plugin 扩展业务能力,每个数据源、每种分析算法、每类图表都是独立 Plugin,按需加载。
这个架构的关键是适配器层。它把 Framework 的能力包装成 Runtime 认识的 Plugin 接口,让两者能无缝协作。适配器要处理协议转换、错误映射、生命周期对齐这些细节,是混合架构里最需要精心设计的部分。
6.3 关键决策点的判断标准
回顾整个选型过程,我提炼出几个关键决策点的判断标准,供你参考:
| 决策点 | 倾向 Runtime + Plugin | 倾向 Framework |
|---|---|---|
| 项目周期 | 6 个月以上 | 3 个月以内 |
| 团队规模 | 5 人以上,有架构师 | 2-3 人,全栈 |
| 定制需求 | 超过 40% 需要定制 | 80% 用现成能力 |
| 性能要求 | 毫秒级延迟敏感 | 秒级可接受 |
| 合规要求 | 强审计、强隔离 | 一般要求 |
| 技术锁定容忍度 | 低 | 高 |
这张表不是绝对的,但能帮你快速定位自己的位置。我的项目在周期、团队、定制需求三个维度都偏向 Runtime + Plugin,所以最终选择了混合架构。
6.4 成本与收益的量化对比
最后说点实在的。混合架构的初期投入比纯 Framework 高大约 40%,主要体现在架构设计和适配器开发上。但六个月后,混合架构的维护成本开始低于纯 Framework,因为扩展新能力的边际成本更低。
具体数据:纯 Framework 方案下,每新增一个数据源类型的平均开发时间是 3 人天;混合架构下是 1.5 人天。当数据源类型超过 10 种时,混合架构的总成本就反超了。对于 Data Agent 这种需要对接多种数据源、持续迭代的项目,混合架构的长期收益是明显的。
7. 实操中的常见问题与排查技巧
7.1 工具调用参数格式错误怎么破
这是最高频的问题。LLM 生成的工具调用参数不符合 JSON Schema,导致校验失败。排查思路分三步:先看是格式错误(比如该传数组传了字符串)还是语义错误(比如传了不存在的字段名);再看是提示词问题还是模型能力问题;最后决定是优化描述还是加后处理。
我的经验是,80% 的参数错误可以通过优化工具描述解决。在描述里明确参数类型、给出正确和错误的示例、说明常见陷阱。剩下 20% 需要在 Runtime 层加参数修复逻辑,比如自动把字符串数字转成数字类型、自动补全缺失的可选参数。
7.2 多轮对话状态丢失的排查路径
状态丢失通常有三个原因:存储层问题、序列化问题、并发问题。排查时先确认状态是否真的写入了存储(查 Redis 或数据库),再确认读取时 key 是否正确,最后检查是否有并发写入覆盖。
我遇到过一个隐蔽的 bug:两个并发请求同时更新同一个会话的状态,后写的覆盖了先写的。解决方案是给状态更新加乐观锁,用版本号控制。这个坑在压测时才会暴露,单机测试发现不了。
7.3 性能瓶颈的定位方法
Data Agent 的性能瓶颈可能在四个地方:LLM 调用、数据查询、Plugin 执行、状态读写。定位方法是分段计时,在每个环节的入口和出口打点,记录耗时。
我常用的工具是 OpenTelemetry,它能自动追踪跨进程、跨服务的调用链。配置好之后,一眼就能看出时间花在哪里。实测下来,大多数性能问题出在 LLM 调用上,尤其是上下文太长导致的 token 处理延迟。优化方向是压缩上下文、缓存常见查询的 LLM 响应。
7.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 工具选择错误 | 描述不清晰 | 检查 description 是否明确场景边界 | 重写描述,加示例 |
| 参数格式错误 | Schema 不严格 | 查看校验失败日志 | 优化 Schema,加后处理 |
| 状态丢失 | 并发覆盖 | 检查写入时序 | 加乐观锁 |
| 响应慢 | 上下文过长 | 分段计时 | 压缩上下文,加缓存 |
| Plugin 崩溃 | 异常未捕获 | 查看 Plugin 日志 | 加异常隔离,独立进程 |
| 权限绕过 | 校验点遗漏 | 审计日志回溯 | 在 Runtime 层统一拦截 |
7.5 几个只有踩过才知道的坑
第一个坑:Plugin 的初始化开销。有些 Plugin 在初始化时要建立数据库连接、加载模型,如果每次调用都重新初始化,性能会很差。解决方案是 Plugin 常驻内存,连接池复用。
第二个坑:LLM 的幻觉在工具调用中的表现。LLM 可能编造不存在的工具名,或者调用工具时传入看似合理但实际不存在的参数。Runtime 必须做严格的校验,不能信任 LLM 的输出。
第三个坑:超时设置的连锁反应。如果 LLM 调用的超时设得太短,会频繁触发重试,反而增加总耗时。如果设得太长,用户等待时间过长。我的经验是 LLM 调用超时设 30 秒,数据查询超时设 60 秒,整体任务超时设 5 分钟,各层超时要协调。
第四个坑:日志的隐私泄露。Data Agent 的日志里可能包含查询结果,这些数据可能敏感。日志脱敏要在写入前做,不能事后处理。我见过因为日志泄露客户数据的真实案例,代价很大。
8. 给不同阶段团队的建议
如果你还在原型验证阶段,团队两三个人,目标是快速验证想法,那就直接用 Framework。别纠结架构,先跑通流程,拿到用户反馈再说。这个阶段速度比优雅重要。
如果你已经过了验证阶段,准备做正式产品,团队有五个人以上,那就认真考虑混合架构。前期多花两周做架构设计,后面能省下几个月的返工时间。重点是把核心数据能力自建,通用能力复用。
如果你在做的是平台型产品,要支持多个业务方接入,那 Runtime + Plugin 是更合适的选择。平台的核心价值在于稳定和可扩展,这两点都需要对底层有完全的控制权。
最后分享一个我在选型时用的决策方法:列出未来 12 个月最可能出现的 10 个需求,评估每条路线满足这些需求的难度。如果超过 3 个需求在某条路线上实现起来很别扭,那条路线就不适合你。这个方法比拍脑袋靠谱得多,也能让团队在选型讨论时有共同的评估框架。
我在实际项目里最大的体会是,技术选型没有标准答案,只有匹配度。别人的最佳实践放到你的场景里可能就是灾难。多花时间理解自己的真实需求,比盲目追新更重要。