跳到主内容

首个 Agent

快速开始

首个 Agent

本节将指导你创建一个完整的调度 Agent(DispatchAgent),它能够监听负荷变化、预测未来负荷、执行经济调度并下发调度指令。本节涵盖 Agent 基本结构、生命周期、工具注册、运行方式与调试技巧。

Agent 基本结构

EnerOS 中的所有 Agent 都实现 eneros_agent::Agent trait。一个 Agent 通常包含以下组成部分:

组成说明
名称与 IDAgent 在内核中的唯一标识
工具注册表(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-agenteneros-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 的完整生命周期包含以下阶段:

阶段状态说明
注册RegisteredAgent 被注册到运行时,但未启动
初始化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类型字符串职责典型场景
DispatchAgentdispatch经济调度与负荷分配实时调度、日前计划
SelfHealingAgentself-healing故障定位、隔离与恢复(FDIR)配网自愈
OperationAgentoperation设备状态监测与运维设备健康管理
PlanningAgentplanning配网规划与方案比选配网扩展
TradingAgenttrading电力市场交易现货市场、虚拟电厂
EnergyAgentenergy能效优化与需求响应需求响应、能效管理
Orchestratororchestrator多智能体编排与协调跨区域协同

内置 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 运行

下一步