Browse Source

enh/refac: ollama advanced params

Timothy Jaeryang Baek 2 weeks ago
parent
commit
8f68b25510

+ 1 - 1
backend/open_webui/routers/ollama.py

@@ -1323,7 +1323,7 @@ async def generate_chat_completion(
     prefix_id = api_config.get("prefix_id", None)
     if prefix_id:
         payload["model"] = payload["model"].replace(f"{prefix_id}.", "")
-    # payload["keep_alive"] = -1 # keep alive forever
+
     return await send_post_request(
         url=f"{url}/api/chat",
         payload=json.dumps(payload),

+ 1 - 6
backend/open_webui/utils/middleware.py

@@ -698,13 +698,8 @@ def apply_params_to_form_data(form_data, model):
         params = deep_update(params, custom_params)
 
     if model.get("ollama"):
+        # Ollama specific parameters
         form_data["options"] = params
-
-        if "format" in params:
-            form_data["format"] = params["format"]
-
-        if "keep_alive" in params:
-            form_data["keep_alive"] = params["keep_alive"]
     else:
         if isinstance(params, dict):
             for key, value in params.items():

+ 44 - 22
backend/open_webui/utils/payload.py

@@ -175,14 +175,26 @@ def apply_model_params_to_body_ollama(params: dict, form_data: dict) -> dict:
         "num_thread": int,
     }
 
-    # Extract keep_alive from options if it exists
-    if "options" in form_data and "keep_alive" in form_data["options"]:
-        form_data["keep_alive"] = form_data["options"]["keep_alive"]
-        del form_data["options"]["keep_alive"]
+    def parse_json(value: str) -> dict:
+        """
+        Parses a JSON string into a dictionary, handling potential JSONDecodeError.
+        """
+        try:
+            return json.loads(value)
+        except Exception as e:
+            return value
+
+    ollama_root_params = {
+        "format": lambda x: parse_json(x),
+        "keep_alive": lambda x: parse_json(x),
+        "think": bool,
+    }
 
-    if "options" in form_data and "format" in form_data["options"]:
-        form_data["format"] = form_data["options"]["format"]
-        del form_data["options"]["format"]
+    for key, value in ollama_root_params.items():
+        if (param := params.get(key, None)) is not None:
+            # Copy the parameter to new name then delete it, to prevent Ollama warning of invalid option provided
+            form_data[key] = value(param)
+            del params[key]
 
     return apply_model_params_to_body(params, form_data, mappings)
 
@@ -279,36 +291,46 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
         openai_payload.get("messages")
     )
     ollama_payload["stream"] = openai_payload.get("stream", False)
-
     if "tools" in openai_payload:
         ollama_payload["tools"] = openai_payload["tools"]
 
-    if "format" in openai_payload:
-        ollama_payload["format"] = openai_payload["format"]
-
     # If there are advanced parameters in the payload, format them in Ollama's options field
     if openai_payload.get("options"):
         ollama_payload["options"] = openai_payload["options"]
         ollama_options = openai_payload["options"]
 
+        def parse_json(value: str) -> dict:
+            """
+            Parses a JSON string into a dictionary, handling potential JSONDecodeError.
+            """
+            try:
+                return json.loads(value)
+            except Exception as e:
+                return value
+
+        ollama_root_params = {
+            "format": lambda x: parse_json(x),
+            "keep_alive": lambda x: parse_json(x),
+            "think": bool,
+        }
+
+        # Ollama's options field can contain parameters that should be at the root level.
+        for key, value in ollama_root_params.items():
+            if (param := ollama_options.get(key, None)) is not None:
+                # Copy the parameter to new name then delete it, to prevent Ollama warning of invalid option provided
+                ollama_payload[key] = value(param)
+                del ollama_options[key]
+
         # Re-Mapping OpenAI's `max_tokens` -> Ollama's `num_predict`
         if "max_tokens" in ollama_options:
             ollama_options["num_predict"] = ollama_options["max_tokens"]
-            del ollama_options[
-                "max_tokens"
-            ]  # To prevent Ollama warning of invalid option provided
+            del ollama_options["max_tokens"]
 
         # Ollama lacks a "system" prompt option. It has to be provided as a direct parameter, so we copy it down.
+        # Comment: Not sure why this is needed, but we'll keep it for compatibility.
         if "system" in ollama_options:
             ollama_payload["system"] = ollama_options["system"]
-            del ollama_options[
-                "system"
-            ]  # To prevent Ollama warning of invalid option provided
-
-        # Extract keep_alive from options if it exists
-        if "keep_alive" in ollama_options:
-            ollama_payload["keep_alive"] = ollama_options["keep_alive"]
-            del ollama_options["keep_alive"]
+            del ollama_options["system"]
 
     # If there is the "stop" parameter in the openai_payload, remap it to the ollama_payload.options
     if "stop" in openai_payload:

+ 0 - 3
src/lib/components/chat/Chat.svelte

