Files
agcore/examples/task_agent_demo.rs
T
徐涛 2d0d5c1592 feat(examples): 新增 Phase B 7 个离线可运行示例
新增 7 个示例覆盖全 Phase 公共 API(不依赖 API key 即可运行):
- agent_session_demo:AgentBuilder → AgentSession → SessionMemory
- custom_tool:BaseTool 注册 + invoke/invoke_all + 权限检查
- prompt_composer:PromptTemplate + PromptComposer + validate_messages
- task_agent_demo:JsonPlanParser + Step 状态机 + 错误路径
- conversation_memory_demo:滑动窗口 + 多角色 + 隔离
- knowledge_search_demo:KnowledgeStore + 关键词检索 + 停用词过滤
- streaming_events_demo:submit_stream 事件消费 + 队列耗尽错误路径
2026-07-03 15:50:51 +08:00

113 lines
4.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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 完成");
}