29 - AgentSpec与新型Agent

声明式Agent规格、手动 reload、DataAgent(NL2SQL)与语音通道

一、AgentSpec 声明式配置

为什么需要 AgentSpec?

传统方式定义 Agent 需要编写 Java 代码(@Component + 继承 AbstractLlmAgent)。AgentSpec 提供声明式 YAML/JSON 规格,当前重点是规格解析、校验、配置合并和管理端维护,适合作为 Agent 配置治理与后续动态 Agent 化的基础。

核心价值:把 Agent 的名称、描述、提示词、能力、模型等元数据沉淀为可审查的规格文件。面试时应讲成"声明式规格管理",不要讲成已经完整实现的文件变更自动上线新 Agent。

YAML Spec 格式示例

内置示例文件:项目在 hub-api/src/main/resources/agent-specs/ 下提供了 2 个开箱即用的示例: translator-agent.yml(翻译助手)和 writer-agent.yml(写作助手), 展示了声明式 Agent 的完整字段用法(含 display 前端元数据)。 启用 agent.defaults.agent-spec.enabled=true 后会在启动时扫描、解析并合并到运行配置。

# agent-specs/deep_research.yml
id: "deep_research"
name: "深度研究Agent"
description: "执行多步骤深度调研任务"
avatar: "research"
systemPrompt: |
  你是专业研究分析师...
tools: [web_search, web_reader, note_taking]
capabilities: [HISTORY_CONTEXT, MODEL_SELECTION, SOURCE_CITATION]
timeoutSeconds: 300
maxRetries: 1
modelId: null

Spec 字段说明

字段必填说明默认值
idAgent 唯一标识,全局不可重复-
nameAgent 显示名称-
modelId使用的 LLM 模型 ID系统默认
timeoutSeconds单次请求超时时间300
maxRetries最大重试次数1
systemPrompt系统提示词(支持多行)-
tools工具列表(匹配 ToolProvider 注册名)[]
capabilities能力声明,使用 AgentCapability 枚举名[HISTORY_CONTEXT]
display前端展示元数据默认显示配置

二、加载与手动 reload 机制

启动加载流程

  AgentSpec 启动加载流程:

  Application Startup
       │
       ▼
  ┌─────────────────────────────────────────────────────┐
  │ ① Startup Scan                                      │
  │   扫描 agent.defaults.agent-spec.spec-dir           │
  │   默认 classpath:agent-specs/                       │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ② Parse YAML                                        │
  │   SnakeYAML 解析 → AgentSpec POJO                   │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ③ Validate                                          │
  │   ├── 必填字段检查:id / name / systemPrompt        │
  │   ├── timeoutSeconds 合法性                         │
  │   └── 能力枚举尽量转换,非法值会被忽略              │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ④ Merge Config                                      │
  │   合并到 AgentReactProperties.agents                │
  │   作为运行时配置来源之一                            │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ⑤ Runtime Use                                       │
  │   被运行时配置读取;动态 Agent 注册仍是演进点       │
  └─────────────────────────────────────────────────────┘

手动 reload(当前实现)

管理端可创建、编辑、删除规格文件,并通过 reload 接口重新扫描规格目录;当前代码没有 AgentSpec 专用 WatchService 文件监听,也没有直接替换 AgentRegistry 中的运行中 Agent 实例。

  手动 reload 流程:

  AgentSpecView / Admin API
       │ POST /api/admin/agent-specs/reload
       ▼
  ┌─────────────────────────────────────────────────────┐
  │ ① Rescan                                           │
  │   重新扫描 spec-dir 与 classpath 示例               │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ② Reload                                            │
  │   重新解析变更的 YAML 文件                          │
  │   校验新配置的合法性                                │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ③ Refresh Cache                                     │
  │   刷新 loadedSpecs 管理端缓存                       │
  │   启动阶段由 Initializer 合并到运行配置             │
  └─────────────────────────────────────────────────────┘

配置优先级

优先级(从低到高):AgentSpec YAML < agent-config.yml 全局配置 < per-agent override(代码 Bean 定义优先级最高)
配置来源优先级说明
AgentSpec YAML最低声明式 spec 文件中的配置
agent-config.yml全局 Agent 配置文件
Per-agent override针对特定 Agent 的覆盖配置
代码 Bean 定义最高@Component 注解的 Java 代码定义

