Agent-Prometheus监控分析
一、介绍
延续上一节的 AI Agent + ELK 能力,本节新增 Prometheus(普罗米修斯)监控系统,让 AI Agent 具备对服务/指标的智能感知与问题定位能力。这类智能监控在企业内极为关键,且有明确的实际应用价值。
实现路径:基于 AI MCP Prometheus 能力层,配合 分阶段提示词(Agent Prompt) 设计,驱动 Agent 完成 自动化分析、方案规划、执行操作、指标检测与结果输出 的全流程闭环。
二、功能流程
如图,Agent-Prometheus 的设计使用流程图;

- 虚线框部分提供了模拟监控与日志的脚本(含运行程序),用于快速产出指标与日志;同时部署 Prometheus 作为指标采集与查询的基础设施(也支持你自行独立部署)。
- 为该场景新增 AI Agent 的执行话术(将动作与策略模块化为 Prompt),并接入 RAG(检索增强生成)知识库,让 Agent 能在查询指标、关联日志、定位问题时具备更强的上下文与知识支撑。
三、工程实现
1. 话术配置


本节更新了最新的 SQL 语句,配置了一套适用于 Prometheus 监控日志检索的 Prompt,同时在
ai_client_tool_mcp中增加了相关的 ES MCP 服务。你可以从本节课程对应的分支
docs/dev-ops/mysql获取 SQL 语句并完成更新。
2. 普罗米修斯监控配置
2.1 启动服务

脚本已放在本节分支的工程目录下。你可以在本地或云服务器等具备 Docker 环境的服务上启动 Prometheus 监控系统。
本节还启动了
grafana-mcp服务,用于访问和分析 Grafana 监控数据。
2.2 模拟写入监控数据
如果是业务系统,配置了 Prometheus 后就会正常生成监控数据。为了方便在本节中进行测试,这里额外提供了脚本,便于快速获取监控数据进行验证。

25-09-28.17:55:17.267 [main ] INFO AiAgentAutoConfiguration - AI Agent 自动装配完成,结果: null
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest - === Prometheus指标模拟生成器 ===
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest - 开始时间: 2025-09-28 17:55:17
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest - 指标文件路径: D:\Images_work\tmp\custom_metrics.prom
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest - ✅ 依赖检查通过
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest -
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest - 🚀 开始生成模拟指标数据...
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest - 💡 运行测试方法来停止脚本
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest - 📊 指标更新间隔: 15秒
25-09-28.17:55:17.268 [main ] INFO PrometheusMetricsGeneratorTest -
25-09-28.17:55:17.273 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:55:17: 📈 指标数据更新成功
25-09-28.17:55:32.284 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:55:32: 📈 指标数据更新成功
25-09-28.17:55:47.300 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:55:47: 📈 指标数据更新成功
25-09-28.17:56:02.313 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:56:02: 📈 指标数据更新成功
25-09-28.17:56:17.315 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:56:17: 📈 指标数据更新成功
25-09-28.17:56:32.324 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:56:32: 📈 指标数据更新成功
25-09-28.17:56:47.329 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:56:47: 📈 指标数据更新成功
25-09-28.17:57:02.331 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:57:02: 📈 指标数据更新成功
25-09-28.17:57:17.334 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:57:17: 📈 指标数据更新成功
25-09-28.17:57:32.339 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:57:32: 📈 指标数据更新成功
25-09-28.17:57:32.339 [main ] INFO PrometheusMetricsGeneratorTest - 指标生成测试完成,共运行 10 次
25-09-28.17:57:32.339 [main ] INFO PrometheusMetricsGeneratorTest -
25-09-28.17:57:32.339 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:57:32: 🛑 接收到停止信号,正在清理...
25-09-28.17:57:32.340 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:57:32: 🗑️ 删除指标文件: D:\Images_work\tmp\custom_metrics.prom
25-09-28.17:57:32.340 [main ] INFO PrometheusMetricsGeneratorTest - 2025-09-28 17:57:32: ✅ 清理完成,脚本已停止执行脚本
docs/dev-ops/generate-metrics.sh会自动灌入数据,你也可以通过运行cn.cactusli.ai.test.PrometheusMetricsGeneratorTest.testGeneratePrometheusMetrics来写入数据。nohup bash ./generate-metrics.sh > /var/log/generate-metrics.log 2>&1 &
这些测试数据是为 Agent–Prometheus 监控分析准备的。你可以结合自己的项目创建不同的案例,并灵活调整 Agent Prompt 话术。
3. 启动监控
确保你已经安装了 Docker,并启动了普罗米修斯监控;

