diff --git a/backend/apps/webui/models/functions.py b/backend/apps/webui/models/functions.py index ac12ab9e3..91fbdb769 100644 --- a/backend/apps/webui/models/functions.py +++ b/backend/apps/webui/models/functions.py @@ -64,7 +64,6 @@ class FunctionResponse(BaseModel): class FunctionForm(BaseModel): id: str name: str - type: str content: str meta: FunctionMeta @@ -75,12 +74,13 @@ class FunctionsTable: self.db.create_tables([Function]) def insert_new_function( - self, user_id: str, form_data: FunctionForm + self, user_id: str, type: str, form_data: FunctionForm ) -> Optional[FunctionModel]: function = FunctionModel( **{ **form_data.model_dump(), "user_id": user_id, + "type": type, "updated_at": int(time.time()), "created_at": int(time.time()), } diff --git a/backend/apps/webui/routers/functions.py b/backend/apps/webui/routers/functions.py index 1021cc10a..ea5fde336 100644 --- a/backend/apps/webui/routers/functions.py +++ b/backend/apps/webui/routers/functions.py @@ -69,12 +69,12 @@ async def create_new_function( with open(function_path, "w") as function_file: function_file.write(form_data.content) - function_module = load_function_module_by_id(form_data.id) + function_module, function_type = load_function_module_by_id(form_data.id) FUNCTIONS = request.app.state.FUNCTIONS FUNCTIONS[form_data.id] = function_module - function = Functions.insert_new_function(user.id, form_data) + function = Functions.insert_new_function(user.id, function_type, form_data) function_cache_dir = Path(CACHE_DIR) / "functions" / form_data.id function_cache_dir.mkdir(parents=True, exist_ok=True) @@ -132,12 +132,12 @@ async def update_toolkit_by_id( with open(function_path, "w") as function_file: function_file.write(form_data.content) - function_module = load_function_module_by_id(id) + function_module, function_type = load_function_module_by_id(id) FUNCTIONS = request.app.state.FUNCTIONS FUNCTIONS[id] = function_module - updated = {**form_data.model_dump(exclude={"id"})} + updated = {**form_data.model_dump(exclude={"id"}), "type": function_type} print(updated) function = Functions.update_function_by_id(id, updated) diff --git a/backend/apps/webui/utils.py b/backend/apps/webui/utils.py index 64d116f11..3e075a8a8 100644 --- a/backend/apps/webui/utils.py +++ b/backend/apps/webui/utils.py @@ -33,9 +33,9 @@ def load_function_module_by_id(function_id): spec.loader.exec_module(module) print(f"Loaded module: {module.__name__}") if hasattr(module, "Pipe"): - return module.Pipe() + return module.Pipe(), "pipe" elif hasattr(module, "Filter"): - return module.Filter() + return module.Filter(), "filter" else: raise Exception("No Function class found") except Exception as e: diff --git a/src/lib/components/workspace/Functions.svelte b/src/lib/components/workspace/Functions.svelte index ebadce50c..aeb53bde0 100644 --- a/src/lib/components/workspace/Functions.svelte +++ b/src/lib/components/workspace/Functions.svelte @@ -74,7 +74,7 @@
+ import { getContext, createEventDispatcher, onMount } from 'svelte'; + + const i18n = getContext('i18n'); + + import CodeEditor from '$lib/components/common/CodeEditor.svelte'; + import { goto } from '$app/navigation'; + import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; + + const dispatch = createEventDispatcher(); + + let formElement = null; + let loading = false; + let showConfirm = false; + + export let edit = false; + export let clone = false; + + export let id = ''; + export let name = ''; + export let meta = { + description: '' + }; + export let content = ''; + + $: if (name && !edit && !clone) { + id = name.replace(/\s+/g, '_').toLowerCase(); + } + + let codeEditor; + let boilerplate = `import os +import requests +from datetime import datetime + + +class Tools: + def __init__(self): + pass + + # Add your custom tools using pure Python code here, make sure to add type hints + # Use Sphinx-style docstrings to document your tools, they will be used for generating tools specifications + # Please refer to function_calling_filter_pipeline.py file from pipelines project for an example + + def get_user_name_and_email_and_id(self, __user__: dict = {}) -> str: + """ + Get the user name, Email and ID from the user object. + """ + + # Do not include :param for __user__ in the docstring as it should not be shown in the tool's specification + # The session user object will be passed as a parameter when the function is called + + print(__user__) + result = "" + + if "name" in __user__: + result += f"User: {__user__['name']}" + if "id" in __user__: + result += f" (ID: {__user__['id']})" + if "email" in __user__: + result += f" (Email: {__user__['email']})" + + if result == "": + result = "User: Unknown" + + return result + + def get_current_time(self) -> str: + """ + Get the current time in a more human-readable format. + :return: The current time. + """ + + now = datetime.now() + current_time = now.strftime("%I:%M:%S %p") # Using 12-hour format with AM/PM + current_date = now.strftime( + "%A, %B %d, %Y" + ) # Full weekday, month name, day, and year + + return f"Current Date and Time = {current_date}, {current_time}" + + def calculator(self, equation: str) -> str: + """ + Calculate the result of an equation. + :param equation: The equation to calculate. + """ + + # Avoid using eval in production code + # https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html + try: + result = eval(equation) + return f"{equation} = {result}" + except Exception as e: + print(e) + return "Invalid equation" + + def get_current_weather(self, city: str) -> str: + """ + Get the current weather for a given city. + :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") + if not 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 + } + + 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: + 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}: {temperature}°C" + except requests.RequestException as e: + return f"Error fetching weather data: {str(e)}" +`; + + const saveHandler = async () => { + loading = true; + dispatch('save', { + id, + name, + meta, + content + }); + }; + + const submitHandler = async () => { + if (codeEditor) { + const res = await codeEditor.formatPythonCodeHandler(); + + if (res) { + console.log('Code formatted successfully'); + saveHandler(); + } + } + }; + + +
+
+
{ + if (edit) { + submitHandler(); + } else { + showConfirm = true; + } + }} + > +
+ +
+ + +
+
+
+ + { + submitHandler(); + }} +> +
+
+
Please carefully review the following warnings:
+ +
    +
  • Tools have a function calling system that allows arbitrary code execution.
  • +
  • Do not install tools from sources you do not fully trust.
  • +
+
+ +
+ I acknowledge that I have read and I understand the implications of my action. I am aware of + the risks associated with executing arbitrary code and I have verified the trustworthiness of + the source. +
+
+
diff --git a/src/routes/(app)/workspace/functions/create/+page.svelte b/src/routes/(app)/workspace/functions/create/+page.svelte index c785c74cd..3b7dc270d 100644 --- a/src/routes/(app)/workspace/functions/create/+page.svelte +++ b/src/routes/(app)/workspace/functions/create/+page.svelte @@ -1,18 +1,18 @@ {#if mounted} - { saveHandler(e.detail);