435 lines
14 KiB
Markdown
435 lines
14 KiB
Markdown
# 示例程序新增方案
|
||
|
||
> 作者: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 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
|
||
- 每个示例在文件顶部有 `//!` 注释说明其演示目的
|