From 358e9710943994174fa62917f4772af682ae923b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sun, 5 Jul 2026 21:04:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=96=B0=E5=A2=9E=20Phase=208=20?= =?UTF-8?q?MVP=20=E9=9B=86=E6=88=90=E5=87=BA=E5=8F=A3=E6=96=B9=E6=A1=88?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/15-phase8-mvp-integration.md | 543 ++++++++++++++++++++++++++++++ 1 file changed, 543 insertions(+) create mode 100644 docs/15-phase8-mvp-integration.md diff --git a/docs/15-phase8-mvp-integration.md b/docs/15-phase8-mvp-integration.md new file mode 100644 index 0000000..32d2a45 --- /dev/null +++ b/docs/15-phase8-mvp-integration.md @@ -0,0 +1,543 @@ +# Phase 8 — MVP 集成出口实现方案 + +- **文档编号**:15 +- **标题**:Phase 8 — MVP 集成出口实现方案 +- **日期**:2026-07-05 +- **状态**:已定稿 +- **涉及模块**:全局(llm/types、agent、tools、memory、prompt、examples) +- **关联文档**:roadmap.md(§Phase 8)、14-phase7-sqlite-store.md + +--- + +## 1. 背景与目标 + +Phase 5-7 已交付 P0 功能闭环:ProviderConfig `from_env()`(Phase 5)、ToolDef IR 正式化(Phase 6)、SqliteStore 持久化(Phase 7)。当前 200 个测试全绿、clippy 0 警告,但缺乏一个"可被人依赖"的集成出口。 + +Phase 8 的目标是完成 API 稳定性扫尾 + Quick Start 示例 + 端到端示例,产出 **v0.2.0-rc.1** 标签。三个 Step 分别对应三类用户群体: + +| Step | 受众 | 交付物 | +|------|------|--------| +| **8.1** | 存量升级者(v0.1 → v0.2) | API 稳定性扫尾 + CHANGELOG | +| **8.2** | 新用户评估者("30 秒决定要不要用") | Quick Start 示例 | +| **8.3** | 技术决策者("这框架能跑真实场景吗") | 端到端集成示例 | + +--- + +## 2. 当前状态 + +| 度量 | 数值 | +|------|------| +| `cargo test --all-targets` | ✅ 200 passed / 0 failed | +| `cargo clippy --all-targets -- -D warnings` | ✅ 0 警告 | +| 已存在 `#[non_exhaustive]` 枚举 | 4 个(StopReason / FinishReason / EvictionPolicy / ProviderType) | +| 已存在 `#[deprecated]` 项 | 3 个(ChatResponse / ToolDefinition / task_agent_demo 中旧类型使用) | +| 已有示例 | 8 个 | +| `StepStatus::Completed` 使用类型 | `ChatResponse`(已 `#[deprecated]`) | + +### 2.1 关键技术债 + +``` +// agent/task.rs —— StepStatus 当前使用已废弃类型 +#[allow(deprecated)] +pub enum StepStatus { + Completed(ChatResponse), // ← ChatResponse 已在 0.1.0 标记 #[deprecated] + ... +} +``` + +`task_agent_demo.rs` 中同时使用了 `ChatResponse` / `OpenaiChatMessage` / `FinishReason` 三个废弃类型,入口处有 `#![allow(deprecated)]`。 + +--- + +## 3. 实施方案 + +### 3.1 Step 8.1 — API 稳定性扫尾 + +拆为 4 个增量 commit: + +| Commit | 内容 | 涉及文件 | +|--------|------|---------| +| **commit 1** | 14 个公开枚举追加 `#[non_exhaustive]` | 各枚举定义文件(详见 §3.1.1) | +| **commit 2** | `StepStatus::Completed(ChatResponse)` → `Completed(MessageResponse)` + `task_agent_demo.rs` 清理全部 3 个废弃类型(`ChatResponse` / `OpenaiChatMessage` / `FinishReason`),移除 `#![allow(deprecated)]` | `src/agent/task.rs`、`examples/task_agent_demo.rs` | +| **commit 3** | CHANGELOG v0.2 条目 + Cargo.toml version → `0.2.0-rc.1` + README 更新 | `CHANGELOG.md`、`Cargo.toml`、`README.md` | +| **commit 4** | 验证:`cargo test + clippy + cargo doc` 零告警 | 无代码改动 | + +#### 3.1.1 `#[non_exhaustive]` 追加清单(14 个枚举) + +按优先级分级: + +| 优先级 | 枚举 | 模块路径 | 理由 | +|--------|------|---------|------| +| **P0 核心** | `Message` | `llm/types/message.rs` | 核心 IR 类型,未来可能新增变体(MultiModal 扩展) | +| | `ContentBlock` | `llm/types/message.rs` | 同上 | +| | `ContentBlockType` | `llm/types/message.rs` | 同上 | +| | `StreamEvent` | `llm/types/response_v2.rs` | 流式事件集,Provider 扩展可能新增事件 | +| | `HookEvent` | `llm/hooks.rs` | 生命周期钩子,框架扩展需要新增事件点 | +| **P0 Error** | `AgentError` | `agent/error.rs` | 顶层错误,下游 match 需保护 | +| | `LlmError` | `llm/error.rs` | LLM 调用错误 | +| | `ToolError` | `tools/error.rs` | 工具系统错误 | +| | `MemoryError` | `memory/error.rs` | 记忆系统错误 | +| | `PromptError` | `prompt/error.rs` | 提示词工程错误 | +| **P1 其他** | `MemoryStrategy` | `memory/conversation.rs` | 对话策略,未来可扩展(如 Summarize) | +| | `StepStatus` | `agent/task.rs` | 步骤状态机,可扩展(如 Cancelled) | +| | `ToolChoice` | `llm/types/request.rs` | Provider 工具选择策略 | +| | `ResponseFormat` | `llm/types/shared.rs` | 响应格式枚举 | + +**明确不加的**: + +| 类别 | 枚举 | 原因 | +|------|------|------| +| 内部 wire-format | `OpenaiChatMessage` / `OpenaiTool` / `OpenaiToolCall` / `ContentField` / `OpenaiContentPart` / `LegacyStreamEvent` | 内部转换层,不构成公共 API 契约 | +| 语义稳定 | `Role` / `ServiceTier` / `Modality` / `ImageDetail` / `AudioFormat` / `StopSequence` | 语义已收敛,协议层无新增变体预期 | +| 使用面窄 | `TemplateValue` / `Permission` / `McpTransport` / `ContentBlockBuilder` / `ExtraError` | 内部实现细节或使用频率极低,下游不直接 match | + +> **`#[non_exhaustive]` 的不可逆性**:一旦 v0.2.0-rc.1 发布,以下游代码可能依赖 `_ =>` 通配分支。在 v0.3+ 中移除 `#[non_exhaustive]` 将构成 semver breaking change(新增变体不再触发编译警告,下游 match 可能遗漏新变体),因此当前追加的标记应视为永久 API 契约。 + +#### 3.1.2 StepStatus 迁移细节 + +```rust +// 变更前 +#[allow(deprecated)] +pub enum StepStatus { + Completed(ChatResponse), // ChatResponse 已 #[deprecated] + ... +} + +// 变更后 +#[non_exhaustive] +pub enum StepStatus { + Completed(MessageResponse), + ... +} +``` + +**字段映射差异**:`MessageResponse` 不是 `ChatResponse` 的简单改名——两者结构不同,迁移需要做字段适配: + +| ChatResponse 字段 | 类型 | MessageResponse 字段 | 类型 | 映射方式 | +|-------------------|------|---------------------|------|---------| +| `message` | `OpenaiChatMessage` | `message` | `Message` | 类型替换:`OpenaiChatMessage::assistant_text(t)` → `Message::Assistant { content: vec![ContentBlock::Text { text: t.into() }] }` | +| `usage` | `Usage` | `usage` | `Usage` | ✅ 同类型,直接迁移 | +| `stop_reason` | `Option` | `stop_reason` | `StopReason` | 类型替换:`Some(FinishReason::Stop)` → `StopReason::Stop`;无 Option 包裹 | +| — | — | `id` | `String` | 新增必填字段,使用空字符串 `""` 占位 | +| — | — | `model` | `String` | 新增必填字段,使用 `"mock"` 或空字符串占位 | +| — | — | `extra` | `HashMap` | 新增字段,使用 `HashMap::new()` 占位 | + +**迁移示例**(`task_agent_demo.rs` 的构造代码): + +```rust +// 旧代码(3 个废弃类型) +StepStatus::Completed(ChatResponse { + message: OpenaiChatMessage::assistant_text("天气:晴,22°C"), + usage: Usage::from_input_output(10, 5), + stop_reason: Some(FinishReason::Stop), +}) + +// 新代码(纯 MessageResponse) +StepStatus::Completed(MessageResponse { + id: String::new(), + model: "mock".into(), + message: Message::assistant("天气:晴,22°C"), + usage: Usage::from_input_output(10, 5), + stop_reason: StopReason::Stop, + extra: HashMap::new(), +}) +``` + +涉及文件: +- `src/agent/task.rs`:枚举定义 + `#[allow(deprecated)]` 移除 + `#[non_exhaustive]` 追加 +- `examples/task_agent_demo.rs`:`ChatResponse{...}` → `MessageResponse{...}` 构造替换,同时替换 `OpenaiChatMessage` / `FinishReason` 引用,移除 `#![allow(deprecated)]` + +### 3.2 Step 8.2 — Quick Start 示例 + +| 属性 | 值 | +|------|-----| +| 文件 | `examples/quick_start.rs` | +| 规模 | ~36 行 | +| Provider | `MockProvider`(FIFO 单响应队列) | +| 工具 | `EchoTool`(回传 `"收到: {input}"`,完整 JSON Schema 参数声明) | +| 执行 | `submit_turn("你好")` → 验证输出包含 `"收到"` | +| 验证 | `cargo run --example quick_start` exit 0 | + +设计要点: +- 展示四层抽象:Agent trait / BaseTool 自定义 / AgentBuilder 装配 / AgentSession 执行 +- 无外部依赖、无 API key、零配置 + +### 3.3 Step 8.3 — 端到端示例 + +| 属性 | 值 | +|------|-----| +| 文件 | `examples/end_to_end.rs` | +| 规模 | ~160 行(**最小可行边界**:3 工具 + 3 轮 + 持久化验证,防止实施中进一步膨胀) | +| Provider | 自动检测 `AG_LLM_*` → `from_env()`,fallback 到 `MockProvider` | +| 工具组合 | EchoTool(回显)+ CalcTool(四则运算,本地执行)+ NoteTool(笔记,通过 MemoryStore trait 操作 SessionMemory) | +| 持久化 | `tempfile::TempDir` + `SqliteStore`,drop 后重建连接验证数据不丢 | +| 对话 | 3 轮:计算 → 记笔记 → 回忆 | +| 验证 | `cargo run --example end_to_end` exit 0(无需任何外部配置) | + +**真实 Provider 切换**:示例在文件顶部注释中说明 "设置 `AG_LLM_BASE_URL` / `AG_LLM_API_KEY` / `AG_LLM_MODEL` 环境变量即可使用真实 LLM Provider(支持 OpenAI / Ollama 等);未设置时自动降级为 MockProvider,零配置可运行。" + +**`from_env()` 部分环境变量策略**:`from_env()` 要求完整的三件套(`{prefix}_BASE_URL` + `{prefix}_API_KEY` + `{prefix}_MODEL`)。当环境变量部分设置时,示例**整体降级到 MockProvider**——不在"半配置"状态下尝试部分初始化。日志输出形如 `"AG_LLM_* 环境变量不完整(检测到: {found_vars}),回退到 MockProvider"`。 + +架构亮点: + +``` + ┌─────────────────────────┐ + │ AgentSession │ + │ (submit_turn × 3) │ + └────┬──────┬──────┬──────┘ + │ │ │ + ┌────┘ │ └──────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌────────┐ ┌──────────┐ + │ EchoTool │ │CalcTool│ │ NoteTool │ + │ (回显) │ │(四则) │ │ (记忆) │ + └──────────┘ └────────┘ └────┬─────┘ + │ + ┌──────▼──────┐ + │ SessionMemory│ + │ (MemoryStore)│ + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ SqliteStore │ + │ (temp dir) │ + └─────────────┘ +``` + +NoteTool 展示 `MemoryStore` trait 解耦能力:不绑定 SqliteStore,上层 `AgentSession` 通过 `SessionMemory` 操作,底层可互换。 + +--- + +## 4. 否决项记录 + +| 否决方案 | 否决原因 | +|---------|---------| +| `#[non_exhaustive]` 仅加 5 个核心类型 | 全面覆盖 Error enums 为零运行时成本,对下游更友好。Error 枚举是下游 match 最密集的地方,漏标会在 v0.3 引入 breakage | +| StepStatus::Completed 留到 v0.3 再修 | rc.1 前清理 deprecated 类型污染最划算——越晚 migration cost 越高,且当前仅 1 个示例 + 1 个测试引用 | +| Quick Start 纯文本路线(不展示自定义工具) | 含 EchoTool 展示核心差异化,仅多 5 行代码但传递了"可以自定义工具"的关键信息 | +| 端到端仅 Echo + Calc(无 NoteTool) | NoteTool 展示 MemoryStore trait 解耦能力是架构亮点,跳过后新用户无法理解 memory 如何集成到 Agent 流程 | +| 持久化仅注释说明不实际运行(方案 Y) | 进程内实操验证(create → drop → reopen → assert)比注释更有说服力,增加约 15 行代码 | + +--- + +## 5. 关键假设 + +1. **MockProvider FIFO 队列满足 auto-tool-loop 消费顺序**:MockProvider 的 `pop()` 按预设顺序弹出。当 LLM 返回多个 tool call 时队列消费顺序与预设一致,无需额外同步 +2. **StepStatus 切换需做字段适配**:`ChatResponse`(3 字段) 到 `MessageResponse`(6 字段) 存在字段类型差异(`message` 类型不同、`stop_reason` 类型 + Option 有无不同、`id`/`model`/`extra` 为新增必填字段),消费者需按字段映射表提供占位值。但消费者仅 1 个(`task_agent_demo.rs`)+ 1 个内联测试,手动适配工作量极小。`StepStatus` 的 `is_terminal()` / `is_pending()` 行为不受影响 +3. **所有 10 个示例零外部配置 exit 0**:已有 8 个示例已验证,新增 2 个(quick_start + end_to_end)均使用 MockProvider fallback,无需 API key +4. **`#[non_exhaustive]` × 14 不触发额外 clippy warning**:当前无代码对以上枚举做 exhaustive match(不含 `_`),追加 `#[non_exhaustive]` 是纯安全标记 + +--- + +## 6. 实施顺序与验证标准 + +### 6.1 提交顺序 + +``` +Step 8.1 (4 commits) + → commit 1: #[non_exhaustive] × 14 + → commit 2: StepStatus 修复(Completed(ChatResponse) → Completed(MessageResponse)) + → commit 3: CHANGELOG v0.2 + Cargo.toml version 0.2.0-rc.1 + README 更新 + → commit 4: 验证(test / clippy / doc 零告警) + +Step 8.2 + → commit 5: examples/quick_start.rs(~36 行) + +Step 8.3 + → commit 6: examples/end_to_end.rs(~160 行) + +最终验证 + → cargo test --all-targets + → cargo clippy --all-targets -- -D warnings + → cargo doc --no-deps + → git tag v0.2.0-rc.1 +``` + +### 6.2 验收标准 + +| 指标 | 要求 | +|------|------| +| `cargo test --all-targets` | 全绿 | +| `cargo clippy --all-targets -- -D warnings` | 0 警告 | +| `cargo doc --no-deps` | 0 warning | +| 所有 10 个示例 | `cargo run --example ` exit 0 | +| Cargo.toml version | `0.2.0-rc.1` | +| CHANGELOG | v0.2 条目完整(Added / Changed / Deprecated / Fixed / Removed 各节) | +| README | 示例列表 + 版本号更新 | +| git tag | `v0.2.0-rc.1` | + +--- + +## 7. 参考来源 + +- **roadmap.md** — Phase 8 原始定义(Step 8.1/8.2/8.3)、依赖关系(Phase 5/6/7 → Phase 8) +- **`src/agent/task.rs`** — `StepStatus` 当前实现,`Completed(ChatResponse)` 类型 +- **`src/llm/types/message.rs`** — `Message` / `ContentBlock` / `ContentBlockType` 枚举定义 +- **`src/llm/types/response_v2.rs`** — `StreamEvent` / `StopReason` 枚举定义(StopReason 已有 `#[non_exhaustive]`) +- **`src/llm/types/shared.rs`** — `ResponseFormat` / `Role` / `FinishReason` 等枚举(FinishReason 已有 `#[non_exhaustive]`) +- **`src/llm/types/request.rs`** — `ToolChoice` 枚举定义 +- **`src/llm/hooks.rs`** — `HookEvent` 枚举定义 +- **`src/llm/error.rs`** — `LlmError` 枚举定义 +- **`src/agent/error.rs`** — `AgentError` 枚举定义 +- **`src/tools/error.rs`** — `ToolError` 枚举定义 +- **`src/memory/error.rs`** — `MemoryError` 枚举定义 +- **`src/memory/conversation.rs`** — `MemoryStrategy` 枚举定义 +- **`src/prompt/error.rs`** — `PromptError` 枚举定义 +- **`examples/task_agent_demo.rs`** — 当前使用 `#[allow(deprecated)]` + `ChatResponse` 的示例 + +--- + +## 8. 实施计划 + +### 8.1 实施步骤 + +#### Step 8.1 — API 稳定性扫尾 + +拆为 4 个增量 commit,依次提交。 + +##### commit 1: #[non_exhaustive] × 14 + +| 属性 | 值 | +|------|-----| +| 涉及文件 | 14 个枚举定义所在文件(见下方清单) | +| 前置依赖 | 无 | +| 预估工作量 | S(<1h) | +| 风险等级 | 低 | + +在每个目标枚举定义处的 `pub enum` 之前加一行 `#[non_exhaustive]`,纯文本属性追加,无逻辑变更。 + +| 目标枚举 | 文件路径 | 行号附近 | +|---------|---------|---------| +| `Message` | `src/llm/types/message.rs` | `pub enum Message` (L22) | +| `ContentBlock` | `src/llm/types/message.rs` | `pub enum ContentBlock` (L99) | +| `ContentBlockType` | `src/llm/types/message.rs` | `pub enum ContentBlockType` (L134) | +| `StreamEvent` | `src/llm/types/response_v2.rs` | `pub enum StreamEvent` (L167) | +| `HookEvent` | `src/llm/hooks.rs` | `pub enum HookEvent` (L9) | +| `AgentError` | `src/agent/error.rs` | `pub enum AgentError` (L20) | +| `LlmError` | `src/llm/error.rs` | `pub enum LlmError` (L10) | +| `ToolError` | `src/tools/error.rs` | `pub enum ToolError` (L6) | +| `MemoryError` | `src/memory/error.rs` | `pub enum MemoryError` (L8) | +| `PromptError` | `src/prompt/error.rs` | `pub enum PromptError` (L3) | +| `MemoryStrategy` | `src/memory/conversation.rs` | `pub enum MemoryStrategy` (L14) | +| `StepStatus` | `src/agent/task.rs` | `pub enum StepStatus` (L59) | +| `ToolChoice` | `src/llm/types/request.rs` | `pub enum ToolChoice` (L14) | +| `ResponseFormat` | `src/llm/types/shared.rs` | `pub enum ResponseFormat` (L70) | + +> **注意**:`StepStatus` 在 commit 2 中会同时被修改(variant 类型替换 + 移除 `#[allow(deprecated)]`)。commit 1 仅追加 `#[non_exhaustive]` 属性,commit 2 再处理变体变更和清理。 + +**验收条件**:`cargo build --all-targets` 通过 + +##### commit 2: StepStatus 修复 + 废弃类型清理 + +| 属性 | 值 | +|------|-----| +| 涉及文件 | `src/agent/task.rs`,`examples/task_agent_demo.rs` | +| 前置依赖 | commit 1(StepStatus 先标记 `#[non_exhaustive]`,此处改 variant 时一并保留,无实际冲突) | +| 预估工作量 | S(<1h,约 20 行改动) | +| 风险等级 | 低 | + +两步操作: + +1. **`src/agent/task.rs`**(L59-L71): + - `StepStatus::Completed(ChatResponse)` → `Completed(MessageResponse)` + - 移除 `#[allow(deprecated)]`(第 13、59 行两处) + +2. **`examples/task_agent_demo.rs`**: + - 替换 3 个废弃类型:`ChatResponse` → `MessageResponse`,`OpenaiChatMessage::assistant_text(t)` → `Message::assistant(t)`,`FinishReason::Stop` → `StopReason::Stop` + - 补充 `id: String::new()`,`model: "mock".into()`,`extra: HashMap::new()` 占位字段 + - 移除 `#![allow(deprecated)]`(第 26 行) + - 移除 `use` 中的 `ChatResponse`、`OpenaiChatMessage`、`FinishReason` + - 添加 `use std::collections::HashMap`,`use agcore::llm::types::{Message, MessageResponse, StopReason}`(注意:`Message::assistant_text(t)` 不存在,需使用 `Message::assistant(t)`) + +字段映射参见 §3.1.2 的字段映射表和迁移示例。 + +**验收条件**:`cargo build --all-targets` 通过,零 deprecated warning + +##### commit 3: CHANGELOG + 版本号 + README + +| 属性 | 值 | +|------|-----| +| 涉及文件 | `CHANGELOG.md`,`Cargo.toml`,`README.md` | +| 前置依赖 | commit 1+2(CHANGELOG 需记录实际变更) | +| 预估工作量 | S(<1h) | +| 风险等级 | 低 | + +1. **`CHANGELOG.md`**:新增 `[0.2.0-rc.1]` 条目,包含: + - **Added**:SqliteStore 持久化 / OllamaProvider / ProviderConfig::from_env / ToolDef IR / Quick Start 和 end_to_end 示例 + - **Changed**:MessageRequest.tools 切换 ToolDef / StepStatus::Completed 类型替换 + - **Deprecated**:ChatResponse / with_system_prompt() / with_client() + - **Non-exhaustive**:14 个枚举标记清单 + +2. **`Cargo.toml`**:第 3 行 `version = "0.1.0"` → `version = "0.2.0-rc.1"` + +3. **`README.md`**:更新示例列表从 7 个改为 10 个(含新增 2 个),版本号同步 + +**验收条件**:人工 review CHANGELOG + `git diff` 确认版本号 + +##### commit 4: 验证 + +| 属性 | 值 | +|------|-----| +| 涉及文件 | 无代码改动 | +| 前置依赖 | commit 3 | +| 预估工作量 | S(<1h,主要等待编译) | +| 风险等级 | 低 | + +运行三条命令: + +```bash +cargo test --all-targets +cargo clippy --all-targets -- -D warnings +cargo doc --no-deps 2>&1 | grep "^warning:" && echo "WARNINGS FOUND" || echo "0 warnings" +``` + +**验收条件**:前两条 0 错误,第三条输出 `0 warnings` + +#### Step 8.2 — Quick Start 示例 + +##### commit 5: examples/quick_start.rs + +| 属性 | 值 | +|------|-----| +| 涉及文件 | `examples/quick_start.rs` | +| 前置依赖 | 无(可从 Phase 7 独立创建) | +| 预估工作量 | S(<1h) | +| 风险等级 | 低 | + +新文件 `examples/quick_start.rs`,~36 行,结构如下: + +``` + 1- 6 use 块(agcore 类型 + Arrow/std 类型) + 7- 8 struct Greeter + impl Agent(name / system_prompt) + 9-14 struct EchoTool + #[async_trait] impl BaseTool(完整 JSON Schema 带 text 参数) +15-20 fn mock_response() -> MessageResponse 辅助函数(构造纯文本响应) +21-33 #[tokio::main] async fn main(): + - ToolRegistry::new() + register EchoTool + - MockProvider 预设 1 条 mock_response + - AgentBuilder::new() + provider + tool_registry + hook_executor → build + - AgentSession::new + submit_turn("你好") + - println!("{}", response.text()) +``` + +**设计约束**: +- EchoTool 的 `parameters()` 返回完整 JSON Schema:`{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}` +- 无外部依赖、无 API key、零配置 +- 展示四层抽象:Agent trait / BaseTool 自定义 / AgentBuilder 装配 / AgentSession 执行 + +**验收条件**:`cargo run --example quick_start` exit 0,输出包含 `"收到"` + +#### Step 8.3 — 端到端示例 + +##### commit 6: examples/end_to_end.rs + +| 属性 | 值 | +|------|-----| +| 涉及文件 | `examples/end_to_end.rs` | +| 前置依赖 | commit 5(示例编写模式已建立);SqliteStore(Phase 7 已完成) | +| 预估工作量 | M(1-4h) | +| 风险等级 | 中 | + +新文件 `examples/end_to_end.rs`,~160 行,最小可行边界(3 工具 + 3 轮 + 持久化验证)。 + +**Provider 初始化策略**: + +``` +if env::var("AG_LLM_BASE_URL").is_ok() && env::var("AG_LLM_API_KEY").is_ok() { + // 使用真实 Provider(AG_LLM_MODEL 非必填,from_env 内部会处理默认值) + let provider: Arc = Arc::from(create_provider( + ProviderType::OpenaiChat, ProviderConfig::from_env("AG_LLM").unwrap() + )?); +} else { + // MockProvider fallback,预设 4 条响应序列 + let found = ["AG_LLM_BASE_URL", "AG_LLM_API_KEY"].iter() + .filter(|k| env::var(k).is_ok()).collect::>(); + eprintln!("AG_LLM_* 环境变量不完整(检测到: {:?}),回退到 MockProvider", found); +} +``` + +**工具定义**: + +| 工具 | 功能 | 关键技术点 | +|------|------|-----------| +| `EchoTool` | 回显输入 | 基础工具注册模式 | +| `CalcTool` | 本地执行四则运算 | 手动解析算术表达式(ponytail:基础 +-*/ 运算无需引入 `rhai` 依赖) | +| `NoteTool` | 通过 MemoryStore trait 读写笔记 | 直接持有 `Arc`,key 前缀 `"note:"`;save 用 `MemoryStore::save(MemoryItem { id: "note:{key}", content, .. })`,query 用 `MemoryStore::list(MemoryFilter { prefix: Some("note:"), .. })` | + +**持久化验证**: + +```rust +let dir = tempfile::TempDir::new()?; +let db_path = dir.path().join("agcore.db"); +let backend = Arc::new(SqliteStore::open(&db_path)?); +// ... 构建 RuntimeBundle + AgentSession,写入数据 ... +drop(bundle); // 释放所有对 backend 的 Arc 引用 +drop(session); +// 此时 backend 无活跃引用,SQLite 连接自动关闭 +let backend2 = Arc::new(SqliteStore::open(&db_path)?); // 重建连接 +// assert 数据仍在 +``` + +**输出示范**: + +``` +=== agcore 端到端演示 === +🔄 Provider: MockProvider (离线回退模式) +💾 SqliteStore: /tmp/agcore_XXXXX/agcore.db +🔧 注册工具: echo, calc, note + +第 1 轮 用户: 帮我算 25 * 4 + → 调用 calc(...) → 100 + → 回答: 25 * 4 = 100 + +第 2 轮 用户: 记下来:结果是 100 + → 调用 note(save, ...) + → 回答: 已记录 + +第 3 轮 用户: 我刚才算了什么? + → 调用 note(query) + → 回答: 您刚才的计算结果是 100 + +📊 用量: prompt=XX, completion=XX + +=== 持久化验证 === +✓ 跨连接数据存活验证通过 + +✓ 端到端演示完成 +``` + +**设计约束**: +- 文件顶部注释说明 `AG_LLM_*` 环境变量切换真实 Provider +- 零外部配置可运行(Mock fallback) +- 最小可行边界:3 工具 + 3 轮 + 持久化验证,不膨胀 + +**验收条件**:`cargo run --example end_to_end` exit 0(零外部配置) + +### 8.2 并行机会 + +commit 1 和 commit 5 可以并行执行(零文件重叠)。commit 5 也可与 commit 2 并行。commit 6 实质上也仅依赖「代码库状态稳定」而非某个具体 commit。 + +| 并行组 | commit A | commit B | 前提 | +|--------|---------|---------|------| +| 1 | commit 1(#[non_exhaustive]) | commit 5(Quick Start) | 零文件重叠 | +| 2 | commit 2(StepStatus 修复) | commit 5(Quick Start) | 零文件重叠 | +| 3 | commit 5(Quick Start) | commit 6(端到端) | 零文件重叠,但存在知识依赖——commit 6 需参考 commit 5 的 `MessageResponse` 构造、`MockProvider` 用法、`AgentBuilder` 装配模式。推荐 commit 5 先行或实施前同步这些模式 | + +### 8.3 风险与应对 + +| 风险 | 影响 | 可能性 | 应对 | +|------|------|--------|------| +| MockProvider 响应序列与 tool-loop 消费顺序不匹配 | commit 6 端到端示例不通过 | 中 | 按 §5 假设 1:设计响应队列时确保每条 Mock 响应的 `stop_reason` 与 ToolUse/Stop 匹配。出现不匹配时改用完整 `MessageResponse` 构造显式控制 | +| NoteTool 与 AgentSession 的数据传递路径需要扩展现有 API | commit 6 需要修改 `session.rs` | 低 | ponytail 方案:NoteTool 直接持有 `Arc` 引用,在 execute 时直接操作 `MemoryStore::save/get`,绕过 AgentSession 的 session_memory 封装 | +| `#[non_exhaustive]` 在某个 enum 上导致 crate 内 match 编译失败 | commit 1 不通过 | 低 | 实施前先运行 `rg "match.*(Message|ContentBlock|ContentBlockType|StreamEvent|HookEvent|AgentError|LlmError|ToolError|MemoryError|PromptError|MemoryStrategy|StepStatus|ToolChoice|ResponseFormat)" src/ --include="*.rs"` 快速扫描 exhaustive match。若某 enum 编译失败,回退该 enum 上的 `#[non_exhaustive]` 属性,标注原因 | + +### 8.4 测试策略 + +| commit | 测试 | 方式 | +|--------|------|------| +| commit 1 | 编译测试 | `cargo build --all-targets` | +| commit 2 | 编译 + 单测 + 无 deprecated warning | `cargo build --all-targets && cargo test` | +| commit 3 | 人工 review | `git diff` | +| commit 4 | 全量自动化 | `cargo test + clippy + doc` | +| commit 5 | 示例运行 | `cargo run --example quick_start` | +| commit 6 | 示例运行 | `cargo run --example end_to_end` | +| 最终 | 全量回归 | 全部三项 + 所有 10 个示例 |