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 事件消费 + 队列耗尽错误路径
This commit is contained in:
徐涛
2026-07-03 15:50:51 +08:00
parent 7b2d2db322
commit 2d0d5c1592
7 changed files with 929 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
//! 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 完成");
}