#4391·hertzbeat

[BUG] hertzbeat-ai sends a hardcoded temperature to every provider and derives baseUrl/model from a fixed code list

Author: xyy45Created Sep 21, 2026Updated Sep 21, 2026

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

Two related things in hertzbeat-ai that make non-OpenAI providers awkward to set up. Both are on current master (2.0-SNAPSHOT). I ran the app against a local recording proxy to see what it actually sends rather than guessing from the code.

1. temperature is fixed in code.

hertzbeat-ai/.../config/LlmConfig.java:104-109 builds the options with .temperature(0.3), and ModelProviderConfig (hertzbeat-common-core/.../entity/dto/ModelProviderConfig.java:33-54) only has type / code / baseUrl / model / apiKey / participationModel, so there is no way to change or drop it. This is the body the proxy recorded, with message contents and tool schemas left out:

json
{
  "model": "deepseek-reasoner",
  "temperature": 0.3,
  "stream": true,
  "stream_options": { "include_usage": true },
  "tools": [ "..." ],
  "messages": [ "..." ]
}

Those six keys are all that goes out. top_p, n, tool_choice, response_format and max_tokens are not sent. So the one generation parameter that does get sent is the one a model is most likely to disagree about, and an operator cannot touch it.

2. baseUrl and model are filled in from a hardcoded list of code values.

LlmConfig.java:79-101: when baseUrl is empty it is chosen by code, and the else branch sets https://api.openai.com/v1. model works the same way and falls through to gpt-5 (I read that off the source, I could not confirm it on the wire since the request is rejected before the model is checked). Nothing is logged about the fallthrough, so all you get back is an error from a host you never configured.

Expected Behavior

Generation parameters that are fixed in code today should be settable per provider, and left out of the request when not set. I am not saying 0.3 is the wrong default, it should stay the default, this is only about making it overridable.

A code the backend does not know should fail or at least warn, instead of quietly inheriting OpenAI's endpoint.

Steps To Reproduce

  1. POST /api/config/provider with {"type":"provider","code":"deepseek","apiKey":"sk-invalid","participationModel":"PROTECTED"}, leave baseUrl empty
  2. POST /api/chat/stream with {"message":"hi"} - the error comes back from api.openai.com, see logs below
  3. For the temperature part, point any provider baseUrl at a local listener and send one chat message; the outbound body carries temperature regardless of provider or model

Environment

  • Version: 2.0-SNAPSHOT (master)
  • Module: hertzbeat-ai
  • OS: macOS, JDK 25, embedded H2

Debug logs

The key was a throwaway value, so the reply below is the vendor's own masked text. Note the host in the OkHttp thread name, and that LlmConfig only logs bean lifecycle at that moment:

2026-09-21 09:43:59 [http-nio-1157-exec-1] INFO  org.apache.hertzbeat.ai.config.LlmConfig - Provider configuration change event received, refreshing ChatClient bean
2026-09-21 09:44:17 [OkHttp https://api.openai.com/...] ERROR org.springframework.ai.chat.model.MessageAggregator - Aggregation Error
java.util.concurrent.CompletionException: com.openai.errors.UnauthorizedException: 401: Incorrect API key provided: sk-fake-***********nope. You can find your API key at https://platform.openai.com/account/api-keys.
	at com.openai.core.http.RetryingHttpClient.executeAsync$executeWithRetries$lambda$1(RetryingHttpClient.kt:127)
	at org.springframework.ai.openai.http.okhttp.SpringAiOpenAiHttpClient$1.onResponse(SpringAiOpenAiHttpClient.java:137)

It also reaches the chat client as-is:

event:error
data:{"conversationId":4,"response":"An error occurred: com.openai.errors.UnauthorizedException: 401: Incorrect API key provided: sk-fake-***********nope. You can find your API key at https://platform.openai.com/account/api-keys.","userMessageId":5,"assistantMessageId":null}

Anything else?

On how far this reaches: the UI cannot produce case 2, since web-app/src/app/pojo/ModelProviderConfig.ts:37-56 only offers openai / zai / zhipu in the dropdown and fills in a baseUrl for them. Case 2 needs POST /api/config/provider, which accepts any code string without checking it. Case 1 does affect anyone using the UI.

This is not the cause of #3971 and I am not proposing a fix for it here. That reporter did set https://api.deepseek.com/v1, so the empty-baseUrl branch is never entered, and I have not reproduced their 400 myself.

I'd like to work on this. What I have in mind is an optional parameter field on ModelProviderConfig plus the matching change in LlmConfig, a warn when code is unknown, and unit tests for LlmConfig, which has none today. No new provider names hardcoded. Say the word if you would rather see it done differently and I'll adjust before opening the PR.