14 KiB
示例程序新增方案
作者: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)缺乏可运行、可独立验证的示例展示。
目标
- 覆盖全 Phase — 每个 Phase 核心能力至少有一个示例
- 可离线运行 — 优先选用 MockProvider 和本地逻辑,不强制依赖 API key
- 真实使用模式 — 示例反映库的预期使用方式(Builder 模式、trait 实现、? 错误传播)
- 验收辅助 — 示例跑通 = 对应模块公共 API 可用且装配正确
非目标
- 不替代单元测试的边界覆盖(内联测试仍负责边界条件)
- 不引入第三方依赖(示例只使用
agcore公开 API) - 不追求 UI 或交互式输入
当前状态分析
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 构建消息链 → 断言验证
关键代码片段示意:
// 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 白名单过滤
关键代码片段示意:
// 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 输出
关键代码片段示意:
// 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 递增)SessionMemoryset/get/snapshot 正确- 多个 session 间数据隔离
新增代码量:约 100 行
🥇 示例:task_agent_demo.rs(Phase 4b)
设计思路:使用 JsonPlanParser 从预定义 JSON 解析 Plan,驱动 Step 状态机转换,观察状态单向流转。
流程:
构造 JSON 输入 → JsonPlanParser::parse → Plan 数据结构 → 模拟 execute_plan → Step 状态变迁 → Hook 事件
关键代码片段示意:
// 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 持久化读取
关键代码片段示意:
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 → 评分排序结果输出 → 阈值过滤观察
关键代码片段示意:
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页面存/取/搜索正确TextOverlapDice 系数在 [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 收尾
关键代码片段示意:
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 存在性判断优雅降级 |
验收标准
-
阶段一全部完成时:
cargo run --example prompt_composer→ 成功退出cargo run --example custom_tool→ 成功退出cargo run --example agent_session_demo→ 成功退出cargo run --example task_agent_demo→ 成功退出
-
阶段二全部完成时:
- 额外 3 个示例均可
cargo run成功
- 额外 3 个示例均可
-
阶段三完成时(可选):
full_integration在有.env配置时成功运行,无配置时友好提示降级
-
全局验收:
cargo build无新增警告- 所有示例输出格式清晰,有说明性 println
- 每个示例在文件顶部有
//!注释说明其演示目的