浏览代码

enh: allow json schema for format

Timothy Jaeryang Baek 3 月之前
父节点
当前提交
c137d3ad17
共有 2 个文件被更改,包括 91 次插入48 次删除
  1. 4 0
      backend/open_webui/utils/payload.py
  2. 87 48
      src/lib/components/chat/Settings/General.svelte

+ 4 - 0
backend/open_webui/utils/payload.py

@@ -116,6 +116,10 @@ def apply_model_params_to_body_ollama(params: dict, form_data: dict) -> dict:
         form_data["keep_alive"] = form_data["options"]["keep_alive"]
         del form_data["options"]["keep_alive"]
 
+    if "options" in form_data and "format" in form_data["options"]:
+        form_data["format"] = form_data["options"]["format"]
+        del form_data["options"]["format"]
+
     return apply_model_params_to_body(params, form_data, mappings)
 
 

+ 87 - 48
src/lib/components/chat/Settings/General.svelte

@@ -9,6 +9,7 @@
 	const i18n = getContext('i18n');
 
 	import AdvancedParams from './Advanced/AdvancedParams.svelte';
+	import Textarea from '$lib/components/common/Textarea.svelte';
 
 	export let saveSettings: Function;
 	export let getModels: Function;
@@ -40,7 +41,7 @@
 	};
 
 	// Advanced
-	let requestFormat = '';
+	let requestFormat = null;
 	let keepAlive: string | null = null;
 
 	let params = {
@@ -70,14 +71,74 @@
 		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 === '') {
+		if (requestFormat === null) {
 			requestFormat = 'json';
 		} else {
-			requestFormat = '';
+			requestFormat = null;
 		}
 
-		saveSettings({ requestFormat: requestFormat !== '' ? requestFormat : undefined });
+		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: {
+				stream_response: params.stream_response !== null ? params.stream_response : undefined,
+				function_calling: params.function_calling !== null ? params.function_calling : undefined,
+				seed: (params.seed !== null ? params.seed : undefined) ?? undefined,
+				stop: params.stop ? params.stop.split(',').filter((e) => e) : undefined,
+				temperature: params.temperature !== null ? params.temperature : undefined,
+				reasoning_effort: params.reasoning_effort !== null ? params.reasoning_effort : undefined,
+				logit_bias: params.logit_bias !== null ? params.logit_bias : undefined,
+				frequency_penalty: params.frequency_penalty !== null ? params.frequency_penalty : undefined,
+				presence_penalty: params.frequency_penalty !== null ? params.frequency_penalty : undefined,
+				repeat_penalty: params.frequency_penalty !== null ? params.frequency_penalty : undefined,
+				repeat_last_n: params.repeat_last_n !== null ? params.repeat_last_n : undefined,
+				mirostat: params.mirostat !== null ? params.mirostat : undefined,
+				mirostat_eta: params.mirostat_eta !== null ? params.mirostat_eta : undefined,
+				mirostat_tau: params.mirostat_tau !== null ? params.mirostat_tau : undefined,
+				top_k: params.top_k !== null ? params.top_k : undefined,
+				top_p: params.top_p !== null ? params.top_p : undefined,
+				min_p: params.min_p !== null ? params.min_p : undefined,
+				tfs_z: params.tfs_z !== null ? params.tfs_z : undefined,
+				num_ctx: params.num_ctx !== null ? params.num_ctx : undefined,
+				num_batch: params.num_batch !== null ? params.num_batch : undefined,
+				num_keep: params.num_keep !== null ? params.num_keep : undefined,
+				max_tokens: params.max_tokens !== null ? params.max_tokens : undefined,
+				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
+		});
+		dispatch('save');
+
+		requestFormat =
+			typeof requestFormat === 'object' ? JSON.stringify(requestFormat, null, 2) : requestFormat;
 	};
 
 	onMount(async () => {
@@ -88,7 +149,12 @@
 		notificationEnabled = $settings.notificationEnabled ?? false;
 		system = $settings.system ?? '';
 
-		requestFormat = $settings.requestFormat ?? '';
+		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 };
@@ -270,7 +336,7 @@
 					<AdvancedParams admin={$user?.role === 'admin'} bind:params />
 					<hr class=" border-gray-100 dark:border-gray-850" />
 
-					<div class=" py-1 w-full justify-between">
+					<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>
 
@@ -302,8 +368,8 @@
 					</div>
 
 					<div>
-						<div class=" py-1 flex w-full justify-between">
-							<div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</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"
@@ -311,9 +377,9 @@
 									toggleRequestFormat();
 								}}
 							>
-								{#if requestFormat === ''}
+								{#if requestFormat === null}
 									<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
-								{:else if requestFormat === 'json'}
+								{:else}
 									<!-- <svg
                             xmlns="http://www.w3.org/2000/svg"
                             viewBox="0 0 20 20"
@@ -328,6 +394,16 @@
 								{/if}
 							</button>
 						</div>
+
+						{#if requestFormat !== null}
+							<div class="flex mt-1 space-x-2">
+								<Textarea
+									className="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+									placeholder={$i18n.t('e.g. "json" or a JSON schema')}
+									bind:value={requestFormat}
+								/>
+							</div>
+						{/if}
 					</div>
 				{/if}
 			</div>
@@ -338,44 +414,7 @@
 		<button
 			class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
 			on:click={() => {
-				saveSettings({
-					system: system !== '' ? system : undefined,
-					params: {
-						stream_response: params.stream_response !== null ? params.stream_response : undefined,
-						function_calling:
-							params.function_calling !== null ? params.function_calling : undefined,
-						seed: (params.seed !== null ? params.seed : undefined) ?? undefined,
-						stop: params.stop ? params.stop.split(',').filter((e) => e) : undefined,
-						temperature: params.temperature !== null ? params.temperature : undefined,
-						reasoning_effort:
-							params.reasoning_effort !== null ? params.reasoning_effort : undefined,
-						logit_bias: params.logit_bias !== null ? params.logit_bias : undefined,
-						frequency_penalty:
-							params.frequency_penalty !== null ? params.frequency_penalty : undefined,
-						presence_penalty:
-							params.frequency_penalty !== null ? params.frequency_penalty : undefined,
-						repeat_penalty:
-							params.frequency_penalty !== null ? params.frequency_penalty : undefined,
-						repeat_last_n: params.repeat_last_n !== null ? params.repeat_last_n : undefined,
-						mirostat: params.mirostat !== null ? params.mirostat : undefined,
-						mirostat_eta: params.mirostat_eta !== null ? params.mirostat_eta : undefined,
-						mirostat_tau: params.mirostat_tau !== null ? params.mirostat_tau : undefined,
-						top_k: params.top_k !== null ? params.top_k : undefined,
-						top_p: params.top_p !== null ? params.top_p : undefined,
-						min_p: params.min_p !== null ? params.min_p : undefined,
-						tfs_z: params.tfs_z !== null ? params.tfs_z : undefined,
-						num_ctx: params.num_ctx !== null ? params.num_ctx : undefined,
-						num_batch: params.num_batch !== null ? params.num_batch : undefined,
-						num_keep: params.num_keep !== null ? params.num_keep : undefined,
-						max_tokens: params.max_tokens !== null ? params.max_tokens : undefined,
-						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
-				});
-				dispatch('save');
+				saveHandler();
 			}}
 		>
 			{$i18n.t('Save')}