运行:docker ps 查看启动容器。
- Prometheus:采集并存储监控数据的数据源。类似 MySQL、ES,它作为数据源将采集到的数据推送上来,用于监控分析。
- Grafana:监控展示面板,可以对采集的数据配置不同的报表和可视化展示。
- Grafana-MCP:基于 AI MCP 的方式获取 Grafana 面板数据,使 AI 能够介入并进行智能分析。
4. 设置面板
访问:http://192.168.1.23:4000/dashboards,输入admin/admin。

5. 添加面板

6. 查看面板

7. MCP 验证
7.1 获取监控 API Key

- 创建一个 Token 并复制,MCP 服务对接需要使用。
7.2 配置文件
stdio 模式配置文件:
{
"mcpServers": {
"grafana-mcp-server": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"-e",
"GRAFANA_URL",
"-e",
"GRAFANA_SERVICE_ACCOUNT_TOKEN",
"mcp/grafana",
"-t",
"stdio",
"-debug"
],
"env": {
"GRAFANA_URL": "http://192.168.1.23:4000",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "sa-cxxx",
"DOCKER_HOST": "tcp://192.168.1.23:4560"
}
}
}
}- 注意,GRAFANA_URL 修改为你的 IP 地址。GRAFANA_API_KEY 是你生成的 Token
SSE 模式配置文件:
{
"baseUri": "http://192.168.1.23:8000",
"sseEndpoint": "/sse"
}7.3 单测案例
代码:cn/cactusli/ai/test/spring/ai/FlowAgentMCPTest.java
/**
* @author 仙人球⁶ᴳ |
* @date 2025/9/24 17:13
* @github https://github.com/lixuanfengs
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class FlowAgentMCPTest {
@Test
public void test() {
OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl("https://integrate.api.nvidia.com")
.apiKey("YccNpRdji73Y8A8yhyBizy_qGB85PE4ngC1ENambN2kCxfumhOu2dusbWFlgkskC")
.completionsPath("v1/chat/completions")
.embeddingsPath("v1/embeddings")
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model("deepseek-ai/deepseek-r1-0528")
.toolCallbacks(new SyncMcpToolCallbackProvider(stdioMcpClient_Grafana()).getToolCallbacks())
.build())
.build();
ChatResponse call = chatModel.call(Prompt.builder().messages(new UserMessage("有哪些工具可以使用?")).build());
log.info("测试结果:{}", JSON.toJSONString(call.getResult()));
}
public McpSyncClient stdioMcpClient_Grafana() {
Map<String, String> env = new HashMap<>();
env.put("GRAFANA_URL", "http://192.168.1.23:4000");
env.put("GRAFANA_API_KEY", "sa-liergou-e58852b4-25bb-482b-bbc8-564e0c56f5ac");
// 任选其一(grafana 安装在远程服务器上):
// 1) 走 SSH:无需在 23 上改 dockerd,只要能 SSH 且用户有权限
// env.put("DOCKER_HOST", "ssh://[email protected]");
// 2) 走 TCP:需要在 23 上改 dockerd,见 https://docs.docker.com/engine/security/protect-access/
env.put("DOCKER_HOST", "tcp://192.168.1.23:4560");
var stdioParams = ServerParameters.builder("docker")
.args("run",
"--rm",
"-i",
"-e",
"GRAFANA_URL",
"-e",
"GRAFANA_API_KEY",
"mcp/grafana",
"-t",
"stdio")
.env(env) // 这里既把 GRAFANA_* 传进容器,也把 DOCKER_HOST 等传给 docker 客户端
.build();
var mcpClient = McpClient.sync(new StdioClientTransport(stdioParams))
.requestTimeout(Duration.ofSeconds(100)).build();
var init = mcpClient.initialize();
log.info("Stdio MCP Initialized: {}", init);
return mcpClient;
}
}25-09-29.15:34:13.275 [main ] INFO FlowAgentMCPTest - 测试结果:
{
"metadata": {
"contentFilters": [],
"empty": true,
"finishReason": "STOP"
},
"output": {
"media": [],
"messageType": "ASSISTANT",
"metadata": {
"role": "ASSISTANT",
"messageType": "ASSISTANT",
"refusal": "",
"finishReason": "STOP",
"index": 0,
"annotations": [
{}
],
"id": "891ae70df7c3452d8e0bdeb168235a4c"
},
"text": "以下是可用的工具列表及其简要描述:\n\n### Elasticsearch 相关工具\n1. **`JavaSDKMCPClient_list_indices`** \n - 列出所有 Elasticsearch 索引,支持正则过滤。\n\n2. **`JavaSDKMCPClient_get_mappings`** \n - 获取指定索引的字段映射信息。\n\n3. **`JavaSDKMCPClient_search`** \n - 执行 Elasticsearch 搜索查询(支持高亮)。\n\n4. **`JavaSDKMCPClient_elasticsearch_health`** \n - 获取 Elasticsearch 集群健康状态(可选包含索引详情)。\n\n5. **`JavaSDKMCPClient_create_index`** \n - 创建新索引(可选配置设置和映射)。\n\n6. **`JavaSDKMCPClient_create_mapping`** \n - 创建或更新索引的映射结构。\n\n7. **`JavaSDKMCPClient_bulk`** \n - 批量导入数据到索引。\n\n8. **`JavaSDKMCPClient_reindex`** \n - 将数据从源索引重新索引到目标索引。\n\n9. **`JavaSDKMCPClient_create_index_template`** \n - 创建或更新索引模板。\n\n10. **`JavaSDKMCPClient_get_index_template`** \n - 获取索引模板信息。\n\n11. **`JavaSDKMCPClient_delete_index_template`** \n - 删除索引模板。\n\n---\n\n### Grafana 事件与告警管理\n12. **`JavaSDKMCPClient_add_activity_to_incident`** \n - 为事件添加注释(附带 URL 作为上下文)。\n\n13. **`JavaSDKMCPClient_create_incident`** \n - 创建新事件(需标题、严重性等信息)。\n\n14. **`JavaSDKMCPClient_get_alert_group`** \n - 通过 ID 获取告警组详情。\n\n15. **`JavaSDKMCPClient_get_alert_rule_by_uid`** \n - 根据 UID 获取告警规则的完整配置。\n\n16. **`JavaSDKMCPClient_list_alert_groups`** \n - 列出告警组(支持多条件过滤)。\n\n17. **`JavaSDKMCPClient_list_alert_rules`** \n - 列出告警规则(支持标签过滤)。\n\n18. **`JavaSDKMCPClient_list_contact_points`** \n - 列出通知联系人点(支持名称过滤)。\n\n---\n\n### 日志与指标分析\n19. **`JavaSDKMCPClient_fetch_pyroscope_profile`** \n - 从 Pyroscope 获取性能分析数据(需指定时间范围和标签)。\n\n20. **`JavaSDKMCPClient_find_error_pattern_logs`** \n - 在 Loki 日志中查找异常错误模式(对比历史平均值)。\n\n21. **`JavaSDKMCPClient_find_slow_requests`** \n - 在 Tempo 中分析慢请求。\n\n22. **`JavaSDKMCPClient_query_loki_logs`** \n - 执行 LogQL 查询(支持日志检索或指标计算)。\n\n23. **`JavaSDKMCPClient_query_loki_stats`** \n - 获取 Loki 日志流的统计信息(仅支持标签选择器)。\n\n24. **`JavaSDKMCPClient_query_prometheus`** \n - 执行 PromQL 查询(支持即时或范围查询)。\n\n---\n\n### Grafana 资源操作\n25. **`JavaSDKMCPClient_generate_deeplink`** \n - 生成 Grafana 资源的深度链接(如仪表盘、面板、Explore)。\n\n26. **`JavaSDKMCPClient_get_dashboard_by_uid`** \n - 根据 UID 获取完整仪表盘配置(**警告:可能消耗大量上下文**)。\n\n27. **`JavaSDKMCPClient_get_dashboard_panel_queries`** \n - 获取仪表盘中面板的查询信息。\n\n28. **`JavaSDKMCPClient_get_dashboard_property`** \n - 用 JSONPath 提取仪表盘特定属性(高效替代完整获取)。\n\n29. **`JavaSDKMCPClient_get_dashboard_summary`** \n - 获取仪表盘摘要(标题、面板数量等元数据)。\n\n30. **`JavaSDKMCPClient_update_dashboard`** \n - 创建/更新仪表盘(支持完整 JSON 或高效补丁操作)。\n\n---\n\n### 数据源与元数据查询\n31. **`JavaSDKMCPClient_get_datasource_by_name`** \n - 根据名称获取数据源详情。\n\n32. **`JavaSDKMCPClient_get_datasource_by_uid`** \n - 根据 UID 获取数据源详情。\n\n33. **`JavaSDKMCPClient_list_datasources`** \n - 列出所有数据源(可选类型过滤)。\n\n34. **`JavaSDKMCPClient_list_loki_label_names`** \n - 列出 Loki 标签名(指定时间范围)。\n\n35. **`JavaSDKMCPClient_list_loki_label_values`** \n - 列出 Loki 标签值(指定标签名和时间范围)。\n\n36. **`JavaSDKMCPClient_list_prometheus_label_names`** \n - 列出 Prometheus 标签名(支持序列选择器过滤)。\n\n37. **`JavaSDKMCPClient_list_prometheus_label_values`** \n - 列出 Prometheus 标签值(指定标签名)。\n\n38. **`JavaSDKMCPClient_list_prometheus_metric_metadata`** \n - 列出 Prometheus 指标元数据(实验性功能)。\n\n39. **`JavaSDKMCPClient_list_prometheus_metric_names`** \n - 列出 Prometheus 指标名称(支持正则过滤)。\n\n40. **`JavaSDKMCPClient_list_pyroscope_label_names`** \n - 列出 Pyroscope 标签名(可选时间范围和匹配器)。\n\n41. **`JavaSDKMCPClient_list_pyroscope_label_values`** \n - 列出 Pyroscope 标签值(指定标签名)。\n\n42. **`JavaSDKMCPClient_list_pyroscope_profile_types`** \n - 列出 Pyroscope 分析类型(如 CPU、内存等)。\n\n---\n\n### 团队与用户管理\n43. **`JavaSDKMCPClient_get_current_oncall_users`** \n - 获取当前值班用户列表(指定值班表 ID)。\n\n44. **`JavaSDKMCPClient_get_oncall_shift`** \n - 获取值班班次详情(通过班次 ID)。\n\n45. **`JavaSDKMCPClient_list_oncall_schedules`** \n - 列出值班表(可选团队 ID 过滤)。\n\n46. **`JavaSDKMCPClient_list_oncall_teams`** \n - 列出值班团队。\n\n47. **`JavaSDKMCPClient_list_oncall_users`** \n - 列出值班用户(可选用户名或 ID 过滤)。\n\n48. **`JavaSDKMCPClient_list_teams`** \n - 搜索 Grafana 团队(支持查询字符串)。\n\n49. **`JavaSDKMCPClient_list_users_by_org`** \n - 列出组织内的用户。\n\n---\n\n### Sift 分析平台\n50. **`JavaSDKMCPClient_get_assertions`** \n - 获取实体验证摘要(指定实体类型和时间范围)。\n\n51. **`JavaSDKMCPClient_get_sift_analysis`** \n - 通过 ID 获取 Sift 分析结果。\n\n52. **`JavaSDKMCPClient_get_sift_investigation`** \n - 通过 ID 获取 Sift 调查详情。\n\n53. **`JavaSDKMCPClient_list_sift_investigations`** \n - 列出 Sift 调查(支持数量限制)。\n\n---\n\n### 其他\n54. **`JavaSDKMCPClient_get_incident`** \n - 通过 ID 获取事件详情。\n\n55. **`JavaSDKMCPClient_list_incidents`** \n - 列出事件(支持状态过滤)。\n\n56. **`JavaSDKMCPClient_search_dashboards`** \n - 搜索仪表盘(支持查询字符串)。\n\n如需使用特定工具,请提供详细需求(如操作对象、参数等)。",
"toolCalls": []
}
}通过 Grafana MCP 服务,我们能够直接访问 Grafana 面板的监控数据。这样,在进行 Ai Agent 分析时,就可以基于实时获取的监控数据开展具体的分析和处理操作。
这一机制使得监控数据不再局限于可视化展示,而是能够与智能 Agent 结合,实现更高效、更自动化的运维分析场景。
四、测试验证
1. 观察日志
访问:http://192.168.1.23:9101/metrics

2. Agent 测试

选择智能体Auto Agent - 自动智能对话体-5,是本次的测试智能体。之后就可以分析系统的监控数据了。
五、AI Agent 重复装配问题修复
1. 问题概述及表现
在调用 chatClient.prompt().call() 后,系统出现多次重复注册 Bean 的问题,特别是 ai_client_tool_mcp_5007、ai_client_tool_mcp_5008、ai_client_tool_mcp_5009 等 MCP 客户端被反复初始化。
应用启动时:AI Agent 自动装配被触发 3-5 次
HTTP 请求时**:每次请求都会触发完整的装配流程
MCP 客户端:重复建立连接到 elasticsearch-mcp-server、mcp-grafana 等服务
性能影响:每次重复装配耗时 2-3 秒,严重影响响应速度
1.1 日志证据
25-10-01.20:48:14.391 [main] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始
25-10-01.20:48:18.003 [main] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始
25-10-01.20:48:20.660 [main] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始
...
25-10-01.20:48:26.139 [pool-2-thread-8] INFO Step1AnalyzerNode - 📊 阶段1: 任务状态分析
25-10-01.20:48:26.141 [http-nio-8091-exec-1] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始 <-- HTTP请求触发2. 原因分析
2.1 事件监听器配置错误(主要问题)
问题代码:
public class AiAgentAutoConfiguration implements ApplicationListener<ApplicationEvent>问题分析:
- 监听了所有 Spring 事件类型
- 应用启动时的多个事件都会触发:
ContextRefreshedEvent(上下文刷新)ApplicationStartedEvent(应用启动)ApplicationReadyEvent(应用就绪)ServletRequestHandledEvent(HTTP 请求处理完成)
- 每次 HTTP 请求也会触发相关事件
影响:启动时触发 3-5 次,每次 HTTP 请求触发 1 次
2.2 缺少执行状态控制
问题:
- 没有标志位防止重复执行
- 即使是同一个事件也可能被多次处理
- 无法判断装配是否已完成
2.3 MCP 客户端重复初始化
问题代码 (AiClientToolMcpNode.java:48-54):
for (AiClientToolMcpVO mcpVO : aiClientToolMcpList) {
// 直接创建,没有检查是否已存在
McpSyncClient mcpSyncClient = createMcpSyncClient(mcpVO);
registerBean(beanName(mcpVO.getMcpId()), McpSyncClient.class, mcpSyncClient);
}影响:
- 每次都重新建立 MCP 连接
- 资源泄露(旧连接未关闭)
- 连接池耗尽风险
- 性能下降(每次初始化耗时 1-2 秒)
2.4 Bean 注册逻辑不够完善
问题代码 (AbstractArmorySupport.java:67-84):
protected synchronized <T> void registerBean(...) {
// 只是简单地移除再注册,没有检查是否必要
if (beanFactory.containsBeanDefinition(beanName)) {
beanFactory.removeBeanDefinition(beanName);
}
beanFactory.registerBeanDefinition(beanName, beanDefinition);
log.info("注册Bean: {} -> {}", beanName, beanInstance);
}3. 问题修复方案
采用多层防护策略,确保问题彻底解决。
3.1 修复策略
┌─────────────────────────────────────────┐
│ 第一层:限制事件类型 │
│ ApplicationEvent → ApplicationReadyEvent │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 第二层:执行标志位控制 │
│ AtomicBoolean initialized │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 第三层:Bean 存在性检查 │
│ 检查 Bean 是否已注册 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 第四层:MCP 客户端复用 │
│ 跳过已初始化的 MCP 连接 │
└─────────────────────────────────────────┘3.2 代码变更详情
变更 1: AiAgentAutoConfiguration.java
文件路径: ai-agent-station-study-app/src/main/java/cn/cactusli/ai/config/AiAgentAutoConfiguration.java
修改前:
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
public class AiAgentAutoConfiguration implements ApplicationListener<ApplicationEvent> {
@Resource
private AiAgentAutoConfigProperties aiAgentAutoConfigProperties;
@Resource
private DefaultArmoryStrategyFactory defaultArmoryStrategyFactory;
@Override
public void onApplicationEvent(ApplicationEvent event) {
try {
log.info("AI Agent 自动装配开始,配置: {}", aiAgentAutoConfigProperties);
// 检查配置是否有效
if (!aiAgentAutoConfigProperties.isEnabled()) {
log.info("AI Agent 自动装配未启用");
return;
}
// ... 执行装配逻辑
} catch (Exception e) {
log.error("AI Agent 自动装配失败", e);
}
}
}修改后:
public class AiAgentAutoConfiguration implements ApplicationListener<ApplicationReadyEvent> {
@Resource
private AiAgentAutoConfigProperties aiAgentAutoConfigProperties;
@Resource
private DefaultArmoryStrategyFactory defaultArmoryStrategyFactory;
/**
* 确保只执行一次的标志位
*/
private final AtomicBoolean initialized = new AtomicBoolean(false);
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
try {
// 确保只执行一次
if (!initialized.compareAndSet(false, true)) {
log.debug("AI Agent 已完成装配,跳过重复执行");
return;
}
log.info("AI Agent 自动装配开始,配置: {}", aiAgentAutoConfigProperties);
// 检查配置是否有效
if (!aiAgentAutoConfigProperties.isEnabled()) {
log.info("AI Agent 自动装配未启用");
return;
}
// ... 执行装配逻辑
} catch (Exception e) {
log.error("AI Agent 自动装配失败", e);
}
}
}关键改动点:
| 改动项 | 修改前 | 修改后 | 效果 |
|---|---|---|---|
| 监听事件类型 | ApplicationEvent(所有事件) | ApplicationReadyEvent(仅应用就绪) | 只在应用完全启动后触发一次 |
| 执行控制 | 无控制 | AtomicBoolean initialized | 线程安全的单次执行保证 |
| 重复检查 | 无检查 | compareAndSet(false, true) | 即使多次触发也只执行一次 |
变更 2: AbstractArmorySupport.java
文件路径: ai-agent-station-study-domain/src/main/java/cn/cactusli/ai/domain/agent/service/armory/AbstractArmorySupport.java
修改前:
protected synchronized <T> void registerBean(String beanName, Class<T> beanClass, T beanInstance) {
// 获取Bean工厂
var beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
// 注册Bean
var beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass, () -> beanInstance);
beanDefinitionBuilder.setScope(BeanDefinition.SCOPE_SINGLETON);
var beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
// 如果Bean已存在,先移除
if (beanFactory.containsBeanDefinition(beanName)) {
beanFactory.removeBeanDefinition(beanName);
}
// 注册新的Bean
beanFactory.registerBeanDefinition(beanName, beanDefinition);
log.info("注册Bean: {} -> {}", beanName, beanInstance);
}修改后:
protected synchronized <T> void registerBean(String beanName, Class<T> beanClass, T beanInstance) {
// 获取Bean工厂
var beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
// 检查 Bean 是否已存在
if (beanFactory.containsSingleton(beanName)) {
T existingBean = (T) beanFactory.getSingleton(beanName);
// 如果是同一个实例,直接跳过
if (existingBean == beanInstance) {
log.debug("Bean已存在,跳过注册: {}", beanName);
return;
}
// 如果实例不同,警告并替换
log.warn("Bean已存在但实例不同,将替换: {} (旧: {} -> 新: {})",
beanName, existingBean, beanInstance);
}
// 注册Bean
var beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass, () -> beanInstance);
beanDefinitionBuilder.setScope(BeanDefinition.SCOPE_SINGLETON);
var beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
// 如果Bean已存在,先移除
if (beanFactory.containsBeanDefinition(beanName)) {
beanFactory.removeBeanDefinition(beanName);
}
// 注册新的Bean
beanFactory.registerBeanDefinition(beanName, beanDefinition);
log.info("注册Bean: {} -> {}", beanName, beanInstance);
}关键改动点:
| 改动项 | 说明 | 效果 |
|---|---|---|
| 单例检查 | 使用 containsSingleton() 检查实例 | 避免重复注册同一实例 |
| 实例比较 | 比较 existingBean == beanInstance | 如果完全相同则跳过 |
| 日志分级 | debug/warn 分级记录 | 便于调试和监控 |
| 提前返回 | 同一实例直接 return | 避免不必要的操作 |
变更 3: AiClientToolMcpNode.java
文件路径: ai-agent-station-study-domain/src/main/java/cn/cactusli/ai/domain/agent/service/armory/AiClientToolMcpNode.java
修改前:
@Override
protected String doApply(ArmoryCommandEntity requestParameter, DefaultArmoryStrategyFactory.DynamicContext dynamicContext) throws Exception {
log.info("Ai Agent 构建节点,Tool MCP 工具配置{}", JSON.toJSONString(requestParameter));
List<AiClientToolMcpVO> aiClientToolMcpList = dynamicContext.getValue(dataName());
if (aiClientToolMcpList == null || aiClientToolMcpList.isEmpty()) {
log.warn("没有需要被初始化的 ai client tool mcp");
return router(requestParameter, dynamicContext);
}
for (AiClientToolMcpVO mcpVO : aiClientToolMcpList) {
// 创建 MCP 服务
McpSyncClient mcpSyncClient = createMcpSyncClient(mcpVO);
// 注册 MCP 对象
registerBean(beanName(mcpVO.getMcpId()), McpSyncClient.class, mcpSyncClient);
}
return router(requestParameter, dynamicContext);
}修改后:
@Override
protected String doApply(ArmoryCommandEntity requestParameter, DefaultArmoryStrategyFactory.DynamicContext dynamicContext) throws Exception {
log.info("Ai Agent 构建节点,Tool MCP 工具配置{}", JSON.toJSONString(requestParameter));
List<AiClientToolMcpVO> aiClientToolMcpList = dynamicContext.getValue(dataName());
if (aiClientToolMcpList == null || aiClientToolMcpList.isEmpty()) {
log.warn("没有需要被初始化的 ai client tool mcp");
return router(requestParameter, dynamicContext);
}
for (AiClientToolMcpVO mcpVO : aiClientToolMcpList) {
String mcpBeanName = beanName(mcpVO.getMcpId());
// 检查 MCP 客户端是否已存在
try {
McpSyncClient existingClient = getBean(mcpBeanName);
if (existingClient != null) {
log.debug("MCP客户端已存在,跳过初始化: {}", mcpBeanName);
continue;
}
} catch (Exception e) {
// Bean 不存在,继续创建
log.debug("MCP客户端不存在,准备初始化: {}", mcpBeanName);
}
// 创建 MCP 服务
McpSyncClient mcpSyncClient = createMcpSyncClient(mcpVO);
// 注册 MCP 对象
registerBean(mcpBeanName, McpSyncClient.class, mcpSyncClient);
}
return router(requestParameter, dynamicContext);
}关键改动点:
| 改动项 | 说明 | 效果 |
|---|---|---|
| Bean 名称预提取 | 提前计算 mcpBeanName | 便于后续使用 |
| 存在性检查 | try-catch 方式检查 Bean | 已存在则跳过创建 |
| 提前 continue | 发现已存在直接跳过 | 避免重复初始化 MCP 连接 |
| 调试日志 | 记录跳过/创建状态 | 便于排查问题 |
4. 修复效果对比
修复前:
20:48:14.391 [main] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始 <-- 启动时第1次
20:48:14.776 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_api_1001
20:48:15.163 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5007
20:48:16.875 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5008
20:48:16.889 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5009
20:48:18.003 [main] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始 <-- 启动时第2次
20:48:18.501 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5007 <-- 重复
20:48:20.375 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5008 <-- 重复
20:48:20.419 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5009 <-- 重复
20:48:20.660 [main] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始 <-- 启动时第3次
20:48:20.839 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5007 <-- 重复
20:48:22.563 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5008 <-- 重复
20:48:22.577 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5009 <-- 重复
...
20:48:26.139 [pool-2-thread-8] INFO Step1AnalyzerNode - 📊 阶段1: 任务状态分析
20:48:26.141 [http-nio-8091-exec-1] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始 <-- HTTP请求触发
20:48:26.310 [http-nio-8091-exec-1] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5007 <-- 再次重复
...问题统计:
- ❌ 启动时装配 3-5 次
- ❌ 每次 HTTP 请求装配 1 次
- ❌ MCP 客户端重复初始化
- ❌ 启动耗时增加 6-10 秒
修复后:
20:48:14.391 [main] INFO AiAgentAutoConfiguration - AI Agent 自动装配开始 <-- 只执行一次
20:48:14.776 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_api_1001
20:48:15.163 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5007
20:48:16.875 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5008
20:48:16.889 [main] INFO AbstractArmorySupport - 注册Bean: ai_client_tool_mcp_5009
20:48:17.171 [main] INFO AiAgentAutoConfiguration - AI Agent 自动装配完成
20:48:20.660 [main] INFO Application - Started Application in 6.297 seconds <-- 启动完成
...
20:48:26.139 [pool-2-thread-8] INFO Step1AnalyzerNode - 📊 阶段1: 任务状态分析 <-- HTTP请求
// 不再触发装配