mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-13 12:55:19 +00:00
refac: tool name collision handling
This commit is contained in:
parent
f592748011
commit
70d0477418
2 changed files with 104 additions and 82 deletions
|
|
@ -5,6 +5,7 @@ import inspect
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import asyncio
|
import asyncio
|
||||||
import yaml
|
import yaml
|
||||||
|
import json
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic.fields import FieldInfo
|
from pydantic.fields import FieldInfo
|
||||||
|
|
@ -85,7 +86,9 @@ async def get_tools(
|
||||||
tool_server_data = server
|
tool_server_data = server
|
||||||
break
|
break
|
||||||
|
|
||||||
assert tool_server_data is not None
|
if tool_server_data is None:
|
||||||
|
log.warning(f"Tool server data not found for {server_id}")
|
||||||
|
continue
|
||||||
|
|
||||||
tool_server_idx = tool_server_data.get("idx", 0)
|
tool_server_idx = tool_server_data.get("idx", 0)
|
||||||
tool_server_connection = (
|
tool_server_connection = (
|
||||||
|
|
@ -131,14 +134,15 @@ async def get_tools(
|
||||||
"spec": spec,
|
"spec": spec,
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: if collision, prepend toolkit name
|
# Handle function name collisions
|
||||||
if function_name in tools_dict:
|
while function_name in tools_dict:
|
||||||
log.warning(
|
log.warning(
|
||||||
f"Tool {function_name} already exists in another tools!"
|
f"Tool {function_name} already exists in another tools!"
|
||||||
)
|
)
|
||||||
log.warning(f"Discarding {tool_id}.{function_name}")
|
# Prepend server ID to function name
|
||||||
else:
|
function_name = f"{server_id}_{function_name}"
|
||||||
tools_dict[function_name] = tool_dict
|
|
||||||
|
tools_dict[function_name] = tool_dict
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
|
@ -198,14 +202,15 @@ async def get_tools(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO: if collision, prepend toolkit name
|
# Handle function name collisions
|
||||||
if function_name in tools_dict:
|
while function_name in tools_dict:
|
||||||
log.warning(
|
log.warning(
|
||||||
f"Tool {function_name} already exists in another tools!"
|
f"Tool {function_name} already exists in another tools!"
|
||||||
)
|
)
|
||||||
log.warning(f"Discarding {tool_id}.{function_name}")
|
# Prepend tool ID to function name
|
||||||
else:
|
function_name = f"{tool_id}_{function_name}"
|
||||||
tools_dict[function_name] = tool_dict
|
|
||||||
|
tools_dict[function_name] = tool_dict
|
||||||
|
|
||||||
return tools_dict
|
return tools_dict
|
||||||
|
|
||||||
|
|
@ -453,8 +458,8 @@ async def set_tool_servers(request: Request):
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.app.state.redis is not None:
|
if request.app.state.redis is not None:
|
||||||
await request.app.state.redis.hmset(
|
await request.app.state.redis.set(
|
||||||
"tool_servers", request.app.state.TOOL_SERVERS
|
"tool_servers", json.dumps(request.app.state.TOOL_SERVERS)
|
||||||
)
|
)
|
||||||
|
|
||||||
return request.app.state.TOOL_SERVERS
|
return request.app.state.TOOL_SERVERS
|
||||||
|
|
@ -463,7 +468,10 @@ async def set_tool_servers(request: Request):
|
||||||
async def get_tool_servers(request: Request):
|
async def get_tool_servers(request: Request):
|
||||||
tool_servers = []
|
tool_servers = []
|
||||||
if request.app.state.redis is not None:
|
if request.app.state.redis is not None:
|
||||||
tool_servers = await request.app.state.redis.hgetall("tool_servers")
|
try:
|
||||||
|
tool_servers = json.loads(await request.app.state.redis.get("tool_servers"))
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Error fetching tool_servers from Redis: {e}")
|
||||||
|
|
||||||
if not tool_servers:
|
if not tool_servers:
|
||||||
await set_tool_servers(request)
|
await set_tool_servers(request)
|
||||||
|
|
@ -536,7 +544,10 @@ async def get_tool_servers_data(
|
||||||
elif auth_type == "session":
|
elif auth_type == "session":
|
||||||
token = session_token
|
token = session_token
|
||||||
|
|
||||||
id = info.get("id", idx)
|
id = info.get("id")
|
||||||
|
if not id:
|
||||||
|
id = str(idx)
|
||||||
|
|
||||||
server_entries.append((id, idx, server, full_url, info, token))
|
server_entries.append((id, idx, server, full_url, info, token))
|
||||||
|
|
||||||
# Create async tasks to fetch data
|
# Create async tasks to fetch data
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
import CameraSolid from '$lib/components/icons/CameraSolid.svelte';
|
import CameraSolid from '$lib/components/icons/CameraSolid.svelte';
|
||||||
import PhotoSolid from '$lib/components/icons/PhotoSolid.svelte';
|
import PhotoSolid from '$lib/components/icons/PhotoSolid.svelte';
|
||||||
import CommandLineSolid from '$lib/components/icons/CommandLineSolid.svelte';
|
import CommandLineSolid from '$lib/components/icons/CommandLineSolid.svelte';
|
||||||
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@
|
||||||
|
|
||||||
export let onClose: Function;
|
export let onClose: Function;
|
||||||
|
|
||||||
let tools = {};
|
let tools = null;
|
||||||
let show = false;
|
let show = false;
|
||||||
let showAllTools = false;
|
let showAllTools = false;
|
||||||
|
|
||||||
|
|
@ -49,15 +50,17 @@
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
await _tools.set(await getTools(localStorage.token));
|
await _tools.set(await getTools(localStorage.token));
|
||||||
|
if ($_tools) {
|
||||||
tools = $_tools.reduce((a, tool, i, arr) => {
|
tools = $_tools.reduce((a, tool, i, arr) => {
|
||||||
a[tool.id] = {
|
a[tool.id] = {
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
description: tool.meta.description,
|
description: tool.meta.description,
|
||||||
enabled: selectedToolIds.includes(tool.id)
|
enabled: selectedToolIds.includes(tool.id)
|
||||||
};
|
};
|
||||||
return a;
|
return a;
|
||||||
}, {});
|
}, {});
|
||||||
|
selectedToolIds = selectedToolIds.filter((id) => $_tools?.some((tool) => tool.id === id));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const detectMobile = () => {
|
const detectMobile = () => {
|
||||||
|
|
@ -105,69 +108,77 @@
|
||||||
align="start"
|
align="start"
|
||||||
transition={flyAndScale}
|
transition={flyAndScale}
|
||||||
>
|
>
|
||||||
{#if Object.keys(tools).length > 0}
|
{#if tools}
|
||||||
<div class="{showAllTools ? '' : 'max-h-28'} overflow-y-auto scrollbar-thin">
|
{#if Object.keys(tools).length > 0}
|
||||||
{#each Object.keys(tools) as toolId}
|
<div class="{showAllTools ? '' : 'max-h-28'} overflow-y-auto scrollbar-thin">
|
||||||
|
{#each Object.keys(tools) as toolId}
|
||||||
|
<button
|
||||||
|
class="flex w-full justify-between gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
|
||||||
|
on:click={() => {
|
||||||
|
tools[toolId].enabled = !tools[toolId].enabled;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex-1 truncate">
|
||||||
|
<Tooltip
|
||||||
|
content={tools[toolId]?.description ?? ''}
|
||||||
|
placement="top-start"
|
||||||
|
className="flex flex-1 gap-2 items-center"
|
||||||
|
>
|
||||||
|
<div class="shrink-0">
|
||||||
|
<WrenchSolid />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=" truncate">{tools[toolId].name}</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=" shrink-0">
|
||||||
|
<Switch
|
||||||
|
state={tools[toolId].enabled}
|
||||||
|
on:change={async (e) => {
|
||||||
|
const state = e.detail;
|
||||||
|
await tick();
|
||||||
|
if (state) {
|
||||||
|
selectedToolIds = [...selectedToolIds, toolId];
|
||||||
|
} else {
|
||||||
|
selectedToolIds = selectedToolIds.filter((id) => id !== toolId);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if Object.keys(tools).length > 3}
|
||||||
<button
|
<button
|
||||||
class="flex w-full justify-between gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
|
class="flex w-full justify-center items-center text-sm font-medium cursor-pointer rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
tools[toolId].enabled = !tools[toolId].enabled;
|
showAllTools = !showAllTools;
|
||||||
}}
|
}}
|
||||||
|
title={showAllTools ? $i18n.t('Show Less') : $i18n.t('Show All')}
|
||||||
>
|
>
|
||||||
<div class="flex-1 truncate">
|
<svg
|
||||||
<Tooltip
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
content={tools[toolId]?.description ?? ''}
|
fill="none"
|
||||||
placement="top-start"
|
viewBox="0 0 24 24"
|
||||||
className="flex flex-1 gap-2 items-center"
|
stroke-width="2.5"
|
||||||
>
|
stroke="currentColor"
|
||||||
<div class="shrink-0">
|
class="size-3 transition-transform duration-200 {showAllTools
|
||||||
<WrenchSolid />
|
? 'rotate-180'
|
||||||
</div>
|
: ''} text-gray-300 dark:text-gray-600"
|
||||||
|
>
|
||||||
<div class=" truncate">{tools[toolId].name}</div>
|
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"
|
||||||
</Tooltip>
|
></path>
|
||||||
</div>
|
</svg>
|
||||||
|
|
||||||
<div class=" shrink-0">
|
|
||||||
<Switch
|
|
||||||
state={tools[toolId].enabled}
|
|
||||||
on:change={async (e) => {
|
|
||||||
const state = e.detail;
|
|
||||||
await tick();
|
|
||||||
if (state) {
|
|
||||||
selectedToolIds = [...selectedToolIds, toolId];
|
|
||||||
} else {
|
|
||||||
selectedToolIds = selectedToolIds.filter((id) => id !== toolId);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/if}
|
||||||
</div>
|
<hr class="border-black/5 dark:border-white/5 my-1" />
|
||||||
{#if Object.keys(tools).length > 3}
|
|
||||||
<button
|
|
||||||
class="flex w-full justify-center items-center text-sm font-medium cursor-pointer rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800"
|
|
||||||
on:click={() => {
|
|
||||||
showAllTools = !showAllTools;
|
|
||||||
}}
|
|
||||||
title={showAllTools ? $i18n.t('Show Less') : $i18n.t('Show All')}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="2.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="size-3 transition-transform duration-200 {showAllTools
|
|
||||||
? 'rotate-180'
|
|
||||||
: ''} text-gray-300 dark:text-gray-600"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="py-4">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr class="border-black/5 dark:border-white/5 my-1" />
|
<hr class="border-black/5 dark:border-white/5 my-1" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue