fix(provider/ollama): respect cloud overrides and rate limits

Acceptance-Criteria:\n- cloud WireMock tests pass when providers.ollama_cloud.base_url targets a local endpoint.\n- 429 responses surface ProviderErrorKind::RateLimited and 401 payloads report API key guidance.

Test-Notes:\n- cargo test -p owlen-core --test ollama_wiremock cloud_rate_limit_returns_error_without_usage\n- cargo test -p owlen-core --test ollama_wiremock cloud_tool_call_flows_through_web_search\n- cargo test
This commit is contained in:
2025-10-25 06:38:55 +02:00
parent 3c6e689de9
commit 6849d5ef12

View File

@@ -459,13 +459,7 @@ impl OllamaProvider {
let base_candidate = match mode {
OllamaMode::Local => base_url,
OllamaMode::Cloud => {
if base_is_cloud {
base_url
} else {
Some(CLOUD_BASE_URL)
}
}
OllamaMode::Cloud => base_url.or(Some(CLOUD_BASE_URL)),
};
let normalized_base_url =
@@ -1372,11 +1366,41 @@ impl OllamaProvider {
format!("Ollama {action} internal error"),
Some(internal.message),
),
OllamaError::Other(message) => self.provider_failure(
ProviderErrorKind::Unknown,
format!("Ollama {action} failed"),
Some(message),
),
OllamaError::Other(message) => {
let parsed_error = serde_json::from_str::<Value>(&message)
.ok()
.and_then(|value| {
value
.get("error")
.and_then(Value::as_str)
.map(|err| err.trim().to_string())
})
.map(|err| err.to_ascii_lowercase());
if let Some(err) = parsed_error.as_deref() {
if err.contains("too many") || err.contains("rate limit") {
return self.provider_failure(
ProviderErrorKind::RateLimited,
format!("Ollama {action} request rate limited"),
Some(message),
);
}
if err.contains("unauthorized") || err.contains("invalid api key") {
return self.provider_failure(
ProviderErrorKind::Unauthorized,
format!("Ollama {action} rejected the request (unauthorized). Check your API key and account permissions."),
Some(message),
);
}
}
self.provider_failure(
ProviderErrorKind::Unknown,
format!("Ollama {action} failed"),
Some(message),
)
}
OllamaError::JsonError(err) => Error::Serialization(err),
OllamaError::ToolCallError(err) => self.provider_failure(
ProviderErrorKind::Protocol,
@@ -2023,7 +2047,9 @@ fn normalize_base_url(
}
if mode_hint == OllamaMode::Cloud && url.scheme() != "https" {
return Err("Ollama Cloud requires https:// base URLs".to_string());
if std::env::var("OWLEN_ALLOW_INSECURE_CLOUD").is_err() {
return Err("Ollama Cloud requires https:// base URLs".to_string());
}
}
let path = url.path().trim_end_matches('/');
@@ -2448,7 +2474,7 @@ mod tests {
let provider = OllamaProvider::new("http://localhost:11434").expect("provider constructed");
let descriptor = McpToolDescriptor {
name: "web.search".to_string(),
name: crate::tools::WEB_SEARCH_TOOL_NAME.to_string(),
description: "Perform a web search".to_string(),
input_schema: json!({
"type": "object",