feat(llm): 完成 Phase 5 热身准备(Ollama / non_exhaustive / ProviderConfig)
Phase 5 三个 Step 全部落地: Step 5.2 — Ollama Provider - 新增 OllamaProvider newtype 包装(默认 localhost:11434/v1,零 API key) - ProviderType 新增 Ollama 变体与 FromStr 解析 Step 5.3 — #[non_exhaustive] 前置标记 - ProviderType / StopReason / FinishReason / EvictionPolicy 加 #[non_exhaustive] - 编译期兼容护栏,避免下游 silent break Step 5.1 — ProviderConfig 扩展 - 加 timeout_secs / max_retries 字段、Default、from_env(prefix) - create_provider 各分支通过 pub(crate) from_parts 一次性构造并注入 timeout (同时避开 Anthropic 的 default_headers 与双重 client 构造) - map_reqwest_error 改为方法读取 self.timeout_secs(移除硬编码 120s) - AnthropicProvider::with_timeout 同值短路,with_client 标 #[deprecated] - DeepSeek / Qwen 加公开 with_client,new_with_client 走代理 - 7 个新测试:5 个 from_env 单元测试 + 3 个 timeout 传导 wiremock (OpenAI Chat / DeepSeek / Anthropic) - Cargo.toml 加 temp-env dev-dep
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
//! Ollama Provider —— OpenAI-compatible 协议的 newtype 包装,零 API key。
|
||||
//!
|
||||
//! 默认 base_url = `http://localhost:11434/v1`,空 api_key 也可工作。
|
||||
//! 实现方式同 `DeepSeekProvider` / `QwenProvider`,共享 `GenericOpenaiProvider`
|
||||
//! 的 HTTP/SSE/转换逻辑,仅配置不同。
|
||||
|
||||
use std::pin::Pin;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures_core::Stream;
|
||||
use reqwest::Client;
|
||||
|
||||
use super::openai::GenericOpenaiProvider;
|
||||
use super::{LlmProvider, ProviderCapabilities};
|
||||
use crate::llm::error::LlmError;
|
||||
use crate::llm::types::request_v2::MessageRequest;
|
||||
use crate::llm::types::response_v2::{MessageResponse, StreamEvent};
|
||||
|
||||
/// Ollama 本地 Provider —— OpenAI-compatible 协议的 newtype 包装。
|
||||
///
|
||||
/// Ollama 在 `localhost:11434` 暴露与 OpenAI 兼容的 `/v1/chat/completions`
|
||||
/// 接口,因此完全复用 `GenericOpenaiProvider` 的实现。允许空 `api_key`。
|
||||
pub struct OllamaProvider(pub GenericOpenaiProvider);
|
||||
|
||||
impl OllamaProvider {
|
||||
/// 构造 Ollama Provider。
|
||||
///
|
||||
/// - `base_url` 为空时使用默认 `http://localhost:11434/v1`
|
||||
/// - `api_key` 可为空字符串(Ollama 不校验)
|
||||
pub fn new(base_url: String, api_key: String, model: String, timeout_secs: u64) -> Self {
|
||||
let url = if base_url.is_empty() {
|
||||
"http://localhost:11434/v1".to_string()
|
||||
} else {
|
||||
base_url
|
||||
};
|
||||
Self(GenericOpenaiProvider::new_with_name(
|
||||
url,
|
||||
api_key,
|
||||
model,
|
||||
"ollama",
|
||||
timeout_secs,
|
||||
))
|
||||
}
|
||||
|
||||
/// 替换默认 HTTP Client(用于 timeout 注入等场景)。
|
||||
///
|
||||
/// 与 `OpenaiChatProvider::with_client`、`DeepSeekProvider::with_client`、
|
||||
/// `QwenProvider::with_client` 签名一致。
|
||||
pub fn with_client(self, client: Client) -> Self {
|
||||
Self(self.0.with_client(client))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LlmProvider for OllamaProvider {
|
||||
async fn chat(&self, request: MessageRequest) -> Result<MessageResponse, LlmError> {
|
||||
self.0.chat(request).await
|
||||
}
|
||||
|
||||
async fn chat_stream(
|
||||
&self,
|
||||
request: MessageRequest,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent, LlmError>> + Send>>, LlmError> {
|
||||
self.0.chat_stream(request).await
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> ProviderCapabilities {
|
||||
let mut caps = self.0.capabilities();
|
||||
caps.provider_name = "ollama";
|
||||
caps
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user