# AG Core v0.3.2 Step 1(Phase 20)— Cargo Features 基础设施改造实施方案 ## 1. 背景与目标 **背景**:agcore 是一个 Rust 编写的智能体核心工具箱,目前约 23,718 行、66 个源文件。v0.3.0 发布后,所有模块在编译时全量捆绑,下游用户无法按需选择模块,即使只使用 LLM 对话也需要编译 sqlite / MCP / agent 引擎等全部依赖。 **目标**:通过 Cargo features 拆分让下游按需选择模块。Step 1 是基础设施变更 —— `Cargo.toml` features 定义 + 依赖 optional 化 + 必要的子模块 cfg 门控,编译通过后打 checkpoint。 **预期效果**: - `default = ["full"]` → v0.3.0 用户零迁移成本 - 最小组合(`document`)零重型外部依赖(仅依赖始终编译的轻量依赖:serde/serde_json/thiserror/async-trait/tracing) - 纯对话组合(`chat + provider-openai`)仅需 ~10 个依赖,不含 sqlite / MCP / engine ## 2. 需求分析 ### 2.1 约束条件 | # | 约束 | 说明 | |---|------|------| | 1 | `default = ["full"]` | 保持向后兼容,v0.3.0 用户零迁移成本 | | 2 | `document` feature 零重型外部依赖(仅依赖始终编译的轻量依赖:serde/serde_json/thiserror/async-trait/tracing) | 纯 std + 始终编译的轻量依赖(serde/serde_json/thiserror/async-trait/tracing) | | 3 | tokio 从 `["full"]` 拆细 | 已验证全库无 net/fs/signal 使用,拆为 `["rt", "sync", "time", "macros", "process", "io-util"]` | | 4 | 重型依赖全部 optional | tokio、reqwest、rusqlite、tracing-subscriber、tokio-stream、futures、futures-util、futures-core、bytes、async-stream、tokio-util、time | | 5 | 始终编译的轻量依赖 | serde、serde_json、thiserror、async-trait、tracing | ### 2.2 关键决策 | # | 决策 | 理由 | |---|------|------| | 1 | `tools` feature 必须 `imply tokio` | `src/tools/registry.rs` 使用 `tokio::time::timeout` | | 2 | `pub mod llm` 门控条件为 `any(feature = "llm-types", feature = "llm")` | `prompt → llm-types` 路径需要 llm 模块编译,但只需 types 子模块 | | 3 | 测试 dev-dependencies 加 `tokio = { version = "1", features = ["rt", "macros"] }` | 现有 `#[tokio::test]` 需要 tokio runtime | | 4 | 快捷组合名保持原名(chat/multi/light) | 文档中说明各组合包含的 feature 约束 | | 5 | `init_tracing()` 函数整体用 `#[cfg(feature = "tracing-init")]` 包裹 | 避免 `use tracing_subscriber` 出现在未启用 feature 时编译失败 | ## 3. 方案设计 ### 3.1 Features 定义(完整 Cargo.toml `[features]` 草案) ```toml [features] default = ["full"] # === 模块级 features === document = [] llm-types = [] prompt = ["llm-types"] llm = ["llm-types", "tokio", "async-stream", "futures-core", "tokio-stream"] tools = ["llm-types", "futures", "tokio-util", "tokio"] tools-mcp = ["tools", "reqwest"] # memory 模块依赖 llm(conversation/vector_store 使用 compact/embedding)、tokio(knowledge.rs 使用 Mutex)、time(types.rs 使用 OffsetDateTime) memory = ["document", "llm", "tokio", "time"] memory-sqlite = ["memory", "rusqlite", "time"] agent = ["llm", "tools", "memory", "futures-util"] engine = ["agent"] # === Provider features === # Provider features — openai/anthropic 额外依赖 bytes(流式解析)和 futures-util(Stream 组合) provider-openai = ["llm", "reqwest", "bytes", "futures-util"] provider-anthropic = ["llm", "reqwest", "bytes", "futures-util"] # deepseek/qwen 使用 openai_compat 适配层,不需要 bytes 和 futures-util provider-deepseek = ["llm", "reqwest"] provider-qwen = ["llm", "reqwest"] provider-ollama = ["llm", "reqwest"] # === 工具 features === tracing-init = ["tracing-subscriber"] # === 快捷组合 === full = [ "document", "llm-types", "prompt", "llm", "tools", "tools-mcp", "memory", "memory-sqlite", "agent", "engine", "provider-openai", "provider-anthropic", "provider-deepseek", "provider-qwen", "provider-ollama", "tracing-init", ] light = ["llm", "provider-openai", "tools", "tools-mcp", "memory", "agent", "engine", "prompt", "document"] chat = ["agent", "provider-openai"] multi = ["engine", "provider-openai"] ``` **features 依赖图(简略)**: ``` document (零外部依赖) └── memory (+llm, +tokio, +time) ─── memory-sqlite (+rusqlite, +time) llm-types (零依赖) ├── prompt └── llm (+tokio, +async-stream, +futures-core, +tokio-stream) ├── tools (+futures, +tokio-util) ─── tools-mcp (+reqwest) ├── provider-openai / provider-anthropic (+reqwest, +bytes, +futures-util) ├── provider-deepseek / provider-qwen / provider-ollama (+reqwest) └── agent (+tools, +memory, +futures-util) ─── engine ``` ### 3.2 依赖 optional 化方案 **始终编译(5 个,不参与门控)**: ```toml serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "2" async-trait = "0.1" tracing = "0.1" ``` **12 个依赖加 `optional = true`**: | 依赖 | 原声明 | 新声明 | |------|--------|--------| | tokio | `{ version = "1", features = ["full"] }` | `{ version = "1", features = ["rt", "sync", "time", "macros", "process", "io-util"], optional = true }` | | reqwest | `{ version = "0.12", features = ["json", "stream"] }` | `{ version = "0.12", features = ["json", "stream"], optional = true }` | | rusqlite | `{ version = "0.32", features = ["bundled"] }` | `{ version = "0.32", features = ["bundled"], optional = true }` | | tracing-subscriber | `{ version = "0.3", features = ["env-filter"] }` | `{ version = "0.3", features = ["env-filter"], optional = true }` | | tokio-stream | `{ version = "0.1" }` | `{ version = "0.1", optional = true }` | | futures | `{ version = "0.3" }` | `{ version = "0.3", optional = true }` | | futures-util | `{ version = "0.3" }` | `{ version = "0.3", optional = true }` | | futures-core | `{ version = "0.3" }` | `{ version = "0.3", optional = true }` | | bytes | `{ version = "1" }` | `{ version = "1", optional = true }` | | async-stream | `{ version = "0.3" }` | `{ version = "0.3", optional = true }` | | tokio-util | `{ version = "0.7", features = ["rt", "sync"] }` | `{ version = "0.7", features = ["rt", "sync"], optional = true }` | | time | `{ version = "0.3", features = ["serde", "parsing", "formatting", "macros"] }` | `{ version = "0.3", features = ["serde", "parsing", "formatting", "macros"], optional = true }` | **dev-dependencies 新增**: ```toml [dev-dependencies] tokio = { version = "1", features = ["rt", "macros"] } ``` ### 3.3 源文件改动清单 共涉及 **8 个文件**(预估 ~100 行改动):`Cargo.toml`、`src/lib.rs`、`src/llm.rs`、`src/llm/cycle.rs`、`src/tools.rs`、`src/memory.rs`、`src/memory/store.rs`、`src/agent/session.rs` --- #### 文件 1:`Cargo.toml` **改动 1.1** — 新增 `[features]` 表(约 45 行,插入在 `[package]` 之后、`[dependencies]` 之前) ```diff + [features] + default = ["full"] + + # === 模块级 features === + document = [] + llm-types = [] + prompt = ["llm-types"] + llm = ["llm-types", "tokio", "async-stream", "futures-core", "tokio-stream"] + tools = ["llm-types", "futures", "tokio-util", "tokio"] + tools-mcp = ["tools", "reqwest"] + # memory 模块依赖 llm(conversation/vector_store 使用 compact/embedding)、tokio(knowledge.rs 使用 Mutex)、time(types.rs 使用 OffsetDateTime) + memory = ["document", "llm", "tokio", "time"] + memory-sqlite = ["memory", "rusqlite", "time"] + agent = ["llm", "tools", "memory", "futures-util"] + engine = ["agent"] + + # === Provider features === + # Provider features — openai/anthropic 额外依赖 bytes(流式解析)和 futures-util(Stream 组合) + provider-openai = ["llm", "reqwest", "bytes", "futures-util"] + provider-anthropic = ["llm", "reqwest", "bytes", "futures-util"] + # deepseek/qwen 使用 openai_compat 适配层,不需要 bytes 和 futures-util + provider-deepseek = ["llm", "reqwest"] + provider-qwen = ["llm", "reqwest"] + provider-ollama = ["llm", "reqwest"] + + # === 工具 features === + tracing-init = ["tracing-subscriber"] + + # === 快捷组合 === + full = [ + "document", "llm-types", "prompt", "llm", + "tools", "tools-mcp", + "memory", "memory-sqlite", + "agent", "engine", + "provider-openai", "provider-anthropic", "provider-deepseek", + "provider-qwen", "provider-ollama", + "tracing-init", + ] + light = ["llm", "provider-openai", "tools", "tools-mcp", "memory", "agent", "engine", "prompt", "document"] + chat = ["agent", "provider-openai"] + multi = ["engine", "provider-openai"] ``` **改动 1.2** — tokio 依赖声明修改 ```diff - tokio = { version = "1", features = ["full"] } + tokio = { version = "1", features = ["rt", "sync", "time", "macros", "process", "io-util"], optional = true } ``` **改动 1.3** — 11 个重型依赖逐行加 `optional = true` ```diff - reqwest = { version = "0.12", features = ["json", "stream"] } + reqwest = { version = "0.12", features = ["json", "stream"], optional = true } - rusqlite = { version = "0.32", features = ["bundled"] } + rusqlite = { version = "0.32", features = ["bundled"], optional = true } - tracing-subscriber = { version = "0.3", features = ["env-filter"] } + tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true } - tokio-stream = "0.1" + tokio-stream = { version = "0.1", optional = true } - futures = "0.3" + futures = { version = "0.3", optional = true } - futures-util = "0.3" + futures-util = { version = "0.3", optional = true } - futures-core = "0.3" + futures-core = { version = "0.3", optional = true } - bytes = "1" + bytes = { version = "1", optional = true } - async-stream = "0.3" + async-stream = { version = "0.3", optional = true } - tokio-util = { version = "0.7", features = ["rt"] } + tokio-util = { version = "0.7", features = ["rt", "sync"], optional = true } - time = { version = "0.3", features = ["serde", "parsing", "formatting", "macros"] } + time = { version = "0.3", features = ["serde", "parsing", "formatting", "macros"], optional = true } ``` **改动 1.4** — `[dev-dependencies]` 新增 tokio ```diff + [dev-dependencies] + tokio = { version = "1", features = ["rt", "macros"] } ``` **说明**:如果原 `Cargo.toml` 已有 `[dev-dependencies]` 则追加该行;若无则新增整个 section。 --- #### 文件 2:`src/lib.rs`(当前约 26 行 → 改动后约 40 行) **当前内容(参考)**: ```rust //! agcore —— 智能体(Agent)核心工具箱。 pub mod llm; pub mod document; pub mod prompt; pub mod tools; pub mod memory; pub mod agent; pub mod engine; pub use document::Document; use tracing_subscriber::{EnvFilter, fmt, prelude::*}; static INIT: std::sync::Once = std::sync::Once::new(); pub fn init_tracing() { INIT.call_once(|| { let filter = EnvFilter::try_from_default_env() .unwrap_or_else(|_| EnvFilter::new("agcore=info")); tracing_subscriber::registry() .with(fmt::layer()) .with(filter) .init(); }); } ``` **改动后内容**: ```diff //! agcore —— 智能体(Agent)核心工具箱。 - pub mod llm; + #[cfg(any(feature = "llm-types", feature = "llm"))] + pub mod llm; - pub mod document; + #[cfg(feature = "document")] + pub mod document; - pub mod prompt; + #[cfg(feature = "prompt")] + pub mod prompt; - pub mod tools; + #[cfg(feature = "tools")] + pub mod tools; - pub mod memory; + #[cfg(feature = "memory")] + pub mod memory; - pub mod agent; + #[cfg(feature = "agent")] + pub mod agent; - pub mod engine; + #[cfg(feature = "engine")] + pub mod engine; - pub use document::Document; + #[cfg(feature = "document")] + pub use document::Document; - use tracing_subscriber::{EnvFilter, fmt, prelude::*}; - static INIT: std::sync::Once = std::sync::Once::new(); - pub fn init_tracing() { - INIT.call_once(|| { - let filter = EnvFilter::try_from_default_env() - .unwrap_or_else(|_| EnvFilter::new("agcore=info")); - tracing_subscriber::registry() - .with(fmt::layer()) - .with(filter) - .init(); - }); - } + #[cfg(feature = "tracing-init")] + use tracing_subscriber::{EnvFilter, fmt, prelude::*}; + + #[cfg(feature = "tracing-init")] + static INIT: std::sync::Once = std::sync::Once::new(); + + #[cfg(feature = "tracing-init")] + pub fn init_tracing() { + INIT.call_once(|| { + let filter = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new("agcore=info")); + tracing_subscriber::registry() + .with(fmt::layer()) + .with(filter) + .init(); + }); + } ``` --- #### 文件 3:`src/llm.rs`(当前约 12 行 → 改动后约 24 行) **改动说明**:为每个子模块声明加 feature 门控。`types` 子模块在 `llm-types` 或 `llm` 任一 feature 启用时编译(`llm` imply `llm-types`,但 `prompt` 也 depend on `llm-types`);其余子模块(compact/convert/cycle 等)仅在 `llm` feature 启用时编译。 ```diff //! LLM 调用周期 —— 大模型基础调用周期控制。 - pub mod types; + #[cfg(feature = "llm-types")] + pub mod types; - pub mod compact; + #[cfg(feature = "llm")] + pub mod compact; - pub mod convert; + #[cfg(feature = "llm")] + pub mod convert; - pub mod cycle; + #[cfg(feature = "llm")] + pub mod cycle; - pub mod embedding; + #[cfg(feature = "llm")] + pub mod embedding; - pub mod error; + #[cfg(feature = "llm")] + pub mod error; - pub mod hooks; + #[cfg(feature = "llm")] + pub mod hooks; - pub mod mock; + #[cfg(feature = "llm")] + pub mod mock; - pub mod provider; + // provider 模块依赖 reqwest(通过 reqwest::Client),仅在任一 provider feature 启用时编译 + #[cfg(any(feature = "provider-openai", feature = "provider-anthropic", feature = "provider-deepseek", feature = "provider-qwen", feature = "provider-ollama"))] + pub mod provider; - pub mod stream; + #[cfg(feature = "llm")] + pub mod stream; ``` --- #### 文件 3b:`src/llm/cycle.rs`(新增文件,约 35 行) **改动说明**:`cycle.rs` 中使用 `crate::tools::ToolRegistry`(第 29 行),依赖 `tools` feature。工具相关字段和方法需加 `#[cfg(feature = "tools")]` 门控。 > ⚠️ 这是 Phase 22.7 原计划的门控变更,因为编译阻塞提前到 Step 1 执行。 ```diff //! Cycle —— 多轮对话与工具调用编排。 use async_trait::async_trait; use futures::StreamExt; // 来自 llm→tokio imply 链 + #[cfg(feature = "tools")] use crate::tools::ToolRegistry; // ... struct / enum 定义 ... // ===== CycleConfig — 工具相关字段加 cfg 门控 ===== pub struct CycleConfig { pub max_retries: usize, pub max_history: usize, + #[cfg(feature = "tools")] pub max_tool_turns: usize, + #[cfg(feature = "tools")] pub tool_timeout_secs: u64, // ... 其他字段 ... } // ===== Cycle — 方法加 cfg 门控 ===== impl Cycle { /// 仅在有 tools feature 时才有工具调用相关方法 + #[cfg(feature = "tools")] pub async fn submit_with_tools(&self, ...) -> Result<...> { // ... } + /// submit_with_tools_stream 方法同样需要 tools 门控, + /// 因参数包含 Arc 而与 submit_with_tools 同理。 + #[cfg(feature = "tools")] + pub async fn submit_with_tools_stream( + &self, ... // 方法签名中包含 Arc 参数 + ) -> Result<...> { + // ... + } + #[cfg(feature = "tools")] async fn run_tool_loop(&self, ...) -> Result<...> { // ... } } + // ===== 顶层函数 — 同样依赖 ToolRegistry ===== + /// run_tool_loop 函数(顶层函数,非 LlmCycle 方法)同样依赖 Arc, + /// 参数包含 Arc,需 #[cfg(feature = "tools")]。 + #[cfg(feature = "tools")] + pub async fn run_tool_loop( + // ... 函数签名中包含 Arc 参数 + ) -> Result<...> { + // ... + } **说明**:`Cycle` 本身的 struct 定义、`submit()` 基础方法、`ResponseStream` 等不依赖 tools 的部分保持无门控,仅在 `llm` feature 下编译即可。 --- #### 文件 4:`src/tools.rs`(当前约 13 行 → 改动后约 15 行) **改动说明**:`mcp` 子模块及对应的 `pub use` 仅在 `tools-mcp` feature 启用时编译。其余子模块(base/error/permission/registry)始终在 `tools` feature 下编译。 ```diff //! 工具系统 —— 工具抽象、注册、调用、权限控制与 MCP 集成。 pub mod base; pub mod error; - pub mod mcp; + #[cfg(feature = "tools-mcp")] + pub mod mcp; pub mod permission; pub mod registry; pub use base::{BaseTool, ToolContext, ToolRef}; pub use error::ToolError; - pub use mcp::{McpClient, McpTransport}; + #[cfg(feature = "tools-mcp")] + pub use mcp::{McpClient, McpTransport}; pub use permission::{Permission, PermissionChecker, PermissionConfig}; pub use registry::{ToolInvocation, ToolRegistry}; ``` --- #### 文件 5:`src/memory/store.rs`(当前约 62 行 → 改动后约 64 行) **改动说明**:`sqlite_store` 子模块及其 `pub use` 仅在 `memory-sqlite` feature 启用时编译。 ```diff //! MemoryStore 抽象接口与默认实现。 use async_trait::async_trait; use crate::memory::error::MemoryError; use crate::memory::types::{MemoryFilter, MemoryItem}; pub mod in_memory; - pub mod sqlite_store; + #[cfg(feature = "memory-sqlite")] + pub mod sqlite_store; pub use in_memory::InMemoryStore; - pub use sqlite_store::SqliteStore; + #[cfg(feature = "memory-sqlite")] + pub use sqlite_store::SqliteStore; ``` **说明**:`MemoryStore` trait、`EvictionConfig`、`EvictionPolicy` 等定义保持不变,不需要 cfg 门控。 --- #### 文件 6:`src/memory.rs`(当前约 32 行 → 改动后约 34 行) **改动说明**:`SqliteStore` 的重新导出仅在 `memory-sqlite` feature 启用时编译。其余子模块声明和 `pub use` 保持不变(`memory` feature 门控由 `src/lib.rs` 负责)。 ```diff //! 记忆系统 —— 对话消息管理、知识页面存储与关键词检索。 // 所有子模块声明保持不变: // pub mod conversation; // pub mod error; // pub mod graph; // pub mod knowledge; // pub mod retriever; // pub mod store; // ... // 高频类型 pub use conversation::{ConversationMemory, ConversationMemoryConfig}; pub use error::MemoryError; pub use graph::{GraphEntity, GraphRelation, InMemoryGraph, KnowledgeGraph, RelationDirection, ScoredEntity}; pub use knowledge::KnowledgeStore; pub use retriever::MemoryRetriever; pub use store::{InMemoryStore, MemoryStore}; + #[cfg(feature = "memory-sqlite")] + pub use store::SqliteStore; // 其余 pub use 保持不变... ``` --- #### 文件 7:`src/agent/session.rs`(新增文件,约 30 行) **改动说明**:`session.rs` 中引用了 `crate::engine::*`(SessionMemoryEntry、SessionSnapshot、EngineError),而 `agent` feature 不含 `engine`(`engine = ["agent"]` 是反向依赖)。需要对 engine 相关导入和方法加门控。 > ⚠️ 阻塞 B3:`src/agent/session.rs:28-29` 无条件引用 `crate::engine::*`,在 `agent` feature 下编译时因缺少 engine 而失败。 ```diff //! Session —— Agent 会话管理。 use async_trait::async_trait; use crate::llm::types::LLMRequest; use crate::memory::MemoryStore; + #[cfg(feature = "engine")] use crate::engine::snapshot::{SessionMemoryEntry, SessionSnapshot}; + #[cfg(feature = "engine")] use crate::engine::EngineError; // ===== AgentSession — pending_memory_restore 字段 ===== + /// AgentSession 结构体中的 pending_memory_restore 字段类型来自 engine 模块, + /// 需要条件编译。 pub struct AgentSession { + // ... 其他字段 ... + + #[cfg(feature = "engine")] + pending_memory_restore: Option>, + // ... 其他字段 ... + } impl Session { /// to_snapshot / from_snapshot / restore_memory 仅在 engine feature 下可用 + #[cfg(feature = "engine")] pub fn to_snapshot(&self) -> SessionSnapshot { // ... } + #[cfg(feature = "engine")] pub fn from_snapshot(snap: SessionSnapshot) -> Result { // ... } + #[cfg(feature = "engine")] async fn restore_memory(&mut self, entries: Vec) -> Result<(), MemoryError> { // ... } } ``` **说明**:`Session` 结构体本身以及不依赖 engine 的方法(如 `new()`、`add_message()`、`get_history()`)保持无门控,仅在 `agent` feature 下编译即可。 --- ## 4. 实施步骤 按 **3 个 commit** 粒度执行,每个 commit 后编译验证。 ### Commit 1:Cargo.toml features 定义 + 依赖 optional 化 **涉及文件**:仅 `Cargo.toml` **操作清单**: 1. 在 `[package]` 之后、`[dependencies]` 之前插入 `[features]` 表(16 个 features + 4 个快捷组合,约 45 行) 2. tokio features 从 `["full"]` 改为 `["rt", "sync", "time", "macros", "process", "io-util"]` 并加 `optional = true` 3. reqwest / rusqlite / tracing-subscriber / tokio-stream / futures / futures-util / futures-core / bytes / async-stream / tokio-util / time 共 11 个依赖加 `optional = true` 4. 在 `[dependencies]` 之后新增 `[dev-dependencies]` 加 `tokio = { version = "1", features = ["rt", "macros"] }` **验证**: ```bash cargo build --no-default-features # 不依赖任何 optional crate,应通过 cargo build -F document # 零外部依赖,应通过 ``` --- ### Commit 2:cfg 门控(pub mod + pub use + init_tracing) **涉及文件**:`src/lib.rs`、`src/llm.rs`、`src/llm/cycle.rs`、`src/tools.rs`、`src/memory/store.rs`、`src/memory.rs`、`src/agent/session.rs` **操作清单**: 按文件逐一执行: 1. `src/lib.rs` — 7 个 `pub mod` 加 `#[cfg(feature = "...")]`、`Document` pub use 加 cfg、`init_tracing` 整体用 `#[cfg(feature = "tracing-init")]` 包裹 2. `src/llm.rs` — 10 个子模块按 `llm-types` / `llm` / provider 分类门控 3. `src/llm/cycle.rs` — `ToolRegistry` 导入加 `#[cfg(feature = "tools")]`,工具字段和方法加相同门控 4. `src/tools.rs` — `pub mod mcp` 和 `pub use mcp::*` 加 `#[cfg(feature = "tools-mcp")]` 5. `src/memory/store.rs` — `pub mod sqlite_store` 和 `pub use sqlite_store::SqliteStore` 加 `#[cfg(feature = "memory-sqlite")]` 6. `src/memory.rs` — `pub use store::SqliteStore` 加 `#[cfg(feature = "memory-sqlite")]` 7. `src/agent/session.rs` — engine 相关导入加 `#[cfg(feature = "engine")]`,to_snapshot/from_snapshot/restore_memory 加相同门控 **验证**: ```bash cargo build -F "full" # 全量回归 cargo build -F "prompt" # 验证 llm::types imply 路径 cargo build -F "tools" # 验证 tokio imply 路径 cargo build -F "memory" # 验证记忆模块不含 sqlite cargo build -F "chat,provider-openai" # 纯对话组合 cargo build -F "chat,provider-openai,tools-mcp" # 带 MCP 对话 ``` --- ### Commit 3:Checkpoint 全量验证 **操作清单**: 1. 完整的验证矩阵执行(见第 5 节) 2. `cargo test -F "full"` 确认 427 passed **验证**: ```bash cargo test -F "full" cargo build -F "light" cargo build -F "multi" ``` ## 5. 验证标准 ### 编译验证矩阵 | 命令 | 验证目标 | 预期结果 | |------|---------|---------| | `cargo build --no-default-features` | 空 crate | 编译通过(无模块) | | `cargo build -F "full"` | 全量回归 | 编译通过,与 v0.3.0 语义一致 | | `cargo test -F "full"` | 测试回归 | `427 passed` | | `cargo build -F "document"` | 文档模块独立 | 编译通过,零外部依赖 | | `cargo build -F "prompt"` | 提示词独立 | 编译通过,`llm::types` imply 路径正确 | | `cargo build -F "tools"` | 工具独立 | 编译通过,tokio imply 路径正确 | | `cargo build -F "memory"` | 记忆模块独立编译 | ✅ 不含 sqlite(依赖 B1/B2 修复) | | `cargo build -F "memory-sqlite"` | 含 SQLite 的记忆模块 | ✅ 含 rusqlite | | `cargo build -F "agent"` | Agent 独立编译 | ✅ 含 llm+tools+memory,不含 engine(依赖 B1/B3 修复) | | `cargo build -F "engine"` | Engine 独立编译 | ✅ imply agent → llm+tools+memory | | `cargo build -F "chat,provider-openai"` | 纯对话组合 | 编译通过,不含 MCP、sqlite | | `cargo build -F "chat,provider-openai,tools-mcp"` | 带 MCP 对话 | 编译通过,含 reqwest 无 sqlite | | `cargo build -F "light"` | 生产常用组合 | 编译通过 | | `cargo build -F "multi"` | 多 provider 组合 | 编译通过 | ### 验证操作指令 每次编译验证后执行(验证编译产物不含意外符号): ```bash # 确认空 crate 确实没有模块符号 cargo build --no-default-features 2>&1 && echo "OK" # 确认 document 零外部依赖(无 reqwest/rusqlite 等符号) cargo build -F "document" 2>&1 && echo "OK" # 全量构建 + 测试 cargo build -F "full" 2>&1 && cargo test -F "full" 2>&1 | tail -5 ``` ### 验证通过条件 - 所有 14 条编译验证命令返回 exit code 0 - `cargo test -F "full"` 输出 `427 passed`(与 v0.3.0 基线一致,不要求测试数精确匹配,但必须全部通过且数量合理) - 无 `unused import` / `unused variable` / `dead code` warning(由 `#[cfg]` 引起的新 warning 需逐一修复) ## 6. 风险评估 | 风险 | 等级 | 缓解措施 | |------|------|---------| | **tokio features 拆细遗漏**:某些代码路径用到 net/fs/signal | 低 | 已通过 SA(静态分析)验证全库无相关使用 | | **`#[tokio::test]` 编译失败**:测试代码无 tokio runtime | 低 | `[dev-dependencies]` 添加 `tokio = { version = "1", features = ["rt", "macros"] }` | | **下游 transitive tokio features 缩小**:依赖 agcore 的 crate 之前通过 agcore 间接获得 `full` tokio,现在范围缩小 | 中 | Phase 27 README 发布说明中明确告知迁移方案;下游如需完整 tokio 需自行添加 | | **imply 链未闭合**:某个 feature 依赖了未 imply 的 feature | 低 | 7 种特征组合全部逐条构建验证;features 定义中有交叉引用的全部显式列出 | | **unused cfg warning**:某些 `#[cfg]` 标记导致编译 warning | 低 | 每个 commit 后检查编译器输出,发现后立即修复 | | **测试依赖循环**:dev-deps 与普通 deps 版本冲突 | 低 | dev-deps 的 tokio 版本与主依赖保持一致 (`version = "1"`,由 cargo 自动选择兼容版本) | | **memory 跨模块 imply 链** | **高** | memory 模块依赖 llm(conversation/vector_store)、tokio(knowledge)、time(types),imply 链必须完整传递 | `memory` feature 定义已包含 `llm`、`tokio`、`time`;验证矩阵覆盖 memory 独立编译 | | **跨模块引用未门控** | **高** | provider.rs 依赖 reqwest、cycle.rs 依赖 ToolRegistry、session.rs 依赖 engine,Step 1 必须添加 cfg 门控 | provider 模块 cfg 改为 provider-xxx 条件;cycle.rs 加 tools 门控;session.rs 加 engine 门控 |