三、DataAgent(NL2SQL 自然语言查询)

核心能力

DataAgent 将用户的自然语言问题转换为 SQL 查询,自动执行并格式化返回结果。适用于业务数据查询、报表生成等场景。

安全防护措施

防护层机制说明
SQL 安全检查字符串白名单必须以 SELECT 开头,并拦截独立出现的 DML/DDL 关键词
语句白名单仅 SELECT禁止 UPDATE / DELETE / DROP / INSERT
字段脱敏列名关键词列名包含 password/secret/token/key 等时返回 [REDACTED]
查询超时30s 限制防止慢查询拖垮数据库
结果集限制最大 100 行无 LIMIT 时自动追加 LIMIT 100,并设置 Statement maxRows
只读连接Connection readOnly执行查询前设置连接只读,未单独实现 Read Replica 路由

执行流程

  DataAgent NL2SQL 执行流程:

  User: "上个月销售额最高的 5 个产品是什么?"
       │
       ▼
  ┌─────────────────────────────────────────────────────┐
  │ ① Intent Parse(意图解析)                          │
  │   识别:查询类型=聚合排序、时间=上月、实体=产品     │
  │   确认目标表:products, orders                      │
  └───────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ② SQL Generation(SQL 生成)                        │
  │   LLM + Schema 上下文 → 生成 SQL                    │
  │   SELECT p.name, SUM(o.amount) as total             │
  │   FROM orders o JOIN products p ON o.product_id=p.id│
  │   WHERE o.created_at >= '2026-03-01'                │
  │   GROUP BY p.name ORDER BY total DESC LIMIT 5       │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ③ Safety Check(安全检查)                          │
  │   ├── 白名单检查:仅 SELECT ✅                     │
  │   ├── 禁止关键词:无 DML/DDL ✅                    │
  │   ├── 自动 LIMIT:最多 100 行 ✅                   │
  │   └── 敏感列脱敏:password/token/key ✅            │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ④ Execute(只读查询)                               │
  │   conn.setReadOnly(true) → Statement → 超时 30s     │
  │   stmt.setMaxRows(100)                              │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ⑤ Result Format(结果格式化)                       │
  │   表格渲染 + 自然语言总结                           │
  │   "上月销售额 TOP5:1. 产品A (¥128万)..."          │
  └─────────────────────────────────────────────────────┘

四、语音通道(VoiceChannel)

语音处理架构

VoiceChannelController 为 Agent 系统增加语音入口,通过 ASR(自动语音识别)把音频转文本,再复用标准 Agent 调用链路;当输出模式为 audio 时,再通过 TTS 返回音频。

  VoiceChannel 全链路流程:

  🎤 用户语音输入
       │
       ▼
  ┌─────────────────────────────────────────────────────┐
  │ ① ASR(Automatic Speech Recognition)               │
  │   支持:OpenAI Whisper 兼容接口                     │
  │   当前实现:上传音频文件 → 同步转写                 │
  │   输出:"帮我查一下今天的天气"                       │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ② Text Processing                                   │
  │   ASR 文本 → 等同文本输入                           │
  │   通过 ChannelServiceSupport 复用 Agent 调用链路    │
  │   进入标准 Agent 执行流程                           │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ③ Agent Processing                                   │
  │   ReAct Loop / Graph 执行                           │
  │   聚合回复文本                                      │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  ┌─────────────────────────────────────────────────────┐
  │ ④ TTS(Text-to-Speech)                             │
  │   支持:OpenAI TTS 兼容接口                         │
  │   同步合成完整回复文本                              │
  └──────────────────┬──────────────────────────────────┘
                     ▼
  🔊 语音播放给用户

语音处理边界

阶段当前实现可继续优化
ASRMultipart 上传音频,同步调用转写 API流式 ASR / 分片上传
Agent复用标准 Agent 调用链路,聚合文本回复语音场景下的首 Token 流式回传
TTS同步合成完整回复文本分句合成 / 流式音频返回
传输HTTP API:/api/voice/chat、/asr、/tts、/voicesWebSocket 双工语音会话
面试表达:当前亮点是“语音入口复用同一套 Agent 核心链路”,不是全链路流式语音。可以把流式 ASR/TTS 和 WebSocket 双工会话作为后续优化点。

