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

14 KiB
Raw Blame History

示例程序新增方案

作者: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 或交互式输入

当前状态分析

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.rsPhase 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.rsPhase 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.rsPhase 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 递增)
  • SessionMemory set/get/snapshot 正确
  • 多个 session 间数据隔离

新增代码量:约 100 行


🥇 示例:task_agent_demo.rsPhase 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.rsPhase 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.rsPhase 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 页面存/取/搜索正确
  • TextOverlap Dice 系数在 [0.0, 1.0] 范围内
  • 停用词过滤正常
  • 低于 min_score 的结果被过滤

新增代码量:约 60 行


🥈 示例:streaming_events_demo.rsPhase 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.rsPhase 全栈集成)

设计思路:端到端演示,将 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 不同步(库重构后示例过时) 将示例加入 CIcargo 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
    • 每个示例在文件顶部有 //! 注释说明其演示目的