零配置注册、插件架构、Pipeline流水线、MCP工具协议
新增Agent只需要两步:
零配置Agent注册:
Step 1: 实现Agent接口 + @Component
┌─────────────────────────────────────────────┐
│ @Component │
│ public class MyNewAgent extends AbstractLlmAgent { │
│ │
│ @Override public String getId() { return "my-agent"; } │
│ @Override public String getName() { return "我的Agent"; } │
│ @Override public String getDescription() { ... } │
│ @Override public String getSystemPrompt() { ... } │
│ @Override │
│ public Set<AgentCapability> getCapabilities() { │
│ return Set.of(MODEL_SELECTION, PLAN_MODE); │
│ } │
│ } │
└─────────────────────────────────────────────┘
Step 2: 重启应用 (或通过Plugin API动态注册)
┌─────────────────────────────────────────────┐
│ AgentRegistry 自动发现: │
│ │
│ @Component │
│ AgentRegistry(List<Agent> agents) { │
│ // Spring自动注入所有Agent实现 │
│ agents.forEach(a -> map.put(a.getId(), a));│
│ } │
│ │
│ ✅ 新Agent自动出现在前端Agent列表 │
│ ✅ 自动获得SSE流式响应 │
│ ✅ 自动获得安全防护 │
│ ✅ 自动获得能力驱动的UI组件 │
└─────────────────────────────────────────────┘
支持运行时动态注册/卸载Agent:
插件系统架构:
PluginManager
│
├── loadPlugin(jarPath/markdownPath)
│ ├── 从JAR或Markdown文件加载Agent定义
│ ├── PluginAgent 包装为标准Agent接口
│ └── AgentRegistry.registerAgent(pluginAgent)
│
├── unloadPlugin(agentId)
│ ├── AgentRegistry.unregisterAgent(agentId)
│ └── 清理资源
│
└── PluginController (REST API)
├── POST /plugins/load 加载插件
├── DELETE /plugins/{id} 卸载插件
└── GET /plugins 列出已加载插件
MarkdownPromptAgent:
┌──────────────────────────────────┐
│ # 翻译助手 │
│ │
│ ## 系统提示词 │
│ 你是一个专业的翻译助手... │
│ │
│ ## 配置 │
│ - id: translate │
│ - capabilities: MODEL_SELECTION │
└──────────────────────────────────┘
→ 自动解析为可运行的Agent
多阶段Agent处理流水线,支持PROCESSOR(处理器)和REVIEWER(审核者):
AgentPipelineExecutor:
Pipeline: [Processor1 → Processor2 → Reviewer1 → Reviewer2]
输入: "帮我优化这份简历"
│
▼
┌──────────────────────────────────────────┐
│ Processor Stage 1: 简历解析 │
│ 输出: 结构化简历数据 │
│ → 累积输出传递给下一阶段 │
├──────────────────────────────────────────┤
│ Processor Stage 2: 问题分析 │
│ 输入: [前序输出] + 原始请求 │
│ 输出: 优化建议列表 │
├──────────────────────────────────────────┤
│ Reviewer Stage 1: 质量审核 │
│ 解析: SCORE: 85, REASONING: ... │
│ 通过阈值: 60分 │
├──────────────────────────────────────────┤
│ Reviewer Stage 2: 合规审核 │
│ 解析: SCORE: 92 │
└──────────────────────────────────────────┘
│
▼
最终结果: min(85, 92) = 85分, 全部输出, 问题列表
Hook生命周期:
BEFORE_AGENT_EXECUTION ← 执行前 (可阻止执行)
│
ON_SYSTEM_PROMPT ← 系统提示词构建后 (可修改提示词)
│
BEFORE_LLM_CALL ← LLM调用前 (可修改消息)
│
[LLM调用]
│
AFTER_LLM_CALL ← LLM调用后 (可处理响应)
│
AFTER_AGENT_EXECUTION ← Agent执行完成后 (异步,不阻塞)
Hook接口:
┌──────────────────────────────────────┐
│ public interface AgentHook { │
│ Set<HookType> supportedTypes(); │
│ int getOrder(); // 执行顺序 │
│ boolean isAsync(); // 是否异步 │
│ void execute(HookContext ctx); │
│ } │
└──────────────────────────────────────┘
应用场景:
· 日志记录Hook: AFTER_LLM_CALL记录每次调用
· 限流Hook: BEFORE_AGENT_EXECUTION检查配额
· 内容审核Hook: AFTER_LLM_CALL审核输出
· 统计Hook: AFTER_AGENT_EXECUTION更新指标
MCP(Model Context Protocol)是标准化的工具调用协议:
MCP Client架构:
AbstractLlmAgent
│ 工具调用
▼
McpClientManager
│
├── 工具注册表:
│ logicalName → McpServer (首个注册优先)
│ "server.tool" → McpServer (限定名)
│
├── 连接管理:
│ · connect(server) → 建立连接
│ · disconnect(serverId) → 断开连接
│ · 自动重连 (60s间隔)
│
└── 工具执行:
· tools/list → 发现可用工具
· tools/call → 执行工具
· 支持SSE流式工具结果
传输方式:
┌──────────┬──────────┬──────────┐
│ SSE │ HTTP │ Stdio │
│ (常用) │ (备选) │ (本地) │
└──────────┴──────────┴──────────┘
| 前缀 | 路由目标 | 示例 |
|---|---|---|
skill_ | SkillToolProvider | skill_search → 内置搜索技能 |
shell_ | ShellToolProvider | shell_ls → 执行Shell命令 |
nos_ | NosUploadToolProvider | nos_upload → 上传到对象存储 |
| 其他 | McpClientManager | web_search → MCP工具 |
工具审批流程 (HITL):
LLM决定调用工具: shell_rm("/tmp/old_data")
│
▼
shouldPauseForApproval() → true (高风险工具)
│
▼
创建审批工单 → ToolApprovalService
│
├── 发送 SSE事件: tool_approval_required
│ {toolName, arguments, approvalId}
│
▼
前端显示审批对话框:
┌─────────────────────────────────┐
│ ⚠️ 工具执行审批 │
│ │
│ 工具: shell_rm │
│ 参数: /tmp/old_data │
│ │
│ [✅ 批准] [❌ 拒绝] │
└─────────────────────────────────┘
│
├── 批准 → waitForDecision()返回
│ → 执行工具
│ → SSE: tool_approval_result(approved)
│
└── 拒绝 → 工具不执行
→ LLM收到"用户拒绝"消息
→ 调整后续行为
List<Agent> 参数,Spring自动收集所有实现了Agent接口且标注了@Component的Bean,注入到这个List中。注册器遍历List,以agentId为Key存入ConcurrentHashMap。新增Agent只需实现接口+@Component,Spring自动发现,无需手动注册。
VoiceAgent 提供语音输入 → 文本 → Agent → 语音输出的完整通道,支持 ASR(语音识别)和 TTS(语音合成)能力。
VoiceAgent 语音通道架构:
┌──────────┐ ┌────────────────────────┐ ┌──────────┐
│ 客户端 │────▶│ VoiceChannelController │────▶│ Agent │
│ (音频/文本) │◀────│ /api/voice/* │◀────│ 系统 │
└──────────┘ └────────┬───────┬───────┘ └──────────┘
│ │
┌─────▼──┐ ┌──▼─────┐
│ ASR │ │ TTS │
│ Service│ │ Service│
└────────┘ └────────┘
前端入口:
· 对话页面输入栏 🎤 录音按钮(仅语音服务可用时显示)
· 点击录音 → 脉动红点 + 计时器 → 自动停止(120s)
· 录音完成 → 自动调用 ASR → 识别文本填入输入框
· 可用性检测: GET /api/voice/voices 探测
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/voice/chat | 语音对话(音频输入 → Agent → 文本/音频输出) |
| POST | /api/voice/asr | 语音转文本(Whisper API) |
| POST | /api/voice/tts | 文本转语音(6种音色,0.25x~4.0x语速) |
| GET | /api/voice/voices | 获取可用音色列表 |
@ConditionalOnProperty 独立控制,OpenAI 实现会覆盖 NoOp 占位 Bean。例如 ASR 用 OpenAI Whisper,TTS 用 Edge 或阿里云。
base-url 即可切换到 OpenAI 兼容服务(如阿里云 DashScope),无需改代码。
hub.voice.asr-provider 和 hub.voice.tts-provider 分别配置。采用 @ConditionalOnProperty 条件装配,NoOp 占位 Bean 保证即使不配置也不报错。新增提供商只需实现接口 + 加条件注解,零侵入。
GET /api/voice/voices 探测请求,有返回则显示 🎤 按钮,无返回则隐藏。这种"能力探测"模式避免了硬编码配置,后端启停语音服务时前端自动适应。
DataAgent 是基于 NL2SQL(Natural Language to SQL)的数据分析 Agent,用户用自然语言提问,Agent 自动将问题转为 SQL 查询并解读结果。无需编写 SQL,对话即可查数据。
DataAgent 架构:
DataAgent (extends AbstractLlmAgent)
│
│ LLM 推理时自动调用工具
│
▼
NL2SqlToolProvider (@Component, @ConditionalOnBean(DataSource.class))
│
├── list_tables() → DatabaseMetaData.getTables()
├── describe_table() → DatabaseMetaData.getColumns() + getIndexInfo()
└── execute_query() → Statement.executeQuery() (只读, 超时30s)
│
▼
DataSource (JDBC — MySQL/PostgreSQL/Oracle/...)
| 工具名 | 说明 | 参数 |
|---|---|---|
list_tables | 获取数据库中所有可用的表名及注释,了解数据库全貌 | 无 |
describe_table | 获取指定表的列结构(列名、类型、可空、注释)、索引信息和前 3 行示例数据 | tableName |
execute_query | 执行 SQL SELECT 查询,自动限制最多返回 100 行,禁止 DML/DDL | sql |
NL2SQL 执行流程:
用户: "最近7天注册了多少用户?"
│
▼
1. 理解需求 — Agent 分析查询意图
│
▼
2. 了解 Schema
├── 调用 list_tables → 发现 users 表
└── 调用 describe_table("users") → 获取列结构
│
▼
3. 生成 SQL
SELECT COUNT(*) FROM users
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
│
▼
4. 执行查询
调用 execute_query(sql) → 只读模式 + 超时30s
│
▼
5. 解读结果
"最近7天共注册了 42 位新用户。"
+ 附上 SQL 代码块 + 数据表格
| 防护层 | 措施 | 实现 |
|---|---|---|
| SQL 白名单 | 仅允许 SELECT 语句 | 必须以 SELECT 开头 |
| 禁止关键词 | 拦截 INSERT/UPDATE/DELETE/DROP/ALTER/CREATE/TRUNCATE/GRANT/REVOKE/EXEC/CALL | 单词边界正则匹配 \b,去除引号内容后检查 |
| 表名校验 | 防止 describe_table 参数注入 | 正则 ^[a-zA-Z_][a-zA-Z0-9_]*$ |
| 只读连接 | 数据库层面禁止写操作 | Connection.setReadOnly(true) |
| 敏感脱敏 | password/secret/token/key 等列值 | 自动替换为 [REDACTED] |
| 结果限制 | 防止大量数据返回 | 最多 100 行,自动添加 LIMIT |
| 超时控制 | 防止慢查询 | 查询 30s 超时,Agent 120s 超时 |
list_tables → describe_table 工具获取真实 Schema(包括列名、类型、注释、示例数据),LLM 能生成准确的 SQL,大幅减少查询失败率。