智能体风险管控
Agent风险管控Checkpoint限流重试
搭建多层执行防护体系,整合调用频次限流、指数退避重试、异常优雅降级能力,规避 Agent 循环调用与服务失控问题;基于 Checkpoint 持久化推理状态,实现复杂任务断点恢复与状态回溯
智能体风险管控
搭建多层执行防护体系,整合调用频次限流、指数退避重试、异常优雅降级能力,规避 Agent 循环调用、服务失控问题;基于 Checkpoint 持久化推理状态,实现复杂任务断点恢复与状态回溯。
一、多层防护架构总览
┌─────────────────────────────────────────────────────────────┐
│ Layer 6: HTTP 请求守卫 │
│ AdminAuthInterceptor (JWT) + PreviewModeInterceptor (只读) │
├─────────────────────────────────────────────────────────────┤
│ Layer 6: 幂等防护 (AOP) │
│ RepeatExecuteLimit → Redisson 分布式锁 + Redis 成功标记 │
├─────────────────────────────────────────────────────────────┤
│ Layer 5: 集群会话锁 (Redis Lease) │
│ Lua 原子操作: ACQUIRE / RENEW / RELEASE │
├─────────────────────────────────────────────────────────────┤
│ Layer 4: JVM 任务注册 │
│ ChatRuntimeRegistry: ConcurrentHashMap.putIfAbsent() │
├─────────────────────────────────────────────────────────────┤
│ Layer 3: Agent 循环限流 (Hooks) │
│ ModelCallLimitHook + ToolCallLimitHook │
├─────────────────────────────────────────────────────────────┤
│ Layer 2: 执行拦截器链 (Interceptors) │
│ 兼容适配 → 输入降级 → 指数退避重试 → 异常兜底 │
├─────────────────────────────────────────────────────────────┤
│ Layer 1: Checkpoint 持久化 + 断点恢复 │
│ MysqlSaver → GRAPH_CHECKPOINT / GRAPH_THREAD │
└─────────────────────────────────────────────────────────────┘
二、调用频次限流
2.1 Agent Hooks 限流(最内层)
在 ChatAgentConfiguration 中为 ReactAgent 注册两个限流 Hook:
| Hook | 单次运行上限 | 线程生命周期上限 | 超限行为 |
|---|---|---|---|
ModelCallLimitHook | 8 次 | 40 次 | END(优雅终止) |
ToolCallLimitHook (tavily_search) | 6 次 | 30 次 | END(优雅终止) |
超限行为 ExitBehavior.END 意味着不会抛异常,而是让 Agent 在当前轮次正常结束并返回已有结果,避免用户看到报错。
2.2 配置参数
在 ChatAgentProperties 中可调:
| 参数 | 默认值 | 说明 |
|---|---|---|
maxModelCallsPerRun | 8 | 单次 Agent 执行最大 LLM 调用数 |
maxModelCallsPerThread | 40 | 单个会话线程最大 LLM 调用数 |
maxToolCallsPerRun | 6 | 单次执行最大 Tavily 搜索次数 |
maxToolCallsPerThread | 30 | 单个会话线程最大 Tavily 搜索次数 |
recommendationTimeoutMs | 3000 | 推荐问题生成超时 |
2.3 分布式幂等限流
通过 @RepeatExecuteLimit 注解 + AOP 实现双层防重:
请求到达
→ Redis success flag 检查(已成功则直接返回)
→ JVM LocalLockCache 检查(本地已加锁则拒绝)
→ Redisson 公平锁获取(分布式锁)
→ 执行业务方法
→ 执行成功 → 设置 Redis success flag(durationTime TTL)
三层检查确保同一操作不会在集群中重复执行。
三、指数退避重试
3.1 ToolRetryInterceptor 配置
在 ChatAgentConfiguration 中配置:
ToolRetryInterceptor.builder()
.toolName("tavily_search")
.maxRetries(2)
.initialDelay(200L) // 初始延迟 200ms
.maxDelay(1200L) // 最大延迟 1200ms
.jitter(true) // 随机抖动,防止惊群效应
.onFailure(OnFailureBehavior.RETURN_MESSAGE)
.build()
3.2 退避策略
| 重试次数 | 退避延迟(约) | 说明 |
|---|---|---|
| 第 1 次 | ~200ms | initialDelay |
| 第 2 次 | ~400ms | initialDelay × 2 |
| 第 3 次 | ~800ms | initialDelay × 4(上限 maxDelay 1200ms) |
加上 jitter=true 随机抖动,实际延迟会在计算值上下波动,避免大量请求同时重试。
3.3 失败兜底
OnFailureBehavior.RETURN_MESSAGE —— 全部重试失败后,返回一段用户可读的消息而非抛出异常,保证对话不中断。
四、异常优雅降级
4.1 TavilyToolInputFallbackInterceptor(输入降级)
拦截所有 tavily_search 调用,处理 LLM 生成异常参数:
| 异常情况 | 降级策略 |
|---|---|
| 参数为空 | 从 RunnableConfig 中取原始用户问题作为查询词 |
JSON 缺少 query 字段 | 注入兜底查询词 |
| 参数为纯文本非 JSON | 自动包装为 {"query": "..."} |
| 无任何可用兜底值 | 返回 ToolCallResponse.error("tavily_search 工具缺少可用的 query 参数") |
4.2 DashScopeCompatibilityInterceptor(兼容适配)
处理 DashScope(阿里云)模型与非标准输出的兼容问题:
| 兼容问题 | 处理方式 |
|---|---|
| 不支持并行工具调用 | 强制 parallelToolCalls = false |
| 不支持 stream options | 移除流式选项 |
| 工具调用片段拆分 | 按 ID 合并碎片化的 tool call(id/name/arguments 跨段补全) |
同时记录完整的请求/响应日志(INFO 级别),用于问题排查。
4.3 ToolErrorInterceptor(异常兜底)
作为拦截器链的最后一环,捕获所有前面未处理的工具调用异常,将其转换为工具响应消息而非让 Agent 崩溃。
4.4 对话级优雅失败
BusinessChatService 的三种结束路径均使用 AtomicBoolean finalized 保证清理只执行一次:
| 路径 | 触发场景 | 行为 |
|---|---|---|
finishSuccessfully | 正常完成 | 发送引用/推荐、归档到 MySQL、释放锁 |
finishWithFailure | 异常失败 | 解析异常链提取 HTTP 错误详情、SSE 推送错误事件 |
stopTask | 用户主动停止 | 中断 ReactAgent、SSE 推送停止状态 |
三种路径均保证:释放 Redis Lease + 移出运行时注册表 + 释放 Reactive 订阅。
4.5 无证据短路线(防幻觉)
在 RAG 检索链路中,若检索不到任何证据,系统返回"当前文档中没有检索到足够证据"而非让模型自由生成,避免幻觉。由 ChatPreparationOrchestrator 管控。
五、Agent 循环调用与失控防护
5.1 多层限流矩阵
| 层级 | 机制 | 限制 |
|---|---|---|
| Hooks | ModelCallLimitHook | 8 次/run,40 次/thread |
| Hooks | ToolCallLimitHook | 6 次/run,30 次/thread |
| 重试 | ToolRetryInterceptor | 最多 2 次,指数退避 |
| 超时 | Tavily HTTP 客户端 | 连接 3s,读取 6s |
| 租约 | Redis Lease | 每 10s 续期,30s TTL |
| 注册 | JVM 任务注册 | putIfAbsent 防同会话并发 |
所有 Hook 超限行为均为 END(非 THROW),Agent 在当前轮次正常结束,不会扩散异常。
5.2 集群会话锁
RedisLeaseManager 基于 Lua 原子脚本 操作 Redis:
| 操作 | Lua 脚本逻辑 |
|---|---|
| ACQUIRE | SET NX PX —— 仅当 key 不存在时设置,带毫秒 TTL |
| RENEW | 仅当 value 等于当前持有者 token 时才续期(防窃取) |
| RELEASE | 仅当 value 等于当前持有者 token 时才删除(防误释放) |
租约 Key 格式:chat:running:{conversationId},TTL 30 秒,每 10 秒续期一次。
5.3 用户主动中断
businessChatReactAgent.interrupt(taskInfo.runnableConfig());
通过 Checkpoint 系统中断 Agent 执行,释放资源。
六、Checkpoint 持久化推理状态
6.1 数据库表结构
GRAPH_CHECKPOINT:
| 字段 | 类型 | 说明 |
|---|---|---|
checkpoint_id | VARCHAR(36) | 检查点唯一 ID(主键) |
thread_id | VARCHAR(36) | 会话线程 ID(外键关联 GRAPH_THREAD) |
node_id | VARCHAR(255) | 当前 Agent 图节点 |
next_node_id | VARCHAR(255) | 下一个节点 |
state_data | JSON | 完整推理状态(含 messages 数组) |
saved_at | TIMESTAMP | 保存时间 |
索引:(thread_id, saved_at) 联合索引,支持按线程查询最新检查点。
GRAPH_THREAD:
| 字段 | 类型 | 说明 |
|---|---|---|
thread_id | VARCHAR(36) | 线程唯一 ID(主键) |
thread_name | VARCHAR(255) | 线程名称(对应 conversationId) |
is_released | BOOLEAN | 是否已释放 |
唯一索引:(thread_name, is_released) 保证同一会话只有一个活跃线程。
6.2 MysqlSaver Bean
@Bean
public MysqlSaver mysqlCheckpointSaver(DataSource dataSource) {
return MysqlSaver.builder()
.dataSource(dataSource)
.createOption(CreateOption.CREATE_IF_NOT_EXISTS)
.build();
}
基于 com.alibaba.cloud.ai.graph.checkpoint.savers.mysql.MysqlSaver,自动建表,注入到 ReactAgent 的 .saver()。
6.3 Checkpoint 管理服务
ChatCheckpointManager 封装三个操作:
| 操作 | 方法 | 行为 |
|---|---|---|
| 读取 | get(RunnableConfig) | 读取指定线程的最新检查点 |
| 列表 | list(RunnableConfig) | 列出线程的所有检查点 |
| 清空 | clearThread(String threadId) | 事务性删除检查点 + 线程记录 |
七、断点恢复与状态回溯
7.1 对话恢复
在 BusinessChatService 加载会话时:
RunnableConfig runnableConfig = RunnableConfig.builder()
.threadId(archiveRecord.conversationId())
.build();
Map<String, Object> state = checkpointManager.get(runnableConfig)
.map(Checkpoint::getState)
.orElseGet(Map::of);
Object messages = state.getOrDefault("messages", List.of());
从 MySQL 读取最新检查点,提取 messages 数组恢复到界面。即使应用重启,对话历史也不丢失。
7.2 会话视图暴露
ConversationSessionView 包含 checkpointCount 字段,显示该会话的检查点数量。
7.3 会话重置清理
int removedCheckpointCount = checkpointManager.clearThread(conversationId);
重置会话时清理所有检查点,返回删除数量。
7.4 执行恢复机制
ReactAgentExecutor 执行时传入相同的 threadId(即 conversationId):
reactAgent.stream(question, taskInfo.runnableConfig())
框架自动在每次模型交互/工具调用后保存检查点。使用相同 threadId 重新执行时,框架可从最后一个检查点恢复。
八、可观测性
8.1 全链路追踪
ConversationTraceRecorder 记录每个执行阶段的:
- 阶段名称、开始/结束时间、执行状态(COMPLETED / FAILED)
- 结构化快照数据
存储到 super_agent_chat_exchange_trace_stage 表。
8.2 用量与限流追踪
| 模型 | 用途 |
|---|---|
ChatLimitStats | 限流统计:已用量 vs 上限、是否触发限流 |
ChatModelUsageTrace | 模型调用:token 消耗、provider、模型名、耗时、估算费用 |
ChatToolTrace | 工具调用:输入/输出、状态、错误信息、耗时 |
ChatDebugTrace | 调试全追踪(含限流统计) |
通过 API /api/chat/exchange/detail 可查看完整的调试追踪信息。
九、HTTP 层守卫
9.1 AdminAuthInterceptor(JWT 认证)
拦截所有 /manage/** 和 /admin/auth/me 请求:
- 从请求头提取 JWT Token
- 验证签名和有效期
- 失败返回 401 + JSON 错误体
9.2 PreviewModeInterceptor(预览模式)
当 app.preview-mode.enabled=true 时,拦截 16 个写操作路径:
- 对话流、文档上传/删除、索引重建等
- 流式端点返回 SSE 错误事件
- REST 端点返回 JSON 错误
9.3 配置
| 参数 | 默认值 | 说明 |
|---|---|---|
app.admin-auth.enabled | — | 管理后台认证开关 |
app.admin-auth.jwt.secret | — | JWT 签名密钥 |
app.admin-auth.jwt.expiration-minutes | — | JWT 过期时间 |
app.preview-mode.enabled | false | 预览模式/只读模式开关 |
十、涉及扩展点(未启用)
作为参考实现的示例代码(ai-example 模块),展示 Hook 和 Interceptor 的扩展能力:
| 示例 | 类型 | 功能 |
|---|---|---|
LoggingHook | AgentHook | 在 Agent 执行前后记录日志 |
SensitiveWordInterceptor | ModelInterceptor | 拦截敏感词(炸药/违法洗钱/攻击学校),注入安全提示 |
十一、完整文件清单
核心配置与拦截器
| 文件 | 职责 |
|---|---|
chatagent/config/ChatAgentConfiguration.java | 集中装配 Hooks、Interceptors、Checkpoint Saver、ReactAgent |
chatagent/config/ChatAgentProperties.java | 所有限流参数配置 |
chatagent/config/TavilySearchProperties.java | Tavily HTTP 超时配置 |
chatagent/support/DashScopeCompatibilityInterceptor.java | DashScope 兼容适配 |
chatagent/support/TavilyToolInputFallbackInterceptor.java | Tavily 输入降级 |
chatagent/support/ChatContextKeys.java | 上下文 Key 常量 |
chatagent/tool/TavilySearchTool.java | Tavily 工具(含超时与追踪) |
Checkpoint 系统
| 文件 | 职责 |
|---|---|
chatagent/service/ChatCheckpointManager.java | Checkpoint 增删查 + 线程清理 |
chatagent/data/GraphCheckpoint.java | Checkpoint 实体 |
chatagent/data/GraphThread.java | Graph 线程实体 |
chatagent/mapper/GraphCheckpointMapper.java | MyBatis-Plus Mapper |
chatagent/mapper/GraphThreadMapper.java | MyBatis-Plus Mapper |
执行与调度
| 文件 | 职责 |
|---|---|
chatagent/rag/executor/ReactAgentExecutor.java | 调用 ReactAgent + Checkpoint 支持 |
chatagent/service/BusinessChatService.java | 编排 Lease、Registry、执行、清理 |
chatagent/service/ChatRuntimeRegistry.java | JVM 级任务注册 |
chatagent/service/ConversationTraceRecorder.java | 全链路追踪记录 |
分布式安全框架
| 文件 | 职责 |
|---|---|
service-lease-framework/.../RedisLeaseManager.java | Redis Lua 分布式租约 |
repeat-execute-limit-framework/.../RepeatExecuteLimit.java | 幂等注解 |
repeat-execute-limit-framework/.../RepeatExecuteLimitAspect.java | AOP 幂等切面 |
HTTP 守卫
| 文件 | 职责 |
|---|---|
auth/support/AdminAuthInterceptor.java | JWT 认证拦截 |
auth/support/PreviewModeInterceptor.java | 预览模式拦截 |
auth/config/AdminWebMvcConfiguration.java | 拦截器注册 |
auth/config/PreviewModeProperties.java | 预览模式配置 |
调试/追踪模型
| 文件 | 职责 |
|---|---|
chatagent/model/debug/ChatLimitStats.java | 限流统计 |
chatagent/model/debug/ChatModelUsageTrace.java | 模型用量追踪 |
chatagent/model/debug/ChatToolTrace.java | 工具追踪 |
chatagent/model/debug/ChatDebugTrace.java | 全量调试追踪 |
chatagent/model/ConversationSessionView.java | 会话视图(含 checkpointCount) |
chatagent/vo/ConversationResetVo.java | 重置结果(含 removedCheckpointCount) |
示例扩展(ai-example)
| 文件 | 职责 |
|---|---|
ai-example/.../LoggingHook.java | AgentHook 示例 |
ai-example/.../SensitiveWordInterceptor.java | ModelInterceptor 敏感词示例 |
数据库 DDL
| 文件 | 职责 |
|---|---|
sql/Mysql/create_table_mysql.sql | GRAPH_CHECKPOINT、GRAPH_THREAD 建表语句 |
十二、设计亮点总结
- 纵深防御:六层防护从 HTTP → AOP → Redis → JVM → Agent Hooks → Checkpoint,每层独立,任一层失效不影响其他层
- 超限优雅终止:
ExitBehavior.END而非THROW,Agent 达到调用上限后正常返回已有结果,用户无感知 - 指数退避 + 抖动:
initialDelay=200ms → 400ms → 800ms,叠加随机抖动,避免重试风暴 - 降级链完整:输入降级(TavilyFallback)→ 重试(Retry)→ 异常兜底(ErrorInterceptor)→ 对话级兜底(finishWithFailure),问题不会穿透到用户
- Checkpoint 全自动:框架在每次模型/工具交互后自动保存状态到 MySQL,无需手动编码;相同 threadId 重启自动恢复
- 租约防窃取:Lua 原子脚本保证 Renew/Release 仅当 token 匹配时执行,防止集群中误释放他人租约
- 预览模式安全阀:一键开启只读模式,拦截所有写操作,适用于演示/线上只读场景
