mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[chatgpt] Add configurable request timeout (#20408)
* [chatgpt] Add configurable request timeout Signed-off-by: Andreas Brenk <mail@andreasbrenk.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
+3
@@ -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;
|
||||
}
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
|
||||
+40
-5
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user