diff --git a/application/llm/google_ai.py b/application/llm/google_ai.py
index ae888042..31943601 100644
--- a/application/llm/google_ai.py
+++ b/application/llm/google_ai.py
@@ -57,25 +57,35 @@ class GoogleLLM(BaseLLM):
for tool_data in tools_list:
if tool_data["type"] == "function":
function = tool_data["function"]
- genai_function = dict(
- name=function["name"],
- description=function["description"],
- parameters={
- "type": "OBJECT",
- "properties": {
- k: {
- **v,
- "type": v["type"].upper() if v["type"] else None,
- }
- for k, v in function["parameters"]["properties"].items()
+ parameters = function["parameters"]
+ properties = parameters.get("properties", {})
+
+ if properties:
+ genai_function = dict(
+ name=function["name"],
+ description=function["description"],
+ parameters={
+ "type": "OBJECT",
+ "properties": {
+ k: {
+ **v,
+ "type": v["type"].upper() if v["type"] else None,
+ }
+ for k, v in properties.items()
+ },
+ "required": (
+ parameters["required"]
+ if "required" in parameters
+ else []
+ ),
},
- "required": (
- function["parameters"]["required"]
- if "required" in function["parameters"]
- else []
- ),
- },
- )
+ )
+ else:
+ genai_function = dict(
+ name=function["name"],
+ description=function["description"],
+ )
+
genai_tool = types.Tool(function_declarations=[genai_function])
genai_tools.append(genai_tool)
diff --git a/application/tools/agent.py b/application/tools/agent.py
index 209184d2..de8ad725 100644
--- a/application/tools/agent.py
+++ b/application/tools/agent.py
@@ -26,6 +26,21 @@ class Agent:
tools_by_id = {str(tool["_id"]): tool for tool in user_tools}
return tools_by_id
+ def _build_tool_parameters(self, action):
+ params = {"type": "object", "properties": {}, "required": []}
+ for param_type in ["query_params", "headers", "body", "parameters"]:
+ if param_type in action and action[param_type].get("properties"):
+ for k, v in action[param_type]["properties"].items():
+ if v.get("filled_by_llm", True):
+ params["properties"][k] = {
+ key: value
+ for key, value in v.items()
+ if key != "filled_by_llm" and key != "value"
+ }
+
+ params["required"].append(k)
+ return params
+
def _prepare_tools(self, tools_dict):
self.tools = [
{
@@ -33,31 +48,16 @@ class Agent:
"function": {
"name": f"{action['name']}_{tool_id}",
"description": action["description"],
- "parameters": {
- **action["parameters"],
- "properties": {
- k: {
- key: value
- for key, value in v.items()
- if key != "filled_by_llm" and key != "value"
- }
- for k, v in action["parameters"]["properties"].items()
- if v.get("filled_by_llm", False)
- },
- "required": [
- key
- for key in action["parameters"]["required"]
- if key in action["parameters"]["properties"]
- and action["parameters"]["properties"][key].get(
- "filled_by_llm", False
- )
- ],
- },
+ "parameters": self._build_tool_parameters(action),
},
}
for tool_id, tool in tools_dict.items()
- for action in tool["actions"]
- if action["active"]
+ for action in (
+ tool["config"]["actions"].values()
+ if tool["name"] == "api_tool"
+ else tool["actions"]
+ )
+ if action.get("active", True)
]
def _execute_tool_action(self, tools_dict, call):
@@ -65,18 +65,59 @@ class Agent:
tool_id, action_name, call_args = parser.parse_args(call)
tool_data = tools_dict[tool_id]
- action_data = next(
- action for action in tool_data["actions"] if action["name"] == action_name
+ action_data = (
+ tool_data["config"]["actions"][action_name]
+ if tool_data["name"] == "api_tool"
+ else next(
+ action
+ for action in tool_data["actions"]
+ if action["name"] == action_name
+ )
)
- for param, details in action_data["parameters"]["properties"].items():
- if param not in call_args and "value" in details:
- call_args[param] = details["value"]
+ query_params, headers, body, parameters = {}, {}, {}, {}
+ param_types = {
+ "query_params": query_params,
+ "headers": headers,
+ "body": body,
+ "parameters": parameters,
+ }
+
+ for param_type, target_dict in param_types.items():
+ if param_type in action_data and action_data[param_type].get("properties"):
+ for param, details in action_data[param_type]["properties"].items():
+ if param not in call_args and "value" in details:
+ target_dict[param] = details["value"]
+
+ for param, value in call_args.items():
+ for param_type, target_dict in param_types.items():
+ if param_type in action_data and param in action_data[param_type].get(
+ "properties", {}
+ ):
+ target_dict[param] = value
tm = ToolManager(config={})
- tool = tm.load_tool(tool_data["name"], tool_config=tool_data["config"])
- print(f"Executing tool: {action_name} with args: {call_args}")
- result = tool.execute_action(action_name, **call_args)
+ tool = tm.load_tool(
+ tool_data["name"],
+ tool_config=(
+ {
+ "url": tool_data["config"]["actions"][action_name]["url"],
+ "method": tool_data["config"]["actions"][action_name]["method"],
+ "headers": headers,
+ "query_params": query_params,
+ }
+ if tool_data["name"] == "api_tool"
+ else tool_data["config"]
+ ),
+ )
+ if tool_data["name"] == "api_tool":
+ print(
+ f"Executing api: {action_name} with query_params: {query_params}, headers: {headers}, body: {body}"
+ )
+ result = tool.execute_action(action_name, **body)
+ else:
+ print(f"Executing tool: {action_name} with args: {call_args}")
+ result = tool.execute_action(action_name, **parameters)
call_id = getattr(call, "id", None)
return result, call_id
diff --git a/application/tools/implementations/api_tool.py b/application/tools/implementations/api_tool.py
new file mode 100644
index 00000000..5d0fec70
--- /dev/null
+++ b/application/tools/implementations/api_tool.py
@@ -0,0 +1,54 @@
+import json
+
+import requests
+from application.tools.base import Tool
+
+
+class APITool(Tool):
+ """
+ API Tool
+ A flexible tool for performing various API actions (e.g., sending messages, retrieving data) via custom user-specified APIs
+ """
+
+ def __init__(self, config):
+ self.config = config
+ self.url = config.get("url", "")
+ self.method = config.get("method", "GET")
+ self.headers = config.get("headers", {"Content-Type": "application/json"})
+ self.query_params = config.get("query_params", {})
+
+ def execute_action(self, action_name, **kwargs):
+ return self._make_api_call(
+ self.url, self.method, self.headers, self.query_params, kwargs
+ )
+
+ def _make_api_call(self, url, method, headers, query_params, body):
+ if query_params:
+ url = f"{url}?{requests.compat.urlencode(query_params)}"
+ if isinstance(body, dict):
+ body = json.dumps(body)
+ try:
+ print(f"Making API call: {method} {url} with body: {body}")
+ response = requests.request(method, url, headers=headers, data=body)
+ response.raise_for_status()
+ try:
+ data = response.json()
+ except ValueError:
+ data = None
+
+ return {
+ "status_code": response.status_code,
+ "data": data,
+ "message": "API call successful.",
+ }
+ except requests.exceptions.RequestException as e:
+ return {
+ "status_code": response.status_code if response else None,
+ "message": f"API call failed: {str(e)}",
+ }
+
+ def get_actions_metadata(self):
+ return []
+
+ def get_config_requirements(self):
+ return {}
diff --git a/frontend/public/toolIcons/tool_api_tool.svg b/frontend/public/toolIcons/tool_api_tool.svg
new file mode 100644
index 00000000..1e923cf3
--- /dev/null
+++ b/frontend/public/toolIcons/tool_api_tool.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/frontend/src/assets/circle-check.svg b/frontend/src/assets/circle-check.svg
new file mode 100644
index 00000000..f0e8390d
--- /dev/null
+++ b/frontend/src/assets/circle-check.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/circle-x.svg b/frontend/src/assets/circle-x.svg
new file mode 100644
index 00000000..d6bdd2c3
--- /dev/null
+++ b/frontend/src/assets/circle-x.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/index.css b/frontend/src/index.css
index b8cf596e..85d6fced 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -50,11 +50,11 @@ body.dark {
@layer components {
.table-default {
- @apply block w-full table-auto content-start justify-center rounded-xl border border-silver dark:border-silver/40 text-center dark:text-bright-gray overflow-auto;
+ @apply block w-full table-auto justify-center rounded-xl border border-silver dark:border-silver/40 text-center dark:text-bright-gray overflow-auto;
}
.table-default th {
- @apply p-4 font-normal text-gray-400 text-nowrap; /* Remove border-r */
+ @apply p-4 font-normal text-gray-400 text-nowrap;
}
.table-default th {
@@ -62,15 +62,15 @@ body.dark {
}
.table-default th:last-child {
- flex: 0; /* Ensure the last column does not stretch unnecessarily */
+ flex: 0;
}
.table-default td {
- @apply border-t w-full border-silver dark:border-silver/40 px-4 py-2; /* Remove border-r */
+ @apply border-t w-full border-silver dark:border-silver/40 px-4 py-2;
}
.table-default td:last-child {
- @apply border-r-0; /* Ensure no right border on the last column */
+ @apply border-r-0;
}
.table-default th,
diff --git a/frontend/src/modals/AddActionModal.tsx b/frontend/src/modals/AddActionModal.tsx
new file mode 100644
index 00000000..c52d89f8
--- /dev/null
+++ b/frontend/src/modals/AddActionModal.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import Exit from '../assets/exit.svg';
+import Input from '../components/Input';
+import { ActiveState } from '../models/misc';
+
+export default function AddActionModal({
+ modalState,
+ setModalState,
+ handleSubmit,
+}: {
+ modalState: ActiveState;
+ setModalState: (state: ActiveState) => void;
+ handleSubmit: (actionName: string) => void;
+}) {
+ const { t } = useTranslation();
+ const [actionName, setActionName] = React.useState('');
+ return (
+
+
+
+
+
+
+ New Action
+
+
+
+ Action Name
+
+ setActionName(e.target.value)}
+ borderVariant="thin"
+ placeholder={'Enter name'}
+ >
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/modals/AddToolModal.tsx b/frontend/src/modals/AddToolModal.tsx
index c1bdb052..d4706431 100644
--- a/frontend/src/modals/AddToolModal.tsx
+++ b/frontend/src/modals/AddToolModal.tsx
@@ -1,11 +1,12 @@
import React, { useRef } from 'react';
+import { useTranslation } from 'react-i18next';
+
import userService from '../api/services/userService';
import Exit from '../assets/exit.svg';
-import { ActiveState } from '../models/misc';
-import { AvailableTool } from './types';
-import ConfigToolModal from './ConfigToolModal';
import { useOutsideAlerter } from '../hooks';
-import { useTranslation } from 'react-i18next';
+import { ActiveState } from '../models/misc';
+import ConfigToolModal from './ConfigToolModal';
+import { AvailableToolType } from './types';
export default function AddToolModal({
message,
@@ -18,12 +19,11 @@ export default function AddToolModal({
setModalState: (state: ActiveState) => void;
getUserTools: () => void;
}) {
- const [availableTools, setAvailableTools] = React.useState(
- [],
- );
- const [selectedTool, setSelectedTool] = React.useState(
- null,
- );
+ const [availableTools, setAvailableTools] = React.useState<
+ AvailableToolType[]
+ >([]);
+ const [selectedTool, setSelectedTool] =
+ React.useState(null);
const [configModalState, setConfigModalState] =
React.useState('INACTIVE');
const modalRef = useRef(null);
@@ -46,7 +46,7 @@ export default function AddToolModal({
});
};
- const handleAddTool = (tool: AvailableTool) => {
+ const handleAddTool = (tool: AvailableToolType) => {
if (Object.keys(tool.configRequirements).length === 0) {
userService
.createTool({
diff --git a/frontend/src/modals/ConfigToolModal.tsx b/frontend/src/modals/ConfigToolModal.tsx
index 4a8ca881..f26029fc 100644
--- a/frontend/src/modals/ConfigToolModal.tsx
+++ b/frontend/src/modals/ConfigToolModal.tsx
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import Exit from '../assets/exit.svg';
import Input from '../components/Input';
import { ActiveState } from '../models/misc';
-import { AvailableTool } from './types';
+import { AvailableToolType } from './types';
import userService from '../api/services/userService';
export default function ConfigToolModal({
@@ -15,13 +15,13 @@ export default function ConfigToolModal({
}: {
modalState: ActiveState;
setModalState: (state: ActiveState) => void;
- tool: AvailableTool | null;
+ tool: AvailableToolType | null;
getUserTools: () => void;
}) {
const { t } = useTranslation();
const [authKey, setAuthKey] = React.useState('');
- const handleAddTool = (tool: AvailableTool) => {
+ const handleAddTool = (tool: AvailableToolType) => {
userService
.createTool({
name: tool.name,
@@ -75,7 +75,7 @@ export default function ConfigToolModal({