Skip to content

Commit cf5ff5b

Browse files
qn895elasticmachinealvarezmelissa87
authored
[AI Infra] Add new temperature parameter to Inference AI, and OpenAI, Bedrock, Gemini connectors (#239806)
## Summary Follow up of #239626. This PR addresses #239181. If a user defines/sets a temperature at the connector level, the connector's temperature will override the Kibana's default temperature for each connector. If temperature is not explicitly set by a user, then we respect the the temperature set by the request. AI connector <img width="866" height="880" alt="Screenshot 2025-10-28 at 09 16 29" src="https://github.com/user-attachments/assets/e5d28754-0b4d-46df-99b7-43c26bb9ab5a" /> <img width="866" height="880" alt="Screenshot 2025-10-28 at 09 20 18" src="https://github.com/user-attachments/assets/71e3a125-e8a3-4bd9-a04d-c34b29e0b7a1" /> <img width="866" height="880" alt="Screenshot 2025-10-28 at 09 18 18" src="https://github.com/user-attachments/assets/6fa62eb0-24dc-4245-bc3f-a84fa10b34c4" /> <img width="866" height="880" alt="Screenshot 2025-10-28 at 09 18 03" src="https://github.com/user-attachments/assets/b60391fd-f23f-40c5-b8a8-87c419f5b2c6" /> Before: <img width="1896" height="1386" alt="image" src="https://github.com/user-attachments/assets/6213ee44-d53e-440e-95c3-ac1abaded3c0" /> After: ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --------- Co-authored-by: Elastic Machine <[email protected]> Co-authored-by: Melissa <[email protected]>
1 parent 5717459 commit cf5ff5b

File tree

17 files changed

+200
-11
lines changed

17 files changed

+200
-11
lines changed

x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/additional_options_fields.tsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ interface AdditionalOptionsFieldsProps {
5656
taskTypeOptions: TaskTypeOption[];
5757
isEdit?: boolean;
5858
allowContextWindowLength?: boolean;
59+
allowTemperature?: boolean;
5960
}
6061

6162
export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = ({
@@ -65,6 +66,7 @@ export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = (
6566
onTaskTypeOptionsSelect,
6667
isEdit,
6768
allowContextWindowLength,
69+
allowTemperature,
6870
}) => {
6971
const { euiTheme } = useEuiTheme();
7072
const { setFieldValue } = useFormContext();
@@ -175,6 +177,100 @@ export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = (
175177
]
176178
);
177179

180+
const temperatureSettings = useMemo(
181+
() =>
182+
(selectedTaskType === CHAT_COMPLETION_TASK_TYPE || selectedTaskType === DEFAULT_TASK_TYPE) &&
183+
allowTemperature ? (
184+
<>
185+
<EuiTitle size="xxs" data-test-subj="temperature-details-label">
186+
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
187+
<EuiFlexItem grow={false}>
188+
<h4>
189+
<FormattedMessage
190+
id="xpack.inferenceEndpointUICommon.components.additionalInfo.temperatureLabel"
191+
defaultMessage="Temperature"
192+
/>
193+
</h4>
194+
</EuiFlexItem>
195+
<EuiFlexItem grow={false}>
196+
<EuiText color="subdued" size="xs">
197+
{LABELS.OPTIONALTEXT}
198+
</EuiText>
199+
</EuiFlexItem>
200+
</EuiFlexGroup>
201+
</EuiTitle>
202+
<EuiText size="xs" color="subdued">
203+
<FormattedMessage
204+
id="xpack.inferenceEndpointUICommon.components.additionalInfo.temperatureHelpInfo"
205+
defaultMessage="Controls the randomness of the model's output. Changing the temperature can affect the general performance of AI Assistant and AI-driven features in Kibana, and we recommend keeping the default value."
206+
/>
207+
</EuiText>
208+
<EuiSpacer size="m" />
209+
<UseField
210+
path="config.temperature"
211+
config={{
212+
validations: [
213+
{
214+
validator: ({ value, path }) => {
215+
if (value !== undefined && value !== null && value !== '') {
216+
const numValue = Number(value);
217+
if (isNaN(numValue) || numValue < 0) {
218+
return {
219+
code: 'ERR_FIELD_INVALID',
220+
path,
221+
message: LABELS.TEMPERATURE_VALIDATION_MESSAGE,
222+
};
223+
}
224+
}
225+
},
226+
isBlocking: false,
227+
},
228+
],
229+
}}
230+
>
231+
{(field) => {
232+
const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field);
233+
return (
234+
<EuiFormRow
235+
id="temperatureSettings"
236+
label={LABELS.TEMPERATURE_LABEL}
237+
fullWidth
238+
isInvalid={isInvalid}
239+
error={errorMessage}
240+
data-test-subj={'configuration-formrow-temperatureSettings'}
241+
>
242+
<EuiFormControlLayout
243+
fullWidth
244+
clear={{
245+
onClick: (e) => {
246+
setFieldValue('config.temperature', undefined);
247+
},
248+
}}
249+
>
250+
<EuiFieldNumber
251+
min={0}
252+
max={1}
253+
step={0.1}
254+
fullWidth
255+
data-test-subj={'temperatureSettingsNumber'}
256+
value={config.temperature ?? ''}
257+
isInvalid={isInvalid}
258+
onChange={(e) => {
259+
const value = e.target.value;
260+
setFieldValue('config.temperature', value === '' ? undefined : value);
261+
}}
262+
/>
263+
</EuiFormControlLayout>
264+
</EuiFormRow>
265+
);
266+
}}
267+
</UseField>
268+
<EuiSpacer size="m" />
269+
</>
270+
) : null,
271+
[setFieldValue, config.temperature, selectedTaskType, allowTemperature]
272+
);
273+
178274
const taskTypeSettings = useMemo(
179275
() =>
180276
selectedTaskType || config.taskType?.length ? (
@@ -289,6 +385,12 @@ export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = (
289385
return (
290386
<EuiFormRow
291387
id="inferenceId"
388+
label={
389+
<FormattedMessage
390+
id="xpack.inferenceEndpointUICommon.components.additionalInfo.inferenceIdLabel"
391+
defaultMessage="Inference ID"
392+
/>
393+
}
292394
isInvalid={isInvalid}
293395
error={errorMessage}
294396
fullWidth
@@ -338,6 +440,7 @@ export const AdditionalOptionsFields: React.FC<AdditionalOptionsFieldsProps> = (
338440
</UseField>
339441
<EuiSpacer size="m" />
340442
{contextWindowLengthSettings}
443+
{temperatureSettings}
341444
</EuiPanel>
342445
</EuiAccordion>
343446
);

x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_flyout_wrapper.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export const InferenceFlyoutWrapper: React.FC<InferenceFlyoutWrapperProps> = ({
126126
provider: inferenceEndpoint?.config.provider ?? '',
127127
providerConfig: inferenceEndpoint?.config.providerConfig,
128128
contextWindowLength: inferenceEndpoint?.config.contextWindowLength ?? undefined,
129+
temperature: inferenceEndpoint?.config.temperature ?? undefined,
129130
},
130131
secrets: {
131132
providerSecrets: {},

x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/components/inference_service_form_fields.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ interface InferenceServicesProps {
9999
isPreconfigured?: boolean;
100100
allowContextWindowLength?: boolean;
101101
reenterSecretsOnEdit?: boolean;
102+
allowTemperature?: boolean;
102103
};
103104
http: HttpSetup;
104105
toasts: IToasts;
@@ -109,6 +110,7 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({
109110
toasts,
110111
config: {
111112
allowContextWindowLength,
113+
allowTemperature,
112114
isEdit,
113115
enforceAdaptiveAllocations,
114116
isPreconfigured,
@@ -140,6 +142,7 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({
140142
'config.taskType',
141143
'config.inferenceId',
142144
'config.contextWindowLength',
145+
'config.temperature',
143146
'config.provider',
144147
'config.providerConfig',
145148
],
@@ -585,6 +588,7 @@ export const InferenceServiceFormFields: React.FC<InferenceServicesProps> = ({
585588
selectedTaskType={selectedTaskType}
586589
isEdit={isEdit}
587590
allowContextWindowLength={allowContextWindowLength}
591+
allowTemperature={allowTemperature}
588592
/>
589593
{/* HIDDEN VALIDATION */}
590594
<ProviderSecretHiddenField

x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/translations.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,17 @@ export const CONTEXT_WINDOW_TASK_TYPE_VALIDATION_MESSAGE = i18n.translate(
223223
defaultMessage: 'Context window length is only applicable for chat completion tasks.',
224224
}
225225
);
226+
227+
export const TEMPERATURE_LABEL = i18n.translate(
228+
'xpack.inferenceEndpointUICommon.components.temperatureTextFieldLabel',
229+
{
230+
defaultMessage: 'Temperature',
231+
}
232+
);
233+
234+
export const TEMPERATURE_VALIDATION_MESSAGE = i18n.translate(
235+
'xpack.inferenceEndpointUICommon.components.temperatureValidationMessage',
236+
{
237+
defaultMessage: 'Temperature must be a number between 0 and 1.',
238+
}
239+
);

x-pack/platform/packages/shared/kbn-inference-endpoint-ui-common/src/types/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface Config {
2828
provider: string;
2929
providerConfig?: Record<string, unknown>;
3030
contextWindowLength?: number;
31+
temperature?: number;
3132
}
3233

3334
export interface Secrets {

x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/bedrock/bedrock_claude_adapter.test.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ import { PassThrough } from 'stream';
99
import { loggerMock } from '@kbn/logging-mocks';
1010
import { noop } from 'rxjs';
1111
import type { InferenceExecutor } from '../../utils/inference_executor';
12-
import { MessageRole, ToolChoiceType } from '@kbn/inference-common';
12+
import { MessageRole, ToolChoiceType, InferenceConnectorType } from '@kbn/inference-common';
1313
import { bedrockClaudeAdapter } from './bedrock_claude_adapter';
1414
import { addNoToolUsageDirective } from './prompts';
1515
import { lastValueFrom, toArray } from 'rxjs';
1616
describe('bedrockClaudeAdapter', () => {
1717
const logger = loggerMock.create();
1818
const executorMock = {
1919
invoke: jest.fn(),
20-
} as InferenceExecutor & { invoke: jest.MockedFn<InferenceExecutor['invoke']> };
20+
getConnector: jest.fn(),
21+
} as InferenceExecutor & {
22+
invoke: jest.MockedFn<InferenceExecutor['invoke']>;
23+
getConnector: jest.MockedFn<InferenceExecutor['getConnector']>;
24+
};
2125

2226
beforeEach(() => {
2327
executorMock.invoke.mockReset();
@@ -31,6 +35,14 @@ describe('bedrockClaudeAdapter', () => {
3135
},
3236
};
3337
});
38+
executorMock.getConnector.mockReset();
39+
executorMock.getConnector.mockReturnValue({
40+
type: InferenceConnectorType.Bedrock,
41+
name: 'bedrock-connector',
42+
connectorId: 'test-connector-id',
43+
config: {},
44+
capabilities: {},
45+
});
3446
});
3547

3648
function getCallParams() {

x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/bedrock/bedrock_claude_adapter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type { ConverseCompletionChunk } from './process_completion_chunks';
2424
import { processConverseCompletionChunks } from './process_completion_chunks';
2525
import { addNoToolUsageDirective } from './prompts';
2626
import { toolChoiceToConverse, toolsToConverseBedrock } from './convert_tools';
27+
import { getTemperatureIfValid } from '../../utils/get_temperature';
2728

2829
export const bedrockClaudeAdapter: InferenceConnectorAdapter = {
2930
chatComplete: ({
@@ -47,13 +48,14 @@ export const bedrockClaudeAdapter: InferenceConnectorAdapter = {
4748
? [{ text: addNoToolUsageDirective(system) }]
4849
: [{ text: system }];
4950
const bedRockTools = noToolUsage ? [] : toolsToConverseBedrock(tools, messages);
51+
const connector = executor.getConnector();
5052

5153
const subActionParams = {
5254
system: systemMessage,
5355
messages: converseMessages,
5456
tools: bedRockTools?.length ? bedRockTools : undefined,
5557
toolChoice: toolChoiceToConverse(toolChoice),
56-
temperature,
58+
...getTemperatureIfValid(temperature, { connector, modelName }),
5759
model: modelName,
5860
stopSequences: ['\n\nHuman:'],
5961
signal: abortSignal,

x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/gemini/gemini_adapter.test.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,29 @@ import { noop, tap, lastValueFrom, toArray, of } from 'rxjs';
1111
import { loggerMock } from '@kbn/logging-mocks';
1212
import type { InferenceExecutor } from '../../utils/inference_executor';
1313
import { observableIntoEventSourceStream } from '../../../util/observable_into_event_source_stream';
14-
import { MessageRole, ToolChoiceType } from '@kbn/inference-common';
14+
import { MessageRole, ToolChoiceType, InferenceConnectorType } from '@kbn/inference-common';
1515
import { geminiAdapter } from './gemini_adapter';
1616

1717
describe('geminiAdapter', () => {
1818
const logger = loggerMock.create();
1919
const executorMock = {
2020
invoke: jest.fn(),
21-
} as InferenceExecutor & { invoke: jest.MockedFn<InferenceExecutor['invoke']> };
21+
getConnector: jest.fn(),
22+
} as InferenceExecutor & {
23+
invoke: jest.MockedFn<InferenceExecutor['invoke']>;
24+
getConnector: jest.MockedFn<InferenceExecutor['getConnector']>;
25+
};
2226

2327
beforeEach(() => {
2428
executorMock.invoke.mockReset();
29+
executorMock.getConnector.mockReset();
30+
executorMock.getConnector.mockReturnValue({
31+
type: InferenceConnectorType.Gemini,
32+
name: 'gemini-connector',
33+
connectorId: 'test-connector-id',
34+
config: {},
35+
capabilities: {},
36+
});
2537
processVertexStreamMock.mockReset().mockImplementation(() => tap(noop));
2638
});
2739

x-pack/platform/plugins/shared/inference/server/chat_complete/adapters/gemini/gemini_adapter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { handleConnectorResponse } from '../../utils';
1414
import { eventSourceStreamIntoObservable } from '../../../util/event_source_stream_into_observable';
1515
import { processVertexStream } from './process_vertex_stream';
1616
import type { GenerateContentResponseChunk, GeminiMessage, GeminiToolConfig } from './types';
17+
import { getTemperatureIfValid } from '../../utils/get_temperature';
1718

1819
export const geminiAdapter: InferenceConnectorAdapter = {
1920
chatComplete: ({
@@ -27,6 +28,7 @@ export const geminiAdapter: InferenceConnectorAdapter = {
2728
abortSignal,
2829
metadata,
2930
}) => {
31+
const connector = executor.getConnector();
3032
return defer(() => {
3133
return executor.invoke({
3234
subAction: 'invokeStream',
@@ -35,7 +37,7 @@ export const geminiAdapter: InferenceConnectorAdapter = {
3537
systemInstruction: system,
3638
tools: toolsToGemini(tools),
3739
toolConfig: toolChoiceToConfig(toolChoice),
38-
temperature,
40+
...getTemperatureIfValid(temperature, { connector, modelName }),
3941
model: modelName,
4042
signal: abortSignal,
4143
stopSequences: ['\n\nHuman:'],

x-pack/platform/plugins/shared/inference/server/chat_complete/utils/get_temperature.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ export const getTemperatureIfValid = (
1313
temperature?: number,
1414
{ connector, modelName }: { connector?: InferenceConnector; modelName?: string } = {}
1515
) => {
16-
if (temperature === undefined) return {};
16+
// If user sets temperature in the connector config, use it by default
17+
if (connector?.config?.temperature) {
18+
return { temperature: connector.config.temperature };
19+
}
20+
21+
if (temperature === undefined || temperature < 0) return {};
1722

23+
// Else, use the temperature from the request
1824
const model =
1925
modelName ?? connector?.config?.providerConfig?.model_id ?? connector?.config?.defaultModel;
2026

@@ -24,6 +30,7 @@ export const getTemperatureIfValid = (
2430
model
2531
) {
2632
const normalizedModelName = model.toLowerCase();
33+
2734
const shouldExcludeTemperature = OPENAI_MODELS_WITHOUT_TEMPERATURE.some(
2835
// e.g openai/gpt-5 or gpt-5-xxx
2936
(m) => normalizedModelName.startsWith(m) || normalizedModelName.endsWith(m)

0 commit comments

Comments
 (0)