refac: tool name collision handling

This commit is contained in:
Timothy Jaeryang Baek 2025-08-18 21:28:28 +04:00
parent f592748011
commit 70d0477418
2 changed files with 104 additions and 82 deletions

View file

@ -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

View file

@ -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}