五、配置参考

# application.yml — AgentSpec 相关配置
agent:
  defaults:
    agent-spec:
      enabled: true
      spec-dir: "classpath:agent-specs/"

# DataAgent 当前安全边界在 NL2SqlToolProvider 代码常量中:
# MAX_ROWS=100, QUERY_TIMEOUT_SECONDS=30, SELECT 白名单,
# password/secret/token/key 等列名关键词脱敏。

# VoiceChannel 配置
hub:
  voice:
    enabled: true
    asr-provider: openai
    tts-provider: openai
    default-voice: alloy
    default-language: zh-CN
    max-audio-size-mb: 25
    max-text-length: 5000
    output-format: mp3
    speed: 1.0

六、面试高频问题

Q: AgentSpec reload 时,正在执行中的对话如何处理?
A: 当前 AgentSpec reload 重新扫描并刷新管理端规格缓存,不会直接替换 AgentRegistry 中正在运行的 Agent 实例。因此不存在“旧实例优雅下线、新实例接管”的已完成链路;这部分可以作为后续动态 Agent 化设计讲。
Q: NL2SQL 如何防止 SQL 注入?Agent 生成的 SQL 也可能恶意?
A: 多层防护:1. SQL 白名单(必须 SELECT 开头);2. 禁止关键词拦截 INSERT/UPDATE/DELETE/DROP 等;3. 自动 LIMIT 100 和 setMaxRows;4. 查询超时 30s;5. 敏感列名关键词脱敏;6. 连接设置 readOnly。关键:永远不信任 LLM 的输出,对待 LLM 生成的 SQL 与对待用户输入一样谨慎。
Q: 语音通道的延迟如何优化?端到端延迟目标是多少?
A: 当前实现是 HTTP 上传音频、同步 ASR、调用 Agent、可选同步 TTS。优化方向可以讲:流式 ASR、Agent 首 Token 回传、TTS 分句合成、WebSocket 双工会话。这样回答更准确:现在的亮点是通道复用与端到端链路打通,流式语音是下一阶段优化。
Q: AgentSpec 与代码定义的 Agent 如何共存?优先级冲突怎么办?
A: 明确优先级:代码 Bean > per-agent config > agent-config.yml > AgentSpec。相同 ID 冲突:代码定义优先,Spec 被忽略并记录 WARN 日志。推荐工作流:Spec 用于快速原型和运营调整,验证稳定后迁移到代码定义。两者通过 AgentRegistry 统一管理,对外暴露一致的路由接口。
Q: DataAgent 查询大表时如何防止拖垮数据库?
A: 当前代码层的硬约束是自动 LIMIT 100、setMaxRows(100)setQueryTimeout(30)、SELECT 白名单和敏感字段脱敏。EXPLAIN 全表扫描拦截、Read Replica 路由、并发闸门和表级黑名单还未在该工具中实现,适合作为生产增强项讲。
Q: 语音通道如何处理多轮对话的上下文?
A: 语音转文字后会通过 ChannelServiceSupport 进入标准 Agent 调用链路,复用用户识别和权限检查;当前 VoiceChannelController 调用时没有传入会话 ID 和历史记录,因此更接近一次语音入口的单轮 Agent 调用。它也不会自动做“200 字摘要”或“TTS 摘要模式”;如果语音回复过长,只会按配置的 maxTextLength 截断传给 TTS。面试时可把语音多轮会话和语音摘要作为后续体验优化点。
Q: AgentSpec 的 tools 字段如何与 MCP 工具对接?
A: tools 列表可以作为声明式规格的一部分被解析和校验;当前实现重点是规格加载与配置合并,并未把 Spec 直接实例化为带工具列表的运行中 Agent。若要做到“Spec 引用 MCP 工具即可上线”,还需要补动态 Agent 工厂和工具绑定链路。
Q: 如果 AgentSpec 的 YAML 格式有误,如何处理?会影响其他 Agent 启动吗?
A: 隔离加载:每个 Spec 文件独立解析,单个失败记录日志并跳过,不影响其他规格。校验重点是必填字段、ID 格式、能力枚举等基础合法性。当前管理端通过 /api/admin/agent-specs 查看已加载规格;不存在 /api/admin/agents/spec-status 这个状态端点。