From 3e040b8a0698c8065df51fa021f7e1294a4f4d1f Mon Sep 17 00:00:00 2001 From: Ovidiu Rusu Date: Wed, 9 Oct 2024 16:49:31 +0300 Subject: [PATCH] Update Agent Connector with the new Agent API (#81) --- actions/agent-connector/CHANGELOG.md | 8 ++ actions/agent-connector/README.md | 50 ++++++++- actions/agent-connector/actions.py | 155 +++++++++++++++------------ actions/agent-connector/package.yaml | 2 +- 4 files changed, 142 insertions(+), 73 deletions(-) diff --git a/actions/agent-connector/CHANGELOG.md b/actions/agent-connector/CHANGELOG.md index 6f731408..44dd9f97 100644 --- a/actions/agent-connector/CHANGELOG.md +++ b/actions/agent-connector/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/). +## [2.0.0] + +- Support new version of Agent API + +## [1.0.1] - 2024-10-09 + +- Update dependencies + ## [1.0.0] - 2024-07-25 First version published, changelog tracking starts. diff --git a/actions/agent-connector/README.md b/actions/agent-connector/README.md index e93dcb35..3b19fa58 100644 --- a/actions/agent-connector/README.md +++ b/actions/agent-connector/README.md @@ -1,7 +1,51 @@ -# Action Bootstrapping +In Sema4.ai Studio, let your agents talk to each other seamlessly. -TBD +## Overview + +The Agent Connector allows agents within the Sema4.ai platform to communicate with each other through a simple and +intuitive interface. This connector provides actions to create communication threads and send messages between agents, +facilitating smooth and efficient inter-agent communication. + +Currently supported features: + +- Get agents +- Get agent by name +- Get threads +- Get a thread +- Create a thread +- Send a message ## Prompts -TBD +``` +use wayback machine to find how robocorp.com has changed over past 2yrs +``` + +> **Changes to Robocorp.com Over the Past 2 Years** +> Snapshot from September 1, 2022 +> +> ... Content shortened ... +> +> Snapshot from August 13, 2024 +> +> ... Content shortened ... +> + +## How It Works + +1. **Creating a Communication Thread**: + - The `create_thread` action allows you to create a new thread for communication with an agent by specifying the + agent's name and a user-defined thread name. This action internally fetches the list of available agents and + validates the agent name before creating the thread. + +2. **Sending Messages**: + - The `send_message` action enables you to send messages within a specified thread and retrieve the assistant's + response. This action ensures smooth communication by providing only the relevant content of the response or an + error message if the operation fails. + +By using these actions, agents within the Sema4.ai platform can effectively communicate, enabling seamless collaboration +and information exchange.In Sema4.ai Desktop let your agents talk to each other. + +## Caveats + +- Agent API is not GA, and is likely to change before release. This action package is experimental and only for demos. diff --git a/actions/agent-connector/actions.py b/actions/agent-connector/actions.py index f8877404..9c8f9707 100644 --- a/actions/agent-connector/actions.py +++ b/actions/agent-connector/actions.py @@ -1,99 +1,116 @@ import json -from sema4ai.actions import action + import requests +from sema4ai.actions import action -API_URL = "http://localhost:8100" +API_URL = "http://localhost:8000" -def _get_all_agents(): +@action +def get_all_agents() -> str: """Fetches a list of all available agents with their IDs and names. Returns: - list or str: A list of dictionaries with assistant IDs and names, - or an error message if the request fails. + A json of the list of dictionaries with agent IDs and names, or an error message if the request fails. """ - url = f"{API_URL}/assistants/" + url = f"{API_URL}/api/v1/agents/" response = requests.get(url) if response.status_code == 200: - assistants = response.json() - return [{"assistant_id": assistant["assistant_id"], "name": assistant["name"]} for assistant in assistants] + agents = response.json() + result = [{"agent_id": agent["id"], "name": agent["name"]} for agent in agents] + return json.dumps(result) else: return f"Error fetching agents: {response.status_code} {response.text}" @action -def create_test_agent(agent_name: str, action_server_url: str) -> str: - """Creates a test agent with the given parameters. +def get_agent_by_name(name: str) -> str: + """Fetches an agent by name. Args: - agent_name (str): The name of the agent. - action_server_url (str): The URL of the action server. + name: The name of the agent Returns: - str: The created agent ID, or error message if the call fails. + A string with the agent ID and name or error message. """ - system_message = f"You are an assistant with the following name: {agent_name}.\nThe current date and time is: {{CURRENT_DATETIME}}.\nYour instructions are to follow users instructions." - url = f"{API_URL}/assistants" - payload = { - "name": agent_name, - "config": { - "configurable": { - "type": "agent", - "type==agent/agent_type": "GPT 4o", - "type==agent/interrupt_before_action": False, - "type==agent/retrieval_description": "Can be used to look up information that was uploaded to this assistant.\nIf the user is referencing particular files, that is often a good hint that information may be here.\nIf the user asks a vague question, they are likely meaning to look up info from this retriever, and you should call it!", - "type==agent/system_message": system_message, - "type==agent/description": "Default description for the agent", - "type==agent/tools": [ - { - "type": "action_server_by_sema4ai", - "name": "Action Server by Sema4.ai", - "description": "Run AI actions with [Sema4.ai Action Server](https://github.com/Sema4AI/actions).", - "config": { - "url": action_server_url, - "api_key": "APIKEY", - "isBundled": "false", - } - } - ] - } - } - } - response = requests.post(url, json=payload) + url = f"{API_URL}/api/v1/agents/" + response = requests.get(url) if response.status_code == 200: - return response.json()["assistant_id"] + agents = response.json() + for agent in agents: + if agent["name"] == name: + return agent["id"] + return f"Error fetching agent, No agent with name '{name}'" else: - return f"Error creating agent: {response.status_code} {response.text}" + return f"Error fetching agents: {response.status_code} {response.text}" @action -def create_thread(agent_name: str, thread_name: str) -> str: - """Creates a new thread for communication with an agent. +def get_thread(agent_name: str, thread_name: str) -> str: + """Fetches a thread for an agent. - Note: Agent names are pre-defined and must match existing agent names. + Args: + agent_name: The name of the agent + thread_name: The name of the thread + + Returns: + The thread ID or error message. + """ + + agent_id = get_agent_by_name(agent_name) + url = f"{API_URL}/api/v1/threads/" + response = requests.get(url) + if response.status_code == 200: + threads = response.json() + for thread in threads: + if thread["agent_id"] == agent_id and thread["name"] == thread_name: + return thread["thread_id"] + return f"Error fetching thread, No thread for agent ID '{agent_id}' and '{thread_name}'" + else: + return f"Error fetching threads: {response.status_code} {response.text}" + + +@action +def get_threads(agent_id: str) -> str: + """Fetches all threads for an agent. Args: - agent_name (str): The name of the pre-defined agent. - thread_name (str): The name of the thread (user-defined). + agent_id: The ID of the agent Returns: - str: The thread ID, or error message if the call fails. + A json string with the List of threads or error message. """ - agents = _get_all_agents() - if isinstance(agents, str): - return agents - assistant = next((agent for agent in agents if agent["name"] == agent_name), None) - if assistant is None: - available_agents = ", ".join(agent["name"] for agent in agents) - return f"No agent found with name '{agent_name}'. Available agents: {available_agents}" + url = f"{API_URL}/api/v1/threads/" + response = requests.get(url) + if response.status_code == 200: + threads = response.json() + result = [] + for thread in threads: + if thread["agent_id"] == agent_id: + result.append( + {"thread_id": thread["thread_id"], "name": thread["name"]} + ) + return json.dumps(result) + else: + return f"Error fetching threads: {response.status_code} {response.text}" - assistant_id = assistant["assistant_id"] - url = f"{API_URL}/threads" - payload = { - "name": thread_name, - "assistant_id": assistant_id - } + +@action +def create_thread(agent_id: str, thread_name: str) -> str: + """Creates a new thread for communication with an agent. + + Note: Agent names are pre-defined and must match existing agent names. + + Args: + agent_id: The id of the agent to create thread in. Use tools get_all_agents or get_agent_by_name to get the id of an agent based on it's name. + thread_name: The name of the thread to be created (user-defined). + + Returns: + The thread ID, or error message if the call fails. + """ + url = f"{API_URL}/api/v1/threads" + payload = {"name": thread_name, "agent_id": agent_id} response = requests.post(url, json=payload) if response.status_code == 200: return response.json()["thread_id"] @@ -108,22 +125,22 @@ def send_message(thread_id: str, message: str) -> str: Note: The thread ID must be obtained from a successful call to `create_thread`. Args: - thread_id (str): The thread ID obtained from `create_thread`. - message (str): The message content. + thread_id: The thread ID obtained from `create_thread`. + message: The message content. Returns: - str: The agent's response, or error message if the call fails. + The agent's response, or error message if the call fails. """ - url = f"{API_URL}/runs/stream" + url = f"{API_URL}/api/v1/runs/stream" payload = { "thread_id": thread_id, "input": [ { "content": message, - "type": 'human', + "type": "human", "example": False, }, - ] + ], } response = requests.post(url, json=payload, stream=True) @@ -134,7 +151,7 @@ def send_message(thread_id: str, message: str) -> str: for line in response.iter_lines(): if line: - decoded_line = line.decode('utf-8') + decoded_line = line.decode("utf-8") if decoded_line.startswith("data: "): collected_data.append(decoded_line[6:]) diff --git a/actions/agent-connector/package.yaml b/actions/agent-connector/package.yaml index db2bc456..26ccd2f7 100644 --- a/actions/agent-connector/package.yaml +++ b/actions/agent-connector/package.yaml @@ -5,7 +5,7 @@ name: Agent Connector description: Actions to connect agents with each other # Package version number, recommend using semver.org -version: 1.0.1 +version: 2.0.0 # Required: A link to where the documentation on the package lives. documentation: https://sema4.ai/