From 4e62d61677ecf0da93e04d689cc4e56b8bd2fa66 Mon Sep 17 00:00:00 2001 From: Rounak Bhatia Date: Fri, 11 Aug 2023 18:36:58 +0530 Subject: [PATCH 001/146] fix --- superagi/models/agent_config.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/superagi/models/agent_config.py b/superagi/models/agent_config.py index f7af6de01..20fb7e7ed 100644 --- a/superagi/models/agent_config.py +++ b/superagi/models/agent_config.py @@ -51,12 +51,12 @@ def update_agent_configurations_table(cls, session, agent_id: Union[int, None], ).first() if agent_toolkits_config: - agent_toolkits_config.value = updated_details_dict['toolkits'] + agent_toolkits_config.value = str(updated_details_dict['toolkits']) else: agent_toolkits_config = AgentConfiguration( agent_id=agent_id, key='toolkits', - value=updated_details_dict['toolkits'] + value=str(updated_details_dict['toolkits']) ) session.add(agent_toolkits_config) @@ -67,20 +67,21 @@ def update_agent_configurations_table(cls, session, agent_id: Union[int, None], ).first() if knowledge_config: - knowledge_config.value = updated_details_dict['knowledge'] + knowledge_config.value = str(updated_details_dict['knowledge']) else: knowledge_config = AgentConfiguration( agent_id=agent_id, key='knowledge', - value=updated_details_dict['knowledge'] + value=str(updated_details_dict['knowledge']) ) session.add(knowledge_config) # Fetch agent configurations agent_configs = session.query(AgentConfiguration).filter(AgentConfiguration.agent_id == agent_id).all() + for agent_config in agent_configs: if agent_config.key in updated_details_dict: - agent_config.value = updated_details_dict[agent_config.key] + agent_config.value = str(updated_details_dict[agent_config.key]) # Commit the changes to the database session.commit() From 7ea2b3c79ce57073634d57700e949d21397a135c Mon Sep 17 00:00:00 2001 From: TransformerOptimus Date: Thu, 17 Aug 2023 21:21:34 +0530 Subject: [PATCH 002/146] fixing the toolkit config in iteration workflow --- superagi/agent/agent_tool_step_handler.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/superagi/agent/agent_tool_step_handler.py b/superagi/agent/agent_tool_step_handler.py index 673eb8fc2..eccfaedec 100644 --- a/superagi/agent/agent_tool_step_handler.py +++ b/superagi/agent/agent_tool_step_handler.py @@ -16,11 +16,12 @@ from superagi.models.agent_execution_feed import AgentExecutionFeed from superagi.models.agent_execution_permission import AgentExecutionPermission from superagi.models.tool import Tool +from superagi.models.toolkit import Toolkit from superagi.models.workflows.agent_workflow_step import AgentWorkflowStep from superagi.models.workflows.agent_workflow_step_tool import AgentWorkflowStepTool from superagi.resource_manager.resource_summary import ResourceSummarizer from superagi.tools.base_tool import BaseTool - +from sqlalchemy import and_ class AgentToolStepHandler: """Handles the tools steps in the agent workflow""" @@ -116,7 +117,9 @@ def _build_tool_obj(self, agent_config, agent_execution_config, tool_name: str): resource_summary = ResourceSummarizer(session=self.session, agent_id=self.agent_id).fetch_or_create_agent_resource_summary( default_summary=agent_config.get("resource_summary")) - tool = self.session.query(Tool).filter(Tool.name == tool_name).first() + + organisation = Agent.find_org_by_agent_id(self.session, self.agent_id) + tool = self.session.query(Tool).join(Toolkit, and_(Tool.toolkit_id == Toolkit.id, Toolkit.organisation_id == organisation.id, Tool.name == tool_name)).first() tool_obj = tool_builder.build_tool(tool) tool_obj = tool_builder.set_default_params_tool(tool_obj, agent_config, agent_execution_config, model_api_key, resource_summary) From bff3814b7943d80fc133fbaf0ad6440d57b6fe97 Mon Sep 17 00:00:00 2001 From: Abhijeet <129729795+luciferlinx101@users.noreply.github.com> Date: Thu, 17 Aug 2023 23:53:09 +0530 Subject: [PATCH 003/146] List file s3 fix (#1076) * List File S3 * Unit Test Added --------- Co-authored-by: Taran <97586318+Tarraann@users.noreply.github.com> --- superagi/helper/s3_helper.py | 36 +++++++++++++++-------- superagi/tools/file/list_files.py | 5 ++++ tests/unit_tests/helper/test_s3_helper.py | 26 ++++++++++++++++ 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/superagi/helper/s3_helper.py b/superagi/helper/s3_helper.py index 2669b77ed..27738b1e9 100644 --- a/superagi/helper/s3_helper.py +++ b/superagi/helper/s3_helper.py @@ -8,8 +8,9 @@ from urllib.parse import unquote import json + class S3Helper: - def __init__(self, bucket_name = get_config("BUCKET_NAME")): + def __init__(self, bucket_name=get_config("BUCKET_NAME")): """ Initialize the S3Helper class. Using the AWS credentials from the configuration file, create a boto3 client. @@ -83,7 +84,7 @@ def get_json_file(self, path): """ try: obj = self.s3.get_object(Bucket=self.bucket_name, Key=path) - s3_response = obj['Body'].read().decode('utf-8') + s3_response = obj['Body'].read().decode('utf-8') return json.loads(s3_response) except: raise HTTPException(status_code=500, detail="AWS credentials not found. Check your configuration.") @@ -107,32 +108,43 @@ def delete_file(self, path): logger.info("File deleted from S3 successfully!") except: raise HTTPException(status_code=500, detail="AWS credentials not found. Check your configuration.") - + def upload_file_content(self, content, file_path): try: self.s3.put_object(Bucket=self.bucket_name, Key=file_path, Body=content) except: raise HTTPException(status_code=500, detail="AWS credentials not found. Check your configuration.") - - def get_download_url_of_resources(self,db_resources_arr): + + def get_download_url_of_resources(self, db_resources_arr): s3 = boto3.client( 's3', aws_access_key_id=get_config("AWS_ACCESS_KEY_ID"), aws_secret_access_key=get_config("AWS_SECRET_ACCESS_KEY"), ) - response_obj={} + response_obj = {} for db_resource in db_resources_arr: response = self.s3.get_object(Bucket=get_config("BUCKET_NAME"), Key=db_resource.path) content = response["Body"].read() bucket_name = get_config("INSTAGRAM_TOOL_BUCKET_NAME") - file_name=db_resource.path.split('/')[-1] - file_name=''.join(char for char in file_name if char != "`") - object_key=f"public_resources/run_id{db_resource.agent_execution_id}/{file_name}" + file_name = db_resource.path.split('/')[-1] + file_name = ''.join(char for char in file_name if char != "`") + object_key = f"public_resources/run_id{db_resource.agent_execution_id}/{file_name}" s3.put_object(Bucket=bucket_name, Key=object_key, Body=content) file_url = f"https://{bucket_name}.s3.amazonaws.com/{object_key}" - resource_execution_id=db_resource.agent_execution_id + resource_execution_id = db_resource.agent_execution_id if resource_execution_id in response_obj: response_obj[resource_execution_id].append(file_url) else: - response_obj[resource_execution_id]=[file_url] - return response_obj \ No newline at end of file + response_obj[resource_execution_id] = [file_url] + return response_obj + + def list_files_from_s3(self, file_path): + file_path = "resources" + file_path + logger.info(f"Listing files from s3 with prefix: {file_path}") + response = self.s3.list_objects_v2(Bucket=get_config("BUCKET_NAME"), Prefix=file_path) + + if 'Contents' in response: + file_list = [obj['Key'] for obj in response['Contents']] + return file_list + + raise Exception(f"Error listing files from s3") diff --git a/superagi/tools/file/list_files.py b/superagi/tools/file/list_files.py index 5f2414329..7290e53db 100644 --- a/superagi/tools/file/list_files.py +++ b/superagi/tools/file/list_files.py @@ -4,8 +4,11 @@ from pydantic import BaseModel, Field from superagi.helper.resource_helper import ResourceHelper +from superagi.helper.s3_helper import S3Helper from superagi.tools.base_tool import BaseTool from superagi.models.agent import Agent +from superagi.types.storage_types import StorageType +from superagi.config.config import get_config class ListFileInput(BaseModel): @@ -52,6 +55,8 @@ def _execute(self): return input_files #+ output_files def list_files(self, directory): + if StorageType.get_storage_type(get_config("STORAGE_TYPE", StorageType.FILE.value)) == StorageType.S3: + return S3Helper().list_files_from_s3(directory) found_files = [] for root, dirs, files in os.walk(directory): for file in files: diff --git a/tests/unit_tests/helper/test_s3_helper.py b/tests/unit_tests/helper/test_s3_helper.py index bceb5d67f..d476c7a7c 100644 --- a/tests/unit_tests/helper/test_s3_helper.py +++ b/tests/unit_tests/helper/test_s3_helper.py @@ -102,3 +102,29 @@ def test_delete_file_fail(s3helper_object): s3helper_object.s3.delete_object = MagicMock(side_effect=Exception()) with pytest.raises(HTTPException): s3helper_object.delete_file('path') + + +def test_list_files_from_s3(s3helper_object): + s3helper_object.s3.list_objects_v2 = MagicMock(return_value={ + 'Contents': [{'Key': 'path/to/file1.txt'}, {'Key': 'path/to/file2.jpg'}] + }) + + file_list = s3helper_object.list_files_from_s3('path/to/') + + assert len(file_list) == 2 + assert 'path/to/file1.txt' in file_list + assert 'path/to/file2.jpg' in file_list + + +def test_list_files_from_s3_no_contents(s3helper_object): + s3helper_object.s3.list_objects_v2 = MagicMock(return_value={}) + + with pytest.raises(Exception): + s3helper_object.list_files_from_s3('path/to/') + + +def test_list_files_from_s3_raises_exception(s3helper_object): + s3helper_object.s3.list_objects_v2 = MagicMock(side_effect=Exception("An error occurred")) + + with pytest.raises(Exception): + s3helper_object.list_files_from_s3('path/to/') From 34c35033058b4352bc402b11c892b91e58e7d138 Mon Sep 17 00:00:00 2001 From: TransformerOptimus Date: Fri, 18 Aug 2023 08:34:33 +0530 Subject: [PATCH 004/146] workflow changes --- superagi/agent/workflow_seed.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/superagi/agent/workflow_seed.py b/superagi/agent/workflow_seed.py index b888c6547..43d9ab255 100644 --- a/superagi/agent/workflow_seed.py +++ b/superagi/agent/workflow_seed.py @@ -102,7 +102,7 @@ def build_recruitment_workflow(cls, session): str(agent_workflow.id) + "_step2", "TASK_QUEUE", "Break the above response array of items", - completion_prompt="Get array of items from the above response. Array should suitable utilization of JSON.parse().") + completion_prompt="Get array of items from the above response. Array should suitable utilization of JSON.parse(). Skip job_description file from list.") step3 = AgentWorkflowStep.find_or_create_tool_workflow_step(session, agent_workflow.id, str(agent_workflow.id) + "_step3", @@ -112,7 +112,7 @@ def build_recruitment_workflow(cls, session): step4 = AgentWorkflowStep.find_or_create_tool_workflow_step(session, agent_workflow.id, str(agent_workflow.id) + "_step4", ReadFileTool().name, - "Read the job description from job description file", + "Read the job description from file mentioned in High-Level GOAL", "Check if the resume matches the job description in goal") step5 = AgentWorkflowStep.find_or_create_tool_workflow_step(session, agent_workflow.id, @@ -124,7 +124,8 @@ def build_recruitment_workflow(cls, session): AgentWorkflowStep.add_next_workflow_step(session, step2.id, step3.id) AgentWorkflowStep.add_next_workflow_step(session, step2.id, -1, "COMPLETE") AgentWorkflowStep.add_next_workflow_step(session, step3.id, step4.id) - AgentWorkflowStep.add_next_workflow_step(session, step4.id, step5.id) + AgentWorkflowStep.add_next_workflow_step(session, step4.id, step5.id, "YES") + AgentWorkflowStep.add_next_workflow_step(session, step4.id, step2.id, "NO") AgentWorkflowStep.add_next_workflow_step(session, step5.id, step2.id) session.commit() From a8c37d40b33986d61b3fa71c647ee0db98a96a9f Mon Sep 17 00:00:00 2001 From: TransformerOptimus Date: Fri, 18 Aug 2023 09:28:34 +0530 Subject: [PATCH 005/146] minor seed file fix --- superagi/agent/workflow_seed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superagi/agent/workflow_seed.py b/superagi/agent/workflow_seed.py index 43d9ab255..7f95b1244 100644 --- a/superagi/agent/workflow_seed.py +++ b/superagi/agent/workflow_seed.py @@ -116,7 +116,7 @@ def build_recruitment_workflow(cls, session): "Check if the resume matches the job description in goal") step5 = AgentWorkflowStep.find_or_create_tool_workflow_step(session, agent_workflow.id, - str(agent_workflow.id) + "_step4", + str(agent_workflow.id) + "_step5", SendEmailTool().name, "Write a custom Email the candidates for job profile based on their experience") From ef8055bb6a5bf1e8057e8960fae7e050f5e6d924 Mon Sep 17 00:00:00 2001 From: Fluder-Paradyne <121793617+Fluder-Paradyne@users.noreply.github.com> Date: Fri, 18 Aug 2023 13:19:34 +0530 Subject: [PATCH 006/146] frontend fixes (#1079) --- gui/pages/Content/APM/ApmDashboard.js | 4 ++-- gui/pages/Content/Toolkits/ToolkitWorkspace.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gui/pages/Content/APM/ApmDashboard.js b/gui/pages/Content/APM/ApmDashboard.js index bd2dd50cb..314ddc915 100644 --- a/gui/pages/Content/APM/ApmDashboard.js +++ b/gui/pages/Content/APM/ApmDashboard.js @@ -294,7 +294,7 @@ export default function ApmDashboard() {
{(showToolTip && toolTipIndex === i) &&
{run.tools_used.slice(3).map((tool,index) => -
{tool}
+
{tool}
)}
}
setToolTipState(true,i)} onMouseLeave={() => setToolTipState(false,i)}> @@ -420,4 +420,4 @@ export default function ApmDashboard() {
); -} \ No newline at end of file +} diff --git a/gui/pages/Content/Toolkits/ToolkitWorkspace.js b/gui/pages/Content/Toolkits/ToolkitWorkspace.js index 104add0a0..8356a1eb6 100644 --- a/gui/pages/Content/Toolkits/ToolkitWorkspace.js +++ b/gui/pages/Content/Toolkits/ToolkitWorkspace.js @@ -116,7 +116,6 @@ export default function ToolkitWorkspace({env, toolkitDetails, internalId}) { }, [internalId]); return (<> - '
From 1aa9436a5c952ebc1cec33f805ba9f9385df5dda Mon Sep 17 00:00:00 2001 From: Maverick-F35 <138012351+Maverick-F35@users.noreply.github.com> Date: Fri, 18 Aug 2023 13:33:42 +0530 Subject: [PATCH 007/146] fixed api_bug (#1080) --- superagi/controllers/api/agent.py | 8 +++++--- superagi/worker.py | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/superagi/controllers/api/agent.py b/superagi/controllers/api/agent.py index a71e60a8f..4f4da0843 100644 --- a/superagi/controllers/api/agent.py +++ b/superagi/controllers/api/agent.py @@ -168,9 +168,9 @@ def update_agent(agent_id: int, agent_with_config: AgentConfigUpdateExtInput,api if project.organisation_id!=organisation.id: raise HTTPException(status_code=404, detail="Agent not found") - db_execution=AgentExecution.get_execution_by_agent_id_and_status(db.session, agent_id, "RUNNING") - if db_execution is not None: - raise HTTPException(status_code=409, detail="Agent is already running,please pause and then update") + # db_execution=AgentExecution.get_execution_by_agent_id_and_status(db.session, agent_id, "RUNNING") + # if db_execution is not None: + # raise HTTPException(status_code=409, detail="Agent is already running,please pause and then update") db_schedule=AgentSchedule.find_by_agent_id(db.session, agent_id) if db_schedule is not None: @@ -295,9 +295,11 @@ def resume_agent_runs(agent_id:int,execution_state_change_input:ExecutionStateCh db_execution_arr=AgentExecution.get_all_executions_by_status_and_agent_id(db.session, agent.id, execution_state_change_input, "PAUSED") for ind_execution in db_execution_arr: ind_execution.status="RUNNING" + execute_agent.delay(ind_execution.id, datetime.now()) db.session.commit() db.session.flush() + return { "result":"success" } diff --git a/superagi/worker.py b/superagi/worker.py index b6e5ea231..bf5e43e29 100644 --- a/superagi/worker.py +++ b/superagi/worker.py @@ -36,10 +36,10 @@ } app.conf.beat_schedule = beat_schedule -@event.listens_for(AgentExecution.status, "set") -def agent_status_change(target, val,old_val,initiator): - if get_config("IN_TESTING",False): - webhook_callback.delay(target.id,val,old_val) +# @event.listens_for(AgentExecution.status, "set") +# def agent_status_change(target, val,old_val,initiator): +# if not get_config("IN_TESTING",False): +# webhook_callback.delay(target.id,val,old_val) @app.task(name="initialize-schedule-agent", autoretry_for=(Exception,), retry_backoff=2, max_retries=5) From 3ff1c459f9d93f41cccbc19a8195b60b10ebb002 Mon Sep 17 00:00:00 2001 From: Fluder-Paradyne <121793617+Fluder-Paradyne@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:39:46 +0530 Subject: [PATCH 008/146] add one condition (#1082) --- superagi/controllers/api/agent.py | 16 ++++++++++++---- tests/unit_tests/controllers/api/test_agent.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/superagi/controllers/api/agent.py b/superagi/controllers/api/agent.py index 4f4da0843..9fc397cca 100644 --- a/superagi/controllers/api/agent.py +++ b/superagi/controllers/api/agent.py @@ -265,9 +265,13 @@ def pause_agent_runs(agent_id:int,execution_state_change_input:ExecutionStateCha try: AgentExecution.validate_run_ids(db.session,execution_state_change_input.run_ids,organisation.id) except Exception as e: - raise HTTPException(status_code=404, detail="One or more run_ids not found") + raise HTTPException(status_code=404, detail="One or more run id(s) not found") db_execution_arr=AgentExecution.get_all_executions_by_status_and_agent_id(db.session, agent.id, execution_state_change_input, "RUNNING") + + if len(db_execution_arr) != len(execution_state_change_input.run_ids): + raise HTTPException(status_code=404, detail="One or more run id(s) not found") + for ind_execution in db_execution_arr: ind_execution.status="PAUSED" db.session.commit() @@ -290,9 +294,13 @@ def resume_agent_runs(agent_id:int,execution_state_change_input:ExecutionStateCh try: AgentExecution.validate_run_ids(db.session,execution_state_change_input.run_ids,organisation.id) except Exception as e: - raise HTTPException(status_code=404, detail="One or more run_ids not found") + raise HTTPException(status_code=404, detail="One or more run id(s) not found") db_execution_arr=AgentExecution.get_all_executions_by_status_and_agent_id(db.session, agent.id, execution_state_change_input, "PAUSED") + + if len(db_execution_arr) != len(execution_state_change_input.run_ids): + raise HTTPException(status_code=404, detail="One or more run id(s) not found") + for ind_execution in db_execution_arr: ind_execution.status="RUNNING" execute_agent.delay(ind_execution.id, datetime.now()) @@ -314,9 +322,9 @@ def get_run_resources(run_id_config:RunIDConfig,api_key: str = Security(validate detail=f"No execution_id found") #Checking if the run_ids whose output files are requested belong to the organisation try: - AgentExecution.validate_run_ids(db.session,run_ids_arr,organisation.id) + AgentExecution.validate_run_ids(db.session, run_ids_arr, organisation.id) except Exception as e: - raise HTTPException(status_code=404, detail="One or more run_ids not found") + raise HTTPException(status_code=404, detail="One or more run id(s) not found") db_resources_arr=Resource.find_by_run_ids(db.session, run_ids_arr) diff --git a/tests/unit_tests/controllers/api/test_agent.py b/tests/unit_tests/controllers/api/test_agent.py index 99fda13b2..f0893469c 100644 --- a/tests/unit_tests/controllers/api/test_agent.py +++ b/tests/unit_tests/controllers/api/test_agent.py @@ -139,7 +139,7 @@ def test_get_run_resources_invalid_run_ids(mock_run_id_config_invalid,mock_api_k json=mock_run_id_config_invalid ) assert response.status_code == 404 - assert response.text == '{"detail":"One or more run_ids not found"}' + assert response.text == '{"detail":"One or more run id(s) not found"}' def test_resume_agent_runs_agent_not_found(mock_execution_state_change_input,mock_api_key_get): with patch('superagi.helper.auth.get_organisation_from_api_key') as mock_get_user_org, \ From 280ea14cd7913713f5e2e9158f39e25be6a3b9ef Mon Sep 17 00:00:00 2001 From: Fluder-Paradyne <121793617+Fluder-Paradyne@users.noreply.github.com> Date: Fri, 18 Aug 2023 19:54:32 +0530 Subject: [PATCH 009/146] api fix (#1087) --- superagi/models/toolkit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/superagi/models/toolkit.py b/superagi/models/toolkit.py index 5a9c0a0e9..91f2a532a 100644 --- a/superagi/models/toolkit.py +++ b/superagi/models/toolkit.py @@ -151,7 +151,8 @@ def get_tool_and_toolkit_arr(cls, session, agent_config_tools_arr: list): toolkits_arr.add(toolkit.id) if tool_obj.get("tools"): for tool_name_str in tool_obj["tools"]: - tool_db_obj=session.query(Tool).filter(Tool.name == tool_name_str.strip()).first() + tool_db_obj = session.query(Tool).filter(Tool.name == tool_name_str.strip(), + Tool.toolkit_id == toolkit.id).first() if tool_db_obj is None: raise Exception("One or more of the Tool(s)/Toolkit(s) does not exist.") From 90eb841c3c4296004430669ef20c2da362faca5f Mon Sep 17 00:00:00 2001 From: Rounak Bhatia Date: Mon, 21 Aug 2023 14:11:38 +0530 Subject: [PATCH 010/146] Tools error fix (#1093) --- superagi/agent/agent_iteration_step_handler.py | 2 +- superagi/models/agent_execution_config.py | 3 ++- tests/unit_tests/agent/test_agent_iteration_step_handler.py | 2 +- tests/unit_tests/models/test_agent_execution_config.py | 5 +++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/superagi/agent/agent_iteration_step_handler.py b/superagi/agent/agent_iteration_step_handler.py index 00d73b8c6..0991decba 100644 --- a/superagi/agent/agent_iteration_step_handler.py +++ b/superagi/agent/agent_iteration_step_handler.py @@ -148,7 +148,7 @@ def _build_tools(self, agent_config: dict, agent_execution_config: dict): if resource_summary is not None: agent_tools.append(QueryResourceTool()) user_tools = self.session.query(Tool).filter( - and_(Tool.id.in_(agent_config["tools"]), Tool.file_name is not None)).all() + and_(Tool.id.in_(agent_execution_config["tools"]), Tool.file_name is not None)).all() for tool in user_tools: agent_tools.append(tool_builder.build_tool(tool)) diff --git a/superagi/models/agent_execution_config.py b/superagi/models/agent_execution_config.py index d555f0339..8e2a43a08 100644 --- a/superagi/models/agent_execution_config.py +++ b/superagi/models/agent_execution_config.py @@ -84,6 +84,7 @@ def fetch_configuration(cls, session, execution_id): parsed_config = { "goal": [], "instruction": [], + "tools": [] } if not agent_configurations: return parsed_config @@ -105,7 +106,7 @@ def eval_agent_config(cls, key, value): """ - if key == "goal" or key == "instruction": + if key == "goal" or key == "instruction" or key == "tools": return eval(value) @classmethod diff --git a/tests/unit_tests/agent/test_agent_iteration_step_handler.py b/tests/unit_tests/agent/test_agent_iteration_step_handler.py index c9bb515ea..fa26bc664 100644 --- a/tests/unit_tests/agent/test_agent_iteration_step_handler.py +++ b/tests/unit_tests/agent/test_agent_iteration_step_handler.py @@ -77,7 +77,7 @@ def test_build_agent_prompt(test_handler, mocker): def test_build_tools(test_handler, mocker): # Arrange agent_config = {'model': 'gpt-3', 'tools': [1, 2, 3], 'resource_summary': True} - agent_execution_config = {'goal': 'Test goal', 'instruction': 'Test instruction'} + agent_execution_config = {'goal': 'Test goal', 'instruction': 'Test instruction', 'tools':[1]} mocker.patch.object(AgentConfiguration, 'get_model_api_key', return_value='test_api_key') mocker.patch.object(ToolBuilder, 'build_tool') diff --git a/tests/unit_tests/models/test_agent_execution_config.py b/tests/unit_tests/models/test_agent_execution_config.py index 4b95e9253..f0850da78 100644 --- a/tests/unit_tests/models/test_agent_execution_config.py +++ b/tests/unit_tests/models/test_agent_execution_config.py @@ -15,13 +15,14 @@ def setUp(self): def test_fetch_configuration(self): test_db_response = [MagicMock(key="goal", value="['test_goal']"), - MagicMock(key="instruction", value="['test_instruction']")] + MagicMock(key="instruction", value="['test_instruction']"), + MagicMock(key="tools", value="[1]")] self.session.query.return_value.filter_by.return_value.all.return_value = test_db_response result = AgentExecutionConfiguration.fetch_configuration(self.session, self.execution) - expected_result = {"goal": ["test_goal"], "instruction": ["test_instruction"]} + expected_result = {"goal": ["test_goal"], "instruction": ["test_instruction"], "tools":[1]} self.assertDictEqual(result, expected_result) def test_eval_agent_config(self): From 97cbc076fbc02ba46aecb095e07689a5a7b2cd20 Mon Sep 17 00:00:00 2001 From: namansleeps Date: Mon, 21 Aug 2023 18:12:44 +0530 Subject: [PATCH 011/146] webhooks frontend + api calls complete almost --- gui/pages/Dashboard/Settings/Settings.js | 8 ++ gui/pages/Dashboard/Settings/Webhooks.js | 128 +++++++++++++++++++++++ gui/pages/api/DashboardService.js | 11 ++ gui/public/images/webhook_icon.svg | 3 + superagi/controllers/webhook.py | 46 +++++++- 5 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 gui/pages/Dashboard/Settings/Webhooks.js create mode 100644 gui/public/images/webhook_icon.svg diff --git a/gui/pages/Dashboard/Settings/Settings.js b/gui/pages/Dashboard/Settings/Settings.js index 402fc6050..a2cd06cea 100644 --- a/gui/pages/Dashboard/Settings/Settings.js +++ b/gui/pages/Dashboard/Settings/Settings.js @@ -5,6 +5,7 @@ import Image from "next/image"; import Model from "@/pages/Dashboard/Settings/Model"; import Database from "@/pages/Dashboard/Settings/Database"; import ApiKeys from "@/pages/Dashboard/Settings/ApiKeys"; +import Webhooks from "@/pages/Dashboard/Settings/Webhooks"; export default function Settings({organisationId, sendDatabaseData}) { const [activeTab, setActiveTab] = useState('model'); @@ -51,12 +52,19 @@ export default function Settings({organisationId, sendDatabaseData}) { alt="database-icon"/> API Keys
+
+ +
{activeTab === 'model' && } {activeTab === 'database' && } {activeTab === 'apikeys' && } + {activeTab === 'webhooks' && }
diff --git a/gui/pages/Dashboard/Settings/Webhooks.js b/gui/pages/Dashboard/Settings/Webhooks.js new file mode 100644 index 000000000..32f104e9b --- /dev/null +++ b/gui/pages/Dashboard/Settings/Webhooks.js @@ -0,0 +1,128 @@ +import React, {useState, useEffect} from 'react'; +import {ToastContainer, toast} from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; +import agentStyles from "@/pages/Content/Agents/Agents.module.css"; +import { + deleteWebhook, + getWebhook, saveWebhook, +} from "@/pages/api/DashboardService"; +import {loadingTextEffect, removeTab} from "@/utils/utils"; +import Image from "next/image"; +import styles from "@/pages/Content/Marketplace/Market.module.css"; +export default function Webhooks() { + const [webhookUrl, setWebhookUrl] = useState(''); + const [webhookName, setWebhookName] = useState(''); + const [webhookId, setWebhookId] = useState(-1); + const [isLoading, setIsLoading] = useState(true) + const [existingWebhook, setExistingWebhook] = useState(false) + const [loadingText, setLoadingText] = useState("Loading Webhooks"); + + useEffect(() => { + loadingTextEffect('Loading Webhooks', setLoadingText, 500); + fetchWebhooks(); + }, []); + + const handleWebhookChange = (event) => { + setWebhookUrl(event.target.value); + }; + const handleWebhookName = (event) => { + setWebhookName(event.target.value); + }; + + const handleSaveWebhook = () => { + if(!webhookUrl || webhookUrl.trim() === ""){ + toast.error("Enter valid webhook", {autoClose: 1800}); + return; + } + + saveWebhook({name : webhookName, url: webhookUrl, headers: {}}) + .then((response) => { + setExistingWebhook(true) + setWebhookId(response.data.id) + toast.success("Webhook created successfully", {autoClose: 1800}); + }) + .catch((error) => { + toast.error("Unable to create webhook", {autoClose: 1800}); + console.error('Error saving webhook', error); + }); + } + + const fetchWebhooks = () => { + getWebhook() + .then((response) => { + setIsLoading(false) + if(response.data){ + setWebhookUrl(response.data.url) + setWebhookName(response.data.name) + setExistingWebhook(true) + setWebhookId(response.data.id) + } + else{ + setWebhookUrl('') + setWebhookName('') + setExistingWebhook(false) + setWebhookId(-1) + } + }) + .catch((error) => { + console.error('Error fetching webhook', error); + }); + } + + const deleteExistingWebhook = () => { + deleteWebhook(webhookId) + .then((response) => { + fetchWebhooks() + toast.success("Webhook deleted successfully", {autoClose: 1800}); + }) + .catch((error) => { + console.error('Error fetching webhook', error); + }); + } + + return (<> +
+
+
+ {!isLoading ?
+
+
Webhooks
+ {existingWebhook && + } +
+ +
+ + +
+ +
+ + +
+
+ + {!existingWebhook &&
+ + +
} + +
:
+
{loadingText}
+
} +
+
+
+ + ) +} \ No newline at end of file diff --git a/gui/pages/api/DashboardService.js b/gui/pages/api/DashboardService.js index 625c6c2af..e2941d367 100644 --- a/gui/pages/api/DashboardService.js +++ b/gui/pages/api/DashboardService.js @@ -320,3 +320,14 @@ export const deleteApiKey = (apiId) => { return api.delete(`/api-keys/${apiId}`); }; +export const saveWebhook = (webhook) => { + return api.post(`/webhook/add`, webhook); +}; + +export const getWebhook = () => { + return api.get(`/webhook/get`); +}; + +export const deleteWebhook = (webhook_id) => { + return api.delete(`/webhook/delete/${webhook_id}`); +}; diff --git a/gui/public/images/webhook_icon.svg b/gui/public/images/webhook_icon.svg new file mode 100644 index 000000000..0e316e430 --- /dev/null +++ b/gui/public/images/webhook_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/superagi/controllers/webhook.py b/superagi/controllers/webhook.py index 0a55bd216..15317cd4c 100644 --- a/superagi/controllers/webhook.py +++ b/superagi/controllers/webhook.py @@ -1,5 +1,5 @@ from datetime import datetime - +from typing import Optional from fastapi import APIRouter from fastapi import Depends from fastapi_jwt_auth import AuthJWT @@ -58,3 +58,47 @@ def create_webhook(webhook: WebHookIn, Authorize: AuthJWT = Depends(check_auth), db.session.flush() return db_webhook + +@router.get("/get", response_model=Optional[WebHookOut]) +def get_all_webhooks( + Authorize: AuthJWT = Depends(check_auth), + organisation=Depends(get_user_organisation), +): + """ + Retrieves a single webhook for the authenticated user's organisation. + + Returns: + JSONResponse: A JSON response containing the retrieved webhook. + + Raises: + """ + webhook = db.session.query(Webhooks).filter(Webhooks.org_id == organisation.id, Webhooks.is_deleted == False).first() + return webhook + +@router.delete("/delete/{webhook_id}", response_model=WebHookOut) +def delete_webhook( + webhook_id: int, + Authorize: AuthJWT = Depends(check_auth), + organisation=Depends(get_user_organisation), +): + """ + Soft-deletes a webhook by setting the value of is_deleted to True. + + Args: + webhook_id (int): The ID of the webhook to delete. + + Returns: + WebHookOut: The deleted webhook. + + Raises: + HTTPException (Status Code=404): If the webhook is not found. + """ + webhook = db.session.query(Webhooks).filter(Webhooks.org_id == organisation.id, Webhooks.id == webhook_id, Webhooks.is_deleted == False).first() + + if webhook is None: + raise HTTPException(status_code=404, detail="Webhook not found") + + webhook.is_deleted = True + db.session.commit() + + return webhook \ No newline at end of file From e19a7e91c390fc8bef1553cd63ba95cb83ea4f2b Mon Sep 17 00:00:00 2001 From: Aditya Sharma <138581531+AdityaSharma13064@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:52:58 +0530 Subject: [PATCH 012/146] Tool-LTM(Updated) (#1039) --- .../agent/agent_iteration_step_handler.py | 6 +- superagi/agent/agent_tool_step_handler.py | 4 +- superagi/agent/output_handler.py | 48 ++++- superagi/agent/tool_builder.py | 4 +- superagi/jobs/agent_executor.py | 8 +- superagi/tools/thinking/prompts/thinking.txt | 3 + superagi/tools/thinking/tools.py | 4 + superagi/tools/tool_response_query_manager.py | 12 +- superagi/vector_store/embedding/openai.py | 12 ++ superagi/vector_store/redis.py | 169 ++++++++++++++++++ superagi/vector_store/vector_factory.py | 9 +- tests/unit_tests/agent/test_output_handler.py | 35 +++- tests/unit_tests/vector_store/test_redis.py | 42 +++++ 13 files changed, 335 insertions(+), 21 deletions(-) create mode 100644 tests/unit_tests/vector_store/test_redis.py diff --git a/superagi/agent/agent_iteration_step_handler.py b/superagi/agent/agent_iteration_step_handler.py index 0991decba..6637c6aa0 100644 --- a/superagi/agent/agent_iteration_step_handler.py +++ b/superagi/agent/agent_iteration_step_handler.py @@ -82,7 +82,7 @@ def execute_step(self): assistant_reply = response['content'] output_handler = get_output_handler(iteration_workflow_step.output_type, agent_execution_id=self.agent_execution_id, - agent_config=agent_config, agent_tools=agent_tools) + agent_config=agent_config,memory=self.memory, agent_tools=agent_tools) response = output_handler.handle(self.session, assistant_reply) if response.status == "COMPLETE": execution.status = "COMPLETED" @@ -153,7 +153,7 @@ def _build_tools(self, agent_config: dict, agent_execution_config: dict): agent_tools.append(tool_builder.build_tool(tool)) agent_tools = [tool_builder.set_default_params_tool(tool, agent_config, agent_execution_config, - model_api_key, resource_summary) for tool in agent_tools] + model_api_key, resource_summary,self.memory) for tool in agent_tools] return agent_tools def _handle_wait_for_permission(self, agent_execution, agent_config: dict, agent_execution_config: dict, @@ -179,7 +179,7 @@ def _handle_wait_for_permission(self, agent_execution, agent_config: dict, agent return False if agent_execution_permission.status == "APPROVED": agent_tools = self._build_tools(agent_config, agent_execution_config) - tool_output_handler = ToolOutputHandler(self.agent_execution_id, agent_config, agent_tools) + tool_output_handler = ToolOutputHandler(self.agent_execution_id, agent_config, agent_tools,self.memory) tool_result = tool_output_handler.handle_tool_response(self.session, agent_execution_permission.assistant_reply) result = tool_result.result diff --git a/superagi/agent/agent_tool_step_handler.py b/superagi/agent/agent_tool_step_handler.py index eccfaedec..3257cd272 100644 --- a/superagi/agent/agent_tool_step_handler.py +++ b/superagi/agent/agent_tool_step_handler.py @@ -56,7 +56,7 @@ def execute_step(self): assistant_reply = self._process_input_instruction(agent_config, agent_execution_config, step_tool, workflow_step) tool_obj = self._build_tool_obj(agent_config, agent_execution_config, step_tool.tool_name) - tool_output_handler = ToolOutputHandler(self.agent_execution_id, agent_config, [tool_obj], + tool_output_handler = ToolOutputHandler(self.agent_execution_id, agent_config, [tool_obj],self.memory, output_parser=AgentSchemaToolOutputParser()) final_response = tool_output_handler.handle(self.session, assistant_reply) step_response = "default" @@ -122,7 +122,7 @@ def _build_tool_obj(self, agent_config, agent_execution_config, tool_name: str): tool = self.session.query(Tool).join(Toolkit, and_(Tool.toolkit_id == Toolkit.id, Toolkit.organisation_id == organisation.id, Tool.name == tool_name)).first() tool_obj = tool_builder.build_tool(tool) tool_obj = tool_builder.set_default_params_tool(tool_obj, agent_config, agent_execution_config, model_api_key, - resource_summary) + resource_summary,self.memory) return tool_obj def _process_output_instruction(self, final_response: str, step_tool: AgentWorkflowStepTool, diff --git a/superagi/agent/output_handler.py b/superagi/agent/output_handler.py index 97cf65814..aeb8e1f74 100644 --- a/superagi/agent/output_handler.py +++ b/superagi/agent/output_handler.py @@ -1,12 +1,15 @@ +import json from superagi.agent.common_types import TaskExecutorResponse, ToolExecutorResponse from superagi.agent.output_parser import AgentSchemaOutputParser from superagi.agent.task_queue import TaskQueue from superagi.agent.tool_executor import ToolExecutor from superagi.helper.json_cleaner import JsonCleaner from superagi.lib.logger import logger +from langchain.text_splitter import TokenTextSplitter from superagi.models.agent import Agent from superagi.models.agent_execution import AgentExecution from superagi.models.agent_execution_feed import AgentExecutionFeed +from superagi.vector_store.base import VectorStore import numpy as np from superagi.models.agent_execution_permission import AgentExecutionPermission @@ -14,13 +17,18 @@ class ToolOutputHandler: """Handles the tool output response from the thinking step""" - def __init__(self, agent_execution_id: int, agent_config: dict, - tools: list, output_parser=AgentSchemaOutputParser()): + def __init__(self, + agent_execution_id: int, + agent_config: dict, + tools: list, + memory:VectorStore=None, + output_parser=AgentSchemaOutputParser()): self.agent_execution_id = agent_execution_id self.task_queue = TaskQueue(str(agent_execution_id)) self.agent_config = agent_config self.tools = tools self.output_parser = output_parser + self.memory=memory def handle(self, session, assistant_reply): """Handles the tool output response from the thinking step. @@ -53,9 +61,37 @@ def handle(self, session, assistant_reply): session.commit() if not tool_response.retry: tool_response = self._check_for_completion(tool_response) - # print("Tool Response:", tool_response) + + self.add_text_to_memory(assistant_reply, tool_response.result) return tool_response + def add_text_to_memory(self, assistant_reply,tool_response_result): + """ + Adds the text generated by the assistant and tool response to the memory. + + Args: + assistant_reply (str): The assistant reply. + tool_response_result (str): The tool response. + + Returns: + None + """ + if self.memory is not None: + data = json.loads(assistant_reply) + task_description = data['thoughts']['text'] + final_tool_response = tool_response_result + prompt = task_description + final_tool_response + text_splitter = TokenTextSplitter(chunk_size=1024, chunk_overlap=10) + chunk_response = text_splitter.split_text(prompt) + metadata = {"agent_execution_id": self.agent_execution_id} + metadatas = [] + for _ in chunk_response: + metadatas.append(metadata) + + self.memory.add_texts(chunk_response, metadatas) + + + def handle_tool_response(self, session, assistant_reply): """Only handle processing of tool response""" action = self.output_parser.parse(assistant_reply) @@ -149,11 +185,11 @@ def handle(self, session, assistant_reply): return TaskExecutorResponse(status=status, retry=False) -def get_output_handler(output_type: str, agent_execution_id: int, agent_config: dict, agent_tools: list = []): +def get_output_handler(output_type: str, agent_execution_id: int, agent_config: dict, agent_tools: list = [],memory=None): if output_type == "tools": - return ToolOutputHandler(agent_execution_id, agent_config, agent_tools) + return ToolOutputHandler(agent_execution_id, agent_config, agent_tools,memory=memory) elif output_type == "replace_tasks": return ReplaceTaskOutputHandler(agent_execution_id, agent_config) elif output_type == "tasks": return TaskOutputHandler(agent_execution_id, agent_config) - return ToolOutputHandler(agent_execution_id, agent_config, agent_tools) + return ToolOutputHandler(agent_execution_id, agent_config, agent_tools,memory=memory) diff --git a/superagi/agent/tool_builder.py b/superagi/agent/tool_builder.py index eb2194468..c331e645d 100644 --- a/superagi/agent/tool_builder.py +++ b/superagi/agent/tool_builder.py @@ -81,7 +81,7 @@ def build_tool(self, tool: Tool): return new_object def set_default_params_tool(self, tool, agent_config, agent_execution_config, model_api_key: str, - resource_summary: str = ""): + resource_summary: str = "",memory=None): """ Set the default parameters for the tools. @@ -113,7 +113,7 @@ def set_default_params_tool(self, tool, agent_config, agent_execution_config, mo agent_execution_id=self.agent_execution_id) if hasattr(tool, 'tool_response_manager'): tool.tool_response_manager = ToolResponseQueryManager(session=self.session, - agent_execution_id=self.agent_execution_id) + agent_execution_id=self.agent_execution_id,memory=memory) if tool.name == "QueryResourceTool": tool.description = tool.description.replace("{summary}", resource_summary) diff --git a/superagi/jobs/agent_executor.py b/superagi/jobs/agent_executor.py index e5ea17e09..44ff89720 100644 --- a/superagi/jobs/agent_executor.py +++ b/superagi/jobs/agent_executor.py @@ -18,6 +18,8 @@ from superagi.types.vector_store_types import VectorStoreType from superagi.vector_store.embedding.openai import OpenAiEmbedding from superagi.vector_store.vector_factory import VectorFactory +from superagi.vector_store.redis import Redis +from superagi.config.config import get_config # from superagi.helper.tool_helper import get_tool_config_by_key @@ -54,11 +56,11 @@ def execute_next_step(self, agent_execution_id): model_api_key = AgentConfiguration.get_model_api_key(session, agent_execution.agent_id, agent_config["model"]) model_llm_source = ModelSourceType.get_model_source_from_model(agent_config["model"]).value try: - vector_store_type = VectorStoreType.get_vector_store_type(agent_config["LTM_DB"]) + vector_store_type = VectorStoreType.get_vector_store_type(get_config("LTM_DB","Redis")) memory = VectorFactory.get_vector_storage(vector_store_type, "super-agent-index1", AgentExecutor.get_embedding(model_llm_source, model_api_key)) - except: - logger.info("Unable to setup the pinecone connection...") + except Exception as e: + logger.info(f"Unable to setup the connection...{e}") memory = None agent_workflow_step = session.query(AgentWorkflowStep).filter( diff --git a/superagi/tools/thinking/prompts/thinking.txt b/superagi/tools/thinking/prompts/thinking.txt index c5fdb002b..ed623e61a 100644 --- a/superagi/tools/thinking/prompts/thinking.txt +++ b/superagi/tools/thinking/prompts/thinking.txt @@ -7,6 +7,9 @@ and the following task, `{task_description}`. Below is last tool response: `{last_tool_response}` +Below is the relevant tool response: +`{relevant_tool_response}` + Perform the task by understanding the problem, extracting variables, and being smart and efficient. Provide a descriptive response, make decisions yourself when confronted with choices and provide reasoning for ideas / decisions. \ No newline at end of file diff --git a/superagi/tools/thinking/tools.py b/superagi/tools/thinking/tools.py index 2c2e0b869..8bdc38ca5 100644 --- a/superagi/tools/thinking/tools.py +++ b/superagi/tools/thinking/tools.py @@ -33,6 +33,7 @@ class ThinkingTool(BaseTool): ) args_schema: Type[ThinkingSchema] = ThinkingSchema goals: List[str] = [] + agent_execution_id:int=None permission_required: bool = False tool_response_manager: Optional[ToolResponseQueryManager] = None @@ -56,6 +57,9 @@ def _execute(self, task_description: str): prompt = prompt.replace("{task_description}", task_description) last_tool_response = self.tool_response_manager.get_last_response() prompt = prompt.replace("{last_tool_response}", last_tool_response) + metadata = {"agent_execution_id":self.agent_execution_id} + relevant_tool_response = self.tool_response_manager.get_relevant_response(query=task_description,metadata=metadata) + prompt = prompt.replace("{relevant_tool_response}",relevant_tool_response) messages = [{"role": "system", "content": prompt}] result = self.llm.chat_completion(messages, max_tokens=self.max_token_limit) return result["content"] diff --git a/superagi/tools/tool_response_query_manager.py b/superagi/tools/tool_response_query_manager.py index 5a2387648..d2caf5e23 100644 --- a/superagi/tools/tool_response_query_manager.py +++ b/superagi/tools/tool_response_query_manager.py @@ -1,12 +1,20 @@ from sqlalchemy.orm import Session from superagi.models.agent_execution_feed import AgentExecutionFeed - +from superagi.vector_store.base import VectorStore class ToolResponseQueryManager: - def __init__(self, session: Session, agent_execution_id: int): + def __init__(self, session: Session, agent_execution_id: int,memory:VectorStore): self.session = session self.agent_execution_id = agent_execution_id + self.memory=memory def get_last_response(self, tool_name: str = None): return AgentExecutionFeed.get_last_tool_response(self.session, self.agent_execution_id, tool_name) + + def get_relevant_response(self, query: str,metadata:dict, top_k: int = 5): + documents = self.memory.get_matching_text(query, metadata=metadata) + relevant_responses = "" + for document in documents["documents"]: + relevant_responses += document.text_content + return relevant_responses diff --git a/superagi/vector_store/embedding/openai.py b/superagi/vector_store/embedding/openai.py index 284f0056a..f578bb16e 100644 --- a/superagi/vector_store/embedding/openai.py +++ b/superagi/vector_store/embedding/openai.py @@ -5,7 +5,19 @@ class OpenAiEmbedding: def __init__(self, api_key, model="text-embedding-ada-002"): self.model = model self.api_key = api_key + + async def get_embedding_async(self, text: str): + try: + openai.api_key = self.api_key + response = await openai.Embedding.create( + input=[text], + engine=self.model + ) + return response['data'][0]['embedding'] + except Exception as exception: + return {"error": exception} + def get_embedding(self, text): try: # openai.api_key = get_config("OPENAI_API_KEY") diff --git a/superagi/vector_store/redis.py b/superagi/vector_store/redis.py index e69de29bb..93e1b906f 100644 --- a/superagi/vector_store/redis.py +++ b/superagi/vector_store/redis.py @@ -0,0 +1,169 @@ +import json +import re +import uuid +from typing import Any, List, Iterable, Mapping +from typing import Optional, Pattern +import traceback +import numpy as np +import redis +from redis.commands.search.field import TagField, VectorField +from redis.commands.search.indexDefinition import IndexDefinition, IndexType + +from superagi.config.config import get_config +from superagi.lib.logger import logger +from superagi.vector_store.base import VectorStore +from superagi.vector_store.document import Document + +DOC_PREFIX = "doc:" + +CONTENT_KEY = "content" +METADATA_KEY = "metadata" +VECTOR_SCORE_KEY = "vector_score" + + +class Redis(VectorStore): + + def delete_embeddings_from_vector_db(self, ids: List[str]) -> None: + pass + + def add_embeddings_to_vector_db(self, embeddings: dict) -> None: + pass + + def get_index_stats(self) -> dict: + pass + + DEFAULT_ESCAPED_CHARS = r"[,.<>{}\[\]\\\"\':;!@#$%^&*()\-+=~\/ ]" + + def __init__(self, index: Any, embedding_model: Any): + """ + Args: + index: An instance of a Redis index. + embedding_model: An instance of a BaseEmbedding model. + vector_group_id: vector group id used to index similar vectors. + """ + redis_url = get_config('REDIS_URL') + self.redis_client = redis.Redis.from_url("redis://" + redis_url + "/0", decode_responses=True) + # self.redis_client = redis.Redis(host=redis_host, port=redis_port) + self.index = index + self.embedding_model = embedding_model + self.content_key = "content", + self.metadata_key = "metadata" + self.index = index + self.vector_key = "content_vector" + + def build_redis_key(self, prefix: str) -> str: + """Build a redis key with a prefix.""" + return f"{prefix}:{uuid.uuid4().hex}" + + def add_texts(self, texts: Iterable[str], + metadatas: Optional[List[dict]] = None, + embeddings: Optional[List[List[float]]] = None, + ids: Optional[list[str]] = None, + **kwargs: Any) -> List[str]: + pipe = self.redis_client.pipeline() + prefix = DOC_PREFIX + str(self.index) + keys = [] + for i, text in enumerate(texts): + id = ids[i] if ids else self.build_redis_key(prefix) + metadata = metadatas[i] if metadatas else {} + embedding = self.embedding_model.get_embedding(text) + embedding_arr = np.array(embedding, dtype=np.float32) + + pipe.hset(id, mapping={CONTENT_KEY: text, self.vector_key: embedding_arr.tobytes(), + METADATA_KEY: json.dumps(metadata)}) + + keys.append(id) + pipe.execute() + return keys + + def get_matching_text(self, query: str, top_k: int = 5, metadata: Optional[dict] = None, **kwargs: Any) -> List[Document]: + + embed_text = self.embedding_model.get_embedding(query) + from redis.commands.search.query import Query + hybrid_fields = self._convert_to_redis_filters(metadata) + + base_query = f"{hybrid_fields}=>[KNN {top_k} @{self.vector_key} $vector AS vector_score]" + return_fields = [METADATA_KEY,CONTENT_KEY, "vector_score",'id'] + query = ( + Query(base_query) + .return_fields(*return_fields) + .sort_by("vector_score") + .paging(0, top_k) + .dialect(2) + ) + + params_dict: Mapping[str, str] = { + "vector": np.array(embed_text) + .astype(dtype=np.float32) + .tobytes() + } + + # print(self.index) + results = self.redis_client.ft(self.index).search(query,params_dict) + + # Prepare document results + documents = [] + for result in results.docs: + documents.append( + Document( + text_content=result.content, + metadata=json.loads(result.metadata) + ) + ) + return {"documents": documents} + + + + def _convert_to_redis_filters(self, metadata: Optional[dict] = None) -> str: + if metadata is not None or len(metadata) == 0: + return "*" + filter_strings = [] + for key in metadata.keys(): + filter_string = "@%s:{%s}" % (key, self.escape_token(str(metadata[key]))) + filter_strings.append(filter_string) + + joined_filter_strings = " & ".join(filter_strings) + return f"({joined_filter_strings})" + + def create_index(self): + try: + # check to see if index exists + temp = self.redis_client.ft(self.index).info() + logger.info(temp) + logger.info("Index already exists!") + except: + vector_dimensions = self.embedding_model.get_embedding("sample") + # schema + schema = ( + TagField("tag"), # Tag Field Name + VectorField(self.vector_key, # Vector Field Name + "FLAT", { # Vector Index Type: FLAT or HNSW + "TYPE": "FLOAT32", # FLOAT32 or FLOAT64 + "DIM": len(vector_dimensions), # Number of Vector Dimensions + "DISTANCE_METRIC": "COSINE", # Vector Search Distance Metric + } + ) + ) + + # index Definition + definition = IndexDefinition(prefix=[DOC_PREFIX], index_type=IndexType.HASH) + + # create Index + self.redis_client.ft(self.index).create_index(fields=schema, definition=definition) + + def escape_token(self, value: str) -> str: + """ + Escape punctuation within an input string. Taken from RedisOM Python. + + Args: + value (str): The input string. + + Returns: + str: The escaped string. + """ + escaped_chars_re = re.compile(Redis.DEFAULT_ESCAPED_CHARS) + + def escape_symbol(match: re.Match) -> str: + return f"\\{match.group(0)}" + + return escaped_chars_re.sub(escape_symbol, value) \ No newline at end of file diff --git a/superagi/vector_store/vector_factory.py b/superagi/vector_store/vector_factory.py index c67f8888b..f6ce73fe7 100644 --- a/superagi/vector_store/vector_factory.py +++ b/superagi/vector_store/vector_factory.py @@ -7,7 +7,8 @@ from superagi.lib.logger import logger from superagi.types.vector_store_types import VectorStoreType from superagi.vector_store import qdrant - +from superagi.vector_store.redis import Redis +from superagi.vector_store.embedding.openai import OpenAiEmbedding from superagi.vector_store.qdrant import Qdrant @@ -72,6 +73,12 @@ def get_vector_storage(cls, vector_store: VectorStoreType, index_name, embedding Qdrant.create_collection(client, index_name, len(sample_embedding)) return qdrant.Qdrant(client, embedding_model, index_name) + + if vector_store == VectorStoreType.REDIS: + index_name = "super-agent-index1" + redis = Redis(index_name, embedding_model) + redis.create_index() + return redis raise ValueError(f"Vector store {vector_store} not supported") diff --git a/tests/unit_tests/agent/test_output_handler.py b/tests/unit_tests/agent/test_output_handler.py index 858fdbc02..45474f217 100644 --- a/tests/unit_tests/agent/test_output_handler.py +++ b/tests/unit_tests/agent/test_output_handler.py @@ -9,6 +9,8 @@ from superagi.helper.json_cleaner import JsonCleaner from superagi.models.agent import Agent from superagi.models.agent_execution_permission import AgentExecutionPermission +import numpy as np +from superagi.agent.output_handler import ToolOutputHandler # Test for ToolOutputHandler @@ -26,7 +28,7 @@ def test_tool_output_handle(parse_mock, execute_mock, get_completed_tasks_mock, # Define what the mock response status should be execute_mock.return_value = Mock(status='PENDING', is_permission_required=False) - handler = ToolOutputHandler(agent_execution_id, agent_config, []) + handler = ToolOutputHandler(agent_execution_id, agent_config, [],None) # Mock session session_mock = MagicMock() @@ -41,6 +43,35 @@ def test_tool_output_handle(parse_mock, execute_mock, get_completed_tasks_mock, parse_mock.assert_called_with(assistant_reply) assert session_mock.add.call_count == 2 + + +@patch('superagi.agent.output_handler.TokenTextSplitter') +def test_add_text_to_memory(TokenTextSplitter_mock): + # Arrange + agent_execution_id = 1 + agent_config = {"agent_id": 2} + tool_output_handler = ToolOutputHandler(agent_execution_id, agent_config,[], None) + + assistant_reply = '{"thoughts": {"text": "This is a task."}}' + tool_response_result = '["Task completed."]' + + text_splitter_mock = MagicMock() + TokenTextSplitter_mock.return_value = text_splitter_mock + text_splitter_mock.split_text.return_value = ["This is a task.", "Task completed."] + + # Mock the VectorStore memory + memory_mock = MagicMock() + tool_output_handler.memory = memory_mock + + # Act + tool_output_handler.add_text_to_memory(assistant_reply, tool_response_result) + + # Assert + TokenTextSplitter_mock.assert_called_once_with(chunk_size=1024, chunk_overlap=10) + text_splitter_mock.split_text.assert_called_once_with('This is a task.["Task completed."]') + memory_mock.add_texts.assert_called_once_with(["This is a task.", "Task completed."], [{"agent_execution_id": agent_execution_id}, {"agent_execution_id": agent_execution_id}]) + + @patch('superagi.models.agent_execution_permission.AgentExecutionPermission') def test_tool_handler_check_permission_in_restricted_mode(op_mock): # Mock the session @@ -54,7 +85,7 @@ def test_tool_handler_check_permission_in_restricted_mode(op_mock): tool = MagicMock() tool.name = "someAction" tool.permission_required = True - handler = ToolOutputHandler(agent_execution_id, agent_config, [tool]) + handler = ToolOutputHandler(agent_execution_id, agent_config, [tool],None) # Act response = handler._check_permission_in_restricted_mode(session_mock, assistant_reply) diff --git a/tests/unit_tests/vector_store/test_redis.py b/tests/unit_tests/vector_store/test_redis.py new file mode 100644 index 000000000..60087c968 --- /dev/null +++ b/tests/unit_tests/vector_store/test_redis.py @@ -0,0 +1,42 @@ +from unittest.mock import MagicMock, patch +import numpy as np +from superagi.vector_store.document import Document +from superagi.vector_store.redis import Redis + + +def test_escape_token(): + redis_object = Redis(None, None) + escaped_token = redis_object.escape_token("An,example.<> string!") + assert escaped_token == "An\\,example\\.\\<\\>\\ string\\!" + +@patch('redis.Redis') +def test_add_texts(redis_mock): + # Arrange + mock_index = "mock_index" + mock_embedding_model = MagicMock() + redis_object = Redis(mock_index, mock_embedding_model) + redis_object.build_redis_key = MagicMock(return_value="mock_key") + texts = ["Hello", "World"] + metadatas = [{"data": 1}, {"data": 2}] + + # Act + redis_object.add_texts(texts, metadatas) + + # Assert + assert redis_object.redis_client.pipeline().hset.call_count == len(texts) + +@patch('redis.Redis') +def test_get_matching_text(redis_mock): + # Arrange + mock_index = "mock_index" + redis_object = Redis(mock_index, None) + redis_object.embedding_model = MagicMock() + redis_object.embedding_model.get_embedding.return_value = np.array([0.1, 0.2, 0.3]) + query = "mock_query" + + # Act + result = redis_object.get_matching_text(query, metadata={}) + + # Assert + redis_object.embedding_model.get_embedding.assert_called_once_with(query) + assert "documents" in result \ No newline at end of file From 3e4b797cab6f3787b603be6e8bf5d85a7e92dc28 Mon Sep 17 00:00:00 2001 From: Sayan Samanta <139119661+sayan1101@users.noreply.github.com> Date: Wed, 23 Aug 2023 11:37:45 +0530 Subject: [PATCH 013/146] Toolkit configuration fix (#1102) --- superagi/controllers/toolkit.py | 6 +++--- tests/unit_tests/controllers/test_toolkit.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/superagi/controllers/toolkit.py b/superagi/controllers/toolkit.py index a61677bef..8fb175e01 100644 --- a/superagi/controllers/toolkit.py +++ b/superagi/controllers/toolkit.py @@ -157,7 +157,8 @@ def install_toolkit_from_marketplace(toolkit_name: str, folder_name=tool['folder_name'], class_name=tool['class_name'], file_name=tool['file_name'], toolkit_id=db_toolkit.id) for config in toolkit['configs']: - ToolConfig.add_or_update(session=db.session, toolkit_id=db_toolkit.id, key=config['key'], value=config['value']) + ToolConfig.add_or_update(session=db.session, toolkit_id=db_toolkit.id, key=config['key'], value=config['value'], key_type = config['key_type'], is_secret = config['is_secret'], is_required = config['is_required']) + return {"message": "ToolKit installed successfully"} @@ -363,5 +364,4 @@ def update_toolkit(toolkit_name: str, organisation: Organisation = Depends(get_u toolkit_id=update_toolkit.id, description=tool["description"]) for tool_config_key in marketplace_toolkit["configs"]: - ToolConfig.add_or_update(db.session, toolkit_id=update_toolkit.id, - key=tool_config_key["key"]) + ToolConfig.add_or_update(db.session, toolkit_id=update_toolkit.id, key=tool_config_key["key"], key_type = tool_config_key['key_type'], is_secret = tool_config_key['is_secret'], is_required = tool_config_key['is_required']) diff --git a/tests/unit_tests/controllers/test_toolkit.py b/tests/unit_tests/controllers/test_toolkit.py index 0694c7666..ed9dcc353 100644 --- a/tests/unit_tests/controllers/test_toolkit.py +++ b/tests/unit_tests/controllers/test_toolkit.py @@ -2,7 +2,7 @@ import pytest from fastapi.testclient import TestClient - +from superagi.types.key_type import ToolConfigKeyType from main import app from superagi.models.organisation import Organisation from superagi.models.tool import Tool @@ -86,11 +86,18 @@ def mock_toolkit_details(): "configs": [ { "key": "config_key_1", - "value": "config_value_1" + "value": "config_value_1", + "value": "config_value_1", + 'key_type': ToolConfigKeyType.STRING, + 'is_secret': True, + 'is_required': False }, { "key": "config_key_2", - "value": "config_value_2" + "value": "config_value_2", + 'key_type': ToolConfigKeyType.FILE, + 'is_secret': True, + 'is_required': False } ] } From 1b1a12e5181f59942ecbccf40e405ac42731b0e5 Mon Sep 17 00:00:00 2001 From: namansleeps Date: Wed, 23 Aug 2023 11:55:04 +0530 Subject: [PATCH 014/146] webhooks compplete frontend --- .../Content/Marketplace/Market.module.css | 21 ++++++++ gui/pages/Dashboard/Settings/Webhooks.js | 54 ++++++++++++++----- gui/pages/_app.css | 1 + 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/gui/pages/Content/Marketplace/Market.module.css b/gui/pages/Content/Marketplace/Market.module.css index 4bd162ade..f4719ede7 100644 --- a/gui/pages/Content/Marketplace/Market.module.css +++ b/gui/pages/Content/Marketplace/Market.module.css @@ -529,3 +529,24 @@ .settings_tab_img{ margin-top: -1px; } + +.checkboxGroup { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + height: 15vh; +} + +.checkboxLabel { + display: flex; + align-items: center; + width: 15vw; + cursor:pointer +} + +.checkboxText { + font-weight: 400; + font-size: 12px; + color: #FFF; + margin-left:5px; +} \ No newline at end of file diff --git a/gui/pages/Dashboard/Settings/Webhooks.js b/gui/pages/Dashboard/Settings/Webhooks.js index 32f104e9b..a3074f22b 100644 --- a/gui/pages/Dashboard/Settings/Webhooks.js +++ b/gui/pages/Dashboard/Settings/Webhooks.js @@ -16,6 +16,15 @@ export default function Webhooks() { const [isLoading, setIsLoading] = useState(true) const [existingWebhook, setExistingWebhook] = useState(false) const [loadingText, setLoadingText] = useState("Loading Webhooks"); + const [selectedCheckboxes, setSelectedCheckboxes] = useState([]); + const checkboxes = [ + { label: 'Agent is running', value: 'checkbox1' }, + { label: 'Agent run is paused', value: 'checkbox2' }, + { label: 'Agent run is completed', value: 'checkbox3' }, + { label: 'Agent is terminated ', value: 'checkbox4' }, + { label: 'Agent run max iteration reached', value: 'checkbox5' }, + ]; + useEffect(() => { loadingTextEffect('Loading Webhooks', setLoadingText, 500); @@ -35,7 +44,7 @@ export default function Webhooks() { return; } - saveWebhook({name : webhookName, url: webhookUrl, headers: {}}) + saveWebhook({name : "Webhook 1", url: webhookUrl, headers: {}}) .then((response) => { setExistingWebhook(true) setWebhookId(response.data.id) @@ -53,13 +62,13 @@ export default function Webhooks() { setIsLoading(false) if(response.data){ setWebhookUrl(response.data.url) - setWebhookName(response.data.name) + // setWebhookName(response.data.name) setExistingWebhook(true) setWebhookId(response.data.id) } else{ setWebhookUrl('') - setWebhookName('') + // setWebhookName('') setExistingWebhook(false) setWebhookId(-1) } @@ -80,6 +89,14 @@ export default function Webhooks() { }); } + const toggleCheckbox = (value) => { + if (selectedCheckboxes.includes(value)) { + setSelectedCheckboxes(selectedCheckboxes.filter((item) => item !== value)); + } else { + setSelectedCheckboxes([...selectedCheckboxes, value]); + } + }; + return (<>
@@ -94,26 +111,37 @@ export default function Webhooks() {
- - -
+ {/**/} + {/**/} + {/*
*/} -
- +
+ +
+ {checkboxes.map((checkbox) => ( + + ))}
- {!existingWebhook &&
+ {!existingWebhook &&
} diff --git a/gui/pages/_app.css b/gui/pages/_app.css index 7be317c86..584b5d7f6 100644 --- a/gui/pages/_app.css +++ b/gui/pages/_app.css @@ -1028,6 +1028,7 @@ p { .justify_space_between{justify-content: space-between} .display_flex{display: inline-flex} +.display_flex_container{display: flex} .align_center{align-items: center} .align_start{align-items: flex-start} From 0ea717b04e7079918ceb20e9f7726baba663c661 Mon Sep 17 00:00:00 2001 From: Rounak Bhatia Date: Wed, 23 Aug 2023 17:03:34 +0530 Subject: [PATCH 015/146] schedule agent fix (#1104) Co-authored-by: Rounak Bhatia --- superagi/jobs/scheduling_executor.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/superagi/jobs/scheduling_executor.py b/superagi/jobs/scheduling_executor.py index e0025725f..b7e62db4e 100644 --- a/superagi/jobs/scheduling_executor.py +++ b/superagi/jobs/scheduling_executor.py @@ -1,7 +1,9 @@ +import ast from datetime import datetime from fastapi import HTTPException from sqlalchemy.orm import sessionmaker +from superagi.models.tool import Tool from superagi.models.workflows.iteration_workflow import IterationWorkflow from superagi.worker import execute_agent @@ -49,16 +51,11 @@ def execute_scheduled_agent(self, agent_id: int, name: str): session.add(db_agent_execution) session.commit() - goal_value = session.query(AgentConfiguration.value).filter(AgentConfiguration.agent_id == agent_id).filter(AgentConfiguration.key == 'goal').first()[0] - instruction_value = session.query(AgentConfiguration.value).filter(AgentConfiguration.agent_id == agent_id).filter(AgentConfiguration.key == 'instruction').first()[0] - - agent_execution_configs = { - "goal": goal_value, - "instruction": instruction_value - } - - - AgentExecutionConfiguration.add_or_update_agent_execution_config(session= session, execution=db_agent_execution,agent_execution_configs=agent_execution_configs) + agent_execution_id = db_agent_execution.id + agent_configurations = session.query(AgentConfiguration).filter(AgentConfiguration.agent_id == agent_id).all() + for agent_config in agent_configurations: + agent_execution_config = AgentExecutionConfiguration(agent_execution_id=agent_execution_id, key=agent_config.key, value=agent_config.value) + session.add(agent_execution_config) organisation = agent.get_agent_organisation(session) From 84085a163f78b0f32c8a3c1983967d7b697bb609 Mon Sep 17 00:00:00 2001 From: Kalki <97698934+jedan2506@users.noreply.github.com> Date: Wed, 23 Aug 2023 17:35:44 +0530 Subject: [PATCH 016/146] Models superagi (#936) Models Superagi --- config_template.yaml | 2 + gui/pages/Content/APM/ApmDashboard.js | 2 +- gui/pages/Content/Agents/AgentCreate.js | 45 ++-- .../Content/Agents/AgentTemplatesList.js | 6 +- gui/pages/Content/Marketplace/Market.js | 50 ++-- .../Content/Marketplace/Market.module.css | 2 +- gui/pages/Content/Marketplace/MarketAgent.js | 5 +- .../Content/Marketplace/MarketKnowledge.js | 4 +- gui/pages/Content/Marketplace/MarketTools.js | 5 +- gui/pages/Content/Models/AddModel.js | 16 ++ .../Content/Models/AddModelMarketPlace.js | 103 ++++++++ gui/pages/Content/Models/MarketModels.js | 57 +++++ gui/pages/Content/Models/ModelDetails.js | 41 +++ gui/pages/Content/Models/ModelForm.js | 163 ++++++++++++ gui/pages/Content/Models/ModelInfo.js | 33 +++ gui/pages/Content/Models/ModelMetrics.js | 91 +++++++ gui/pages/Content/Models/ModelTemplate.js | 48 ++++ gui/pages/Content/Models/Models.js | 36 +++ gui/pages/Content/Toolkits/AddTool.js | 1 - gui/pages/Dashboard/Content.js | 28 ++- gui/pages/Dashboard/Settings/Model.js | 218 +++++++--------- gui/pages/Dashboard/Settings/Settings.js | 37 +-- gui/pages/Dashboard/SideBar.js | 1 + gui/pages/_app.css | 195 +++++++++++++-- gui/pages/api/DashboardService.js | 43 ++++ gui/public/images/google_palm_logo.svg | 9 + gui/public/images/huggingface_logo.svg | 9 + gui/public/images/icon_error.svg | 8 + gui/public/images/icon_info.svg | 8 + gui/public/images/marketplace_download.svg | 10 + gui/public/images/marketplace_logo.png | Bin 0 -> 707 bytes gui/public/images/models.svg | 3 + gui/public/images/openai_logo.svg | 9 + gui/public/images/plus.png | Bin 0 -> 344 bytes gui/public/images/replicate_logo.svg | 9 + gui/utils/utils.js | 20 ++ main.py | 8 + .../520aa6776347_create_models_config.py | 36 +++ .../5d5f801f28e7_create_model_table.py | 42 ++++ .../be1d922bf2ad_create_call_logs_table.py | 39 +++ requirements.txt | 1 + .../agent/agent_iteration_step_handler.py | 37 +-- superagi/agent/agent_message_builder.py | 15 +- superagi/agent/agent_tool_step_handler.py | 11 +- superagi/agent/output_parser.py | 6 +- superagi/agent/prompts/initialize_tasks.txt | 1 - superagi/agent/tool_builder.py | 6 +- superagi/apm/analytics_helper.py | 1 - superagi/apm/call_log_helper.py | 82 ++++++ superagi/controllers/agent_template.py | 4 +- superagi/controllers/models_controller.py | 131 ++++++++++ superagi/helper/models_helper.py | 19 ++ superagi/helper/token_counter.py | 15 +- superagi/jobs/agent_executor.py | 18 +- superagi/llms/hugging_face.py | 110 ++++++++ superagi/llms/llm_model_factory.py | 62 +++-- superagi/llms/replicate.py | 113 +++++++++ superagi/llms/utils/__init__.py | 0 .../llms/utils/huggingface_utils/__init__.py | 0 .../huggingface_utils/public_endpoints.py | 1 + .../llms/utils/huggingface_utils/tasks.py | 85 +++++++ superagi/models/agent.py | 3 +- superagi/models/agent_config.py | 29 +-- superagi/models/call_logs.py | 36 +++ superagi/models/configuration.py | 29 ++- superagi/models/models.py | 198 +++++++++++++++ superagi/models/models_config.py | 121 +++++++++ .../workflows/iteration_workflow_step.py | 2 +- superagi/resource_manager/resource_manager.py | 2 +- superagi/resource_manager/resource_summary.py | 11 +- superagi/tools/code/write_code.py | 6 +- superagi/tools/code/write_spec.py | 6 +- superagi/tools/code/write_test.py | 6 +- superagi/types/model_source_types.py | 4 + superagi/worker.py | 2 +- .../test_agent_iteration_step_handler.py | 2 +- .../agent/test_agent_message_builder.py | 12 +- .../agent/test_agent_tool_step_handler.py | 2 +- tests/unit_tests/apm/test_call_log_helper.py | 79 ++++++ .../controllers/test_models_controller.py | 102 ++++++++ tests/unit_tests/helper/test_token_counter.py | 53 +++- tests/unit_tests/llms/test_hugging_face.py | 77 ++++++ tests/unit_tests/llms/test_model_factory.py | 72 +++--- tests/unit_tests/llms/test_replicate.py | 63 +++++ tests/unit_tests/models/test_call_logs.py | 44 ++++ tests/unit_tests/models/test_models.py | 234 ++++++++++++++++++ tests/unit_tests/models/test_models_config.py | 114 +++++++++ .../unit_tests/tools/code/test_write_code.py | 3 + .../unit_tests/tools/code/test_write_spec.py | 3 + .../unit_tests/tools/code/test_write_test.py | 5 +- tools.json | 6 +- 91 files changed, 3094 insertions(+), 392 deletions(-) create mode 100644 gui/pages/Content/Models/AddModel.js create mode 100644 gui/pages/Content/Models/AddModelMarketPlace.js create mode 100644 gui/pages/Content/Models/MarketModels.js create mode 100644 gui/pages/Content/Models/ModelDetails.js create mode 100644 gui/pages/Content/Models/ModelForm.js create mode 100644 gui/pages/Content/Models/ModelInfo.js create mode 100644 gui/pages/Content/Models/ModelMetrics.js create mode 100644 gui/pages/Content/Models/ModelTemplate.js create mode 100644 gui/pages/Content/Models/Models.js create mode 100644 gui/public/images/google_palm_logo.svg create mode 100644 gui/public/images/huggingface_logo.svg create mode 100644 gui/public/images/icon_error.svg create mode 100644 gui/public/images/icon_info.svg create mode 100644 gui/public/images/marketplace_download.svg create mode 100644 gui/public/images/marketplace_logo.png create mode 100644 gui/public/images/models.svg create mode 100644 gui/public/images/openai_logo.svg create mode 100644 gui/public/images/plus.png create mode 100644 gui/public/images/replicate_logo.svg create mode 100644 migrations/versions/520aa6776347_create_models_config.py create mode 100644 migrations/versions/5d5f801f28e7_create_model_table.py create mode 100644 migrations/versions/be1d922bf2ad_create_call_logs_table.py create mode 100644 superagi/apm/call_log_helper.py create mode 100644 superagi/controllers/models_controller.py create mode 100644 superagi/helper/models_helper.py create mode 100644 superagi/llms/hugging_face.py create mode 100644 superagi/llms/replicate.py create mode 100644 superagi/llms/utils/__init__.py create mode 100644 superagi/llms/utils/huggingface_utils/__init__.py create mode 100644 superagi/llms/utils/huggingface_utils/public_endpoints.py create mode 100644 superagi/llms/utils/huggingface_utils/tasks.py create mode 100644 superagi/models/call_logs.py create mode 100644 superagi/models/models.py create mode 100644 superagi/models/models_config.py create mode 100644 tests/unit_tests/apm/test_call_log_helper.py create mode 100644 tests/unit_tests/controllers/test_models_controller.py create mode 100644 tests/unit_tests/llms/test_hugging_face.py create mode 100644 tests/unit_tests/llms/test_replicate.py create mode 100644 tests/unit_tests/models/test_call_logs.py create mode 100644 tests/unit_tests/models/test_models.py create mode 100644 tests/unit_tests/models/test_models_config.py diff --git a/config_template.yaml b/config_template.yaml index fedd3b642..51c090dec 100644 --- a/config_template.yaml +++ b/config_template.yaml @@ -4,6 +4,8 @@ PINECONE_ENVIRONMENT: YOUR_PINECONE_ENVIRONMENT OPENAI_API_KEY: YOUR_OPEN_API_KEY PALM_API_KEY: YOUR_PALM_API_KEY +REPLICATE_API_TOKEN: YOUR_REPLICATE_API_TOKEN +HUGGING_API_TOKEN: YOUR_HUGGING_FACE_API_TOKEN # For locally hosted LLMs comment out the next line and uncomment the one after # to configure a local llm point your browser to 127.0.0.1:7860 and click on the model tab in text generation web ui. diff --git a/gui/pages/Content/APM/ApmDashboard.js b/gui/pages/Content/APM/ApmDashboard.js index 314ddc915..d56f12466 100644 --- a/gui/pages/Content/APM/ApmDashboard.js +++ b/gui/pages/Content/APM/ApmDashboard.js @@ -75,7 +75,7 @@ export default function ApmDashboard() { const fetchData = async () => { try { const [metricsResponse, agentsResponse, activeRunsResponse, toolsUsageResponse] = await Promise.all([getMetrics(), getAllAgents(), getActiveRuns(), getToolsUsage()]); - const models = ['gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-4-32k', 'google-palm-bison-001']; + const models = ['gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-4-32k', 'google-palm-bison-001', 'replicate-llama13b-v2-chat']; assignDefaultDataPerModel(metricsResponse.data.agent_details.model_metrics, models); assignDefaultDataPerModel(metricsResponse.data.tokens_details.model_metrics, models); diff --git a/gui/pages/Content/Agents/AgentCreate.js b/gui/pages/Content/Agents/AgentCreate.js index 3f7f62b30..04989cb6f 100644 --- a/gui/pages/Content/Agents/AgentCreate.js +++ b/gui/pages/Content/Agents/AgentCreate.js @@ -10,7 +10,7 @@ import { getLlmModels, updateExecution, uploadFile, - getAgentDetails, addAgentRun, + getAgentDetails, addAgentRun, fetchModels, getAgentWorkflows } from "@/pages/api/DashboardService"; import { @@ -56,6 +56,7 @@ export default function AgentCreate({ const [searchValue, setSearchValue] = useState(''); const [showButton, setShowButton] = useState(false); const [showPlaceholder, setShowPlaceholder] = useState(true); + const [modelsArray, setModelsArray] = useState([]); const constraintsArray = [ "If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember.", @@ -68,8 +69,8 @@ export default function AgentCreate({ const [goals, setGoals] = useState(['Describe the agent goals here']); const [instructions, setInstructions] = useState(['']); - const [modelsArray, setModelsArray] = useState([]); - const [model, setModel] = useState(''); + const models = ['gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-4-32k', 'google-palm-bison-001', 'replicate-llama13b-v2-chat'] + const [model, setModel] = useState(models[1]); const modelRef = useRef(null); const [modelDropdown, setModelDropdown] = useState(false); @@ -150,9 +151,9 @@ export default function AgentCreate({ }, [toolNames]); useEffect(() => { - getLlmModels() + fetchModels() .then((response) => { - const models = response.data || []; + const models = response.data.map(model => model.name) || []; const selected_model = localStorage.getItem("agent_model_" + String(internalId)) || ''; setModelsArray(models); if (models.length > 0 && !selected_model) { @@ -160,6 +161,7 @@ export default function AgentCreate({ } else { setModel(selected_model); } + console.log(response) }) .catch((error) => { console.error('Error fetching models:', error); @@ -350,8 +352,8 @@ export default function AgentCreate({ const handleModelSelect = (index) => { setLocalStorageValue("agent_model_" + String(internalId), modelsArray[index], setModel); - if (modelsArray[index] === "google-palm-bison-001") { - setAgentWorkflow("Fixed Task Queue") + if (modelsArray[index] === "google-palm-bison-001" || modelsArray[index] === "replicate-llama13b-v2-chat") { + setAgentType("Fixed Task Queue") } setModelDropdown(false); }; @@ -941,13 +943,28 @@ export default function AgentCreate({ alt="expand-icon"/>
- {modelDropdown &&
- {modelsArray?.map((model, index) => ( -
handleModelSelect(index)} - style={{padding: '12px 14px', maxWidth: '100%'}}> - {model} -
))} -
} + {modelDropdown && ( +
+
+ {modelsArray?.map((model, index) => ( +
handleModelSelect(index)} + style={{padding: '12px 14px', maxWidth: '100%'}}> + {model} +
+ ))} +
+
+
openNewTab(-4, "Marketplace", "Marketplace", false)} className="custom_select_option horizontal_container mxw_100 padding_12_14 gap_6 bt_white"> + marketplace_logo + Browse models from marketplace +
+
openNewTab(-5, "new model", "Add_Model", false)} className="custom_select_option horizontal_container mxw_100 padding_12_14 gap_6 bt_white"> + plus_image + Add new custom model +
+
+
+ )}
diff --git a/gui/pages/Content/Agents/AgentTemplatesList.js b/gui/pages/Content/Agents/AgentTemplatesList.js index 0dedde4b2..797383b04 100644 --- a/gui/pages/Content/Agents/AgentTemplatesList.js +++ b/gui/pages/Content/Agents/AgentTemplatesList.js @@ -74,9 +74,9 @@ export default function AgentTemplatesList({
- {agentTemplates.length > 0 ?
+ {agentTemplates.length > 0 ?
{agentTemplates.map((item) => ( -
handleTemplateClick(item)}>
{item.name}
@@ -84,7 +84,7 @@ export default function AgentTemplatesList({
))} -
diff --git a/gui/pages/Content/Marketplace/Market.js b/gui/pages/Content/Marketplace/Market.js index 55d89fe90..9ab03a4ed 100644 --- a/gui/pages/Content/Marketplace/Market.js +++ b/gui/pages/Content/Marketplace/Market.js @@ -4,7 +4,9 @@ import styles from './Market.module.css'; import MarketKnowledge from './MarketKnowledge'; import MarketAgent from './MarketAgent'; import MarketTools from './MarketTools'; +import MarketModels from '../Models/MarketModels'; import ToolkitTemplate from './ToolkitTemplate'; +import ModelTemplate from "../Models/ModelTemplate"; import {EventBus} from "@/utils/eventBus"; import AgentTemplate from "./AgentTemplate"; import KnowledgeTemplate from "./KnowledgeTemplate"; @@ -64,48 +66,36 @@ export default function Market({env}) {
-
- -
-
- -
-
- -
+ + + +
{activeTab === 'market_tools' && } {activeTab === 'market_knowledge' && } {activeTab === 'market_agents' && } + {activeTab === 'market_models' && }
:
{detailType === 'agent_template' && } {detailType === 'knowledge_template' && } {detailType === 'tool_template' && } + {detailType === 'model_template' && }
}
); diff --git a/gui/pages/Content/Marketplace/Market.module.css b/gui/pages/Content/Marketplace/Market.module.css index 4bd162ade..c309f41cd 100644 --- a/gui/pages/Content/Marketplace/Market.module.css +++ b/gui/pages/Content/Marketplace/Market.module.css @@ -290,7 +290,7 @@ display: flex; justify-content: flex-start; flex-wrap: wrap; - gap: 0.3vw; + gap: 6px; } .agent_resources { diff --git a/gui/pages/Content/Marketplace/MarketAgent.js b/gui/pages/Content/Marketplace/MarketAgent.js index 53fa3f34f..3055ebd27 100644 --- a/gui/pages/Content/Marketplace/MarketAgent.js +++ b/gui/pages/Content/Marketplace/MarketAgent.js @@ -1,6 +1,5 @@ import React, {useEffect, useState} from "react"; import Image from "next/image"; -import styles from './Market.module.css'; import {fetchAgentTemplateList} from "@/pages/api/DashboardService"; import {EventBus} from "@/utils/eventBus"; import {loadingTextEffect} from "@/utils/utils"; @@ -48,8 +47,8 @@ export default function MarketAgent() {
{!isLoading ?
- {agentTemplates.length > 0 ?
{agentTemplates.map((item, index) => ( -
handleTemplateClick(item)}> + {agentTemplates.length > 0 ?
{agentTemplates.map((item, index) => ( +
handleTemplateClick(item)}>
{item.name}
by SuperAgi 
{!isLoading ?
- {knowledgeTemplates.length > 0 ?
{knowledgeTemplates.map((item, index) => ( -
0 ?
{knowledgeTemplates.map((item, index) => ( +
handleTemplateClick(item)}>
diff --git a/gui/pages/Content/Marketplace/MarketTools.js b/gui/pages/Content/Marketplace/MarketTools.js index 87a140d4e..90d1cbccd 100644 --- a/gui/pages/Content/Marketplace/MarketTools.js +++ b/gui/pages/Content/Marketplace/MarketTools.js @@ -1,6 +1,5 @@ import React, {useEffect, useState} from "react"; import Image from "next/image"; -import styles from './Market.module.css'; import {fetchToolTemplateList} from "@/pages/api/DashboardService"; import {EventBus} from "@/utils/eventBus"; import {loadingTextEffect, excludedToolkits, returnToolkitIcon} from "@/utils/utils"; @@ -50,8 +49,8 @@ export default function MarketTools() {
{!isLoading ?
- {toolTemplates.length > 0 ?
{toolTemplates.map((item) => ( -
handleTemplateClick(item)}> + {toolTemplates.length > 0 ?
{toolTemplates.map((item) => ( +
handleTemplateClick(item)}>
tool-icon
diff --git a/gui/pages/Content/Models/AddModel.js b/gui/pages/Content/Models/AddModel.js new file mode 100644 index 000000000..206a0c468 --- /dev/null +++ b/gui/pages/Content/Models/AddModel.js @@ -0,0 +1,16 @@ +import React, {useEffect, useState} from "react"; +import ModelForm from "./ModelForm"; + +export default function AddModel(internalId){ + return( +
+
+
+
+ +
+
+
+
+ ) +} \ No newline at end of file diff --git a/gui/pages/Content/Models/AddModelMarketPlace.js b/gui/pages/Content/Models/AddModelMarketPlace.js new file mode 100644 index 000000000..1c0adafeb --- /dev/null +++ b/gui/pages/Content/Models/AddModelMarketPlace.js @@ -0,0 +1,103 @@ +import React, {useState, useEffect} from "react"; +import Image from "next/image"; +import {openNewTab, modelIcon} from "@/utils/utils"; +import {fetchApiKey, storeModel} from "@/pages/api/DashboardService"; +import {toast} from "react-toastify"; + +export default function AddModelMarketPlace(template){ + const [modelTokenLimit, setModelTokenLimit] = useState(4096); + const [modelVersion, setModelVersion] = useState(''); + const [modelEndpoint, setModelEndpoint] = useState(''); + const [tokenError, setTokenError] = useState(false); + const [templateData, setTemplateData] = useState(template.template); + const [isLoading, setIsLoading] = useState(false); + + useEffect(()=>{ + console.log(templateData) + checkModelProvider().then().catch(); + },[]) + + const checkModelProvider = async () => { + const response = await fetchApiKey(templateData.provider); + if(response && response.data && response.data[0].api_key === '') { + setTokenError(true) + return true + } + else { + setTokenError(false) + return false + } + } + + const storeModelDetails = () => { + storeModel(templateData.model_name, templateData.description, modelEndpoint, templateData.model_provider_id, modelTokenLimit, "Marketplace", modelVersion).then((response) =>{ + setIsLoading(false) + if (response.data.error) { + toast.error(response.data.error,{autoClose: 1800}); + } else if (response.data.success) { + toast.success(response.data.success,{autoClose: 1800}); + } + }).catch((error) => { + console.log("SORRY, There was an error storing the model details" + error); + setIsLoading(false) + }); + } + + return( +
+
+
+
+ Add Model + +
+ {templateData.model_name} +
+ By {templateData.provider} ·  + logo-icon + {templateData.provider} +
+
+ {templateData.provider === 'Hugging Face' &&
+ {templateData.provider} Model Endpoint + setModelEndpoint(event.target.value)}/> +
} + + {templateData.provider === 'Replicate' &&
+ {templateData.provider} Version + setModelVersion(event.target.value)}/> +
} + + Token Limit + setModelTokenLimit(+event.target.value)} /> + + {tokenError &&
+ error-icon +
+ The {templateData.provider} auth token is not added to your settings. In order to start using the model, you need to add the auth token to your settings. You can find the auth token in the {templateData.provider} dashboard. +
+ + +
+
+
} + +
+ error-icon +
+ In order to get the endpoint for this model, you will need to deploy it on your Replicate dashboard. Once you have deployed your model on Hugging Face, you will be able to access the endpoint through the Hugging Face dashboard. The endpoint is a URL that you can use to send requests to your model. + +
+
+ + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/gui/pages/Content/Models/MarketModels.js b/gui/pages/Content/Models/MarketModels.js new file mode 100644 index 000000000..5b864febf --- /dev/null +++ b/gui/pages/Content/Models/MarketModels.js @@ -0,0 +1,57 @@ +import React, {useState, useEffect} from "react"; +import styles from "@/pages/Content/Marketplace/Market.module.css"; +import Image from "next/image"; +import {loadingTextEffect, modelIcon, returnToolkitIcon} from "@/utils/utils"; +import {EventBus} from "@/utils/eventBus"; +import {fetchMarketPlaceModel} from "@/pages/api/DashboardService"; + +export default function MarketModels(){ + const [showMarketplace, setShowMarketplace] = useState(false); + const [isLoading, setIsLoading] = useState(false) + const [loadingText, setLoadingText] = useState("Loading Models"); + const [modelTemplates, setModelTemplates] = useState([]); + + useEffect(() => { + loadingTextEffect('Loading Models', setLoadingText, 500); + },[]); + + useEffect(() => { + fetchMarketPlaceModel().then((response) => { + console.log(response.data) + setModelTemplates(response.data) + }) + },[]) + + function handleTemplateClick(item) { + const contentType = 'model_template'; + EventBus.emit('openTemplateDetails', {item, contentType}); + } + + return( +
+
+ {!isLoading ?
+ {modelTemplates.length > 0 ?
{modelTemplates.map((item) => ( +
handleTemplateClick(item)}> +
{item.model_name && item.model_name.includes('/') ? item.model_name.split('/')[1] : item.model_name}
+
+ by { item.model_name && item.model_name.includes('/') ? item.model_name.split('/')[0] : item.provider } + is_verified· + source-icon + {item.provider}· + download-icon + {item.installs} +
+
{item.description}
+
+ ))}
:
+ no-permissions + No Models found! +
} +
:
+
{loadingText}
+
} +
+
+ ) +} \ No newline at end of file diff --git a/gui/pages/Content/Models/ModelDetails.js b/gui/pages/Content/Models/ModelDetails.js new file mode 100644 index 000000000..751b50568 --- /dev/null +++ b/gui/pages/Content/Models/ModelDetails.js @@ -0,0 +1,41 @@ +import React, {useState, useEffect} from "react"; +import Image from "next/image"; +import ModelMetrics from "./ModelMetrics"; +import ModelInfo from "./ModelInfo"; +import {fetchModel} from "@/pages/api/DashboardService"; + +export default function ModelDetails({modelId, modelName}){ + const [modelDetails, setModelDetails] = useState([]) + const [selectedOption, setSelectedOption] = useState('metrics') + + useEffect(() => { + const fetchModelDetails = async () => { + try { + const response = await fetchModel(modelId); + console.log(response.data) + setModelDetails(response.data) + } catch(error) { + console.log(`Error Fetching the Details of the Model ${modelName}`, error) + } + }; + + fetchModelDetails().then().catch(); + },[]) + + return( +
+
+ { modelDetails.name ? (modelDetails.name.split('/')[1] || modelDetails.name) : ""} + {modelDetails.description} +
+ + +
+
+ {selectedOption === 'metrics' && } + {selectedOption === 'details' && } +
+ ) +} \ No newline at end of file diff --git a/gui/pages/Content/Models/ModelForm.js b/gui/pages/Content/Models/ModelForm.js new file mode 100644 index 000000000..cf9eb430e --- /dev/null +++ b/gui/pages/Content/Models/ModelForm.js @@ -0,0 +1,163 @@ +import React, {useEffect, useRef, useState} from "react"; +import {removeTab, openNewTab, createInternalId} from "@/utils/utils"; +import Image from "next/image"; +import {fetchApiKey, storeModel, verifyEndPoint} from "@/pages/api/DashboardService"; +import {BeatLoader, ClipLoader} from "react-spinners"; +import {ToastContainer, toast} from 'react-toastify'; + +export default function ModelForm(internalId){ + const models = ['OpenAI','Replicate','Hugging Face','Google Palm']; + const [selectedModel, setSelectedModel] = useState('Select a Model'); + const [modelName, setModelName] = useState(''); + const [modelDescription, setModelDescription] = useState(''); + const [modelTokenLimit, setModelTokenLimit] = useState(4096); + const [modelEndpoint, setModelEndpoint] = useState(''); + const [modelDropdown, setModelDropdown] = useState(false); + const [modelVersion, setModelVersion] = useState(''); + const [tokenError, setTokenError] = useState(false); + const [lockAddition, setLockAddition] = useState(true); + const [isLoading, setIsLoading] = useState(false) + const modelRef = useRef(null); + + useEffect(() => { + function handleClickOutside(event) { + if (modelRef.current && !modelRef.current.contains(event.target)) { + setModelDropdown(false) + } + } + },[]); + + useEffect(() => { + const fetchMyAPI = async () => { + const error = await checkModelProvider(selectedModel) + if(selectedModel !== 'Select a Model' && !error) + setLockAddition(false) + else + setLockAddition(true) + } + + fetchMyAPI(); + },[selectedModel]) + + const handleModelSelect = async (index) => { + setSelectedModel(models[index]) + setModelDropdown(false); + } + + const checkModelProvider = async (model_provider) => { + const response = await fetchApiKey(model_provider); + if(selectedModel !== 'Select a Model'){ + if(response && response.data && response.data[0].api_key === '') { + setTokenError(true) + return true + } + else { + setTokenError(false) + return false + } + } + } + + const handleAddModel = () =>{ + setIsLoading(true) + fetchApiKey(selectedModel).then((response) =>{ + if(response.data.length > 0) + { + const modelProviderId = response.data[0].id + verifyEndPoint(response.data[0].api_key, modelEndpoint, selectedModel).then((response) =>{ + console.log(response) + if(response.data.success) + storeModelDetails(modelProviderId) + else{ + toast.error("The Endpoint is not Valid",{autoClose: 1800}); + setIsLoading(false); + } + }).catch((error) => { + console.log("Error Message:: " + error) + }) + } + }) + } + + const storeModelDetails = (modelProviderId) => { + storeModel(modelName,modelDescription, modelEndpoint, modelProviderId, modelTokenLimit, "Custom", modelVersion).then((response) =>{ + setIsLoading(false) + if (response.data.error) { + toast.error(response.data.error,{autoClose: 1800}); + } else if (response.data.success) { + toast.success(response.data.success,{autoClose: 1800}); + } + }).catch((error) => { + console.log("SORRY, There was an error storing the model details" + error); + setIsLoading(false) + }); + } + + return( +
+
Add new model
+ + Name + setModelName(event.target.value)}/> + + Description +