//! 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 { self.0.chat(request).await } async fn chat_stream( &self, request: MessageRequest, ) -> Result> + Send>>, LlmError> { self.0.chat_stream(request).await } fn capabilities(&self) -> ProviderCapabilities { let mut caps = self.0.capabilities(); caps.provider_name = "ollama"; caps } }