From 829be90d196c70798b042b725b889daeb4fe14fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 11 Jun 2026 22:49:27 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E6=96=B9=E6=A1=88=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/8-examples-plan.md | 434 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 docs/8-examples-plan.md diff --git a/docs/8-examples-plan.md b/docs/8-examples-plan.md new file mode 100644 index 0000000..b955c6c --- /dev/null +++ b/docs/8-examples-plan.md @@ -0,0 +1,434 @@ +# 示例程序新增方案 + +> 作者:Proposal Agent +> 日期:2026-06-11 +> 对应版本:agcore v0.1 + +## 背景与目标 + +### 问题 + +当前 `examples/` 目录下只有一个 `simple_visit.rs`,仅演示了 `LlmCycle::submit()` 的基本 LLM 调用(Phase 0),且依赖真实 API key 才能运行。v0.1 已实现的全部 7 个 Phase 的能力(Phase 0~4c)缺乏可运行、可独立验证的示例展示。 + +### 目标 + +1. **覆盖全 Phase** — 每个 Phase 核心能力至少有一个示例 +2. **可离线运行** — 优先选用 MockProvider 和本地逻辑,不强制依赖 API key +3. **真实使用模式** — 示例反映库的预期使用方式(Builder 模式、trait 实现、? 错误传播) +4. **验收辅助** — 示例跑通 = 对应模块公共 API 可用且装配正确 + +### 非目标 + +- 不替代单元测试的边界覆盖(内联测试仍负责边界条件) +- 不引入第三方依赖(示例只使用 `agcore` 公开 API) +- 不追求 UI 或交互式输入 + +--- + +## 当前状态分析 + +```text +examples/ +└── simple_visit.rs # 仅 Phase 0 基础调用,需 API key +``` + +### 现有示例覆盖缺口 + +| Phase | 模块 | 示例覆盖 | 缺口 | +|-------|------|---------|------| +| Phase 0 | LLM 调用周期 | `simple_visit.rs` | 流式事件、重试逻辑、Auto-compaction 未演示 | +| Phase 1 | 提示词工程 | ❌ | 模板变量插值、消息组合、条件渲染 | +| Phase 2 | 工具系统 | ❌ | 自定义工具注册、并行调用、权限检查 | +| Phase 3 | 记忆系统 | ❌ | 对话记忆滑动窗口、知识页面存储、关键词检索 | +| Phase 4a | 核心胶水层 | ❌ | Agent/AgentSession/RuntimeBundle/AgentBuilder 装配 | +| Phase 4b | 任务执行 | ❌ | PlanParser/Step 状态机/TaskAgent | +| Phase 4c | 会话级记忆 | ❌ | SessionMemory set/get/snapshot | + +--- + +## 设计方案 + +### 总体架构 + +新增示例按三层优先级组织,每个示例为一个独立 `.rs` 文件,统一放在 `examples/` 目录下。 + +``` +examples/ +├── simple_visit.rs # [已有] 基本 LLM 调用(Phase 0) +├── prompt_composer.rs # [新增] 提示词组合(Phase 1)🥇 +├── custom_tool.rs # [新增] 自定义工具(Phase 2)🥇 +├── agent_session_demo.rs # [新增] Agent 会话(Phase 4a+4c)🥇 +├── task_agent_demo.rs # [新增] 任务规划(Phase 4b)🥇 +├── conversation_memory_demo.rs # [新增] 对话记忆(Phase 3)🥈 +├── knowledge_search_demo.rs # [新增] 知识检索(Phase 3)🥈 +├── streaming_events_demo.rs # [新增] 流式事件(Phase 0)🥈 +└── full_integration.rs # [新增] 全栈集成(Phase 全栈)🥉 +``` + +### 详细设计 + +#### 🥇 示例:`prompt_composer.rs`(Phase 1) + +**设计思路**:纯本地运行,不依赖任何外部服务。通过构造模板、组合消息来验证 Prompt Engineering 模块的公共 API。 + +**流程**: +``` +TemplateContext 构造 → PromptTemplate 填充变量 → PromptComposer 构建消息链 → 断言验证 +``` + +**关键代码片段示意**: +```rust +// 1. 构造模板 +let mut registry = PromptTemplateRegistry::new(); +registry.register(PromptTemplate::new("weather", "今日{location}天气:{condition},温度{temperature}")); + +// 2. 填充变量 +let template = registry.get("weather").unwrap(); +let rendered = template.render(&TemplateContext::from([ + ("location", "北京"), + ("condition", "晴"), + ("temperature", "25°C"), +])?; + +// 3. 组合消息 +let composer = PromptComposer::new() + .system("你是一个天气助手") + .user(rendered) + .assistant(/* 可选历史 */); + +let messages = composer.compose(); +assert_eq!(messages.len(), 2); +``` + +**验证点**: +- `TemplateContext` 变量插值正确 +- `PromptComposer` 消息顺序正确 +- `PromptError` 在缺失变量时正确返回 + +**新增代码量**:约 60 行 + +--- + +#### 🥇 示例:`custom_tool.rs`(Phase 2) + +**设计思路**:实现一个模拟工具(如 `WeatherTool`),注册到 `ToolRegistry`,演示单次调用、并行调用、权限检查。 + +**流程**: +``` +实现 BaseTool → 注册到 ToolRegistry → invoke 单次 → invoke_all 并行 → PermissionChecker 白名单过滤 +``` + +**关键代码片段示意**: +```rust +// 1. 实现工具 +struct WeatherTool; +#[async_trait] +impl BaseTool for WeatherTool { + fn name(&self) -> &str { "get_weather" } + fn parameters(&self) -> Value { json!({"type":"object","properties":{"city":{"type":"string"}}}) } + async fn execute(&self, args: Value, _ctx: &ToolContext) -> Result { + Ok(json!({"city": args["city"], "temperature": 22, "condition": "晴"})) + } +} + +// 2. 注册 + 调用 +let mut registry = ToolRegistry::new(); +registry.register(WeatherTool.into())?; +let result = registry.invoke("get_weather", json!({"city": "北京"})).await?; + +// 3. 并行调用 +let results = registry.invoke_all(vec![...], 30).await; + +// 4. 权限检查 +let checker = PermissionChecker::new(PermissionConfig::white_list(vec!["get_weather"])); +assert!(checker.check("get_weather").is_ok()); +assert!(checker.check("delete_file").is_err()); +``` + +**验证点**: +- 工具注册/查找/调用完整链路 +- 并行调用结果数正确 +- 权限白名单/黑名单行为 +- `ToolError::NotFound` 未注册工具 + +**新增代码量**:约 80 行 + +--- + +#### 🥇 示例:`agent_session_demo.rs`(Phase 4a + 4c) + +**设计思路**:使用 `MockProvider` 模拟 LLM 响应,完整演示 `Agent → AgentBuilder → RuntimeBundle → AgentSession` 的装配流程及 `SessionMemory` 的读写。 + +**流程**: +``` +实现 Agent → AgentBuilder 构造 RuntimeBundle → AgentSession::new → submit_turn → session_data 读写 → snapshot 输出 +``` + +**关键代码片段示意**: +```rust +// 1. 定义 Agent +struct CalculatorAgent; +impl Agent for CalculatorAgent { + fn name(&self) -> &str { "calculator" } + fn system_prompt(&self) -> Option<&str> { Some("你是计算器助手") } +} + +// 2. 装配 RuntimeBundle +let bundle = AgentBuilder::new() + .provider(Arc::new(mock_provider)) + .tool_registry(Arc::new(tool_registry)) + .hook_executor(Arc::new(hook_executor)) + .build()?; + +// 3. 创建会话 +let mut session = AgentSession::new(Arc::new(CalculatorAgent), "session-1", Arc::new(bundle)); + +// 4. 提交对话 +let response = session.submit_turn("1+1=?").await?; + +// 5. SessionMemory 读写 +session.set_session_data("last_result", "2").await?; +let result = session.get_session_data("last_result").await?; +println!("{}", session.session_memory().snapshot().await?); +``` + +**验证点**: +- `AgentBuilder::build()` 必填字段校验 +- `submit_turn` 流程完整(hook 触发、cost 累计、turn_index 递增) +- `SessionMemory` set/get/snapshot 正确 +- 多个 session 间数据隔离 + +**新增代码量**:约 100 行 + +--- + +#### 🥇 示例:`task_agent_demo.rs`(Phase 4b) + +**设计思路**:使用 `JsonPlanParser` 从预定义 JSON 解析 Plan,驱动 Step 状态机转换,观察状态单向流转。 + +**流程**: +``` +构造 JSON 输入 → JsonPlanParser::parse → Plan 数据结构 → 模拟 execute_plan → Step 状态变迁 → Hook 事件 +``` + +**关键代码片段示意**: +```rust +// 1. 解析 Plan +let parser = JsonPlanParser; +let input = r#"{"steps": [{"description": "查天气"}, {"description": "算结果"}]}"#; +let mut plan = parser.parse(input, "完成今日任务").await?; + +// 2. 模拟 step 执行 +assert!(plan.steps[0].status.is_pending()); +step.status = StepStatus::Running; +step.status = StepStatus::Completed(response); +assert!(step.status.is_terminal()); + +// 3. 失败路径 +step.status = StepStatus::Failed(AgentError::Other("API 不可用".into())); +assert!(step.status.is_terminal()); +``` + +**验证点**: +- 合法 JSON 解析正确 +- 非法 JSON / 空步骤 / 缺字段返回 `AgentError::PlanParse` +- 状态机单向转换(Pending → Running → Completed/Failed/Skipped) +- `is_terminal()` / `is_pending()` 语义正确 + +**新增代码量**:约 70 行 + +--- + +#### 🥈 示例:`conversation_memory_demo.rs`(Phase 3) + +**设计思路**:演示 `ConversationMemory` 的多轮消息写入、滑动窗口淘汰、冷热分离存储。 + +**流程**: +``` +ConversationMemory::new → add_message × N → 触发窗口淘汰 → get_history 验证 → MemoryStore 持久化读取 +``` + +**关键代码片段示意**: +```rust +let config = ConversationMemoryConfig { + strategy: MemoryStrategy::SlidingWindow, + max_turns: 5, + ..Default::default() +}; +let mut memory = ConversationMemory::new(store, "session-1", config); + +// 写入 10 条消息 +for i in 0..10 { + memory.add_message(OpenaiChatMessage::user_text(format!("消息 {i}"))).await?; +} + +// 验证窗口大小为 5 +let history = memory.get_history().await?; +assert_eq!(history.len(), 5); +assert!(history[0].content().contains("消息 5")); +``` + +**验证点**: +- 滑动窗口淘汰旧消息 +- Full 策略保留全部消息 +- 冷存储 `MemoryStore` 写入/读取正确 +- `CompactConfig` 触发自动压缩 + +**新增代码量**:约 70 行 + +--- + +#### 🥈 示例:`knowledge_search_demo.rs`(Phase 3) + +**设计思路**:演示 `KnowledgeStore` 页面存储 + `MemoryRetriever` 关键词检索与 Dice 系数评分。 + +**流程**: +``` +KnowledgeStore 创建页面 → MemoryRetriever::search → 评分排序结果输出 → 阈值过滤观察 +``` + +**关键代码片段示意**: +```rust +let store = KnowledgeStore::new(memory_store); +store.save_page("Rust 入门", "Rust 是一门系统编程语言...", vec!["rust", "编程"]).await?; +store.save_page("Python 简介", "Python 是动态类型语言...", vec!["python", "动态"]).await?; + +let retriever = MemoryRetriever::new(store, RetrieverConfig::default()); +let result = retriever.search("Rust 语言").await?; + +for item in &result.items { + println!(" 页面: {} (评分: {:.2})", item.page.title, item.score); + assert!(item.score >= 0.0 && item.score <= 1.0); +} +``` + +**验证点**: +- `KnowledgeStore` 页面存/取/搜索正确 +- `TextOverlap` Dice 系数在 [0.0, 1.0] 范围内 +- 停用词过滤正常 +- 低于 `min_score` 的结果被过滤 + +**新增代码量**:约 60 行 + +--- + +#### 🥈 示例:`streaming_events_demo.rs`(Phase 0 — 流式接口) + +**设计思路**:调用 `LlmCycle::submit_stream()` 获取事件流,展示了语义事件的消费模式。可选使用 API key 或 MockProvider。 + +**流程**: +``` +LlmCycle::submit_stream → 事件循环 match StreamEvent → 输出类型/内容 → TurnComplete 收尾 +``` + +**关键代码片段示意**: +```rust +let mut cycle = LlmCycle::new(provider, config); +let mut stream = cycle.submit_stream("讲个笑话".into(), vec![]).await?; + +use futures_util::StreamExt; +while let Some(event) = stream.next().await { + match event { + StreamEvent::AssistantTextDelta { text } => print!("{text}"), + StreamEvent::TurnComplete { reason } => println!("\n\n完成,原因: {reason:?}"), + StreamEvent::Error { message } => eprintln!("错误: {message}"), + _ => {} // 其他事件 + } +} +``` + +**验证点**: +- 流式链路完整(请求 → 事件 → 完成) +- 事件枚举覆盖所有变体 +- 错误事件正确处理 + +**新增代码量**:约 80 行 + +--- + +#### 🥉 示例:`full_integration.rs`(Phase 全栈集成) + +**设计思路**:端到端演示,将 v0.1 所有模块装配为一个可运行的智能体。需真实 API key。 + +**流程**: +``` +创建 Agent(带 system prompt + 2 个工具)→ 注入 MemoryStore/Retriever → AgentSession → 多轮对话 → 知识检索 → SessionMemory 桥接 → 输出运行摘要 +``` + +**验证点**: +- Phase 4 "胶水层"真正将 Phase 0~3 粘合 +- `submit_turn` 内部调用工具 +- `ConversationMemory` 回写 +- 全链路无类型/装配错误 + +**新增代码量**:约 150 行 + +--- + +## 实现计划 + +### 阶段一:第一梯队(优先级 🥇) + +| 示例 | 预计代码量 | 可并行实施 | +|------|-----------|-----------| +| `prompt_composer.rs` | ~60 行 | ✅ 与 2/3 并行 | +| `custom_tool.rs` | ~80 行 | ✅ 与 1/3 并行 | +| `agent_session_demo.rs` | ~100 行 | ✅ 与 1/2 并行 | +| `task_agent_demo.rs` | ~70 行 | ✅ 与 1/2/3 并行 | + +**验证标准**:`cargo run --example ` 全部成功退出(code 0)。 + +### 阶段二:第二梯队(优先级 🥈) + +| 示例 | 预计代码量 | 前置依赖 | +|------|-----------|---------| +| `conversation_memory_demo.rs` | ~70 行 | 无 | +| `knowledge_search_demo.rs` | ~60 行 | 无 | +| `streaming_events_demo.rs` | ~80 行 | 无 | + +### 阶段三:第三梯队(优先级 🥉) + +| 示例 | 预计代码量 | 前置依赖 | +|------|-----------|---------| +| `full_integration.rs` | ~150 行 | 需 `.env` 配置 API key | + +### 总工作量估算 + +| 合计 | 代码行数 | 文件数 | +|------|---------|-------| +| 第一阶段 | ~310 行 | 4 个 | +| 第二阶段 | ~210 行 | 3 个 | +| 第三阶段 | ~150 行 | 1 个 | +| **总计** | **~670 行** | **8 个文件** | + +--- + +## 风险评估 + +| 风险 | 影响 | 概率 | 缓解措施 | +|------|------|------|---------| +| 示例与库 API 不同步(库重构后示例过时) | 高 | 中 | 将示例加入 CI:`cargo test --examples` | +| MockProvider 行为与真实 Provider 差异 | 低 | 低 | 示例明确标注离线/在线模式 | +| 示例代码量膨胀超过预期 | 低 | 低 | 每个示例控制在 200 行以内,超过则拆分子函数 | +| `full_integration.rs` 依赖 API key,CI 会跳过 | 中 | 高 | 用 `#[cfg(not(ci))]` 或 `.env` 存在性判断优雅降级 | + +--- + +## 验收标准 + +1. **阶段一全部完成时**: + - `cargo run --example prompt_composer` → 成功退出 + - `cargo run --example custom_tool` → 成功退出 + - `cargo run --example agent_session_demo` → 成功退出 + - `cargo run --example task_agent_demo` → 成功退出 + +2. **阶段二全部完成时**: + - 额外 3 个示例均可 `cargo run` 成功 + +3. **阶段三完成时**(可选): + - `full_integration` 在有 `.env` 配置时成功运行,无配置时友好提示降级 + +4. **全局验收**: + - `cargo build` 无新增警告 + - 所有示例输出格式清晰,有说明性 println + - 每个示例在文件顶部有 `//!` 注释说明其演示目的