//! task_agent_demo —— Plan 解析、Step 状态机、错误路径。 //! //! 演示: //! 1. `JsonPlanParser::parse` 解析合法 JSON 输入 //! 2. `Plan` / `Step` 数据结构遍历 //! 3. `StepStatus` 状态机:Pending → Running → Completed / Failed / Skipped //! 4. `is_pending()` / `is_terminal()` 语义 //! 5. 错误路径:非法 JSON / 空 steps / 缺字段 → `AgentError::PlanParse` //! //! 运行:`cargo run --example task_agent_demo` //! //! ## 已知技术债(v0.2 迁移指南) //! //! 本示例使用 `#[deprecated]` 标记的旧 wire-format 类型: //! - `ChatResponse`、`OpenaiChatMessage`、`FinishReason` —— `OpenaiChatProvider::chat_inner()` //! 内部转换层仍在使用(参见 `docs/10a-phase0-types-and-trait.md` §2.5.1), //! 故结构体定义保留。 //! - `StepStatus::Completed(ChatResponse)` —— 因为 `Step` 的"已完成"变体需携带 //! provider 响应,目前沿用旧的 `ChatResponse`。 //! //! **触发迁移的条件**:v0.2 引入 IR 层的 `StepResult` / 切换为 `MessageResponse`。 //! **迁移路径**:将本文件 `ChatResponse`/`OpenaiChatMessage`/`FinishReason` 替换为 //! `MessageResponse`/`Message`/`StopReason`,移除顶部 `#![allow(deprecated)]`。 //! 上层应用代码(`TaskAgent` 消费者)也可同步迁移。 #![allow(deprecated)] use agcore::agent::{AgentError, JsonPlanParser, PlanParser, Step, StepStatus}; use agcore::llm::types::openai_message::OpenaiChatMessage; use agcore::llm::types::shared::FinishReason; use agcore::llm::types::{ChatResponse, Usage}; #[tokio::main] async fn main() { // 1. JsonPlanParser 解析合法 JSON println!("=== JsonPlanParser 解析合法 JSON ==="); let parser = JsonPlanParser; let input = r#"{ "steps": [ {"description": "查询北京天气"}, {"description": "根据天气计算穿衣建议"}, {"description": "生成最终回复"} ] }"#; let plan = parser .parse(input, "为用户生成今日出行建议") .await .expect("合法 JSON 应解析成功"); println!("Plan goal: {}", plan.goal); println!("Plan id: {}", plan.id); println!("Steps: {}", plan.steps.len()); for (i, step) in plan.steps.iter().enumerate() { println!( " [{}] {} (pending={}, terminal={})", i, step.description, step.status.is_pending(), step.status.is_terminal() ); } assert!(plan.steps.iter().all(|s| s.status.is_pending())); // 2. Step 状态机:Pending → Running → Completed println!("\n=== Step 状态机:Pending → Running → Completed ==="); let mut step = plan.steps.into_iter().next().expect("应有 step"); println!("初始: pending={}", step.status.is_pending()); assert!(step.status.is_pending()); step.status = StepStatus::Running; println!("Running: pending={}, terminal={}", step.status.is_pending(), step.status.is_terminal()); step.status = StepStatus::Completed(ChatResponse { message: OpenaiChatMessage::assistant_text("天气:晴,22°C"), usage: Usage::from_input_output(5, 10), stop_reason: Some(FinishReason::Stop), }); println!("Completed: pending={}, terminal={}", step.status.is_pending(), step.status.is_terminal()); assert!(step.status.is_terminal()); // 3. 失败路径 println!("\n=== Step 状态机:失败路径 ==="); let mut fail_step = Step::new(0, "调用天气 API"); fail_step.status = StepStatus::Failed(AgentError::Other("API 不可用".into())); println!("Failed: pending={}, terminal={}", fail_step.status.is_pending(), fail_step.status.is_terminal()); assert!(fail_step.status.is_terminal()); // 4. 跳过路径 let mut skipped = Step::new(0, "可选步骤"); skipped.status = StepStatus::Skipped; println!("Skipped: terminal={}", skipped.status.is_terminal()); assert!(skipped.status.is_terminal()); // 5. 错误路径 1:非法 JSON println!("\n=== 错误路径:非法 JSON ==="); let err = parser.parse("not json", "goal").await.unwrap_err(); println!("错误: {err}"); assert!(matches!(err, AgentError::PlanParse(_))); // 6. 错误路径 2:空 steps println!("\n=== 错误路径:空 steps ==="); let err = parser.parse(r#"{"steps": []}"#, "goal").await.unwrap_err(); println!("错误: {err}"); assert!(matches!(err, AgentError::PlanParse(_))); // 7. 错误路径 3:缺 description 字段 println!("\n=== 错误路径:缺 description 字段 ==="); let bad_input = r#"{"steps": [{"description": "ok"}, {"no_desc": true}]}"#; let err = parser.parse(bad_input, "goal").await.unwrap_err(); println!("错误: {err}"); assert!(matches!(err, AgentError::PlanParse(_))); println!("\n✓ task_agent_demo 完成"); }