refactor(core): 完成 v0.3.2 Cargo features 拆分基础设施

- 定义 16 个 features + 4 个快捷组合,default = ["full"] 保持向后兼容
- 12 个重型依赖 optional 化(tokio/reqwest/rusqlite 等)
- 全模块 #[cfg(feature)] 门控注入(llm/tools/memory/agent/engine)
- 将 LlmProvider trait 及关联类型移出 provider 模块归属 llm(ADR-1)
- 为 session.rs bundle() 方法添加 engine feature 门控
- 更新 12 个内部文件 + 4 个示例文件的 import 路径
- 向后兼容:provider.rs 保留 pub use 重导出老路径
This commit is contained in:
徐涛
2026-07-19 07:58:04 +08:00
parent 249fba8aaf
commit 932a06f512
26 changed files with 1366 additions and 124 deletions
+2 -2
View File
@@ -13,7 +13,7 @@ use crate::agent::error::AgentError;
use crate::agent::runtime::{AgentConfig, RuntimeBundle};
use crate::agent::summary::SummaryConfig;
use crate::llm::hooks::HookExecutor;
use crate::llm::provider::LlmProvider;
use crate::llm::LlmProvider;
use crate::memory::retriever::MemoryRetriever;
use crate::memory::store::MemoryStore;
use crate::tools::ToolRegistry;
@@ -132,9 +132,9 @@ impl AgentBuilder {
mod tests {
use super::*;
use crate::llm::error::LlmError;
use crate::llm::provider::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, StreamEvent};
use crate::llm::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use async_trait::async_trait;
use futures_core::Stream;
use std::pin::Pin;
+1 -1
View File
@@ -18,7 +18,7 @@ use std::time::Duration;
use crate::agent::summary::SummaryConfig;
use crate::llm::compact::CompactConfig;
use crate::llm::hooks::HookExecutor;
use crate::llm::provider::LlmProvider;
use crate::llm::LlmProvider;
use crate::memory::retriever::MemoryRetriever;
use crate::memory::store::MemoryStore;
use crate::tools::ToolRegistry;
+10 -1
View File
@@ -25,12 +25,14 @@ use crate::agent::error::AgentError;
use crate::agent::runtime::RuntimeBundle;
use crate::agent::session_memory::SessionMemory;
use crate::agent::summary::{format_messages_as_text, SummaryConfig};
#[cfg(feature = "engine")]
use crate::engine::snapshot::{SessionMemoryEntry, SessionSnapshot};
#[cfg(feature = "engine")]
use crate::engine::EngineError;
use crate::llm::cycle::{CostTracker, CycleConfig, LlmCycle};
use crate::llm::error::LlmError;
use crate::llm::hooks::{HookContext, HookEvent};
use crate::llm::provider::LlmProvider;
use crate::llm::LlmProvider;
use crate::llm::stream::StreamEvent;
use crate::llm::types::message::Message;
use crate::llm::types::response_v2::MessageResponse;
@@ -67,6 +69,7 @@ pub struct AgentSession {
/// `None` 表示无 pending restore(正常状态)。
/// 调用 `restore_memory()` 后会被消费并设为 `None`。
/// 这是 transient state,不参与序列化(AgentSession 本身不 derive Serialize)。
#[cfg(feature = "engine")]
pending_memory_restore: Option<HashMap<String, SessionMemoryEntry>>,
}
@@ -127,6 +130,7 @@ impl AgentSession {
slots,
current_slot_id: "default".to_string(),
last_summary_turn: None,
#[cfg(feature = "engine")]
pending_memory_restore: None,
}
}
@@ -147,6 +151,7 @@ impl AgentSession {
}
/// RuntimeBundle 引用(Phase 17 新增,供 SessionManager::create_child 继承父 bundle)。
#[cfg(feature = "engine")]
pub(crate) fn bundle(&self) -> &Arc<RuntimeBundle> {
&self.bundle
}
@@ -500,6 +505,7 @@ impl AgentSession {
/// 通过 `SessionMemory::list_entries()` 获取完整条目(保留 `metadata` 和 `created_at`)。
///
/// `Arc<dyn Agent>` 和 `Arc<RuntimeBundle>` **不进入快照**——由 `from_snapshot()` 调用方注入。
#[cfg(feature = "engine")]
pub async fn to_snapshot(&self) -> SessionSnapshot {
// 拍平 session_memory → HashMap<String, SessionMemoryEntry>
// 失败时回退到空 map(错误已记录,不阻断 checkpoint 主流程)。
@@ -541,6 +547,7 @@ impl AgentSession {
/// 由调用方显式 `await session.restore_memory()` 写回持久层。
///
/// 调用方负责提供与 `snapshot.agent_name` 对应的 `Arc<dyn Agent>`(引擎层只保留名字做调试用)。
#[cfg(feature = "engine")]
pub fn from_snapshot(
snapshot: SessionSnapshot,
agent: Arc<dyn Agent>,
@@ -601,6 +608,7 @@ impl AgentSession {
///
/// **完整恢复**:使用 `SessionMemory::set_with_meta()` 保留原始 `metadata` 和 `created_at`
/// ——不像 `set()` 会清空 metadata 并把 created_at 设为当前时间。
#[cfg(feature = "engine")]
pub async fn restore_memory(&mut self) -> Result<(), EngineError> {
// 取出 pending 并立即清空(避免重复 restore 时二次写入;幂等性保证)
let entries = self.pending_memory_restore.take();
@@ -624,6 +632,7 @@ impl AgentSession {
}
/// 是否有待写回的 `session_memory_data``from_snapshot()` 后尚未 `restore_memory()`)。
#[cfg(feature = "engine")]
pub fn has_pending_memory_restore(&self) -> bool {
self.pending_memory_restore
.as_ref()
+16 -4
View File
@@ -1,19 +1,31 @@
//! agcore —— 智能体(Agent)核心工具箱。
pub mod agent;
pub mod document;
pub mod engine;
#[cfg(any(feature = "llm-types", feature = "llm"))]
pub mod llm;
pub mod memory;
#[cfg(feature = "document")]
pub mod document;
#[cfg(feature = "prompt")]
pub mod prompt;
#[cfg(feature = "tools")]
pub mod tools;
#[cfg(feature = "memory")]
pub mod memory;
#[cfg(feature = "agent")]
pub mod agent;
#[cfg(feature = "engine")]
pub mod engine;
#[cfg(feature = "document")]
pub use document::Document;
#[cfg(feature = "tracing-init")]
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
#[cfg(feature = "tracing-init")]
static INIT: std::sync::Once = std::sync::Once::new();
/// 初始化 tracing 日志订阅(仅在启用 `tracing-init` feature 时可用)。
#[cfg(feature = "tracing-init")]
pub fn init_tracing() {
INIT.call_once(|| {
let filter =
+33 -9
View File
@@ -1,12 +1,36 @@
//! LLM 调用周期 —— 大模型基础调用周期控制。
pub mod compact;
pub mod convert;
pub mod cycle;
pub mod embedding;
pub mod error;
pub mod hooks;
pub mod mock;
pub mod provider;
pub mod stream;
#[cfg(feature = "llm-types")]
pub mod types;
#[cfg(feature = "llm")]
pub mod compact;
#[cfg(feature = "llm")]
pub mod convert;
#[cfg(feature = "llm")]
pub mod cycle;
#[cfg(feature = "llm")]
pub mod embedding;
#[cfg(feature = "llm")]
pub mod error;
#[cfg(feature = "llm")]
pub mod hooks;
#[cfg(feature = "llm")]
pub mod mock;
// 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;
/// Provider 抽象接口(trait + 能力元数据),仅依赖 `llm` feature,不引入 reqwest。
#[cfg(feature = "llm")]
pub mod provider_trait;
#[cfg(feature = "llm")]
pub mod stream;
// 重导出 Provider 抽象接口到 `crate::llm::` 顶层,便于下游 `use crate::llm::LlmProvider`。
#[cfg(feature = "llm")]
pub use provider_trait::{LlmProvider, ProviderCapabilities, ProviderFeatures};
+7 -3
View File
@@ -19,13 +19,14 @@ use crate::llm::compact::{CompactConfig, CompactState, microcompact, should_comp
use crate::llm::cycle::retry::should_retry;
use crate::llm::error::LlmError;
use crate::llm::hooks::{HookContext, HookEvent, HookExecutor};
use crate::llm::provider::LlmProvider;
use crate::llm::LlmProvider;
use crate::llm::stream::StreamEvent;
use crate::llm::types::message::{ContentBlock, Message};
use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, PartialMessageResponse, StopReason};
use crate::llm::types::tool::ToolDef;
use crate::llm::types::ToolChoice;
#[cfg(feature = "tools")]
use crate::tools::ToolRegistry;
/// LLM 调用周期配置。
@@ -528,6 +529,7 @@ impl LlmCycle {
///
/// 注意:OpenAI API 要求 tool 消息必须紧跟在对应的 Assistanttool_calls)消息之后。
/// 因此 push 工具结果前必须先 push Assistant 响应,否则 API 拒绝请求。
#[cfg(feature = "tools")]
pub async fn submit_with_tools(
&mut self,
prompt: String,
@@ -631,6 +633,7 @@ impl LlmCycle {
/// 直接调用模块函数 `run_tool_loop`。
///
/// ponytail: 返回的流是 `Item = StreamEvent`(非 `Result`),所有错误事件化为 `StreamEvent::Error`。
#[cfg(feature = "tools")]
pub async fn submit_with_tools_stream(
&mut self,
prompt: String,
@@ -737,6 +740,7 @@ fn truncate_tool_result(s: &str, max_bytes: usize) -> String {
///
/// **所有错误事件化**:通过 `tx.send(Error{..})` 表达错误,最终 `return` 结束 task。
/// 不返回 `Result`,因为错误已通过事件流传递。
#[cfg(feature = "tools")]
async fn run_tool_loop(
mut messages: Vec<Message>,
provider: Arc<dyn LlmProvider>,
@@ -933,7 +937,7 @@ async fn run_tool_loop(
#[cfg(test)]
mod tests {
use super::*;
use crate::llm::provider::{ProviderCapabilities, ProviderFeatures};
use crate::llm::{ProviderCapabilities, ProviderFeatures};
use crate::tools::{BaseTool, ToolRegistry};
use async_trait::async_trait;
use futures_core::Stream;
@@ -1394,7 +1398,7 @@ mod tests {
/// 使用自定义 MockProvider 返回 chat_stream Err。
#[tokio::test(flavor = "multi_thread")]
async fn test_submit_with_tools_stream_chat_stream_err() {
use crate::llm::provider::{ProviderCapabilities, ProviderFeatures};
use crate::llm::{ProviderCapabilities, ProviderFeatures};
struct ErrProvider;
#[async_trait]
+2 -2
View File
@@ -8,7 +8,7 @@
//! ```no_run
//! use std::sync::Arc;
//! use agcore::llm::mock::MockProvider;
//! use agcore::llm::provider::LlmProvider;
//! use agcore::llm::LlmProvider;
//! use agcore::llm::types::message::{ContentBlock, Message};
//! use agcore::llm::types::response_v2::{MessageResponse, StopReason};
//! use agcore::llm::types::Usage;
@@ -43,10 +43,10 @@ use async_stream::stream;
use futures_core::Stream;
use crate::llm::error::LlmError;
use crate::llm::provider::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use crate::llm::types::message::{ContentBlock, ContentBlockType, Message};
use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, PartialUsage, StreamEvent};
use crate::llm::{LlmProvider, ProviderCapabilities, ProviderFeatures};
/// 按调用顺序返回预设响应的 [`LlmProvider`]。
///
+10 -60
View File
@@ -4,16 +4,17 @@ pub mod openai;
pub mod openai_compat;
pub mod registry;
use std::pin::Pin;
use std::time::Duration;
use futures_core::Stream;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use crate::llm::error::LlmError;
use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, StreamEvent};
// 向后兼容重导出 —— v0.3.2 Step 3 起,`LlmProvider` / `ProviderCapabilities` /
// `ProviderFeatures` 定义移至 `provider_trait` 模块(`#[cfg(feature = "llm")]`)。
// 此处重导出使老路径 `agcore::llm::provider::LlmProvider` 仍可用。
// 推荐下游迁移至 `agcore::llm::LlmProvider`。
pub use super::provider_trait::{LlmProvider, ProviderCapabilities, ProviderFeatures};
/// Provider 类型枚举 —— `create_provider()` 在编译期 exhaustive match 中使用。
///
@@ -255,63 +256,12 @@ pub fn create_provider(
}
}
/// Provider 能力描述 —— 静态元信息,调用方据此决定可用特性
/// Provider 能力描述、功能开关集合与 `LlmProvider` trait 定义
///
/// 设计依据(见 `docs/10-llm-provider-refinement.md` §4 任务 6 决策):
/// `ProviderCapabilities` 与 trait 同文件(`provider.rs`),不分散到类型目录
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderCapabilities {
/// 人类可读的 Provider 名(如 `"openai"` / `"anthropic"`)。
pub provider_name: &'static str,
/// 支持的模型列表(`None` 表示"未列举全部")。
pub supported_models: Option<Vec<String>>,
/// 详细功能开关。
pub features: ProviderFeatures,
}
/// Provider 功能开关集合。
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProviderFeatures {
/// 是否支持流式响应。
pub streaming: bool,
/// 是否支持 thinking / 推理。
pub thinking: bool,
/// 是否支持图片输入。
pub vision: bool,
/// 是否支持音频输入。
pub audio_input: bool,
/// 是否支持工具调用。
pub tool_use: bool,
/// 是否支持并行工具调用。
pub parallel_tool_calls: bool,
/// system prompt 是否放在 messages 中(`true`)还是顶层 `system` 字段(`false`)。
pub system_prompt_in_messages: bool,
/// 模型上下文窗口(tokens);`0` 表示未知。
pub max_context_window: u32,
}
/// LLM Provider 抽象接口。
/// v0.3.2 Step 3 起,上述类型已移至 `src/llm/provider_trait.rs``#[cfg(feature = "llm")]`),
/// 使纯 Mock 场景无需引入任何 provider feature
///
/// 所有具体的 LLM 后端实现(OpenAI、Anthropic、DeepSeek、Qwen 等)
/// 均需实现此 trait,以实现可插拔替换。
///
/// 修订(Phase 0):签名由 `chat(ChatRequest) → ChatResponse` 切换为
/// `chat(MessageRequest) → MessageResponse``chat_stream` 返回新 `StreamEvent` 流,
/// 新增 `capabilities()` 方法。
#[async_trait::async_trait]
pub trait LlmProvider: Send + Sync {
/// 发送聊天请求并返回完整响应。
async fn chat(&self, request: MessageRequest) -> Result<MessageResponse, LlmError>;
/// 流式聊天请求 —— 返回新 IR `StreamEvent` 流。
async fn chat_stream(
&self,
request: MessageRequest,
) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>, LlmError>;
/// 返回 Provider 静态能力描述。
fn capabilities(&self) -> ProviderCapabilities;
}
/// 详见 `docs/27-step3-phase26-ci-verification.md` §3.2 工作 0 + ADR-1。
#[cfg(test)]
mod tests {
+1 -1
View File
@@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use tracing::{debug, error, info, warn};
use super::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use crate::llm::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use crate::llm::error::LlmError;
use crate::llm::types::message::{ContentBlock, ContentBlockType, Message};
use crate::llm::types::request_v2::MessageRequest;
+1 -1
View File
@@ -11,7 +11,7 @@ use futures_core::Stream;
use reqwest::Client;
use super::openai::GenericOpenaiProvider;
use super::{LlmProvider, ProviderCapabilities};
use crate::llm::{LlmProvider, ProviderCapabilities};
use crate::llm::error::LlmError;
use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, StreamEvent};
+1 -1
View File
@@ -21,7 +21,7 @@ use serde::Serialize;
use serde_json::Value;
use tracing::{debug, error, info};
use super::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use crate::llm::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use crate::llm::convert::{from_openai, to_openai};
use crate::llm::error::LlmError;
use crate::llm::types::message::{ContentBlock, ContentBlockType, Message};
+1 -2
View File
@@ -15,12 +15,11 @@ use std::pin::Pin;
use async_trait::async_trait;
use futures_core::Stream;
use super::ProviderCapabilities;
use super::openai::GenericOpenaiProvider;
use crate::llm::error::LlmError;
use crate::llm::provider::LlmProvider;
use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, StreamEvent};
use crate::llm::{LlmProvider, ProviderCapabilities};
// =============================================================================
// DeepSeek
+2 -1
View File
@@ -3,7 +3,8 @@
use std::collections::HashMap;
use crate::llm::error::LlmError;
use crate::llm::provider::{LlmProvider, ProviderConfig, ProviderType, create_provider};
use crate::llm::provider::{ProviderConfig, ProviderType, create_provider};
use crate::llm::LlmProvider;
/// Provider 注册表 —— 管理多个 LLM Provider 实例。
///
+71
View File
@@ -0,0 +1,71 @@
//! LLM Provider 抽象接口 —— trait 定义与能力元数据。
//!
//! 独立于具体 provider 实现(OpenAI / Anthropic / DeepSeek / Qwen / Ollama),
//! 仅依赖 `llm` feature,不引入 `reqwest`。纯 Mock 场景可仅启用 `llm` feature。
use std::pin::Pin;
use futures_core::Stream;
use serde::{Deserialize, Serialize};
use crate::llm::error::LlmError;
use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, StreamEvent};
/// Provider 能力描述 —— 静态元信息,调用方据此决定可用特性。
///
/// 设计依据(见 `docs/10-llm-provider-refinement.md` §4 任务 6 决策):
/// `ProviderCapabilities` 与 trait 同文件,不分散到类型目录。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderCapabilities {
/// 人类可读的 Provider 名(如 `"openai"` / `"anthropic"`)。
pub provider_name: &'static str,
/// 支持的模型列表(`None` 表示"未列举全部")。
pub supported_models: Option<Vec<String>>,
/// 详细功能开关。
pub features: ProviderFeatures,
}
/// Provider 功能开关集合。
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ProviderFeatures {
/// 是否支持流式响应。
pub streaming: bool,
/// 是否支持 thinking / 推理。
pub thinking: bool,
/// 是否支持图片输入。
pub vision: bool,
/// 是否支持音频输入。
pub audio_input: bool,
/// 是否支持工具调用。
pub tool_use: bool,
/// 是否支持并行工具调用。
pub parallel_tool_calls: bool,
/// system prompt 是否放在 messages 中(`true`)还是顶层 `system` 字段(`false`)。
pub system_prompt_in_messages: bool,
/// 模型上下文窗口(tokens);`0` 表示未知。
pub max_context_window: u32,
}
/// LLM Provider 抽象接口。
///
/// 所有具体的 LLM 后端实现(OpenAI、Anthropic、DeepSeek、Qwen 等)
/// 均需实现此 trait,以实现可插拔替换。
///
/// 修订(Phase 0):签名由 `chat(ChatRequest) → ChatResponse` 切换为
/// `chat(MessageRequest) → MessageResponse``chat_stream` 返回新 `StreamEvent` 流,
/// 新增 `capabilities()` 方法。
#[async_trait::async_trait]
pub trait LlmProvider: Send + Sync {
/// 发送聊天请求并返回完整响应。
async fn chat(&self, request: MessageRequest) -> Result<MessageResponse, LlmError>;
/// 流式聊天请求 —— 返回新 IR `StreamEvent` 流。
async fn chat_stream(
&self,
request: MessageRequest,
) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>, LlmError>;
/// 返回 Provider 静态能力描述。
fn capabilities(&self) -> ProviderCapabilities;
}
+3 -1
View File
@@ -16,7 +16,9 @@ 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, SqliteStore};
pub use store::{InMemoryStore, MemoryStore};
#[cfg(feature = "memory-sqlite")]
pub use store::SqliteStore;
#[allow(deprecated)]
pub use vector::{InMemoryVectorRetriever, VectorRetriever};
pub use vector_store::{InMemoryVectorStore, PersistentVectorStore, RagPipeline, VectorStore};
+2
View File
@@ -6,9 +6,11 @@ use crate::memory::error::MemoryError;
use crate::memory::types::{MemoryFilter, MemoryItem};
pub mod in_memory;
#[cfg(feature = "memory-sqlite")]
pub mod sqlite_store;
pub use in_memory::InMemoryStore;
#[cfg(feature = "memory-sqlite")]
pub use sqlite_store::SqliteStore;
/// 底层记忆存储抽象接口。
+2
View File
@@ -2,12 +2,14 @@
pub mod base;
pub mod error;
#[cfg(feature = "tools-mcp")]
pub mod mcp;
pub mod permission;
pub mod registry;
pub use base::{BaseTool, ToolContext, ToolRef};
pub use error::ToolError;
#[cfg(feature = "tools-mcp")]
pub use mcp::{McpClient, McpTransport};
pub use permission::{Permission, PermissionChecker, PermissionConfig};
pub use registry::{ToolInvocation, ToolRegistry};