从 API 到 Agent:配置驱动的动态装配,如何构建可扩展的 AI 应用
当 AI 应用只有一个模型、一个固定提示词时,直接在代码中初始化客户端通常已经足够。但当系统需要支持多个模型供应商、多个知识库、不同的 MCP 工具组合,以及“写博客、修订文章、发布内容”等多类任务时,把这些能力全部写死在代码中,会很快失去可维护性。
更通用的思路是:将 Agent 看作由配置驱动的装配结果,而不是写死在某个 Controller 或 Service 中的一段调用代码。
一、问题的本质:不是动态调用,而是动态装配
很多人会把这类设计理解成“动态调用 API”。其实 API 调用只是其中一个环节。真正发生的是下面这条链路:
读取配置 → 创建组件 → 注册或缓存组件 → 组合为 Agent → 执行任务
配置可以来自数据库、配置中心或管理后台。例如,一套“技术博客助手”可以配置为:
模型:某个兼容 OpenAI 协议的模型服务
知识库:产品文档与历史文章
工具:联网检索、创建草稿、更新文章、发布文章
角色:技术编辑
规则:发布前必须人工确认
另一套“客服助手”则可以使用不同模型、不同知识库和不同工具。两者无需分别开发一套业务代码,只需在装配时选择不同配置。
二、区分四个核心对象
要让系统可控,首先要把几个容易混在一起的概念拆开。
| 对象 | 职责 | 示例 |
|---|---|---|
| Agent 定义 | 描述一类任务如何完成 | blog-publish-v3 |
| Agent 运行实例 | 记录某一次具体任务 | “生成一篇 MCP 入门文章” |
| Client | 连接某种外部能力的对象 | OpenAiApi、BlogCmsClient |
| MCP Server | 对外部系统能力的安全封装 | 搜索、CMS、知识库服务 |
Agent 定义是蓝图,Client 是零部件,Agent 运行实例则是一次真实生产任务。
三、为什么要动态创建 Client
以模型 API 为例,数据库中可以保存:
apiId: 1001
baseUrl: https://api.example.com
apiKey: ******
chatPath: /v1/chat/completions
embeddingPath: /v1/embeddings
装配器读取配置后创建客户端:
OpenAiApi api = OpenAiApi.builder()
.baseUrl(config.getBaseUrl())
.apiKey(config.getApiKey())
.completionsPath(config.getChatPath())
.embeddingsPath(config.getEmbeddingPath())
.build();
随后以稳定的名字保存或注册,例如 ai_client_api_1001。后续构建聊天模型、Embedding 模型时,只需要按 ID 找到这个 Client。
这套方法不局限于大模型接口。任何“由配置决定、需长期复用、数量可控”的外部能力都可以采用相同机制:
| 能力 | 可动态创建的对象 |
|---|---|
| 博客平台 | BlogCmsClient |
| 搜索服务 | SearchClient |
| 知识库 | KnowledgeBaseClient |
| 通知系统 | EmailClient、SlackClient |
| 对象存储 | S3Client、MinioClient |
四、一次 Agent 装配应如何运行
一个典型装配过程可以分为五步。
1. 加载配置
根据 agentId 或 clientId 查询模型、接口、提示词、顾问能力、工具和权限策略。查询可以并行,但结果应汇总到本次装配上下文中。
这个上下文只是本次装配的“零件箱”,不适合作为长期运行对象的存储位置。
2. 创建基础 Client
先创建模型 API Client、CMS Client、搜索 Client 等基础对象。对于 Spring 应用,可以将这类对象动态注册为 Bean;对于其他技术栈,也可以保存在受版本管理的 Client Registry 或连接池中。
3. 组装模型与工具
在 API Client 之上构建 Chat Model、Embedding Model,并连接 MCP Server。此时还应限定这套 Agent 可使用的工具范围:
blog-publish-v3 可使用:
- search_articles
- create_post_draft
- publish_post
- record_blog_metadata
工具白名单是权限边界的一部分,而不是提示词里的建议。
4. 组装角色、提示词和上下文
系统提示词、文章写作规范、用户请求、检索结果、记忆和知识库片段,会被组合成一次模型请求。模型在需要时发起工具调用,后端执行工具并将结果回传给模型,直到产生最终的结构化结果。
5. 创建运行记录并执行
每次执行都应产生 AgentRun:记录所用 Agent 版本、输入、工具调用、审批状态、输出与失败原因。这样既便于重试,也便于审计。
五、博客场景的推荐工作流
不要让一个万能 Agent 同时随意生成、修改和发布。将副作用不同的任务拆开,系统会可靠得多。
生成草稿:检索资料 → 生成文章 → 创建草稿
修订文章:读取文章 → 根据反馈修改 → 保存新版本
确认发布:校验用户确认 → 发布 → 保存文章元数据
发布是高风险动作。模型可以提出“调用 publish_post”,但不能凭一句提示词就拥有发布权。应由后端策略强制要求人工确认:
draft → pending_approval → published
只有用户在界面中确认后,后端才签发一次性确认令牌,并允许发布工具执行。
六、动态注册 Bean 的边界
在 Spring 中,动态注册 Bean 很适合配置驱动的组件装配,但不应把一切对象都注册进容器。
适合动态注册的对象:
- 按 Agent 或平台区分的模型、CMS、搜索 Client;
- 生命周期较长且可复用的连接对象;
- 数量有限、名称明确、可审计的组件。
不适合动态注册的对象:
- 每一次请求的临时数据;
- 每个用户各不相同且数量巨大的会话对象;
- 未校验的用户输入配置;
- 需要频繁热替换的已被其他单例对象注入的依赖。
后者通常应放在请求上下文、缓存或数据库中。动态替换 Bean 后,已经注入旧对象的单例未必会自动获得新依赖;生产系统更稳妥的方式是按 agentId + version 从注册表获取正确组件。
七、安全与工程化原则
要让配置驱动的 Agent 能进入生产环境,至少要坚持以下规则:
- 凭证只保存在服务端,浏览器和模型都不直接持有 API Key。
- MCP 工具参数使用严格 Schema,禁止提供“任意路径写文件”之类的宽泛工具。
- 发布、删除、付款等动作必须经后端策略校验和人工确认。
- 每次运行使用幂等键,避免任务重试造成重复发布。
- 配置、提示词和工具集合都要版本化,确保任务可复现、可追踪。
结语
动态装配的价值,不在于让模型“更自由”,而在于让系统能以可配置、可治理的方式组合能力。
当模型、知识库、MCP 工具、提示词和权限策略都被当作可装配组件时,一个 AI 应用就可以从单一聊天窗口,演进为支持多 Agent、多任务和多平台发布的业务系统。模型负责理解与生成;代码负责权限、流程、状态和可靠性。两者各司其职,才能真正把 Agent 从演示功能做成可长期运行的产品。