@@ -1642,9 +1642,6 @@
 				params: {
 					...$settings?.params,
 					...params,
-
-					format: $settings.requestFormat ?? undefined,
-					keep_alive: $settings.keepAlive ?? undefined,
 					stop:
 						(params?.stop ?? $settings?.params?.stop ?? undefined)
 							? (params?.stop.split(',').map((token) => token.trim()) ?? $settings.params.stop).map(

+ 114 - 0
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte

@@ -1,5 +1,6 @@
 <script lang="ts">
 	import Switch from '$lib/components/common/Switch.svelte';
+	import Textarea from '$lib/components/common/Textarea.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Plus from '$lib/components/icons/Plus.svelte';
 	import { getContext } from 'svelte';
@@ -34,6 +35,9 @@
 		repeat_penalty: null,
 		use_mmap: null,
 		use_mlock: null,
+		think: null,
+		format: null,
+		keep_alive: null,
 		num_keep: null,
 		num_ctx: null,
 		num_batch: null,
@@ -1092,6 +1096,76 @@
 		</div>
 	{/if}
 
+	<div class=" py-0.5 w-full justify-between">
+		<Tooltip
+			content={$i18n.t(
+				'This option enables or disables the use of the Ollama Think feature, which allows the model to think before generating a response. When enabled, the model can take a moment to process the conversation context and generate a more thoughtful response.'
+			)}
+			placement="top-start"
+			className="inline-tooltip"
+		>
+			<div class=" py-0.5 flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">
+					{'think'} ({$i18n.t('Ollama')})
+				</div>
+				<button
+					class="p-1 px-3 text-xs flex rounded-sm transition"
+					on:click={() => {
+						params.think = (params?.think ?? null) === null ? true : params.think ? false : null;
+					}}
+					type="button"
+				>
+					{#if params.think === true}
+						<span class="ml-2 self-center">{$i18n.t('On')}</span>
+					{:else if params.think === false}
+						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					{/if}
+				</button>
+			</div>
+		</Tooltip>
+	</div>
+
+	<div class=" py-0.5 w-full justify-between">
+		<Tooltip
+			content={$i18n.t(
+				'This option enables or disables the use of the Ollama Think feature, which allows the model to think before generating a response. When enabled, the model can take a moment to process the conversation context and generate a more thoughtful response.'
+			)}
+			placement="top-start"
+			className="inline-tooltip"
+		>
+			<div class=" py-0.5 flex w-full justify-between">
+				<div class=" self-center text-xs font-medium">
+					{'format'} ({$i18n.t('Ollama')})
+				</div>
+				<button
+					class="p-1 px-3 text-xs flex rounded-sm transition"
+					on:click={() => {
+						params.format = (params?.format ?? null) === null ? 'json' : null;
+					}}
+					type="button"
+				>
+					{#if (params?.format ?? null) === null}
+						<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+					{:else}
+						<span class="ml-2 self-center">{$i18n.t('JSON')}</span>
+					{/if}
+				</button>
+			</div>
+		</Tooltip>
+
+		{#if (params?.format ?? null) !== null}
+			<div class="flex mt-0.5 space-x-2">
+				<Textarea
+					className="w-full  text-sm bg-transparent outline-hidden"
+					placeholder={$i18n.t('e.g. "json" or a JSON schema')}
+					bind:value={params.format}
+				/>
+			</div>
+		{/if}
+	</div>
+
 	<div class=" py-0.5 w-full justify-between">
 		<Tooltip
 			content={$i18n.t(
@@ -1368,6 +1442,46 @@
 			{/if}
 		</div>
 
+		<div class=" py-0.5 w-full justify-between">
+			<Tooltip
+				content={$i18n.t(
+					'This option controls how long the model will stay loaded into memory following the request (default: 5m)'
+				)}
+				placement="top-start"
+				className="inline-tooltip"
+			>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">
+						{'keep_alive'} ({$i18n.t('Ollama')})
+					</div>
+					<button
+						class="p-1 px-3 text-xs flex rounded-sm transition"
+						on:click={() => {
+							params.keep_alive = (params?.keep_alive ?? null) === null ? '5m' : null;
+						}}
+						type="button"
+					>
+						{#if (params?.keep_alive ?? null) === null}
+							<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
+						{/if}
+					</button>
+				</div>
+			</Tooltip>
+
+			{#if (params?.keep_alive ?? null) !== null}
+				<div class="flex mt-0.5 space-x-2">
+					<input
+						class="w-full text-sm bg-transparent outline-hidden"
+						type="text"
+						placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
+						bind:value={params.keep_alive}
+					/>
+				</div>
+			{/if}
+		</div>
+
 		{#if custom && admin}
 			<div class="flex flex-col justify-center">
 				{#each Object.keys(params?.custom_params ?? {}) as key}

+ 5 - 122
src/lib/components/chat/Settings/General.svelte

@@ -10,12 +10,11 @@
 
 	import AdvancedParams from './Advanced/AdvancedParams.svelte';
 	import Textarea from '$lib/components/common/Textarea.svelte';
-
 	export let saveSettings: Function;
 	export let getModels: Function;
 
 	// General
-	let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light', 'oled-dark'];
+	let themes = ['dark', 'light', 'oled-dark'];
 	let selectedTheme = 'system';
 
 	let languages: Awaited<ReturnType<typeof getLanguages>> = [];
@@ -40,10 +39,6 @@
 		}
 	};
 
-	// Advanced
-	let requestFormat = null;
-	let keepAlive: string | null = null;
-
 	let params = {
 		// Advanced
 		stream_response: null,
@@ -71,37 +66,7 @@
 		num_gpu: null
 	};
 
-	const validateJSON = (json) => {
-		try {
-			const obj = JSON.parse(json);
-
-			if (obj && typeof obj === 'object') {
-				return true;
-			}
-		} catch (e) {}
-		return false;
-	};
-
-	const toggleRequestFormat = async () => {
-		if (requestFormat === null) {
-			requestFormat = 'json';
-		} else {
-			requestFormat = null;
-		}
-
-		saveSettings({ requestFormat: requestFormat !== null ? requestFormat : undefined });
-	};
-
 	const saveHandler = async () => {
-		if (requestFormat !== null && requestFormat !== 'json') {
-			if (validateJSON(requestFormat) === false) {
-				toast.error($i18n.t('Invalid JSON schema'));
-				return;
-			} else {
-				requestFormat = JSON.parse(requestFormat);
-			}
-		}
-
 		saveSettings({
 			system: system !== '' ? system : undefined,
 			params: {
@@ -130,15 +95,12 @@
 				use_mmap: params.use_mmap !== null ? params.use_mmap : undefined,
 				use_mlock: params.use_mlock !== null ? params.use_mlock : undefined,
 				num_thread: params.num_thread !== null ? params.num_thread : undefined,
-				num_gpu: params.num_gpu !== null ? params.num_gpu : undefined
-			},
-			keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined,
-			requestFormat: requestFormat !== null ? requestFormat : undefined
+				num_gpu: params.num_gpu !== null ? params.num_gpu : undefined,
+				keep_alive: params.keep_alive !== null ? params.keep_alive : undefined,
+				format: params.format !== null ? params.format : undefined
+			}
 		});
 		dispatch('save');
-
-		requestFormat =
-			typeof requestFormat === 'object' ? JSON.stringify(requestFormat, null, 2) : requestFormat;
 	};
 
 	onMount(async () => {
@@ -149,14 +111,6 @@
 		notificationEnabled = $settings.notificationEnabled ?? false;
 		system = $settings.system ?? '';
 
-		requestFormat = $settings.requestFormat ?? null;
-		if (requestFormat !== null && requestFormat !== 'json') {
-			requestFormat =
-				typeof requestFormat === 'object' ? JSON.stringify(requestFormat, null, 2) : requestFormat;
-		}
-
-		keepAlive = $settings.keepAlive ?? null;
-
 		params = { ...params, ...$settings.params };
 		params.stop = $settings?.params?.stop ? ($settings?.params?.stop ?? []).join(',') : null;
 	});
@@ -335,77 +289,6 @@
 
 				{#if showAdvanced}
 					<AdvancedParams admin={$user?.role === 'admin'} bind:params />
-					<hr class=" border-gray-100 dark:border-gray-850" />
-
-					<div class=" w-full justify-between">
-						<div class="flex w-full justify-between">
-							<div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
-
-							<button
-								class="p-1 px-3 text-xs flex rounded-sm transition"
-								type="button"
-								on:click={() => {
-									keepAlive = keepAlive === null ? '5m' : null;
-								}}
-							>
-								{#if keepAlive === null}
-									<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
-								{:else}
-									<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
-								{/if}
-							</button>
-						</div>
-
-						{#if keepAlive !== null}
-							<div class="flex mt-1 space-x-2">
-								<input
-									class="w-full text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
-									type="text"
-									placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
-									bind:value={keepAlive}
-								/>
-							</div>
-						{/if}
-					</div>
-
-					<div>
-						<div class=" flex w-full justify-between">
-							<div class=" self-center text-xs font-medium">{$i18n.t('Request Mode')}</div>
-
-							<button
-								class="p-1 px-3 text-xs flex rounded-sm transition"
-								on:click={() => {
-									toggleRequestFormat();
-								}}
-							>
-								{#if requestFormat === null}
-									<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
-								{:else}
-									<!-- <svg
-                            xmlns="http://www.w3.org/2000/svg"
-                            viewBox="0 0 20 20"
-                            fill="currentColor"
-                            class="w-4 h-4 self-center"
-                        >
-                            <path
-                                d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
-                            />
-                        </svg> -->
-									<span class="ml-2 self-center"> {$i18n.t('JSON')} </span>
-								{/if}
-							</button>
-						</div>
-
-						{#if requestFormat !== null}
-							<div class="flex mt-1 space-x-2">
-								<Textarea
-									className="w-full  text-sm dark:text-gray-300 dark:bg-gray-900 outline-hidden"
-									placeholder={$i18n.t('e.g. "json" or a JSON schema')}
-									bind:value={requestFormat}
-								/>
-							</div>
-						{/if}
-					</div>
 				{/if}
 			</div>
 		{/if}