Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/src/locales/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ const TRANSLATIONS = {
temperature: {
title: "LLM Temperature",
"desc-end":
"The higher the number the more creative. For some models this can lead to incoherent responses when set too high.",
"The higher the number the more creative. For some models this can lead to incoherent responses when set too high. Leave blank to use your model provider's default.",
placeholder: "Provider default",
},
},
"vector-workspace": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
import { useTranslation } from "react-i18next";
function recommendedSettings(provider = null) {
switch (provider) {
case "mistral":
return { temp: 0 };
default:
return { temp: 0.7 };
}
}

export default function ChatTemperatureSettings({
settings,
workspace,
setHasChanges,
}) {
const defaults = recommendedSettings(settings?.LLMProvider);
export default function ChatTemperatureSettings({ workspace, setHasChanges }) {
const { t } = useTranslation();
return (
<div>
<div className="flex flex-col gap-y-[8px]">
<div className="flex flex-col gap-y-[8px]">
<label htmlFor="name" className="block input-label">
{t("chat.temperature.title")}
Expand All @@ -31,10 +18,9 @@ export default function ChatTemperatureSettings({
min={0.0}
step={0.1}
onWheel={(e) => e.target.blur()}
defaultValue={workspace?.openAiTemp ?? defaults.temp}
defaultValue={workspace?.openAiTemp ?? ""}
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="0.7"
required={true}
placeholder={t("chat.temperature.placeholder")}
autoComplete="off"
onChange={() => setHasChanges(true)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export default function ChatSettings({ workspace }) {
setHasChanges={setHasChanges}
/>
<ChatTemperatureSettings
settings={settings}
workspace={workspace}
setHasChanges={setHasChanges}
/>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/utils/types.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export function castToType(key, value) {
const definitions = {
openAiTemp: {
cast: (value) => Number(value),
cast: (value) => (value === "" ? null : Number(value)),
},
openAiHistory: {
cast: (value) => Number(value),
Expand Down
1 change: 0 additions & 1 deletion server/__tests__/utils/chats/openaiCompatible.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ describe('OpenAICompatibleChat', () => {
metrics: {},
}),
handleStream: jest.fn().mockResolvedValue('Mock streamed response'),
defaultTemp: 0.7,
};
getLLMProvider.mockReturnValue(mockLLMConnector);
resolveProviderConnector.mockResolvedValue({
Expand Down
4 changes: 4 additions & 0 deletions server/endpoints/agentWebsocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ function agentWebsocket(app) {
return;
}

AgentHandler.registerSessionSocket(
agentHandler.invocation.workspace_id,
socket
);
socket.on("message", relayToSocket);
socket.on("close", () => {
// Abort the running agent loop (stop button, tab close, disconnect) so
Expand Down
12 changes: 11 additions & 1 deletion server/models/workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,17 @@ const Workspace = {
validatedUpdates.router_id = null;
}

return this._update(id, validatedUpdates);
const result = await this._update(id, validatedUpdates);

// A live agent session keeps the settings it was built with, so end any
// open sessions for this workspace - the next agent message starts a
// fresh session with the updated settings.
if (result.workspace) {
const { AgentHandler } = require("../utils/agents");
AgentHandler.closeWorkspaceSessions(id);
}

return result;
},

/**
Expand Down
27 changes: 20 additions & 7 deletions server/utils/AiProviders/anthropic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,22 @@ class AnthropicLLM {
* These models reject `temperature`/`top_p`/`top_k` with a 400 error.
* @type {string[]}
*/
noTemperatureModels = [
static noTemperatureModels = [
"claude-opus-4-7",
"claude-opus-4-8",
"claude-sonnet-5",
// Add other models here if identified
];

/**
* Whether the model supports the temperature parameter at all.
* @param {string} modelName
* @returns {boolean}
*/
static modelSupportsTemperature(modelName = "") {
return !this.noTemperatureModels.some((model) => modelName.includes(model));
}

constructor(embedder = null, modelPreference = null) {
if (!process.env.ANTHROPIC_API_KEY)
throw new Error("No Anthropic API key was set.");
Expand All @@ -50,7 +59,6 @@ class AnthropicLLM {

this.maxTokens = null;
this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0.7;
this.log(
`Initialized with ${this.model}. Cache ${this.cacheControl ? `enabled (${this.cacheControl.ttl})` : "disabled"}`
);
Expand Down Expand Up @@ -92,10 +100,9 @@ class AnthropicLLM {
* @param {number} temperature - The temperature to use.
* @returns {number|undefined} The temperature value or undefined if not supported.
*/
temperatureParam(temperature = this.defaultTemp) {
temperatureParam(temperature = this.temperature) {
if (typeof temperature !== "number") return undefined;
if (this.noTemperatureModels.some((model) => this.model.includes(model)))
return undefined;
if (!AnthropicLLM.modelSupportsTemperature(this.model)) return undefined;
return parseFloat(temperature);
}

Expand Down Expand Up @@ -210,7 +217,10 @@ class AnthropicLLM {
];
}

async getChatCompletion(messages = null, { temperature = 0.7 }) {
async getChatCompletion(
messages = null,
{ temperature = this.temperature } = {}
) {
await this.assertModelMaxTokens();
try {
const systemContent = messages[0].content;
Expand Down Expand Up @@ -258,7 +268,10 @@ class AnthropicLLM {
}
}

async streamGetChatCompletion(messages = null, { temperature = 0.7 }) {
async streamGetChatCompletion(
messages = null,
{ temperature = this.temperature } = {}
) {
await this.assertModelMaxTokens();
const systemContent = messages[0].content;
const measuredStreamRequest = await LLMPerformanceMonitor.measureStream({
Expand Down
11 changes: 8 additions & 3 deletions server/utils/AiProviders/apipie/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ class ApiPieLLM {
};

this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0.7;

if (!fs.existsSync(cacheFolder))
fs.mkdirSync(cacheFolder, { recursive: true });
Expand Down Expand Up @@ -187,7 +186,10 @@ class ApiPieLLM {
];
}

async getChatCompletion(messages = null, { temperature = 0.7 }) {
async getChatCompletion(
messages = null,
{ temperature = this.temperature } = {}
) {
if (!(await this.isValidChatCompletionModel(this.model)))
throw new Error(
`ApiPie chat: ${this.model} is not valid for chat completion!`
Expand Down Expand Up @@ -227,7 +229,10 @@ class ApiPieLLM {
};
}

async streamGetChatCompletion(messages = null, { temperature = 0.7 }) {
async streamGetChatCompletion(
messages = null,
{ temperature = this.temperature } = {}
) {
if (!(await this.isValidChatCompletionModel(this.model)))
throw new Error(
`ApiPie chat: ${this.model} is not valid for chat completion!`
Expand Down
20 changes: 17 additions & 3 deletions server/utils/AiProviders/azureOpenAi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class AzureOpenAiLLM {
};

this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0.7;
this.#log(
`Initialized. Model "${this.model}" @ ${this.promptWindowLimit()} tokens.\nAPI-Version: ${this.apiVersion}.\nModel Type: ${this.isOTypeModel ? "reasoning" : "default"}`
);
Expand All @@ -65,6 +64,15 @@ class AzureOpenAiLLM {
}
}

/**
* Whether the deployment supports the temperature parameter. Azure does not
* expose model metadata, so this relies on the user-declared AZURE_OPENAI_MODEL_TYPE.
* @returns {boolean}
*/
static modelSupportsTemperature() {
return process.env.AZURE_OPENAI_MODEL_TYPE !== "reasoning";
}

#log(text, ...args) {
console.log(`\x1b[32m[AzureOpenAi]\x1b[0m ${text}`, ...args);
}
Expand Down Expand Up @@ -150,7 +158,10 @@ class AzureOpenAiLLM {
];
}

async getChatCompletion(messages = [], { temperature = 0.7 }) {
async getChatCompletion(
messages = [],
{ temperature = this.temperature } = {}
) {
if (!this.model)
throw new Error(
"No AZURE_OPENAI_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an LLM chat model like GPT-3.5."
Expand Down Expand Up @@ -185,7 +196,10 @@ class AzureOpenAiLLM {
};
}

async streamGetChatCompletion(messages = [], { temperature = 0.7 }) {
async streamGetChatCompletion(
messages = [],
{ temperature = this.temperature } = {}
) {
if (!this.model)
throw new Error(
"No AZURE_OPENAI_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an LLM chat model like GPT-3.5."
Expand Down
27 changes: 20 additions & 7 deletions server/utils/AiProviders/bedrock/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,21 @@ class AWSBedrockLLM {
"us.deepseek.r1-v1:0",
];

noTemperatureModels = [
static noTemperatureModels = [
"anthropic.claude-opus-4-7",
"anthropic.claude-opus-4-8",
"anthropic.claude-sonnet-5",
];

/**
* Whether the model supports the temperature parameter at all.
* @param {string} modelName
* @returns {boolean}
*/
static modelSupportsTemperature(modelName = "") {
return !this.noTemperatureModels.some((model) => modelName.includes(model));
}

constructor(embedder = null, modelPreference = null) {
if (!process.env.AWS_BEDROCK_LLM_API_KEY)
throw new Error("AWS_BEDROCK_LLM_API_KEY is required for AWS Bedrock.");
Expand Down Expand Up @@ -91,7 +100,6 @@ class AWSBedrockLLM {
}

this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0.7;
this.#log(
`Initialized with model: ${this.model}. Region: ${this.region}. Context Window: ${contextWindowLimit}.`
);
Expand All @@ -105,10 +113,9 @@ class AWSBedrockLLM {
return Number(process.env.AWS_BEDROCK_LLM_MAX_TOKENS) || 4096;
}

temperatureParam(temperature = this.defaultTemp) {
temperatureParam(temperature = this.temperature) {
if (typeof temperature !== "number") return undefined;
if (this.noTemperatureModels.some((model) => this.model.includes(model)))
return undefined;
if (!AWSBedrockLLM.modelSupportsTemperature(this.model)) return undefined;
return parseFloat(temperature);
}

Expand Down Expand Up @@ -211,7 +218,10 @@ class AWSBedrockLLM {

// --- Chat completions ---

async getChatCompletion(messages = null, { temperature }) {
async getChatCompletion(
messages = null,
{ temperature = this.temperature } = {}
) {
if (!messages?.length)
throw new Error(
"AWSBedrock::getChatCompletion requires a non-empty messages array."
Expand Down Expand Up @@ -246,7 +256,10 @@ class AWSBedrockLLM {
};
}

async streamGetChatCompletion(messages = null, { temperature }) {
async streamGetChatCompletion(
messages = null,
{ temperature = this.temperature } = {}
) {
if (!Array.isArray(messages) || messages.length === 0) {
throw new Error(
"AWSBedrock::streamGetChatCompletion requires a non-empty messages array."
Expand Down
15 changes: 12 additions & 3 deletions server/utils/AiProviders/cerebras/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ class CerebrasLLM {
this.limits = null;

this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0;

CerebrasLLM.cacheContextWindows(true);
this.#log(`Initialized with model: ${this.model}`);
Expand Down Expand Up @@ -197,7 +196,12 @@ class CerebrasLLM {
return textResponse;
}

async getChatCompletion(messages = null, { temperature = 0.7 }) {
async getChatCompletion(
messages = null,
// These models degrade quickly at higher temperatures, so an unset
// workspace temperature falls back to 0 instead of the provider default.
{ temperature = this.temperature ?? 0 } = {}
) {
const result = await LLMPerformanceMonitor.measureAsyncFunction(
this.openai.chat.completions
.create({
Expand Down Expand Up @@ -234,7 +238,12 @@ class CerebrasLLM {
};
}

async streamGetChatCompletion(messages = null, { temperature = 0.7 }) {
async streamGetChatCompletion(
messages = null,
// These models degrade quickly at higher temperatures, so an unset
// workspace temperature falls back to 0 instead of the provider default.
{ temperature = this.temperature ?? 0 } = {}
) {
const measuredStreamRequest = await LLMPerformanceMonitor.measureStream({
func: this.openai.chat.completions.create({
model: this.model,
Expand Down
11 changes: 8 additions & 3 deletions server/utils/AiProviders/cohere/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class CohereLLM {
};

this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0.7;
this.#log(
`Initialized with model ${this.model}. ctx: ${this.promptWindowLimit()}`
);
Expand Down Expand Up @@ -79,7 +78,10 @@ class CohereLLM {
return [prompt, ...chatHistory, { role: "user", content: userPrompt }];
}

async getChatCompletion(messages = null, { temperature = 0.7 }) {
async getChatCompletion(
messages = null,
{ temperature = this.temperature } = {}
) {
const result = await LLMPerformanceMonitor.measureAsyncFunction(
this.openai.chat.completions
.create({
Expand Down Expand Up @@ -115,7 +117,10 @@ class CohereLLM {
};
}

async streamGetChatCompletion(messages = null, { temperature = 0.7 }) {
async streamGetChatCompletion(
messages = null,
{ temperature = this.temperature } = {}
) {
const measuredStreamRequest = await LLMPerformanceMonitor.measureStream({
func: this.openai.chat.completions.create({
model: this.model,
Expand Down
Loading
Loading