Refactor LLM API key handling for Ollama

The LLM client now conditionally adds the `Authorization` header only
when an API key is provided. The `llm_configured` check is updated to
reflect that an API key is not strictly required for Ollama-style
endpoints. A new integration test verifies the functionality against an
Ollama-compatible endpoint without an API key.
This commit is contained in:
2026-04-16 14:19:43 +02:00
parent 144d1f768b
commit 3a656bd2a9
4 changed files with 116 additions and 12 deletions
+8 -8
View File
@@ -74,6 +74,9 @@ struct ResponseMessage {
/// Single-shot chat completion against an OpenAI-compatible endpoint.
/// The `api_key` in `settings` is used as a Bearer token; do not log it.
/// An empty `api_key` is treated as "no auth" — the `Authorization` header
/// is omitted entirely, which is the correct mode for Ollama's
/// OpenAI-compatible endpoint (some gateways object to a blank Bearer).
pub async fn chat_once(
client: &reqwest::Client,
settings: &LlmSettings<'_>,
@@ -94,14 +97,11 @@ pub async fn chat_once(
],
};
let response = client
.post(&url)
.bearer_auth(settings.api_key)
.timeout(timeout)
.json(&body)
.send()
.await
.map_err(LlmError::Http)?;
let mut req = client.post(&url).timeout(timeout).json(&body);
if !settings.api_key.is_empty() {
req = req.bearer_auth(settings.api_key);
}
let response = req.send().await.map_err(LlmError::Http)?;
let status = response.status();
if !status.is_success() {
+7 -4
View File
@@ -105,11 +105,14 @@ impl Config {
}
}
/// True iff all three required fields for the external analysis LLM are
/// non-empty. Used to gate the "Fall abschließen" UI and handler — if
/// no LLM is configured, the close action must not be reachable.
/// True iff the external analysis LLM is configured. `llm_api_key` is
/// intentionally **not** required — Ollama's OpenAI-compatible endpoint
/// at `/v1/chat/completions` accepts unauthenticated requests and is a
/// supported deployment target. For hosted providers (Ionos, OpenAI) the
/// key remains necessary in practice; the provider will reject with 401
/// if it is missing, which is correct feedback.
pub fn llm_configured(&self) -> bool {
!self.llm_url.is_empty() && !self.llm_api_key.is_empty() && !self.llm_model.is_empty()
!self.llm_url.is_empty() && !self.llm_model.is_empty()
}
/// Sane defaults for integration tests. Not a `Default` impl on purpose: