首个 Agent
本节将指导你创建一个完整的调度 Agent(DispatchAgent),它能够监听负荷变化、预测未来负荷、执行经济调度并下发调度指令。本节涵盖 Agent 基本结构、生命周期、工具注册、运行方式与调试技巧。
Agent 基本结构
EnerOS 中的所有 Agent 都实现 eneros_agent::Agent trait。一个 Agent 通常包含以下组成部分:
| 组成 | 说明 |
|---|---|
| 名称与 ID | Agent 在内核中的唯一标识 |
| 工具注册表(ToolRegistry) | Agent 可调用的工具集合 |
| 上下文(AgentContext) | 提供测量、命令、记忆等运行时能力 |
run 方法 | Agent 的主循环逻辑 |
下面是一个完整的 DispatchAgent 实现,包含工具定义、Agent 实现与主函数入口。
完整 DispatchAgent 示例
将以下代码保存为 examples/dispatch_agent.rs:
use eneros_agent::{Agent, AgentContext, AgentResult, AgentStatus};
use eneros_tool::{Tool, ToolRegistry, ToolError, ToolOutput};
use serde::{Deserialize, Serialize};
// ============================================================
// 工具 1:负荷预测工具
// ============================================================
#[derive(Debug, Serialize, Deserialize)]
struct LoadForecastInput {
current_load_mw: f64,
horizon_minutes: u32,
}
#[derive(Debug, Serialize, Deserialize)]
struct LoadForecastOutput {
forecast_mw: Vec<f64>,
confidence: f64,
}
struct LoadForecastTool;
#[async_trait::async_trait]
impl Tool for LoadForecastTool {
fn name(&self) -> &str {
"load_forecast"
}
fn description(&self) -> &str {
"根据当前负荷预测未来一段时间内的负荷曲线"
}
async fn execute(&self, input: serde_json::Value) -> Result<ToolOutput, ToolError> {
let input: LoadForecastInput = serde_json::from_value(input)
.map_err(|e| ToolError::InvalidInput(e.to_string()))?;
// 简化示例:使用线性外推 + 噪声
let mut forecast = Vec::with_capacity(input.horizon_minutes as usize);
for i in 0..input.horizon_minutes {
let t = (i + 1) as f64;
let value = input.current_load_mw * (1.0 + 0.001 * t.sin());
forecast.push(value);
}
let output = LoadForecastOutput {
forecast_mw: forecast,
confidence: 0.92,
};
Ok(ToolOutput::json(output))
}
}
// ============================================================
// 工具 2:经济调度工具
// ============================================================
#[derive(Debug, Serialize, Deserialize)]
struct EconomicDispatchInput {
forecast_mw: Vec<f64>,
generators: Vec<Generator>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Generator {
name: String,
pmin_mw: f64,
pmax_mw: f64,
cost_per_mwh: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct EconomicDispatchOutput {
dispatch: Vec<GeneratorDispatch>,
total_cost: f64,
}
#[derive(Debug, Serialize, Deserialize)]
struct GeneratorDispatch {
name: String,
output_mw: f64,
}
struct EconomicDispatchTool;
#[async_trait::async_trait]
impl Tool for EconomicDispatchTool {
fn name(&self) -> &str {
"economic_dispatch"
}
fn description(&self) -> &str {
"基于负荷预测与机组参数执行经济调度(按成本比例分配)"
}
async fn execute(&self, input: serde_json::Value) -> Result<ToolOutput, ToolError> {
let input: EconomicDispatchInput = serde_json::from_value(input)
.map_err(|e| ToolError::InvalidInput(e.to_string()))?;
let total_load: f64 = input.forecast_mw.iter().sum::<f64>()
/ input.forecast_mw.len().max(1) as f64;
// 简化示例:按容量比例分配,不考虑成本最优
let total_capacity: f64 = input.generators.iter()
.map(|g| g.pmax_mw - g.pmin_mw)
.sum();
let mut dispatch = Vec::new();
let mut total_cost = 0.0;
for gen in &input.generators {
let share = (gen.pmax_mw - gen.pmin_mw) / total_capacity.max(1e-9);
let output_mw = (gen.pmin_mw + share * (total_load - input.generators.iter().map(|g| g.pmin_mw).sum::<f64>()))
.clamp(gen.pmin_mw, gen.pmax_mw);
total_cost += output_mw * gen.cost_per_mwh;
dispatch.push(GeneratorDispatch {
name: gen.name.clone(),
output_mw,
});
}
let output = EconomicDispatchOutput {
dispatch,
total_cost,
};
Ok(ToolOutput::json(output))
}
}
// ============================================================
// Agent 实现
// ============================================================
struct DispatchAgent {
name: String,
tools: ToolRegistry,
}
impl DispatchAgent {
fn new() -> Self {
let mut tools = ToolRegistry::new();
tools.register(LoadForecastTool);
tools.register(EconomicDispatchTool);
Self {
name: "dispatch-agent".to_string(),
tools,
}
}
fn with_generators() -> Self {
let mut agent = Self::new();
// 此处可在 Agent 内部初始化时预设发电机信息
agent
}
}
#[async_trait::async_trait]
impl Agent for DispatchAgent {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
"0.1.0"
}
async fn run(&mut self, ctx: &mut AgentContext) -> AgentResult<()> {
ctx.log_info("DispatchAgent 启动").await?;
// 1. 获取当前负荷
let load = ctx
.get_measurement("total_load")
.await?
.ok_or_else(|| eneros_agent::AgentError::MeasurementNotFound("total_load".into()))?;
ctx.log_info(&format!("当前负荷: {:.2} MW", load)).await?;
// 2. 预测未来 60 分钟负荷
let forecast_input = LoadForecastInput {
current_load_mw: load,
horizon_minutes: 60,
};
let forecast_output: LoadForecastOutput = self
.tools
.call_json("load_forecast", forecast_input)
.await?;
ctx.log_info(&format!(
"负荷预测完成,置信度: {:.2}",
forecast_output.confidence
))
.await?;
// 3. 执行经济调度
let dispatch_input = EconomicDispatchInput {
forecast_mw: forecast_output.forecast_mw.clone(),
generators: vec![
Generator {
name: "G1".to_string(),
pmin_mw: 10.0,
pmax_mw: 100.0,
cost_per_mwh: 50.0,
},
Generator {
name: "G2".to_string(),
pmin_mw: 5.0,
pmax_mw: 80.0,
cost_per_mwh: 65.0,
},
Generator {
name: "G3".to_string(),
pmin_mw: 0.0,
pmax_mw: 50.0,
cost_per_mwh: 80.0,
},
],
};
let dispatch_output: EconomicDispatchOutput = self
.tools
.call_json("economic_dispatch", dispatch_input)
.await?;
// 4. 下发调度指令
for gen in &dispatch_output.dispatch {
ctx.send_command(
"set_generation",
serde_json::json!({
"generator": gen.name,
"output_mw": gen.output_mw
}),
)
.await?;
}
ctx.log_info(&format!(
"调度完成,总成本: {:.2} 元",
dispatch_output.total_cost
))
.await?;
Ok(())
}
}
// ============================================================
// 主函数
// ============================================================
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 初始化日志
tracing_subscriber::fmt::init();
// 创建 Agent 上下文
let mut ctx = AgentContext::builder()
.agent_id("dispatch-001")
.measurement_endpoint("http://localhost:8080/api/v1")
.command_endpoint("http://localhost:8080/api/v1")
.build();
// 设置当前负荷(演示用)
ctx.set_measurement("total_load", 150.0).await?;
// 创建并运行 Agent
let mut agent = DispatchAgent::with_generators();
let status = agent.run(&mut ctx).await;
match status {
Ok(()) => println!("Agent 运行成功"),
Err(e) => println!("Agent 运行失败: {:?}", e),
}
Ok(())
}
将上述文件加入 examples/dispatch_agent.rs,并在根 Cargo.toml 的 [workspace.dependencies] 中确认 eneros-agent、eneros-tool 已声明,然后运行:
cargo run --release --example dispatch_agent
预期输出:
2026-07-06T10:00:00 INFO dispatch_agent: DispatchAgent 启动
2026-07-06T10:00:00 INFO dispatch_agent: 当前负荷: 150.00 MW
2026-07-06T10:00:00 INFO dispatch_agent: 负荷预测完成,置信度: 0.92
2026-07-06T10:00:01 INFO dispatch_agent: 调度完成,总成本: 8250.00 元
Agent 运行成功
Agent 生命周期
EnerOS Agent 的完整生命周期包含以下阶段:
| 阶段 | 状态 | 说明 |
|---|---|---|
| 注册 | Registered | Agent 被注册到运行时,但未启动 |
| 初始化 | Initializing | 调用 init() 方法,加载工具与配置 |
| 就绪 | Ready | 初始化完成,等待调度 |
| 运行 | Running | 调用 run() 方法执行主逻辑 |
| 暂停 | Paused | 被外部暂停,可恢复 |
| 停止 | Stopped | 主动停止,资源已释放 |
| 错误 | Error | 运行异常退出 |
| 完成 | Completed | 主逻辑正常完成 |
状态转移图
Registered -> Initializing -> Ready -> Running -> Completed
^ |
| v
+------ Paused
|
v
Stopped
^
|
Running -> Error
生命周期钩子
#[async_trait::async_trait]
impl Agent for DispatchAgent {
async fn init(&mut self, ctx: &mut AgentContext) -> AgentResult<()> {
// 初始化时加载历史数据
ctx.log_info("初始化 DispatchAgent").await?;
Ok(())
}
async fn run(&mut self, ctx: &mut AgentContext) -> AgentResult<()> {
// 主逻辑
Ok(())
}
async fn pause(&mut self, ctx: &mut AgentContext) -> AgentResult<()> {
// 暂停时保存上下文
ctx.save_checkpoint("dispatch_pause").await?;
Ok(())
}
async fn resume(&mut self, ctx: &mut AgentContext) -> AgentResult<()> {
// 恢复时加载上下文
ctx.load_checkpoint("dispatch_pause").await?;
Ok(())
}
async fn stop(&mut self, ctx: &mut AgentContext) -> AgentResult<()> {
// 停止时清理资源
ctx.log_info("DispatchAgent 已停止").await?;
Ok(())
}
}
工具注册详解
ToolRegistry 是 Agent 的工具容器,支持注册、查询、调用。
注册工具
use eneros_tool::ToolRegistry;
let mut tools = ToolRegistry::new();
// 注册单个工具
tools.register(LoadForecastTool);
tools.register(EconomicDispatchTool);
// 批量注册
tools.register_all(vec![
Box::new(LoadForecastTool),
Box::new(EconomicDispatchTool),
]);
查询工具
// 列出所有工具
for name in tools.list() {
println!("已注册工具: {}", name);
}
// 检查工具是否存在
if tools.contains("load_forecast") {
println!("负荷预测工具可用");
}
// 获取工具描述
if let Some(desc) = tools.description("economic_dispatch") {
println!("描述: {}", desc);
}
调用工具
// 直接调用并解析输出
let output: LoadForecastOutput = tools
.call_json("load_forecast", LoadForecastInput {
current_load_mw: 150.0,
horizon_minutes: 60,
})
.await?;
// 获取原始 ToolOutput
let raw = tools.call("load_forecast", json!({"current_load_mw": 150.0})).await?;
println!("输出类型: {:?}", raw.kind);
内置工具集
EnerOS 在 eneros-tool crate 中提供 30+ 内置工具:
| 工具 | 类别 | 说明 |
|---|---|---|
load_forecast | 预测 | 负荷预测 |
generation_forecast | 预测 | 新能源出力预测 |
economic_dispatch | 优化 | 经济调度 |
optimal_power_flow | 优化 | 最优潮流 |
contingency_analysis | 安全 | N-1 故障分析 |
state_estimation | 估计 | 状态估计 |
topology_analysis | 拓扑 | 拓扑分析 |
fault_location | 运维 | 故障定位 |
equipment_health | 运维 | 设备健康评估 |
market_clearing | 交易 | 市场出清 |
demand_response | 能效 | 需求响应 |
weather_query | 数据 | 天气查询 |
运行 Agent
通过 CLI 运行
# 运行内置 Agent
./target/release/enerosctl agent run dispatch-agent
# 指定 Agent ID
./target/release/enerosctl agent run dispatch-agent --id dispatch-001
# 指定配置
./target/release/enerosctl agent run dispatch-agent --config agent.toml
# 列出所有已注册 Agent
./target/release/enerosctl agent list
# 查看 Agent 状态
./target/release/enerosctl agent status dispatch-001
# 停止 Agent
./target/release/enerosctl agent stop dispatch-001
通过 REST API 运行
# 创建并运行 Agent
curl -X POST http://localhost:8080/api/v1/agents \
-H "Content-Type: application/json" \
-d '{
"type": "dispatch",
"id": "dispatch-001",
"config": {
"generators": [
{"name": "G1", "pmin_mw": 10, "pmax_mw": 100, "cost_per_mwh": 50},
{"name": "G2", "pmin_mw": 5, "pmax_mw": 80, "cost_per_mwh": 65}
]
}
}'
响应:
{
"agent_id": "dispatch-001",
"status": "running",
"started_at": "2026-07-06T10:00:00Z"
}
# 查看 Agent 状态
curl http://localhost:8080/api/v1/agents/dispatch-001
# 停止 Agent
curl -X DELETE http://localhost:8080/api/v1/agents/dispatch-001
# 列出所有 Agent
curl http://localhost:8080/api/v1/agents
通过 Rust 代码运行
use eneros_agent::{AgentRuntime, AgentRuntimeBuilder};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建运行时
let mut runtime: AgentRuntime = AgentRuntimeBuilder::new()
.api_endpoint("http://localhost:8080/api/v1")
.max_agents(100)
.build();
// 注册并启动 Agent
let agent = DispatchAgent::with_generators();
let agent_id = runtime.register(agent).await?;
runtime.start(&agent_id).await?;
// 等待完成
runtime.wait(&agent_id).await?;
println!("Agent {} 已完成", agent_id);
Ok(())
}
内置 Agent 类型
EnerOS 在 eneros-agent crate 中提供 7 种领域 Agent,开箱即用:
| Agent | 类型字符串 | 职责 | 典型场景 |
|---|---|---|---|
| DispatchAgent | dispatch | 经济调度与负荷分配 | 实时调度、日前计划 |
| SelfHealingAgent | self-healing | 故障定位、隔离与恢复(FDIR) | 配网自愈 |
| OperationAgent | operation | 设备状态监测与运维 | 设备健康管理 |
| PlanningAgent | planning | 配网规划与方案比选 | 配网扩展 |
| TradingAgent | trading | 电力市场交易 | 现货市场、虚拟电厂 |
| EnergyAgent | energy | 能效优化与需求响应 | 需求响应、能效管理 |
| Orchestrator | orchestrator | 多智能体编排与协调 | 跨区域协同 |
内置 Agent 配置示例
# 运行自愈 Agent
curl -X POST http://localhost:8080/api/v1/agents \
-H "Content-Type: application/json" \
-d '{
"type": "self-healing",
"id": "sh-001",
"config": {
"network_id": "net_a1b2c3d4",
"auto_isolate": true,
"auto_restore": true,
"max_restore_time_s": 60
}
}'
# 运行交易 Agent
curl -X POST http://localhost:8080/api/v1/agents \
-H "Content-Type: application/json" \
-d '{
"type": "trading",
"id": "trade-001",
"config": {
"market_endpoint": "https://market.example.com/api",
"strategy": "price_taker",
"max_bid_mw": 50
}
}'
Agent 状态管理
持久化状态
EnerOS 将 Agent 的状态自动持久化到 SQLite,便于重启后恢复:
// 在 run 中保存状态
ctx.save_state("last_dispatch", &dispatch_output).await?;
// 重启后加载
let last: EconomicDispatchOutput = ctx.load_state("last_dispatch").await?
.unwrap_or_default();
检查点(Checkpoint)
// 创建检查点
ctx.save_checkpoint("before_dispatch").await?;
// 执行可能失败的操作
let result = self.tools.call("economic_dispatch", input).await;
if result.is_err() {
// 回滚到检查点
ctx.load_checkpoint("before_dispatch").await?;
}
记忆(Memory)
// 写入记忆
ctx.remember("event", "负荷突增 20MW").await?;
// 查询记忆
let events = ctx.recall("event", 10).await?; // 最近 10 条
for ev in events {
println!("[{}] {}", ev.timestamp, ev.content);
}
调试技巧
1. 启用 trace 级别日志
ENEROS_LOG=trace cargo run --example dispatch_agent
2. 单步执行工具
// 临时打印工具调用详情
let raw = tools.call("load_forecast", input).await?;
println!("工具输出: {}", serde_json::to_string_pretty(&raw)?);
3. 使用 Mock 上下文
use eneros_agent::MockAgentContext;
let mut mock = MockAgentContext::new();
mock.set_measurement("total_load", 150.0).await;
mock.expect_command("set_generation").times(3);
let mut agent = DispatchAgent::new();
agent.run(&mut mock).await.unwrap();
4. 查看 Agent 日志
# 实时查看 Agent 日志
curl -N http://localhost:8080/api/v1/agents/dispatch-001/logs
# 查看历史日志
curl "http://localhost:8080/api/v1/agents/dispatch-001/logs?limit=100&level=info"
5. 通过 Dashboard 调试
访问 http://localhost:8080/dashboard/agents,可:
- 查看所有 Agent 列表与状态
- 点击 Agent 查看实时日志
- 查看工具调用统计
- 手动触发 Agent 运行
下一步
- 配置文件 - 了解 eneros.toml 配置选项