mirror of
				https://github.com/open-webui/open-webui
				synced 2025-06-26 18:26:48 +00:00 
			
		
		
		
	feat: tools integration
This commit is contained in:
		
							parent
							
								
									c5683dd24c
								
							
						
					
					
						commit
						b434ebf3ad
					
				@ -41,7 +41,7 @@ class ToolModel(BaseModel):
 | 
			
		||||
    user_id: str
 | 
			
		||||
    name: str
 | 
			
		||||
    content: str
 | 
			
		||||
    specs: dict
 | 
			
		||||
    specs: List[dict]
 | 
			
		||||
    meta: ToolMeta
 | 
			
		||||
    updated_at: int  # timestamp in epoch
 | 
			
		||||
    created_at: int  # timestamp in epoch
 | 
			
		||||
@ -74,7 +74,7 @@ class ToolsTable:
 | 
			
		||||
        self.db.create_tables([Tool])
 | 
			
		||||
 | 
			
		||||
    def insert_new_tool(
 | 
			
		||||
        self, user_id: str, form_data: ToolForm, specs: dict
 | 
			
		||||
        self, user_id: str, form_data: ToolForm, specs: List[dict]
 | 
			
		||||
    ) -> Optional[ToolModel]:
 | 
			
		||||
        tool = ToolModel(
 | 
			
		||||
            **{
 | 
			
		||||
 | 
			
		||||
@ -52,7 +52,18 @@ def load_toolkit_module_from_path(tools_id, tools_path):
 | 
			
		||||
 | 
			
		||||
@router.get("/", response_model=List[ToolResponse])
 | 
			
		||||
async def get_toolkits(user=Depends(get_current_user)):
 | 
			
		||||
    toolkits = [ToolResponse(**toolkit) for toolkit in Tools.get_tools()]
 | 
			
		||||
    toolkits = [toolkit for toolkit in Tools.get_tools()]
 | 
			
		||||
    return toolkits
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
############################
 | 
			
		||||
# ExportToolKits
 | 
			
		||||
############################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.get("/export", response_model=List[ToolModel])
 | 
			
		||||
async def get_toolkits(user=Depends(get_current_user)):
 | 
			
		||||
    toolkits = [toolkit for toolkit in Tools.get_tools()]
 | 
			
		||||
    return toolkits
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -77,7 +88,7 @@ async def create_new_toolkit(form_data: ToolForm, user=Depends(get_admin_user)):
 | 
			
		||||
            toolkit = Tools.insert_new_tool(user.id, form_data, specs)
 | 
			
		||||
 | 
			
		||||
            if toolkit:
 | 
			
		||||
                return ToolResponse(**toolkit)
 | 
			
		||||
                return toolkit
 | 
			
		||||
            else:
 | 
			
		||||
                raise HTTPException(
 | 
			
		||||
                    status_code=status.HTTP_400_BAD_REQUEST,
 | 
			
		||||
@ -91,7 +102,7 @@ async def create_new_toolkit(form_data: ToolForm, user=Depends(get_admin_user)):
 | 
			
		||||
    else:
 | 
			
		||||
        raise HTTPException(
 | 
			
		||||
            status_code=status.HTTP_400_BAD_REQUEST,
 | 
			
		||||
            detail=ERROR_MESSAGES.NAME_TAG_TAKEN,
 | 
			
		||||
            detail=ERROR_MESSAGES.ID_TAKEN,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -105,7 +116,7 @@ async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)):
 | 
			
		||||
    toolkit = Tools.get_tool_by_id(id)
 | 
			
		||||
 | 
			
		||||
    if toolkit:
 | 
			
		||||
        return ToolResponse(**toolkit)
 | 
			
		||||
        return toolkit
 | 
			
		||||
    else:
 | 
			
		||||
        raise HTTPException(
 | 
			
		||||
            status_code=status.HTTP_401_UNAUTHORIZED,
 | 
			
		||||
@ -137,7 +148,7 @@ async def update_toolkit_by_id(
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if toolkit:
 | 
			
		||||
            return ToolResponse(**toolkit)
 | 
			
		||||
            return toolkit
 | 
			
		||||
        else:
 | 
			
		||||
            raise HTTPException(
 | 
			
		||||
                status_code=status.HTTP_400_BAD_REQUEST,
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ class ERROR_MESSAGES(str, Enum):
 | 
			
		||||
    COMMAND_TAKEN = "Uh-oh! This command is already registered. Please choose another command string."
 | 
			
		||||
    FILE_EXISTS = "Uh-oh! This file is already registered. Please choose another file."
 | 
			
		||||
 | 
			
		||||
    ID_TAKEN = "Uh-oh! This id is already registered. Please choose another id string."
 | 
			
		||||
    MODEL_ID_TAKEN = "Uh-oh! This model id is already registered. Please choose another model id string."
 | 
			
		||||
 | 
			
		||||
    NAME_TAG_TAKEN = "Uh-oh! This name tag is already registered. Please choose another name tag string."
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@
 | 
			
		||||
	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
 | 
			
		||||
 | 
			
		||||
	import { goto } from '$app/navigation';
 | 
			
		||||
	import { deleteToolById, getTools } from '$lib/apis/tools';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
@ -78,7 +79,12 @@
 | 
			
		||||
			<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
 | 
			
		||||
				<a href={`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`}>
 | 
			
		||||
					<div class=" flex-1 self-center pl-5">
 | 
			
		||||
						<div class=" font-bold">{tool.name}</div>
 | 
			
		||||
						<div class=" font-bold flex items-center gap-1.5">
 | 
			
		||||
							<div>
 | 
			
		||||
								{tool.name}
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class=" text-gray-500 text-xs font-medium">{tool.id}</div>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
 | 
			
		||||
							{tool.meta.description}
 | 
			
		||||
						</div>
 | 
			
		||||
@ -89,7 +95,7 @@
 | 
			
		||||
				<a
 | 
			
		||||
					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 | 
			
		||||
					type="button"
 | 
			
		||||
					href={`/workspace/tools/edit?command=${encodeURIComponent(tool.id)}`}
 | 
			
		||||
					href={`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`}
 | 
			
		||||
				>
 | 
			
		||||
					<svg
 | 
			
		||||
						xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
@ -134,9 +140,16 @@
 | 
			
		||||
				<button
 | 
			
		||||
					class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 | 
			
		||||
					type="button"
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						// deletePrompt(prompt.command);
 | 
			
		||||
						// deleteTool
 | 
			
		||||
					on:click={async () => {
 | 
			
		||||
						const res = await deleteToolById(localStorage.token, tool.id).catch((error) => {
 | 
			
		||||
							toast.error(error);
 | 
			
		||||
							return null;
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
						if (res) {
 | 
			
		||||
							toast.success('Tool deleted successfully');
 | 
			
		||||
							tools.set(await getTools(localStorage.token));
 | 
			
		||||
						}
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					<svg
 | 
			
		||||
 | 
			
		||||
@ -7,9 +7,10 @@
 | 
			
		||||
	export let value = '';
 | 
			
		||||
 | 
			
		||||
	let codeEditor;
 | 
			
		||||
	let boilerplate = `from datetime import datetime
 | 
			
		||||
	let boilerplate = `import os
 | 
			
		||||
import requests
 | 
			
		||||
import os
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tools:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
@ -27,7 +28,9 @@ class Tools:
 | 
			
		||||
        """
 | 
			
		||||
        value = os.getenv(variable_name)
 | 
			
		||||
        if value is not None:
 | 
			
		||||
            return f"The value of the environment variable '{variable_name}' is '{value}'"
 | 
			
		||||
            return (
 | 
			
		||||
                f"The value of the environment variable '{variable_name}' is '{value}'"
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            return f"The environment variable '{variable_name}' does not exist."
 | 
			
		||||
 | 
			
		||||
@ -62,38 +65,35 @@ class Tools:
 | 
			
		||||
        :param city: The name of the city to get the weather for.
 | 
			
		||||
        :return: The current weather information or an error message.
 | 
			
		||||
        """
 | 
			
		||||
        api_key = os.getenv('OPENWEATHER_API_KEY')
 | 
			
		||||
        api_key = os.getenv("OPENWEATHER_API_KEY")
 | 
			
		||||
        if not api_key:
 | 
			
		||||
            return "API key is not set in the environment variable 'OPENWEATHER_API_KEY'."
 | 
			
		||||
            return (
 | 
			
		||||
                "API key is not set in the environment variable 'OPENWEATHER_API_KEY'."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        base_url = "http://api.openweathermap.org/data/2.5/weather"
 | 
			
		||||
        params = {
 | 
			
		||||
            'q': city,
 | 
			
		||||
            'appid': api_key,
 | 
			
		||||
            'units': 'metric'  # Optional: Use 'imperial' for Fahrenheit
 | 
			
		||||
            "q": city,
 | 
			
		||||
            "appid": api_key,
 | 
			
		||||
            "units": "metric",  # Optional: Use 'imperial' for Fahrenheit
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            response = requests.get(base_url, params=params)
 | 
			
		||||
            response.raise_for_status()  # Raise HTTPError for bad responses (4xx and 5xx)
 | 
			
		||||
            data = response.json()
 | 
			
		||||
            
 | 
			
		||||
            if data.get('cod') != 200:
 | 
			
		||||
 | 
			
		||||
            if data.get("cod") != 200:
 | 
			
		||||
                return f"Error fetching weather data: {data.get('message')}"
 | 
			
		||||
            
 | 
			
		||||
            weather_description = data['weather'][0]['description']
 | 
			
		||||
            temperature = data['main']['temp']
 | 
			
		||||
            humidity = data['main']['humidity']
 | 
			
		||||
            wind_speed = data['wind']['speed']
 | 
			
		||||
            
 | 
			
		||||
            return (f"Weather in {city}:\n"
 | 
			
		||||
                    f"Description: {weather_description}\n"
 | 
			
		||||
                    f"Temperature: {temperature}°C\n"
 | 
			
		||||
                    f"Humidity: {humidity}%\n"
 | 
			
		||||
                    f"Wind Speed: {wind_speed} m/s")
 | 
			
		||||
 | 
			
		||||
            weather_description = data["weather"][0]["description"]
 | 
			
		||||
            temperature = data["main"]["temp"]
 | 
			
		||||
            humidity = data["main"]["humidity"]
 | 
			
		||||
            wind_speed = data["wind"]["speed"]
 | 
			
		||||
 | 
			
		||||
            return f"Weather in {city}: {temperature}°C"
 | 
			
		||||
        except requests.RequestException as e:
 | 
			
		||||
            return f"Error fetching weather data: {str(e)}"
 | 
			
		||||
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
	export const formatHandler = async () => {
 | 
			
		||||
 | 
			
		||||
@ -8,15 +8,18 @@
 | 
			
		||||
 | 
			
		||||
	const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
	let formElement = null;
 | 
			
		||||
 | 
			
		||||
	let loading = false;
 | 
			
		||||
 | 
			
		||||
	let id = '';
 | 
			
		||||
	let name = '';
 | 
			
		||||
	let meta = {
 | 
			
		||||
	export let edit = false;
 | 
			
		||||
 | 
			
		||||
	export let id = '';
 | 
			
		||||
	export let name = '';
 | 
			
		||||
	export let meta = {
 | 
			
		||||
		description: ''
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	let content = '';
 | 
			
		||||
	export let content = '';
 | 
			
		||||
 | 
			
		||||
	$: if (name) {
 | 
			
		||||
		id = name.replace(/\s+/g, '_').toLowerCase();
 | 
			
		||||
@ -49,6 +52,7 @@
 | 
			
		||||
<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
 | 
			
		||||
	<div class="mx-auto w-full md:px-0 h-full">
 | 
			
		||||
		<form
 | 
			
		||||
			bind:this={formElement}
 | 
			
		||||
			class=" flex flex-col max-h-[100dvh] h-full"
 | 
			
		||||
			on:submit|preventDefault={() => {
 | 
			
		||||
				submitHandler();
 | 
			
		||||
@ -60,6 +64,7 @@
 | 
			
		||||
					on:click={() => {
 | 
			
		||||
						goto('/workspace/tools');
 | 
			
		||||
					}}
 | 
			
		||||
					type="button"
 | 
			
		||||
				>
 | 
			
		||||
					<div class=" self-center">
 | 
			
		||||
						<svg
 | 
			
		||||
@ -96,6 +101,7 @@
 | 
			
		||||
							placeholder="Toolkit ID (e.g. my_toolkit)"
 | 
			
		||||
							bind:value={id}
 | 
			
		||||
							required
 | 
			
		||||
							disabled={edit}
 | 
			
		||||
						/>
 | 
			
		||||
					</div>
 | 
			
		||||
					<input
 | 
			
		||||
@ -112,8 +118,9 @@
 | 
			
		||||
						bind:value={content}
 | 
			
		||||
						bind:this={codeEditor}
 | 
			
		||||
						on:save={() => {
 | 
			
		||||
							// submit form
 | 
			
		||||
							submitHandler();
 | 
			
		||||
							if (formElement) {
 | 
			
		||||
								formElement.requestSubmit();
 | 
			
		||||
							}
 | 
			
		||||
						}}
 | 
			
		||||
					/>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,28 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import { goto } from '$app/navigation';
 | 
			
		||||
	import { createNewTool, getTools } from '$lib/apis/tools';
 | 
			
		||||
	import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
 | 
			
		||||
	import { tools } from '$lib/stores';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	const saveHandler = async (data) => {
 | 
			
		||||
		console.log(data);
 | 
			
		||||
		const res = await createNewTool(localStorage.token, {
 | 
			
		||||
			id: data.id,
 | 
			
		||||
			name: data.name,
 | 
			
		||||
			meta: data.meta,
 | 
			
		||||
			content: data.content
 | 
			
		||||
		}).catch((error) => {
 | 
			
		||||
			toast.error(error);
 | 
			
		||||
			return null;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (res) {
 | 
			
		||||
			toast.success('Tool created successfully');
 | 
			
		||||
			tools.set(await getTools(localStorage.token));
 | 
			
		||||
 | 
			
		||||
			await goto('/workspace/tools');
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,57 @@
 | 
			
		||||
<script>
 | 
			
		||||
	import { goto } from '$app/navigation';
 | 
			
		||||
	import { page } from '$app/stores';
 | 
			
		||||
	import { getToolById, getTools, updateToolById } from '$lib/apis/tools';
 | 
			
		||||
	import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
 | 
			
		||||
	import { tools } from '$lib/stores';
 | 
			
		||||
	import { onMount } from 'svelte';
 | 
			
		||||
	import { toast } from 'svelte-sonner';
 | 
			
		||||
 | 
			
		||||
	let tool = null;
 | 
			
		||||
 | 
			
		||||
	const saveHandler = async (data) => {
 | 
			
		||||
		console.log(data);
 | 
			
		||||
		const res = await updateToolById(localStorage.token, tool.id, {
 | 
			
		||||
			id: data.id,
 | 
			
		||||
			name: data.name,
 | 
			
		||||
			meta: data.meta,
 | 
			
		||||
			content: data.content
 | 
			
		||||
		}).catch((error) => {
 | 
			
		||||
			toast.error(error);
 | 
			
		||||
			return null;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (res) {
 | 
			
		||||
			toast.success('Tool updated successfully');
 | 
			
		||||
			tools.set(await getTools(localStorage.token));
 | 
			
		||||
 | 
			
		||||
			await goto('/workspace/tools');
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		console.log('mounted');
 | 
			
		||||
		const id = $page.url.searchParams.get('id');
 | 
			
		||||
 | 
			
		||||
		if (id) {
 | 
			
		||||
			tool = await getToolById(localStorage.token, id).catch((error) => {
 | 
			
		||||
				toast.error(error);
 | 
			
		||||
				goto('/workspace/tools');
 | 
			
		||||
				return null;
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<ToolkitEditor />
 | 
			
		||||
{#if tool}
 | 
			
		||||
	<ToolkitEditor
 | 
			
		||||
		edit={true}
 | 
			
		||||
		id={tool.id}
 | 
			
		||||
		name={tool.name}
 | 
			
		||||
		meta={tool.meta}
 | 
			
		||||
		content={tool.content}
 | 
			
		||||
		on:save={(e) => {
 | 
			
		||||
			saveHandler(e.detail);
 | 
			
		||||
		}}
 | 
			
		||||
	/>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user