[chatgpt] Add configurable request timeout (#20408)

* [chatgpt] Add configurable request timeout

Signed-off-by: Andreas Brenk <mail@andreasbrenk.com>
This commit is contained in:
Andreas Brenk
2026-03-27 09:13:16 +01:00
committed by GitHub
parent 66a4099fc0
commit 348d9f07c3
6 changed files with 84 additions and 23 deletions
+20 -18
View File
@@ -21,19 +21,20 @@ The binding supports a single thing type `account`, which corresponds to the Ope
The `account` thing requires the API key that allows accessing the account.
API keys can be created and managed under <https://platform.openai.com/account/api-keys>.
| Name | Type | Description | Default | Required | Advanced |
|------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|----------|----------|
| apiKey | text | The API key to be used for the requests | N/A | yes | no |
| temperature | decimal | A value between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | 0.5 | no | no |
| topP | decimal | A value between 0 and 1. An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. | 1.0 | no | yes |
| Name | Type | Description | Default | Required | Advanced |
|------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|----------|----------|
| apiKey | text | The API key to be used for the requests | N/A | yes | no |
| temperature | decimal | A value between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | 0.5 | no | no |
| topP | decimal | A value between 0 and 1. An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. | 1.0 | no | yes |
| apiUrl | text | The server API where to reach the AI service | <https://api.openai.com/v1/chat/completions> | no | yes |
| modelUrl | text | The model url where to retrieve the available models from | <https://api.openai.com/v1/models> | no | yes |
| model | text | The model to be used for the HLI service | gpt-4o-mini | no | yes |
| systemMessage | text | Here you need to describe your openHAB system that will help AI control your smart home. | N/A | if HLI | yes |
| maxTokens | decimal | The maximum number of tokens to generate in the completion. | 500 | no | yes |
| keepContext | decimal | How long should the HLI service retain context between requests (in minutes) | 2 | no | yes |
| contextThreshold | decimal | Limit total tokens included in context. | 10000 | no | yes |
| useSemanticModel | boolean | Use the semantic model to determine the location of an item. | true | no | yes |
| model | text | The model to be used for the HLI service | gpt-4o-mini | no | yes |
| systemMessage | text | Here you need to describe your openHAB system that will help AI control your smart home. | N/A | if HLI | yes |
| maxTokens | decimal | The maximum number of tokens to generate in the completion. | 500 | no | yes |
| keepContext | decimal | How long should the HLI service retain context between requests (in minutes) | 2 | no | yes |
| contextThreshold | decimal | Limit total tokens included in context. | 10000 | no | yes |
| useSemanticModel | boolean | Use the semantic model to determine the location of an item. | true | no | yes |
| requestTimeout | decimal | Timeout in seconds for chat API requests. Used as default for all channels. | 10 | no | yes |
The advanced parameters `apiUrl` and `modelUrl` can be used, if any other ChatGPT-compatible service is used, e.g. a local installation of [LocalAI](https://github.com/go-skynet/LocalAI).
@@ -48,13 +49,14 @@ It is possible to extend the thing with further channels of type `chat`, so that
Each channel of type `chat` takes the following configuration parameters:
| Name | Type | Description | Default | Required | Advanced |
|---------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------|----------|
| model | text | The model to be used for the responses. | gpt-4o | yes | no |
| systemMessage | text | The system message helps set the behavior of the assistant. | N/A | yes | no |
| temperature | decimal | A value between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | 0.5 | no | yes |
| topP | decimal | A value between 0 and 1. An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. | 1.0 | no | yes |
| maxTokens | decimal | The maximum number of tokens to generate in the completion. | 1000 | no | yes |
| Name | Type | Description | Default | Required | Advanced |
|----------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------|----------|
| model | text | The model to be used for the responses. | gpt-4o | yes | no |
| systemMessage | text | The system message helps set the behavior of the assistant. | N/A | yes | no |
| temperature | decimal | A value between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. | 0.5 | no | yes |
| topP | decimal | A value between 0 and 1. An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both. | 1.0 | no | yes |
| maxTokens | decimal | The maximum number of tokens to generate in the completion. | 1000 | no | yes |
| requestTimeout | decimal | Timeout in seconds for this channel. Overrides the thing-level timeout. | N/A | no | yes |
## Items Configuration
@@ -13,6 +13,7 @@
package org.openhab.binding.chatgpt.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link ChatGPTChannelConfiguration} class contains fields mapping chat channel configuration parameters.
@@ -31,4 +32,6 @@ public class ChatGPTChannelConfiguration {
public String systemMessage = "";
public int maxTokens = 500;
public @Nullable Integer requestTimeout;
}
@@ -33,4 +33,5 @@ public class ChatGPTConfiguration {
public String systemMessage = "";
public Integer keepContext = 2;
public Integer contextThreshold = 10000;
public Integer requestTimeout = ChatGPTHandler.DEFAULT_REQUEST_TIMEOUT_S;
}
@@ -59,7 +59,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
@NonNullByDefault
public class ChatGPTHandler extends BaseThingHandler {
private static final int REQUEST_TIMEOUT_MS = 10_000;
static final int DEFAULT_REQUEST_TIMEOUT_S = 10;
private final Logger logger = LoggerFactory.getLogger(ChatGPTHandler.class);
private HttpClient httpClient;
@@ -83,7 +84,8 @@ public class ChatGPTHandler extends BaseThingHandler {
String queryJson = prepareRequestBody(channelUID);
if (queryJson != null) {
String response = sendPrompt(queryJson);
final var timeout = resolveTimeout(channelUID);
String response = sendPrompt(queryJson, timeout);
processChatResponse(channelUID, response);
}
}
@@ -168,9 +170,14 @@ public class ChatGPTHandler extends BaseThingHandler {
}
public @Nullable String sendPrompt(String queryJson) {
return sendPrompt(queryJson, config != null ? config.requestTimeout : null);
}
public @Nullable String sendPrompt(String queryJson, @Nullable Integer timeoutSeconds) {
Request request = httpClient.newRequest(apiUrl).method(HttpMethod.POST)
.timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey).content(new StringContentProvider(queryJson));
.timeout(timeoutSeconds != null ? timeoutSeconds : DEFAULT_REQUEST_TIMEOUT_S, TimeUnit.SECONDS)
.header("Content-Type", "application/json").header("Authorization", "Bearer " + apiKey)
.content(new StringContentProvider(queryJson));
logger.trace("Query '{}'", queryJson);
try {
ContentResponse response = request.send();
@@ -194,6 +201,18 @@ public class ChatGPTHandler extends BaseThingHandler {
return this.config;
}
private @Nullable Integer resolveTimeout(ChannelUID channelUID) {
Channel channel = getThing().getChannel(channelUID);
if (channel != null) {
ChatGPTChannelConfiguration channelConfig = channel.getConfiguration()
.as(ChatGPTChannelConfiguration.class);
if (channelConfig.requestTimeout != null) {
return channelConfig.requestTimeout;
}
}
return config != null ? config.requestTimeout : null;
}
@Override
public void initialize() {
this.config = getConfigAs(ChatGPTConfiguration.class);
@@ -210,11 +229,23 @@ public class ChatGPTHandler extends BaseThingHandler {
this.apiUrl = config.apiUrl;
this.modelUrl = config.modelUrl;
if (!isValidTimeout(config.requestTimeout)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/requestTimeout.configuration-error");
return;
}
if (thing.getChannels().stream()
.map(channel -> channel.getConfiguration().as(ChatGPTChannelConfiguration.class))
.anyMatch(config -> !isValidTimeout(config.requestTimeout))) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/requestTimeout.configuration-error");
return;
}
updateStatus(ThingStatus.UNKNOWN);
scheduler.execute(() -> {
try {
Request request = httpClient.newRequest(modelUrl).timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
Request request = httpClient.newRequest(modelUrl).timeout(DEFAULT_REQUEST_TIMEOUT_S, TimeUnit.SECONDS)
.method(HttpMethod.GET).header("Authorization", "Bearer " + apiKey);
ContentResponse response = request.send();
if (response.getStatus() == 200) {
@@ -260,4 +291,8 @@ public class ChatGPTHandler extends BaseThingHandler {
public Collection<Class<? extends ThingHandlerService>> getServices() {
return List.of(ChatGPTModelOptionProvider.class, ChatGPTHLIService.class);
}
private boolean isValidTimeout(@Nullable Integer timeout) {
return timeout == null || timeout > 0;
}
}
@@ -16,6 +16,8 @@ thing-type.config.chatgpt.account.apiUrl.label = API URL
thing-type.config.chatgpt.account.apiUrl.description = The server API where to reach the AI service.
thing-type.config.chatgpt.account.modelUrl.label = Model URL
thing-type.config.chatgpt.account.modelUrl.description = The model url where to retrieve the available models from.
thing-type.config.chatgpt.account.requestTimeout.label = Request Timeout
thing-type.config.chatgpt.account.requestTimeout.description = Timeout in seconds for chat API requests. Used as default for all channels.
# channel types
@@ -32,8 +34,11 @@ channel-type.config.chatgpt.chat.systemMessage.label = System Message
channel-type.config.chatgpt.chat.systemMessage.description = The system message helps set the behavior of the assistant.
channel-type.config.chatgpt.chat.temperature.label = Temperature
channel-type.config.chatgpt.chat.temperature.description = Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.
channel-type.config.chatgpt.chat.requestTimeout.label = Request Timeout
channel-type.config.chatgpt.chat.requestTimeout.description = Timeout in seconds for this channel. Overrides the thing-level timeout. Set to 0 to use the thing default.
# status messages
offline.configuration-error = No API key configured
offline.communication-error = Could not connect to OpenAI API
requestTimeout.configuration-error = Invalid requestTimeout specified
@@ -19,6 +19,10 @@
<label>Authentication</label>
<description>Authentication for connecting to OpenAI API.</description>
</parameter-group>
<parameter-group name="connection">
<label>Connection</label>
<description>Connection settings for the OpenAI API.</description>
</parameter-group>
<parameter-group name="hli">
<label>HLI Configuration</label>
<description>Configure HLI service.</description>
@@ -48,6 +52,12 @@
</options>
<limitToOptions>false</limitToOptions>
</parameter>
<parameter name="requestTimeout" type="integer" min="1" groupName="connection">
<label>Request Timeout</label>
<description>Timeout in seconds for chat API requests. Used as default for all channels.</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="model" type="text" required="true" groupName="hli">
<label>Model</label>
<description>ID of the model to use.</description>
@@ -160,6 +170,11 @@
<default>1</default>
<advanced>true</advanced>
</parameter>
<parameter name="requestTimeout" type="integer" min="1">
<label>Request Timeout</label>
<description>Timeout in seconds for this channel. Overrides the thing-level timeout.</description>
<advanced>true</advanced>
</parameter>
</config-description>
</channel-type>
</thing:thing-descriptions>