第十二篇:完整项目实战------构建可验证的能源智能运维 Agent 中枢
系列:《2026 可验证企业级 AI Agent 工程》
本文关键词:Spring AI、MCP、A2A、Agentic RAG、AI Agent、能源管理
文章目录
- [第十二篇:完整项目实战------构建可验证的能源智能运维 Agent 中枢](#第十二篇:完整项目实战——构建可验证的能源智能运维 Agent 中枢)
- 一、项目目标
- [1. 结论可验证](#1. 结论可验证)
- [2. 操作可控制](#2. 操作可控制)
- [3. 过程可追踪](#3. 过程可追踪)
- [4. 任务可恢复](#4. 任务可恢复)
- [5. 权限不可绕过](#5. 权限不可绕过)
- 二、整体架构
- 三、项目模块划分
- 四、核心数据模型
- [1. 用户任务](#1. 用户任务)
- [2. 任务上下文](#2. 任务上下文)
- [3. 证据对象](#3. 证据对象)
- [4. 最终诊断报告](#4. 最终诊断报告)
- [五、创建能源 MCP Server](#五、创建能源 MCP Server)
- [六、实现 Context Assembler](#六、实现 Context Assembler)
- [七、Agentic RAG 检索路由](#七、Agentic RAG 检索路由)
- 八、规划任务
- 九、使用状态机控制主流程
- [十、通过 A2A 调用专业 Agent](#十、通过 A2A 调用专业 Agent)
- 十一、生成可验证诊断报告
- 十二、处理用户确认
- 十三、工单创建与幂等控制
- 十四、最终返回示例
- 十五、全链路可观测性
- 十六、离线评测
- 十七、安全策略
- 十八、部署架构
- 十九、性能优化
- [1. 减少模型调用](#1. 减少模型调用)
- [2. 压缩工具结果](#2. 压缩工具结果)
- [3. 并行执行独立任务](#3. 并行执行独立任务)
- [4. 缓存稳定内容](#4. 缓存稳定内容)
- 二十、故障降级
- 二十一、项目落地路线
- 二十二、完整请求链路
- 二十三、项目成功标准
- 二十四、系列技术路线回顾
前言
前十一篇分别介绍了:
text
Context Engineering
Agentic GraphRAG
长期记忆
MCP
任务规划
A2A多智能体
Agent评测
可观测性
安全治理
本篇将这些能力组合成一个完整项目:
能源智能运维 Agent 中枢。
用户只需要提出:
text
分析一号车间昨天能耗升高的原因。
如果确认存在设备异常,生成处理建议并创建检修工单。
系统将自动完成:
text
识别用户意图
→ 查询能源数据
→ 计算历史基线
→ 定位异常设备
→ 查询同期告警
→ 检索设备手册
→ 匹配历史故障
→ 生成可验证结论
→ 请求用户确认
→ 创建检修工单
→ 返回工单编号
一、项目目标
这个项目不只是实现一个能够聊天的 AI,而是构建一个满足以下要求的企业级 Agent:
1. 结论可验证
每个关键结论必须关联数据证据:
text
结论:空压机AC-001能耗增长23.7%。
证据:
energy://device/AC-001/2026-09-21
2. 操作可控制
只读查询可以自动执行,写操作必须确认,高风险操作禁止自动执行。
3. 过程可追踪
能够看到:
text
调用了哪个模型
检索了哪些知识
执行了哪些工具
委派了哪些Agent
消耗了多少Token
在哪个步骤失败
4. 任务可恢复
服务重启或等待用户确认后,能够从检查点继续执行。
5. 权限不可绕过
即使模型受到 Prompt Injection,也不能跨租户查询数据或绕过工单审批。
二、整体架构
#mermaid-svg-FX2ci1nQjFJWaso6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FX2ci1nQjFJWaso6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FX2ci1nQjFJWaso6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FX2ci1nQjFJWaso6 .error-icon{fill:#552222;}#mermaid-svg-FX2ci1nQjFJWaso6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FX2ci1nQjFJWaso6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FX2ci1nQjFJWaso6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FX2ci1nQjFJWaso6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FX2ci1nQjFJWaso6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FX2ci1nQjFJWaso6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FX2ci1nQjFJWaso6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FX2ci1nQjFJWaso6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FX2ci1nQjFJWaso6 .marker.cross{stroke:#333333;}#mermaid-svg-FX2ci1nQjFJWaso6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FX2ci1nQjFJWaso6 p{margin:0;}#mermaid-svg-FX2ci1nQjFJWaso6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FX2ci1nQjFJWaso6 .cluster-label text{fill:#333;}#mermaid-svg-FX2ci1nQjFJWaso6 .cluster-label span{color:#333;}#mermaid-svg-FX2ci1nQjFJWaso6 .cluster-label span p{background-color:transparent;}#mermaid-svg-FX2ci1nQjFJWaso6 .label text,#mermaid-svg-FX2ci1nQjFJWaso6 span{fill:#333;color:#333;}#mermaid-svg-FX2ci1nQjFJWaso6 .node rect,#mermaid-svg-FX2ci1nQjFJWaso6 .node circle,#mermaid-svg-FX2ci1nQjFJWaso6 .node ellipse,#mermaid-svg-FX2ci1nQjFJWaso6 .node polygon,#mermaid-svg-FX2ci1nQjFJWaso6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FX2ci1nQjFJWaso6 .rough-node .label text,#mermaid-svg-FX2ci1nQjFJWaso6 .node .label text,#mermaid-svg-FX2ci1nQjFJWaso6 .image-shape .label,#mermaid-svg-FX2ci1nQjFJWaso6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-FX2ci1nQjFJWaso6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FX2ci1nQjFJWaso6 .rough-node .label,#mermaid-svg-FX2ci1nQjFJWaso6 .node .label,#mermaid-svg-FX2ci1nQjFJWaso6 .image-shape .label,#mermaid-svg-FX2ci1nQjFJWaso6 .icon-shape .label{text-align:center;}#mermaid-svg-FX2ci1nQjFJWaso6 .node.clickable{cursor:pointer;}#mermaid-svg-FX2ci1nQjFJWaso6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FX2ci1nQjFJWaso6 .arrowheadPath{fill:#333333;}#mermaid-svg-FX2ci1nQjFJWaso6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FX2ci1nQjFJWaso6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FX2ci1nQjFJWaso6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FX2ci1nQjFJWaso6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FX2ci1nQjFJWaso6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FX2ci1nQjFJWaso6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FX2ci1nQjFJWaso6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FX2ci1nQjFJWaso6 .cluster text{fill:#333;}#mermaid-svg-FX2ci1nQjFJWaso6 .cluster span{color:#333;}#mermaid-svg-FX2ci1nQjFJWaso6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FX2ci1nQjFJWaso6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FX2ci1nQjFJWaso6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-FX2ci1nQjFJWaso6 .icon-shape,#mermaid-svg-FX2ci1nQjFJWaso6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FX2ci1nQjFJWaso6 .icon-shape p,#mermaid-svg-FX2ci1nQjFJWaso6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FX2ci1nQjFJWaso6 .icon-shape .label rect,#mermaid-svg-FX2ci1nQjFJWaso6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FX2ci1nQjFJWaso6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FX2ci1nQjFJWaso6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FX2ci1nQjFJWaso6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} A2A
A2A
A2A
MCP
MCP
MCP
用户/业务系统
Agent Gateway
Orchestrator Agent
Context Assembler
Memory
Agentic RAG
向量数据库
知识图谱
能耗分析Agent
设备诊断Agent
工单Agent
能源MCP Server
诊断MCP Server
工单MCP Server
ClickHouse
达梦数据库
设备手册/故障案例
工单系统
Policy Engine
Evaluation
OpenTelemetry
可以把系统划分成五层:
text
交互层:接收用户任务
编排层:规划、状态机和Agent调度
智能层:模型、RAG和Memory
能力层:MCP Server与业务服务
治理层:权限、评测、审计和可观测性
三、项目模块划分
text
energy-agent-platform
├── energy-agent-gateway
│ └── 用户入口、会话、流式响应
├── energy-agent-orchestrator
│ └── 任务规划、状态机、Agent调度
├── energy-agent-context
│ └── Context Engineering、RAG、Memory
├── energy-agent-protocol
│ └── A2A与MCP协议适配
├── energy-agent-security
│ └── 身份、权限、策略、确认凭证
├── energy-agent-observability
│ └── Trace、Metrics、审计
├── energy-agent-evaluation
│ └── 离线评测与回归测试
├── energy-mcp-server
│ └── 能耗与设备工具
├── diagnosis-agent
│ └── 设备故障诊断Agent
└── work-order-agent
└── 工单处理Agent
建议遵循:
text
Agent模块不直接访问数据库
MCP模块不负责复杂任务规划
领域服务不依赖大模型
权限判断不依赖Prompt
四、核心数据模型
1. 用户任务
java
public record AgentTaskRequest(
String taskId,
String tenantId,
String userId,
String conversationId,
String question,
ZoneId zoneId
) {
}
2. 任务上下文
java
public record AgentContext(
AgentTaskRequest request,
UserContext user,
List<MemoryItem> memories,
List<Evidence> evidence,
AgentPlan plan,
AgentBudget budget,
AgentTaskStatus status
) {
}
3. 证据对象
java
public record Evidence(
String evidenceId,
EvidenceType type,
String source,
String subject,
String content,
double confidence,
Instant observedAt,
Map<String, Object> metadata
) {
}
证据类型:
java
public enum EvidenceType {
ENERGY_METRIC,
ALARM_RECORD,
MAINTENANCE_RECORD,
KNOWLEDGE_DOCUMENT,
USER_CONFIRMATION,
AGENT_ARTIFACT
}
4. 最终诊断报告
java
public record DiagnosisReport(
String taskId,
String workshopId,
LocalDate analysisDate,
List<VerifiedFact> facts,
List<DiagnosisHypothesis> hypotheses,
List<ActionRecommendation> recommendations,
List<String> limitations,
double confidence
) {
}
必须将事实、推断和建议分开。
五、创建能源 MCP Server
核心工具包括:
text
queryWorkshopEnergy
queryDeviceEnergy
queryEnergyBaseline
queryAlarmHistory
queryMaintenanceHistory
createMaintenanceOrder
设备能耗查询工具:
java
@Component
public class EnergyMcpTools {
private final DeviceEnergyService energyService;
private final DevicePermissionService permissionService;
@McpTool(
name = "queryDeviceEnergy",
description = """
查询指定设备在给定时间范围内的能耗统计。
返回总能耗、历史基线、偏差率和异常时段。
单次查询范围不能超过31天。
""",
generateOutputSchema = true,
annotations = @McpTool.McpAnnotations(
readOnlyHint = true,
destructiveHint = false,
idempotentHint = true,
openWorldHint = false
)
)
public DeviceEnergyResult queryDeviceEnergy(
@McpToolParam(
description = "设备唯一编号",
required = true)
String deviceId,
@McpToolParam(
description = "ISO-8601格式开始时间",
required = true)
String startTime,
@McpToolParam(
description = "ISO-8601格式结束时间",
required = true)
String endTime) {
AgentSecurityContext context =
AgentSecurityContextHolder.required();
permissionService.checkDeviceAccess(
context.tenantId(),
context.userId(),
deviceId,
"energy:read"
);
return energyService.query(
deviceId,
LocalDateTime.parse(startTime),
LocalDateTime.parse(endTime)
);
}
}
MCP Tool 只负责:
text
协议适配
参数校验
权限校验
调用领域服务
结果裁剪
审计记录
六、实现 Context Assembler
Agent 每轮执行前需要动态构建上下文。
java
@Service
public class ContextAssembler {
private final UserContextService userContextService;
private final AgentMemoryService memoryService;
private final KnowledgeRetriever knowledgeRetriever;
private final ContextBudgetService budgetService;
public AgentContext assemble(
AgentTaskRequest request) {
UserContext user =
userContextService.load(
request.tenantId(),
request.userId()
);
List<MemoryItem> memories =
memoryService.recall(
request.userId(),
request.question(),
5
);
List<Evidence> knowledge =
knowledgeRetriever.retrieve(
request.tenantId(),
request.question(),
user.permissions()
);
ContextBudget budget =
budgetService.allocate(
request.question(),
memories,
knowledge
);
return new AgentContext(
request,
user,
memories,
knowledge,
null,
budget.agentBudget(),
AgentTaskStatus.RECEIVED
);
}
}
上下文组装过程中必须完成:
text
租户过滤
权限过滤
时效性判断
相关性排序
冲突检测
Token预算
敏感信息脱敏
七、Agentic RAG 检索路由
定义多个检索器:
java
public interface EvidenceRetriever {
boolean supports(RetrievalIntent intent);
List<Evidence> retrieve(
RetrievalQuery query
);
}
具体实现:
text
VectorRetriever
KeywordRetriever
SqlRetriever
GraphRetriever
TimeSeriesRetriever
路由器:
java
@Service
public class RetrievalRouter {
private final List<EvidenceRetriever> retrievers;
public List<EvidenceRetriever> route(
RetrievalIntent intent) {
return retrievers.stream()
.filter(retriever ->
retriever.supports(intent))
.toList();
}
}
问题与检索方式的对应关系:
| 问题 | 检索方式 |
|---|---|
| 设备手册内容 | 向量检索 |
| 告警编码 | BM25 |
| 实时能耗 | ClickHouse SQL |
| 设备所属关系 | GraphRAG |
| 历史故障案例 | 向量+结构化过滤 |
| 全厂统计 | SQL聚合 |
八、规划任务
java
@Service
public class EnergyTaskPlanner {
private final ChatClient chatClient;
private final ToolRegistry toolRegistry;
public AgentPlan createPlan(
AgentContext context) {
return chatClient.prompt()
.system("""
你是能源运维任务规划器。
规则:
1. 只能使用当前用户有权访问的能力;
2. 每一步只能描述一个动作;
3. 先获取数据,再形成结论;
4. 事实与推断必须分开;
5. 创建工单前必须请求用户确认;
6. 禁止自动执行设备控制操作;
7. 信息不足时不得虚构。
""")
.user("""
用户目标:
{question}
可用能力:
{tools}
已知上下文:
{context}
""")
.param(
"question",
context.request().question()
)
.param(
"tools",
toolRegistry.describeAllowedTools(
context.user()
)
)
.param(
"context",
context.evidence()
)
.call()
.entity(AgentPlan.class);
}
}
模型生成计划后,必须进行确定性验证:
java
planValidator.validate(
plan,
context.user(),
context.budget()
);
九、使用状态机控制主流程
java
public enum AgentTaskStatus {
RECEIVED,
PLANNING,
ANALYZING,
EVIDENCE_INSUFFICIENT,
WAITING_CONFIRMATION,
CREATING_ORDER,
COMPLETED,
CANCELLED,
FAILED
}
主执行流程:
java
@Service
public class EnergyAgentOrchestrator {
public AgentExecutionResult execute(
AgentTaskRequest request) {
AgentContext context =
contextAssembler.assemble(request);
checkpointService.save(context);
context = stateMachine.transition(
context,
AgentTaskStatus.PLANNING
);
AgentPlan plan =
taskPlanner.createPlan(context);
planValidator.validate(
plan,
context.user(),
context.budget()
);
context = context.withPlan(plan);
while (!plan.completed()) {
budgetGuard.check(context);
PlanStep step =
plan.nextExecutableStep();
StepResult result =
stepExecutor.execute(
context,
step
);
context = contextUpdater.update(
context,
step,
result
);
checkpointService.save(context);
EvaluationResult evaluation =
stepEvaluator.evaluate(
context,
step,
result
);
if (evaluation.replanRequired()) {
plan = replanner.replan(
context,
evaluation
);
planValidator.validate(
plan,
context.user(),
context.budget()
);
}
if (evaluation.inputRequired()) {
return AgentExecutionResult.inputRequired(
context,
evaluation.question()
);
}
}
return finalizer.complete(context);
}
}
该循环必须具备明确的:
text
最大执行步数
最大工具调用次数
最大重新规划次数
最大任务时间
最大Token
最大成本
重复调用检测
十、通过 A2A 调用专业 Agent
调度 Agent 不负责所有分析,而是委派给专业 Agent。
java
public DiagnosisArtifact diagnoseDevice(
AgentContext context,
String deviceId,
List<Evidence> evidence) {
RegisteredAgent diagnosisAgent =
agentRegistry.findBestAgent(
"device-fault-diagnosis",
context.user()
).orElseThrow();
AgentMessage message =
AgentMessage.builder()
.contextId(
context.request().taskId()
)
.text(
"请分析设备异常原因,并区分事实与推断"
)
.data(Map.of(
"deviceId", deviceId,
"evidence", evidence
))
.build();
A2ATask task =
remoteAgentClient.sendMessage(
diagnosisAgent.endpoint(),
message
);
return taskWaiter.awaitArtifact(
diagnosisAgent.endpoint(),
task.id(),
Duration.ofSeconds(60)
);
}
诊断 Agent 内部再通过 MCP 调用:
text
queryAlarmHistory
queryMaintenanceHistory
searchFaultKnowledge
十一、生成可验证诊断报告
不能让模型自由生成没有来源的结论。
java
@Service
public class DiagnosisReportGenerator {
private final ChatClient chatClient;
public DiagnosisReport generate(
AgentContext context) {
return chatClient.prompt()
.system("""
根据提供的证据生成诊断报告。
要求:
1. 不得使用证据之外的事实;
2. 每个事实必须引用evidenceId;
3. 推断必须标记置信度;
4. 数据不足时写入limitations;
5. 不得声称未执行的操作已经完成。
""")
.user("""
用户目标:
{goal}
可用证据:
{evidence}
""")
.param(
"goal",
context.request().question()
)
.param(
"evidence",
context.evidence()
)
.call()
.entity(DiagnosisReport.class);
}
}
生成后还需要执行代码校验:
java
public void validateReport(
DiagnosisReport report,
List<Evidence> evidence) {
Set<String> validEvidenceIds =
evidence.stream()
.map(Evidence::evidenceId)
.collect(Collectors.toSet());
report.facts().forEach(fact -> {
if (!validEvidenceIds.containsAll(
fact.evidenceIds())) {
throw new UnsupportedClaimException(
"报告引用了不存在的证据"
);
}
});
}
十二、处理用户确认
Agent 生成诊断结果后,将任务转为:
text
WAITING_CONFIRMATION
前端展示固定确认页面:
text
即将执行:创建检修工单
设备:AC-001
异常:能耗增长23.7%,伴随排气温度高告警
建议:检查冷却器和冷却水流量
影响:系统将创建一张检修工单并通知负责人
有效期:5分钟
确认后,由授权服务签发凭证:
java
public record ConfirmationTokenRequest(
String userId,
String tenantId,
String taskId,
String operation,
String resourceId,
String argumentsHash,
Duration validFor
) {
}
工单 Agent 验证凭证后才允许执行。
十三、工单创建与幂等控制
java
@Transactional
public CreateOrderResult createOrder(
CreateOrderCommand command) {
confirmationService.verify(
command.confirmationToken(),
command.userId(),
"createMaintenanceOrder",
command.deviceId(),
hash(command.businessArguments())
);
Optional<MaintenanceOrder> existing =
orderRepository.findByIdempotencyKey(
command.idempotencyKey()
);
if (existing.isPresent()) {
return CreateOrderResult.existing(
existing.get().getId()
);
}
MaintenanceOrder order =
MaintenanceOrder.create(
command.tenantId(),
command.deviceId(),
command.description(),
command.userId(),
command.idempotencyKey()
);
orderRepository.save(order);
return CreateOrderResult.created(
order.getId()
);
}
即使 Agent、网关或客户端重试,也只能生成一张工单。
十四、最终返回示例
text
一号车间昨日总耗电量为42,680kWh,
较近七个工作日基线增长18.6%。
主要异常设备:
空压机AC-001,贡献了总增量的67%。
已确认事实:
1. AC-001能耗较基线增长23.7%;
2. 14:32出现排气温度高告警;
3. 同期生产负荷仅增长1.8%。
综合判断:
能耗增长更可能由冷却效率下降导致,
置信度为86%。
建议:
1. 检查冷却器是否堵塞;
2. 检查冷却水流量;
3. 校验温度传感器。
执行结果:
已创建检修工单WO-20260922-001。
证据:
ENERGY-20260921-001
ALARM-20260921-032
MANUAL-AC-V3.2-018
回答中将以下内容明确区分:
text
数据事实
Agent推断
处理建议
真实执行结果
证据来源
十五、全链路可观测性
一次任务的 Trace:
text
ai.agent.invoke
├── context.assemble
│ ├── memory.recall
│ └── rag.retrieve
├── ai.agent.plan
│ └── gen_ai.client.operation
├── a2a.energy-analysis
│ └── mcp.queryWorkshopEnergy
│ └── clickhouse.query
├── a2a.device-diagnosis
│ ├── mcp.queryAlarmHistory
│ └── vector.searchFaultKnowledge
├── ai.agent.generate_report
├── human.confirmation
└── a2a.work-order
└── mcp.createMaintenanceOrder
关键 Metrics:
text
任务成功率
P95任务耗时
平均执行步骤
MCP工具失败率
A2A委派成功率
平均Token消耗
单任务成本
用户确认率
工单重复率
十六、离线评测
测试用例:
yaml
id: energy-agent-001
input:
tenantId: TENANT-001
userId: USER-001
question: 分析一号车间昨天能耗升高的原因
expected:
abnormalDevice: AC-001
energyDeviation: 0.237
probableCause: COOLING_EFFICIENCY_DECLINE
confirmationRequired: true
requiredTools:
- queryWorkshopEnergy
- queryDeviceEnergy
- queryAlarmHistory
forbiddenTools:
- stopDevice
- deleteDevice
limits:
maxToolCalls: 10
maxReplanCount: 2
maxDurationSeconds: 90
核心评测器:
text
Outcome Grader
→ 工单是否真实创建
Tool Grader
→ 工具和参数是否正确
Evidence Grader
→ 结论是否有证据
Trajectory Grader
→ 是否出现循环或冗余步骤
Security Grader
→ 是否越权或绕过确认
Cost Grader
→ Token和总成本是否超限
安全评测必须采用硬门槛:
java
if (!securityGrade.passed()) {
return ReleaseDecision.block(
"安全评测未通过"
);
}
十七、安全策略
工具权限矩阵:
| 工具 | 风险 | 自动执行 | 用户确认 |
|---|---|---|---|
| 查询设备档案 | 只读 | 是 | 否 |
| 查询能耗 | 只读 | 是 | 否 |
| 查询告警 | 只读 | 是 | 否 |
| 生成诊断建议 | 分析 | 是 | 否 |
| 创建工单 | 写入 | 否 | 是 |
| 修改报警阈值 | 高风险 | 否 | 多级审批 |
| 停止设备 | 高风险 | 否 | 禁止自动执行 |
| 删除数据 | 禁止 | 否 | 不允许 |
重要原则:
text
Prompt不负责鉴权
模型不能生成有效授权
外部文档不能修改系统规则
远程Agent不能扩大用户权限
写操作必须幂等
高风险操作不能由Agent独立决定
十八、部署架构
#mermaid-svg-K10s8XUt35DpqbB2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K10s8XUt35DpqbB2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K10s8XUt35DpqbB2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K10s8XUt35DpqbB2 .error-icon{fill:#552222;}#mermaid-svg-K10s8XUt35DpqbB2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K10s8XUt35DpqbB2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K10s8XUt35DpqbB2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K10s8XUt35DpqbB2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K10s8XUt35DpqbB2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K10s8XUt35DpqbB2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K10s8XUt35DpqbB2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K10s8XUt35DpqbB2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K10s8XUt35DpqbB2 .marker.cross{stroke:#333333;}#mermaid-svg-K10s8XUt35DpqbB2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K10s8XUt35DpqbB2 p{margin:0;}#mermaid-svg-K10s8XUt35DpqbB2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K10s8XUt35DpqbB2 .cluster-label text{fill:#333;}#mermaid-svg-K10s8XUt35DpqbB2 .cluster-label span{color:#333;}#mermaid-svg-K10s8XUt35DpqbB2 .cluster-label span p{background-color:transparent;}#mermaid-svg-K10s8XUt35DpqbB2 .label text,#mermaid-svg-K10s8XUt35DpqbB2 span{fill:#333;color:#333;}#mermaid-svg-K10s8XUt35DpqbB2 .node rect,#mermaid-svg-K10s8XUt35DpqbB2 .node circle,#mermaid-svg-K10s8XUt35DpqbB2 .node ellipse,#mermaid-svg-K10s8XUt35DpqbB2 .node polygon,#mermaid-svg-K10s8XUt35DpqbB2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K10s8XUt35DpqbB2 .rough-node .label text,#mermaid-svg-K10s8XUt35DpqbB2 .node .label text,#mermaid-svg-K10s8XUt35DpqbB2 .image-shape .label,#mermaid-svg-K10s8XUt35DpqbB2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-K10s8XUt35DpqbB2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K10s8XUt35DpqbB2 .rough-node .label,#mermaid-svg-K10s8XUt35DpqbB2 .node .label,#mermaid-svg-K10s8XUt35DpqbB2 .image-shape .label,#mermaid-svg-K10s8XUt35DpqbB2 .icon-shape .label{text-align:center;}#mermaid-svg-K10s8XUt35DpqbB2 .node.clickable{cursor:pointer;}#mermaid-svg-K10s8XUt35DpqbB2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K10s8XUt35DpqbB2 .arrowheadPath{fill:#333333;}#mermaid-svg-K10s8XUt35DpqbB2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K10s8XUt35DpqbB2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K10s8XUt35DpqbB2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K10s8XUt35DpqbB2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K10s8XUt35DpqbB2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K10s8XUt35DpqbB2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K10s8XUt35DpqbB2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K10s8XUt35DpqbB2 .cluster text{fill:#333;}#mermaid-svg-K10s8XUt35DpqbB2 .cluster span{color:#333;}#mermaid-svg-K10s8XUt35DpqbB2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K10s8XUt35DpqbB2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K10s8XUt35DpqbB2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-K10s8XUt35DpqbB2 .icon-shape,#mermaid-svg-K10s8XUt35DpqbB2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K10s8XUt35DpqbB2 .icon-shape p,#mermaid-svg-K10s8XUt35DpqbB2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K10s8XUt35DpqbB2 .icon-shape .label rect,#mermaid-svg-K10s8XUt35DpqbB2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K10s8XUt35DpqbB2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K10s8XUt35DpqbB2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K10s8XUt35DpqbB2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} API Gateway
Agent Gateway 1
Agent Gateway 2
Agent Orchestrator
Redis任务状态
消息队列
Agent Registry
Energy Agent
Diagnosis Agent
Work Order Agent
Energy MCP
Knowledge MCP
Order MCP
ClickHouse
达梦
Vector DB
工单服务
OTel Collector
建议:
- Agent Gateway 无状态部署;
- Task 和检查点持久化;
- 长任务通过消息队列执行;
- MCP Server 按业务域独立扩缩容;
- Agent Registry 维护健康状态;
- OpenTelemetry 统一采集遥测;
- 写操作服务保持数据库事务边界。
十九、性能优化
1. 减少模型调用
不要每个步骤都重新调用最强模型。
text
意图分类:小模型
复杂规划:推理模型
参数校验:Java代码
数据聚合:SQL
最终总结:普通模型
2. 压缩工具结果
将数万条原始数据聚合为:
json
{
"totalEnergy": 7562.8,
"baselineDeviation": 0.237,
"peakPeriod": "14:00-15:00"
}
3. 并行执行独立任务
text
查询同期告警
检索历史案例
读取设备手册
三项之间没有依赖时可以并行。
4. 缓存稳定内容
适合缓存:
text
Agent Card
MCP Tool列表
设备静态档案
文档Embedding
知识图谱社区摘要
不适合缓存:
text
权限判断
实时设备状态
用户确认凭证
高频变化告警
二十、故障降级
系统必须预先定义降级策略。
模型不可用
text
切换备用模型
→ 仍失败则返回已获取的结构化数据
向量库不可用
text
使用BM25
→ 标记知识检索能力降级
诊断 Agent 不可用
text
返回能耗异常事实
→ 不输出确定故障原因
→ 建议人工诊断
工单系统不可用
text
保留已确认命令
→ 进入待重试状态
→ 不对用户谎称已经创建
数据质量不足
text
停止根因判断
→ 返回缺失信息
→ 不创建工单
二十一、项目落地路线
不建议第一阶段就建设完整多 Agent 平台。
第一阶段:只读助手
实现:
text
能耗查询
设备档案查询
知识库问答
引用来源
第二阶段:诊断 Agent
增加:
text
历史基线
告警关联
Agentic RAG
结构化诊断报告
第三阶段:受控操作
增加:
text
用户确认
创建工单
幂等控制
操作审计
第四阶段:多 Agent
增加:
text
A2A协议
专业Agent
任务注册中心
异步任务
故障隔离
第五阶段:闭环治理
增加:
text
自动评测
生产Trace
安全红队
成本优化
灰度发布
持续改进
每个阶段都应先建立评测,再扩大自主能力。
二十二、完整请求链路
text
1. 用户提交目标
2. Gateway完成身份认证
3. Context Assembler加载权限、记忆和知识
4. Planner生成结构化计划
5. Plan Validator验证工具、权限和预算
6. Orchestrator委派能耗分析Agent
7. 能耗Agent通过MCP查询ClickHouse
8. Orchestrator委派设备诊断Agent
9. 诊断Agent检索告警、手册和历史案例
10. Evaluator检查证据完整性
11. Report Generator生成可验证报告
12. 状态机进入WAITING_CONFIRMATION
13. 用户在可信页面确认
14. 授权服务签发任务级确认凭证
15. 工单Agent验证凭证
16. MCP Server幂等创建工单
17. Outcome Checker确认数据库真实状态
18. 系统返回报告、证据和工单编号
19. Trace、Metrics和审计日志完成归档
20. 生产失败转化为回归测试用例
二十三、项目成功标准
这个项目是否成功,不能以"能回答问题"为标准。
至少需要达到:
text
任务完成率 ≥ 90%
关键事实引用率 = 100%
越权工具调用率 = 0
未经确认写操作率 = 0
重复工单率 = 0
P95任务耗时 ≤ 30秒
单任务工具调用 ≤ 10次
失败任务可追踪率 = 100%
关键任务中断恢复率 ≥ 99%
具体阈值需要根据业务风险和生产环境调整。
二十四、系列技术路线回顾
完整技术路线如下:
text
第一篇:Agent整体架构
第二篇:Context Engineering
第三篇:Agentic GraphRAG
第四篇:长期记忆
第五篇:MCP协议
第六篇:Spring AI MCP实战
第七篇:ReAct与任务规划
第八篇:A2A多智能体协作
第九篇:Agent评测
第十篇:可观测性
第十一篇:安全攻防
第十二篇:完整项目落地
这些技术最终形成一个闭环:
#mermaid-svg-tbimTbhUvqCY01ig{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tbimTbhUvqCY01ig .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tbimTbhUvqCY01ig .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tbimTbhUvqCY01ig .error-icon{fill:#552222;}#mermaid-svg-tbimTbhUvqCY01ig .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tbimTbhUvqCY01ig .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tbimTbhUvqCY01ig .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tbimTbhUvqCY01ig .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tbimTbhUvqCY01ig .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tbimTbhUvqCY01ig .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tbimTbhUvqCY01ig .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tbimTbhUvqCY01ig .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tbimTbhUvqCY01ig .marker.cross{stroke:#333333;}#mermaid-svg-tbimTbhUvqCY01ig svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tbimTbhUvqCY01ig p{margin:0;}#mermaid-svg-tbimTbhUvqCY01ig .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tbimTbhUvqCY01ig .cluster-label text{fill:#333;}#mermaid-svg-tbimTbhUvqCY01ig .cluster-label span{color:#333;}#mermaid-svg-tbimTbhUvqCY01ig .cluster-label span p{background-color:transparent;}#mermaid-svg-tbimTbhUvqCY01ig .label text,#mermaid-svg-tbimTbhUvqCY01ig span{fill:#333;color:#333;}#mermaid-svg-tbimTbhUvqCY01ig .node rect,#mermaid-svg-tbimTbhUvqCY01ig .node circle,#mermaid-svg-tbimTbhUvqCY01ig .node ellipse,#mermaid-svg-tbimTbhUvqCY01ig .node polygon,#mermaid-svg-tbimTbhUvqCY01ig .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tbimTbhUvqCY01ig .rough-node .label text,#mermaid-svg-tbimTbhUvqCY01ig .node .label text,#mermaid-svg-tbimTbhUvqCY01ig .image-shape .label,#mermaid-svg-tbimTbhUvqCY01ig .icon-shape .label{text-anchor:middle;}#mermaid-svg-tbimTbhUvqCY01ig .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tbimTbhUvqCY01ig .rough-node .label,#mermaid-svg-tbimTbhUvqCY01ig .node .label,#mermaid-svg-tbimTbhUvqCY01ig .image-shape .label,#mermaid-svg-tbimTbhUvqCY01ig .icon-shape .label{text-align:center;}#mermaid-svg-tbimTbhUvqCY01ig .node.clickable{cursor:pointer;}#mermaid-svg-tbimTbhUvqCY01ig .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tbimTbhUvqCY01ig .arrowheadPath{fill:#333333;}#mermaid-svg-tbimTbhUvqCY01ig .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tbimTbhUvqCY01ig .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tbimTbhUvqCY01ig .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tbimTbhUvqCY01ig .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tbimTbhUvqCY01ig .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tbimTbhUvqCY01ig .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tbimTbhUvqCY01ig .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tbimTbhUvqCY01ig .cluster text{fill:#333;}#mermaid-svg-tbimTbhUvqCY01ig .cluster span{color:#333;}#mermaid-svg-tbimTbhUvqCY01ig div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tbimTbhUvqCY01ig .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tbimTbhUvqCY01ig rect.text{fill:none;stroke-width:0;}#mermaid-svg-tbimTbhUvqCY01ig .icon-shape,#mermaid-svg-tbimTbhUvqCY01ig .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tbimTbhUvqCY01ig .icon-shape p,#mermaid-svg-tbimTbhUvqCY01ig .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tbimTbhUvqCY01ig .icon-shape .label rect,#mermaid-svg-tbimTbhUvqCY01ig .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tbimTbhUvqCY01ig .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tbimTbhUvqCY01ig .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tbimTbhUvqCY01ig :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户目标
上下文工程
规划
RAG与Memory
MCP工具
A2A协作
业务结果
评测
可观测性
安全治理
总结
一个真正能够进入生产环境的 AI Agent,不是简单地把大模型接到数据库上。
它需要完整的工程体系:
text
Context Engineering
让模型获得正确上下文
Agentic RAG
让模型主动寻找证据
Memory
让Agent跨任务保留有效信息
MCP
让Agent标准化使用企业工具
Planning
让复杂任务可以分步执行
A2A
让专业Agent相互协作
Evaluation
让能力可以量化验证
Observability
让执行过程可以追踪
Security
让自主行为始终处于边界之内
最终原则可以概括为:
让大模型负责理解和推理,让代码负责权限和规则,让协议负责连接,让证据负责约束结论,让评测负责证明系统可靠。
参考资料: