Files
agcore/src/llm/provider.rs
T
徐涛 a74c24b6fe feat(llm): 重写 Provider 适配层,支持 OpenAI Chat / Anthropic / DeepSeek / Qwen
重写 OpenaiChatProvider:移除 Phase 0 临时桥接,实现真实流式状态机
(ContentBlockStart / *Delta / ContentBlockEnd / ToolCallEnd),
MessageComplete.full_response 由 PartialMessageResponse::finalize 产出。

新增 AnthropicProvider:实现 Messages API + SSE 事件序列
(message_start → content_block_start → content_block_delta →
content_block_stop → message_delta → message_stop),
529(overloaded)映射为 RateLimit;thinking signature 由
partial.set_thinking_signature 内部写入。

新增 DeepSeekProvider / QwenProvider:OpenAI-compatible 协议的
newtype 包装,共享 GenericOpenaiProvider 的 HTTP / SSE / 转换逻辑;
Qwen 通过 extra_headers 注入 X-DashScope-SSE: enable。

新增 convert.rs 公共转换模块:从 Phase 0 cycle.rs / Phase 0
OpenaiProvider 桥接层提取 from_openai / to_openai /
content_to_blocks / blocks_to_content,避免跨 Provider 重复逻辑。

新增 wiremock dev-dependency + 14 个集成测试:
- OpenaiChatProvider:基础文本 / 401 / 500 / 流式 MessageComplete
- AnthropicProvider:基础 / 401 / 529 / 流式 SSE 序列 / max_tokens 默认值
- DeepSeek / Qwen:基础文本响应

171 个测试全部通过(之前 157,新增 14)。
2026-07-02 22:24:30 +08:00

145 lines
5.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
pub mod anthropic;
pub mod openai;
pub mod openai_compat;
pub mod registry;
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 类型枚举 —— `create_provider()` 在编译期 exhaustive match 中使用。
///
/// 设计依据(见 `docs/10-llm-provider-refinement.md` §2.5 Decision-05):
/// 当前协议数量(5 种以内)完全可控,enum 的编译期安全检查优于运行时的 `HashMap::get()`。
/// 未来如果扩展到 15+ 种以上,再改为注册表模式。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderType {
/// OpenAI Chat Completions API(兼容 DeepSeek / Qwen 等 `/chat/completions` 端点)。
OpenaiChat,
/// OpenAI Response API。
OpenaiResponse,
/// Anthropic Messages API。
Anthropic,
/// DeepSeekOpenAI-compatible `/chat/completions`)。
DeepSeek,
/// Qwen / 阿里云百炼(OpenAI-compatible `/chat/completions`)。
Qwen,
}
impl std::str::FromStr for ProviderType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"openai" | "openai-chat" | "openai_chat" => Ok(ProviderType::OpenaiChat),
"openai-response" | "openai_response" | "response" => Ok(ProviderType::OpenaiResponse),
"anthropic" | "claude" => Ok(ProviderType::Anthropic),
"deepseek" => Ok(ProviderType::DeepSeek),
"qwen" | "dashscope" | "tongyi" => Ok(ProviderType::Qwen),
_ => Err(format!("未知的 Provider 类型: {s}")),
}
}
}
/// Provider 构造参数 —— 通用 base_url + api_key + model。
pub struct ProviderConfig {
pub base_url: String,
pub api_key: String,
pub model: String,
}
/// Provider 工厂 —— exhaustive match 在编译期保证新 Provider 被注册。
pub fn create_provider(
provider_type: ProviderType,
config: ProviderConfig,
) -> Result<Box<dyn LlmProvider>, LlmError> {
match provider_type {
ProviderType::OpenaiChat => Ok(Box::new(openai::OpenaiChatProvider::new(
config.base_url,
config.api_key,
config.model,
))),
ProviderType::OpenaiResponse => Err(LlmError::Other(
"OpenaiResponse Provider 在 Phase 1 暂不实现;请使用 OpenaiChat".into(),
)),
ProviderType::Anthropic => Ok(Box::new(anthropic::AnthropicProvider::new(
config.base_url,
config.api_key,
config.model,
))),
ProviderType::DeepSeek => Ok(Box::new(openai_compat::DeepSeekProvider::new(
config.base_url,
config.api_key,
config.model,
))),
ProviderType::Qwen => Ok(Box::new(openai_compat::QwenProvider::new(
config.base_url,
config.api_key,
config.model,
))),
}
}
/// Provider 能力描述 —— 静态元信息,调用方据此决定可用特性。
///
/// 设计依据(见 `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 抽象接口。
///
/// 所有具体的 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;
}