Browse Source

enh: external tool server custom name/description support

Timothy Jaeryang Baek 1 month ago
parent
commit
a38e44e870

+ 2 - 2
backend/open_webui/routers/tools.py

@@ -51,11 +51,11 @@ async def get_tools(request: Request, user=Depends(get_verified_user)):
                 **{
                     "id": f"server:{server['idx']}",
                     "user_id": f"server:{server['idx']}",
-                    "name": server["openapi"]
+                    "name": server.get("openapi", {})
                     .get("info", {})
                     .get("title", "Tool Server"),
                     "meta": {
-                        "description": server["openapi"]
+                        "description": server.get("openapi", {})
                         .get("info", {})
                         .get("description", ""),
                     },

+ 17 - 4
backend/open_webui/utils/tools.py

@@ -493,6 +493,8 @@ async def get_tool_servers_data(
             url_path = server.get("path", "openapi.json")
             full_url = f"{server.get('url')}/{url_path}"
 
+            info = server.get("info", {})
+
             auth_type = server.get("auth_type", "bearer")
             token = None
 
@@ -500,26 +502,37 @@ async def get_tool_servers_data(
                 token = server.get("key", "")
             elif auth_type == "session":
                 token = session_token
-            server_entries.append((idx, server, full_url, token))
+            server_entries.append((idx, server, full_url, info, token))
 
     # Create async tasks to fetch data
-    tasks = [get_tool_server_data(token, url) for (_, _, url, token) in server_entries]
+    tasks = [
+        get_tool_server_data(token, url) for (_, _, url, _, token) in server_entries
+    ]
 
     # Execute tasks concurrently
     responses = await asyncio.gather(*tasks, return_exceptions=True)
 
     # Build final results with index and server metadata
     results = []
-    for (idx, server, url, _), response in zip(server_entries, responses):
+    for (idx, server, url, info, _), response in zip(server_entries, responses):
         if isinstance(response, Exception):
             log.error(f"Failed to connect to {url} OpenAPI tool server")
             continue
 
+        openapi_data = response.get("openapi", {})
+
+        if info and isinstance(openapi_data, dict):
+            if "name" in info:
+                openapi_data["info"]["title"] = info.get("name", "Tool Server")
+
+            if "description" in info:
+                openapi_data["info"]["description"] = info.get("description", "")
+
         results.append(
             {
                 "idx": idx,
                 "url": server.get("url"),
-                "openapi": response.get("openapi"),
+                "openapi": openapi_data,
                 "info": response.get("info"),
                 "specs": response.get("specs"),
             }

+ 50 - 1
src/lib/components/AddServerModal.svelte

@@ -22,7 +22,6 @@
 	export let edit = false;
 
 	export let direct = false;
-
 	export let connection = null;
 
 	let url = '';
@@ -33,6 +32,9 @@
 
 	let accessControl = {};
 
+	let name = '';
+	let description = '';
+
 	let enable = true;
 
 	let loading = false;
@@ -69,6 +71,10 @@
 				config: {
 					enable: enable,
 					access_control: accessControl
+				},
+				info: {
+					name,
+					description
 				}
 			}).catch((err) => {
 				toast.error($i18n.t('Connection failed'));
@@ -95,6 +101,10 @@
 			config: {
 				enable: enable,
 				access_control: accessControl
+			},
+			info: {
+				name: name,
+				description: description
 			}
 		};
 
@@ -108,6 +118,9 @@
 		key = '';
 		auth_type = 'bearer';
 
+		name = '';
+		description = '';
+
 		enable = true;
 		accessControl = null;
 	};
@@ -120,6 +133,9 @@
 			auth_type = connection?.auth_type ?? 'bearer';
 			key = connection?.key ?? '';
 
+			name = connection.info?.name ?? '';
+			description = connection.info?.description ?? '';
+
 			enable = connection.config?.enable ?? true;
 			accessControl = connection.config?.access_control ?? null;
 		}
@@ -276,6 +292,39 @@
 						{#if !direct}
 							<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
 
+							<div class="flex gap-2">
+								<div class="flex flex-col w-full">
+									<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Name')}</div>
+
+									<div class="flex-1">
+										<input
+											class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
+											type="text"
+											bind:value={name}
+											placeholder={$i18n.t('Enter name')}
+											autocomplete="off"
+											required
+										/>
+									</div>
+								</div>
+							</div>
+
+							<div class="flex flex-col w-full mt-2">
+								<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Description')}</div>
+
+								<div class="flex-1">
+									<input
+										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
+										type="text"
+										bind:value={description}
+										placeholder={$i18n.t('Enter description')}
+										autocomplete="off"
+									/>
+								</div>
+							</div>
+
+							<hr class=" border-gray-100 dark:border-gray-700/10 my-2.5 w-full" />
+
 							<div class="my-2 -mx-2">
 								<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
 									<AccessControl bind:accessControl />