一、引言:能源数据不只是一堆数字
在智能电网和双碳目标的推动下,能源数据正在以前所未有的速度增长------采集终端上传的电压电流曲线、电表的分钟级读数、分布式光伏的发电量、气象站的辐照度与温度......这些数据本身并不值钱,值钱的是从里面读出的结论:哪条线路存在窃电嫌疑?明天某个园区的负荷峰值会出现在几点?光伏出力衰减是否异常?
然而,能源数据分析有一个鲜明的特点:没有任何单一的分析方法能覆盖全部问题。负荷预测需要时序模型,窃电检测需要统计离群点算法,能效评估需要业务规则,数据质量治理又需要另一套逻辑。把所有这些塞进一个"全能智能体",结果通常是每个子任务都做得平庸,还容易在工具和上下文之间来回横跳、互相污染。
这正是多智能体协同的用武之地。本文记录我在能源数据分析场景中落地的一套分配-执行-校验(Assign-Execute-Validate)闭环架构,它把分析任务拆解后分发给专业智能体,再通过多层校验和闭环反馈保证结果的可靠性。
二、为什么是"多"智能体:任务画像决定架构
在设计之前,我先对能源数据分析任务做了画像,发现它们有三个共性:
第一,任务可拆且子任务异质。 一次完整的"园区能耗诊断"至少包含:数据质量检查、缺失值处理、负荷聚类、异常时段定位、原因归因、报告生成。每一步的方法论完全不同,强行由一个智能体串行完成,要么上下文爆炸,要么步骤间思路漂移。
第二,结果必须可解释、可追溯。 能源分析结果往往直接关联决策(是否停电检修、是否预警、是否收费),分析师需要一个"为什么"的完整链条,而不是黑盒输出。
第三,错误代价高。 一条误判的窃电结论可能引发纠纷,一个错误的负荷预测可能导致调度失稳。因此结果必须经过交叉验证,不能"模型说啥就是啥"。
这三个特征指向同一个答案:用多个职责单一的专业智能体取代一个全能智能体,并用一个明确的分工协议(分配)、一套可并行的执行机制(执行)、一组独立的校验手段(校验)把它们组织成闭环。
三、架构总览:分配-执行-校验闭环
整个架构由三个核心组件和一条反馈回路构成:
┌────────────┐ 任务 ┌──────────────────────────────────┐
│ 用户/上游 │ ──────▶ │ Dispatcher │
└────────────┘ │ 1. 任务解析与拆解 │
│ 2. 子任务→智能体匹配 │
│ 3. 依赖图构建(拓扑排序) │
└──────────────┬───────────────────┘
│ 子任务包
┌───────────────▼───────────────────┐
│ Executor 池 │
│ ┌─────────┐ ┌─────────┐ ┌──────┐ │
│ │ 清洗Agent │ │ 建模Agent │ │ 归因Agent │ │
│ └─────────┘ └─────────┘ └──────┘ │
└───────────────┬───────────────────┘
│ 结果
┌───────────────▼───────────────────┐
│ Validator │
│ 规则校验 + 交叉校验 + 基准比对 │
└───────┬───────────────┬───────────┘
不通过,回退 │ │ 通过
(修订后重分配)│ ▼
┌───────▼───────┐ ┌──────────────┐
│ Dispatcher 修订 │ │ 汇总输出 │
└───────────────┘ └──────────────┘
设计上我刻意让三个角色彼此独立:Dispatcher不懂具体算法,只懂任务结构和智能体能力清单;Executor只处理自己领域内的数据,不关心全局;Validator不参与执行,只做"挑毛病"。独立性是闭环可信的前提------如果校验者和执行者共享同一套推理,校验就形同虚设。
四、分配层:任务拆解与智能体匹配
分配层的核心是一个智能体注册表------每个智能体声明自己能处理的任务类型、所需输入、产出格式和置信度评估方式。Dispatcher基于注册表把任务拆成依赖图,再拓扑排序得到执行顺序。
python
from dataclasses import dataclass, field
from typing import Any, Callable
@dataclass
class AgentSpec:
name: str
skills: list[str] # 能力标签,如 ["load_forecast", "arima"]
input_schema: dict
output_schema: dict
confidence_fn: Callable[[dict], float] # 自评置信度,供校验层参考
@dataclass
class SubTask:
id: str
agent_name: str
params: dict
depends_on: list[str] = field(default_factory=list)
result: dict = None
status: str = "pending" # pending/running/done/failed
class Dispatcher:
def __init__(self, registry: dict[str, AgentSpec]):
self.registry = registry
def decompose(self, task: str, context: dict) -> list[SubTask]:
# 用LLM把任务解析为子任务列表,并绑定智能体与依赖
plan = llm_plan(task, available=list(self.registry.keys()))
return [SubTask(
id=t["id"],
agent_name=self._match(t["capability"], context),
params=t["params"],
depends_on=t.get("depends_on", []),
) for t in plan]
def _match(self, capability: str, context: dict) -> str:
# 能力标签匹配 + 上下文约束(如数据源归属)
candidates = [n for n, s in self.registry.items()
if capability in s.skills]
return self._score_by_context(candidates, context)
关键细节是:decompose产出的不是自由文本计划,而是结构化的SubTask列表,每个子任务都声明了依赖。这样执行层才能并行,校验层才能按依赖逐级校验,Dispatcher才能在失败时只重放受影响的分支而不是全部重来。
五、执行层:专业智能体的并行协同
能源场景里我维护了四个常驻专业智能体:数据清洗、时序建模、异常检测、能效归因。它们共享一个统一的执行接口,内部可以完全不同------清洗智能体主要调用pandas规则,建模智能体则运行ARIMA或LightGBM。
以"用电异常检测"为例,异常检测智能体的核心是一个三倍标准差与业务规则混合的检测器:
python
import pandas as pd
import numpy as np
class AnomalyAgent:
def __init__(self, spec: AgentSpec):
self.spec = spec
def run(self, sub: SubTask, context: dict) -> dict:
df = context[sub.params["data_ref"]] # 从共享上下文取数据
series = df["power_kw"]
# 规则1: 统计离群点(三倍标准差)
mean, std = series.mean(), series.std()
stat_outliers = series[np.abs(series - mean) > 3 * std]
# 规则2: 业务规则------夜间非生产时段仍有持续高负荷
night = series.between_time("23:00", "06:00")
biz_outliers = night[night > sub.params["night_threshold_kw"]]
merged = pd.concat([stat_outliers, biz_outliers]).index.unique()
conf = self.spec.confidence_fn({
"n_stat": len(stat_outliers), "n_biz": len(biz_outliers)})
return {"anomaly_index": list(merged), "confidence": conf}
执行层的调度器维护一个就绪队列,每当某个子任务的依赖全部完成,就把它的status置为ready并投入执行池:
python
from concurrent.futures import ThreadPoolExecutor
class ExecutorPool:
def __init__(self, agents: dict[str, AgentSpec], max_workers: int = 8):
self.agents, self.max_workers = agents, max_workers
def execute(self, tasks: list[SubTask], context: dict) -> dict:
ready = [t for t in tasks if not t.depends_on]
done, pending = {}, {t.id: t for t in tasks}
with ThreadPoolExecutor(max_workers=self.max_workers) as pool:
while ready:
futures = {pool.submit(self._run, t, context): t for t in ready}
for fut, t in futures.items():
try:
t.result = fut.result()
t.status = "done"
done[t.id] = t
except Exception as e:
t.status = "failed"
raise ExecutionFailure(t.id, e)
# 释放依赖已满足的下游任务
ready = [t for t in pending.values()
if t.status == "pending" and all(d in done for d in t.depends_on)]
return {tid: t.result for tid, t in done.items()}
def _run(self, t: SubTask, ctx: dict):
return self.agents[t.agent_name].run(t, ctx)
依赖无关的子任务(比如多个变电站各自做异常检测)天然并行,实测在8核机器上,一个涉及120个台区的分析任务执行时间从串行的28分钟降到约6分钟。
六、校验层:多维度交叉验证
校验层是三段闭环里最容易偷懒、也最不能偷懒的部分。我做了三层校验,由轻到重:
第一层,契约校验。 检查结果是否符合输出schema、字段是否缺失、数值范围是否合理(比如功率不可能为负、负荷预测误差不应为NaN)。这层是纯规则,零成本。
第二层,交叉校验。 对同一结论,用不同的方法或不同的智能体独立验证。这是多智能体架构独有的红利:异常检测智能体说"某用户窃电",归因智能体应该能从其用电曲线中找到支持证据;预测智能体给出明天的峰值,物理校验智能体可以用"同时段历史相似日的峰值"做对照。
第三层,基准比对。 把结果与历史统计基线、行业经验值或上游系统的权威数据比对,偏差超阈值即判不通过。
python
class Validator:
def __init__(self, rules: list[Callable], base_threshold: float = 0.2):
self.rules = rules
self.base_threshold = base_threshold
def validate(self, sub: SubTask, results: dict, baselines: dict) -> Verdict:
# 1. 契约校验
for rule in self.rules:
if not rule(sub.result):
return Verdict(pass_=False, reason=f"契约违规:{rule.__name__}")
# 2. 交叉校验:同一数据点应被多个智能体一致支持
cross = results.get("cross_checks", [])
if cross and any(c["support"] < 0.5 for c in cross):
return Verdict(False, "交叉校验不通过:证据支持度不足")
# 3. 基准比对:与历史基线/权威值比较
if sub.id in baselines:
dev = abs(sub.result["value"] - baselines[sub.id]["value"]) \
/ max(baselines[sub.id]["value"], 1e-9)
if dev > self.base_threshold:
return Verdict(False, f"偏离基线{dev:.1%}")
return Verdict(pass_=True, reason="ok")
一个真实的例子:某次负荷预测模型输出明日峰值8500kW,但基准比对发现它比过去30天同天气条件的峰值中位数高出34%,远超20%阈值------后来查明是训练数据里混入了春节停工期的低负荷样本。如果没有这一层,这份预测就会直接进入调度系统。
七、闭环反馈:失败驱动的再分配
校验不通过,任务不是简单重试,而是进入反馈回路:把校验结论(失败原因、偏差数据)回传给Dispatcher,由其决定三条路径之一:
- 修订参数重执行------数据质量问题(缺失、异常值),修订清洗参数后只重放受影响分支;
- 切换智能体重执行------模型效果差,换一个候选智能体(如ARIMA换LightGBM);
- 降级与转人工------连续两轮校验不通过,标记为需人工介入,不再无限循环。
python
class ClosedLoop:
MAX_ROUNDS = 2
def run(self, task: str, context: dict) -> dict:
for round_no in range(self.MAX_ROUNDS + 1):
tasks = self.dispatcher.decompose(task, context)
results = self.executor.execute(tasks, context)
failures = []
for t in tasks:
v = self.validator.validate(t, results, self.baselines)
if not v.pass_:
failures.append((t, v.reason))
if not failures:
return self.aggregate(results)
if round_no == self.MAX_ROUNDS:
return self.degrade_to_human(task, failures) # 转人工
context = self.dispatcher.adapt(
task, context, failures) # 修订:换参数/换智能体/裁剪分支
# 理论不可达
raise RuntimeError("unreachable")
闭环收敛的度量我特别关注两个指标:首轮校验通过率 与平均闭环轮数。上线初期首轮通过率只有61%,大部分失败集中在数据质量问题;在给清洗智能体补充了"节假日标识、台区停电时段表"两个上下文后,通过率升到87%,平均闭环轮数稳定在1.15左右------这说明大部分任务一轮通过,少数任务一轮修订后收敛,几乎不进入转人工分支。
八、工程实践中的五个关键决策
1. 智能体接口统一,内部自由。 所有智能体实现同一个run(sub, context)签名,共享context作为数据总线。这带来两个好处:调度器不用关心智能体差异;新增智能体(如辐照度预测)只需注册,零改动现有代码。
2. 上下文按需注入,而不是全量共享。 能源数据动辄上亿行,不可能塞进每个智能体的上下文。我用"数据引用+惰性加载":context里存的是DataFrame的引用和schema描述,智能体只加载自己需要的列。既省内存,也防止无关数据污染推理。
3. 校验规则先规则后模型。 能用确定性规则校验的绝不用LLM。规则零成本、可解释、可单测;LLM校验只用在"需要语义判断"的环节(如报告表述是否与数据矛盾)。
4. 全链路trace。 每个子任务带trace_id,记录分配决策、执行日志、校验结论、修订原因,按任务维度落盘。能源行业审计要求高,"为什么这么判"必须随时可查。
5. 失败样例回归。 我把每个"第一轮校验失败→修订成功"的案例固化为回归测试。任何对注册表、提示词、规则的改动,都要先跑一遍历史失败集,防止"修好A弄坏B"。这是整个闭环能持续演进而不退化的保障。
九、总结与展望
回看这套架构的落地过程,我最深的体会是:多智能体的价值不在"多",而在"分工+校验"带来的可靠性增量。分配层让任务结构显式化,执行层让专业能力并行化,校验层让错误可以被发现、被定位、被修复------而闭环,把这三者连成一条不断自我修正的回路。
目前这套体系已支撑用电异常初筛、台区负荷预测、光伏出力归因三类业务,日均处理数万条分析请求,误报率较单智能体方案下降约55%,分析结论全部可追溯。下一步,我计划做两件事:一是引入智能体间的"质询"机制,让归因智能体主动挑战异常检测的结论,进一步压降误报;二是把校验规则沉淀为可配置的规则库,让业务人员不经开发就能调整校验口径。
能源数据的价值在于结论,而结论的可信度,取决于架构对错误的容忍与纠正能力。分配-执行-校验闭环,正是我在这个问题上交出的工程答卷。