feat(ollama): add local provider implementation and request timeout support

Introduce `OllamaLocalProvider` for communicating with a local Ollama daemon, including health checks, model listing, and stream generation. Export the provider in the Ollama module. Extend `OllamaClient` to accept an optional request timeout and apply it to the underlying HTTP client configuration.
This commit is contained in:
2025-10-15 21:01:18 +02:00
parent 3525cb3949
commit cdc425ae93
3 changed files with 84 additions and 1 deletions

View File

@@ -0,0 +1,79 @@
use std::time::Duration;
use async_trait::async_trait;
use owlen_core::provider::{
GenerateRequest, GenerateStream, ModelInfo, ModelProvider, ProviderMetadata, ProviderStatus,
ProviderType,
};
use owlen_core::{Error as CoreError, Result as CoreResult};
use serde_json::Value;
use tokio::time::timeout;
use super::OllamaClient;
const DEFAULT_BASE_URL: &str = "http://localhost:11434";
const DEFAULT_HEALTH_TIMEOUT_SECS: u64 = 5;
/// ModelProvider implementation for a local Ollama daemon.
pub struct OllamaLocalProvider {
client: OllamaClient,
health_timeout: Duration,
}
impl OllamaLocalProvider {
/// Construct a new local provider using the shared [`OllamaClient`].
pub fn new(
base_url: Option<String>,
request_timeout: Option<Duration>,
health_timeout: Option<Duration>,
) -> CoreResult<Self> {
let base_url = base_url.unwrap_or_else(|| DEFAULT_BASE_URL.to_string());
let health_timeout =
health_timeout.unwrap_or_else(|| Duration::from_secs(DEFAULT_HEALTH_TIMEOUT_SECS));
let mut metadata =
ProviderMetadata::new("ollama_local", "Ollama (Local)", ProviderType::Local, false);
metadata
.metadata
.insert("base_url".into(), Value::String(base_url.clone()));
if let Some(timeout) = request_timeout {
metadata.metadata.insert(
"request_timeout_ms".into(),
Value::String(timeout.as_millis().to_string()),
);
}
let client = OllamaClient::new(&base_url, None, metadata, request_timeout)?;
Ok(Self {
client,
health_timeout,
})
}
}
#[async_trait]
impl ModelProvider for OllamaLocalProvider {
fn metadata(&self) -> &ProviderMetadata {
self.client.metadata()
}
async fn health_check(&self) -> CoreResult<ProviderStatus> {
match timeout(self.health_timeout, self.client.health_check()).await {
Ok(Ok(status)) => Ok(status),
Ok(Err(CoreError::Network(_))) | Ok(Err(CoreError::Timeout(_))) => {
Ok(ProviderStatus::Unavailable)
}
Ok(Err(err)) => Err(err),
Err(_) => Ok(ProviderStatus::Unavailable),
}
}
async fn list_models(&self) -> CoreResult<Vec<ModelInfo>> {
self.client.list_models().await
}
async fn generate_stream(&self, request: GenerateRequest) -> CoreResult<GenerateStream> {
self.client.generate_stream(request).await
}
}

View File

@@ -1,3 +1,5 @@
pub mod local;
pub mod shared;
pub use local::OllamaLocalProvider;
pub use shared::OllamaClient;

View File

@@ -29,12 +29,14 @@ impl OllamaClient {
base_url: impl AsRef<str>,
api_key: Option<String>,
provider_metadata: ProviderMetadata,
request_timeout: Option<Duration>,
) -> CoreResult<Self> {
let base_url = Url::parse(base_url.as_ref())
.map_err(|err| CoreError::Config(format!("invalid base url: {}", err)))?;
let timeout = request_timeout.unwrap_or_else(|| Duration::from_secs(DEFAULT_TIMEOUT_SECS));
let http = Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.timeout(timeout)
.build()
.map_err(map_reqwest_error)?;