Files
agcore/docs/8-examples-plan.md
T
2026-06-11 22:49:27 +08:00

435 lines
14 KiB
Markdown
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.
# 示例程序新增方案
> 作者: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<Value, ToolError> {
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 <name>` 全部成功退出(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 keyCI 会跳过 | 中 | 高 | 用 `#[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
- 每个示例在文件顶部有 `//!` 注释说明其演示目的