From 254a5cffd38f96e663be8c0f4be8cabe5a61fdcb Mon Sep 17 00:00:00 2001 From: Faridun Mirzoev Date: Sun, 29 Mar 2026 16:13:00 -0700 Subject: [PATCH 1/3] Add AG2 framework integration guide Adds documentation for using Arcade tools with AG2 (formerly AutoGen), an open-source multi-agent framework. Includes quick start example with AssistantAgent + ConversableAgent pattern and register_for_llm/register_for_execution tool registration. --- app/_components/agent-framework-tabs.tsx | 7 + app/en/get-started/agent-frameworks/_meta.tsx | 3 + .../agent-frameworks/ag2/_meta.tsx | 16 + .../ag2/use-arcade-tools/page.mdx | 378 ++++++++++++++++++ app/en/home/landing-page.tsx | 13 +- public/images/icons/ag2.svg | 40 ++ 6 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 app/en/get-started/agent-frameworks/ag2/_meta.tsx create mode 100644 app/en/get-started/agent-frameworks/ag2/use-arcade-tools/page.mdx create mode 100644 public/images/icons/ag2.svg diff --git a/app/_components/agent-framework-tabs.tsx b/app/_components/agent-framework-tabs.tsx index ef7b703b0..bb95ab009 100644 --- a/app/_components/agent-framework-tabs.tsx +++ b/app/_components/agent-framework-tabs.tsx @@ -6,6 +6,13 @@ export function AgentFrameworkTabs() {
+ + + +You will build an AG2 agent that uses Arcade tools to help users with Gmail. + + + + + +- +- [Obtain an Arcade API key](/get-started/setup/api-keys) +- The [`uv` package manager](https://docs.astral.sh/uv/) + + + + + +- How to retrieve Arcade tools and register them with AG2 agents +- How to build an AG2 agent with Arcade tools +- How to implement "just in time" (JIT) tool authorization using Arcade's client + + + + +## The agent architecture you will build in this guide + +AG2 provides a [ConversableAgent](https://docs.ag2.ai/latest/docs/user-guide/basic-concepts/conversable-agent/) class that implements a conversational agent. In this guide, you will use an `AssistantAgent` (the LLM-backed agent) and a `ConversableAgent` (which executes tools on behalf of the user) to create an interactive Gmail assistant that can call Arcade tools. + +## Integrate Arcade tools into an AG2 agent + + + +### Create a new project + +Create a new directory and initialize a virtual environment: + +```bash +mkdir ag2-arcade-example +cd ag2-arcade-example +uv init +uv venv +``` + + + + +```bash +source .venv/bin/activate +``` + + + + +```powershell +. ".venv\Scripts\Activate.ps1" +``` + + + + +Install the necessary packages: + +```bash +uv add "ag2[openai]" arcadepy +``` + +Create a new file called `.env` and add the following environment variables: + +```env filename=".env" +# Arcade API key +ARCADE_API_KEY=YOUR_ARCADE_API_KEY +# Arcade user ID (this is the email address you used to login to Arcade) +ARCADE_USER_ID={arcade_user_id} +# OpenAI API key +OPENAI_API_KEY=YOUR_OPENAI_API_KEY +``` + +### Import the necessary packages + +Create a new file called `main.py` and add the following code: + +```python filename="main.py" +import os +from arcadepy import Arcade +from autogen import AssistantAgent, ConversableAgent +from dotenv import load_dotenv +``` + +- `Arcade`: The Arcade client for tool authorization and execution. +- `AssistantAgent`: The LLM-backed agent that decides which tools to call. +- `ConversableAgent`: Executes tools on behalf of the user. + + + AG2 uses the `autogen` package namespace. When you install `ag2[openai]`, the + imports come from the `autogen` module. + + +### Configure the clients + +```python filename="main.py" +load_dotenv() + +# Initialize Arcade client +arcade = Arcade(api_key=os.environ["ARCADE_API_KEY"]) +ARCADE_USER_ID = os.environ["ARCADE_USER_ID"] + +# LLM configuration for AG2 +llm_config = { + "config_list": [ + {"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]} + ], +} +``` + +### Write a helper function to call Arcade tools + +This function handles tool authorization and execution through the Arcade client. It checks whether you have authorized access to the requested tool, prompts you to authorize if needed, and then executes the tool. + + + This function captures the authorization flow outside of the agent's context, + which is a good practice for security and context engineering. By handling + everything in the helper function, you remove the risk of the LLM replacing + the authorization URL or leaking it, and you keep the context free from any + authorization-related traces, which reduces the risk of hallucinations, and + reduces context bloat. + + +```python filename="main.py" +def call_arcade_tool(tool_name: str, inputs: dict) -> dict: + """Authorize (if needed) and execute an Arcade tool, returning the output.""" + auth = arcade.tools.authorize(tool_name=tool_name, user_id=ARCADE_USER_ID) + if auth.status != "completed": + print(f"\n[Auth required] Visit this URL to authorize {tool_name}:\n {auth.url}\n") + arcade.auth.wait_for_completion(auth.id) + + result = arcade.tools.execute( + tool_name=tool_name, + input=inputs, + user_id=ARCADE_USER_ID, + ) + if not result.output: + return {} + data = result.output.model_dump() + return data.get("value") or {} +``` + +### Define tool wrapper functions + +AG2 registers tools as typed Python functions. Define a wrapper for each Arcade tool you want the agent to use. Each function calls the `call_arcade_tool` helper with the appropriate tool name and inputs. + +```python filename="main.py" +def list_emails(max_results: int = 10, is_unread: bool = False) -> dict: + """List emails from Gmail inbox. + - max_results: maximum number of emails to return (default 10) + - is_unread: if True, return only unread emails + """ + return call_arcade_tool( + "Gmail.ListEmails", + {"max_results": max_results, "is_unread": is_unread}, + ) + + +def send_email(to: str, subject: str, body: str) -> dict: + """Send a new email. + - to: recipient email address + - subject: email subject line + - body: plain-text email body + """ + return call_arcade_tool( + "Gmail.SendEmail", + {"recipient": to, "subject": subject, "body": body}, + ) + + +def reply_to_email(email_id: str, body: str) -> dict: + """Reply to an email by message ID. + - email_id: ID of the message to reply to + - body: plain-text reply body + """ + return call_arcade_tool( + "Gmail.ReplyToEmail", + {"reply_to_message_id": email_id, "body": body, "reply_to_whom": "ONLY_THE_SENDER"}, + ) +``` + +### Create the agents and register the tools + +Create the `AssistantAgent` and `ConversableAgent`, then register each tool function using the `register_for_llm` and `register_for_execution` pattern. This tells the assistant which tools it can call and the user proxy how to execute them. + +```python filename="main.py" +assistant = AssistantAgent( + name="GmailAssistant", + llm_config=llm_config, + system_message="You are a helpful Gmail assistant. Use the provided tools to help the user manage their email.", +) + +user_proxy = ConversableAgent( + name="User", + human_input_mode="ALWAYS", + is_termination_msg=lambda msg: "TERMINATE" in (msg.get("content") or ""), +) + +# Register tools: each function is registered for both LLM (caller) and execution (executor) +for func, description in [ + (list_emails, "List emails from Gmail inbox. Filter by unread status and max results."), + (send_email, "Send a new email to a recipient with subject and body."), + (reply_to_email, "Reply to an email by message ID with a body."), +]: + user_proxy.register_for_execution()(func) + assistant.register_for_llm(description=description)(func) +``` + +### Start the conversation + +```python filename="main.py" +if __name__ == "__main__": + print("Gmail Assistant ready. Type your request (or 'exit' to quit).") + while True: + message = input("\nYou: ").strip() + if not message or message.lower() in ("exit", "quit"): + break + user_proxy.run( + recipient=assistant, + message=message, + clear_history=False, + ).process() +``` + +### Run the agent + +```bash +uv run main.py +``` + +You should see the agent responding to your prompts, handling tool calls and authorization requests. Here are some example prompts you can try: + +- "Show my unread emails" +- "Send an email to someone@example.com about scheduling a demo" +- "Summarize my latest 3 emails" + + + +## Tips for selecting tools + +- **Relevance**: Pick only the tools you need. Avoid using all tools at once. +- **Avoid conflicts**: Be mindful of duplicate or overlapping functionality. + +## Next steps + +Now that you have integrated Arcade tools into your AG2 agent, you can: + +- Add more tools from different MCP servers, such as Slack or Notion. +- Explore [AG2's group chat](https://docs.ag2.ai/latest/docs/user-guide/advanced-concepts/groupchat/groupchat/) patterns to coordinate multiple agents. +- Check out the [build-with-ag2 examples](https://github.com/ag2ai/build-with-ag2/tree/main/arcade) for more integration patterns. + +## Example code + +
+**main.py** (full file) + +```python filename="main.py" +import os +from arcadepy import Arcade +from autogen import AssistantAgent, ConversableAgent +from dotenv import load_dotenv + +load_dotenv() + +# Initialize Arcade client +arcade = Arcade(api_key=os.environ["ARCADE_API_KEY"]) +ARCADE_USER_ID = os.environ["ARCADE_USER_ID"] + +# LLM configuration for AG2 +llm_config = { + "config_list": [ + {"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]} + ], +} + + +def call_arcade_tool(tool_name: str, inputs: dict) -> dict: + """Authorize (if needed) and execute an Arcade tool, returning the output.""" + auth = arcade.tools.authorize(tool_name=tool_name, user_id=ARCADE_USER_ID) + if auth.status != "completed": + print(f"\n[Auth required] Visit this URL to authorize {tool_name}:\n {auth.url}\n") + arcade.auth.wait_for_completion(auth.id) + + result = arcade.tools.execute( + tool_name=tool_name, + input=inputs, + user_id=ARCADE_USER_ID, + ) + if not result.output: + return {} + data = result.output.model_dump() + return data.get("value") or {} + + +def list_emails(max_results: int = 10, is_unread: bool = False) -> dict: + """List emails from Gmail inbox. + - max_results: maximum number of emails to return (default 10) + - is_unread: if True, return only unread emails + """ + return call_arcade_tool( + "Gmail.ListEmails", + {"max_results": max_results, "is_unread": is_unread}, + ) + + +def send_email(to: str, subject: str, body: str) -> dict: + """Send a new email. + - to: recipient email address + - subject: email subject line + - body: plain-text email body + """ + return call_arcade_tool( + "Gmail.SendEmail", + {"recipient": to, "subject": subject, "body": body}, + ) + + +def reply_to_email(email_id: str, body: str) -> dict: + """Reply to an email by message ID. + - email_id: ID of the message to reply to + - body: plain-text reply body + """ + return call_arcade_tool( + "Gmail.ReplyToEmail", + {"reply_to_message_id": email_id, "body": body, "reply_to_whom": "ONLY_THE_SENDER"}, + ) + + +assistant = AssistantAgent( + name="GmailAssistant", + llm_config=llm_config, + system_message="You are a helpful Gmail assistant. Use the provided tools to help the user manage their email.", +) + +user_proxy = ConversableAgent( + name="User", + human_input_mode="ALWAYS", + is_termination_msg=lambda msg: "TERMINATE" in (msg.get("content") or ""), +) + +# Register tools: each function is registered for both LLM (caller) and execution (executor) +for func, description in [ + (list_emails, "List emails from Gmail inbox. Filter by unread status and max results."), + (send_email, "Send a new email to a recipient with subject and body."), + (reply_to_email, "Reply to an email by message ID with a body."), +]: + user_proxy.register_for_execution()(func) + assistant.register_for_llm(description=description)(func) + +if __name__ == "__main__": + print("Gmail Assistant ready. Type your request (or 'exit' to quit).") + while True: + message = input("\nYou: ").strip() + if not message or message.lower() in ("exit", "quit"): + break + user_proxy.run( + recipient=assistant, + message=message, + clear_history=False, + ).process() +``` + +
diff --git a/app/en/home/landing-page.tsx b/app/en/home/landing-page.tsx index 997f6c327..dfc0cc430 100644 --- a/app/en/home/landing-page.tsx +++ b/app/en/home/landing-page.tsx @@ -137,6 +137,12 @@ const POPULAR_INTEGRATIONS_ROW2 = [ // Supported agent frameworks const FRAMEWORKS = [ + { + name: "AG2", + href: "/get-started/agent-frameworks/ag2/use-arcade-tools", + icon: "/images/icons/ag2.svg", + invertInDark: true, + }, { name: "LangChain", href: "/guides/agent-frameworks/langchain/use-arcade-tools", @@ -386,10 +392,15 @@ export function LandingPage() { {/* Power Your Agent — desktop: col 2 row 3 */}
+ + + + + + + + + + + From 2d36fd3e3337405c0d7dfed057b17ed306ccbb19 Mon Sep 17 00:00:00 2001 From: Mateo Torres Date: Sun, 29 Mar 2026 22:12:48 -0300 Subject: [PATCH 2/3] replicated the structure of other tutorials on the AG2 integration getting started guide --- .../ag2/use-arcade-tools/page.mdx | 405 +++++++++++++----- 1 file changed, 289 insertions(+), 116 deletions(-) diff --git a/app/en/get-started/agent-frameworks/ag2/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/ag2/use-arcade-tools/page.mdx index dfe689c97..eb7fec54c 100644 --- a/app/en/get-started/agent-frameworks/ag2/use-arcade-tools/page.mdx +++ b/app/en/get-started/agent-frameworks/ag2/use-arcade-tools/page.mdx @@ -12,7 +12,7 @@ import { Steps, Tabs, Callout } from "nextra/components"; -You will build an AG2 agent that uses Arcade tools to help users with Gmail. +You will build an AG2 agent that uses Arcade tools to help users with Gmail and Slack. @@ -26,7 +26,7 @@ You will build an AG2 agent that uses Arcade tools to help users with Gmail. -- How to retrieve Arcade tools and register them with AG2 agents +- How to dynamically retrieve Arcade tools and register them with AG2 agents - How to build an AG2 agent with Arcade tools - How to implement "just in time" (JIT) tool authorization using Arcade's client @@ -35,7 +35,7 @@ You will build an AG2 agent that uses Arcade tools to help users with Gmail. ## The agent architecture you will build in this guide -AG2 provides a [ConversableAgent](https://docs.ag2.ai/latest/docs/user-guide/basic-concepts/conversable-agent/) class that implements a conversational agent. In this guide, you will use an `AssistantAgent` (the LLM-backed agent) and a `ConversableAgent` (which executes tools on behalf of the user) to create an interactive Gmail assistant that can call Arcade tools. +AG2 provides a [ConversableAgent](https://docs.ag2.ai/latest/docs/user-guide/basic-concepts/conversable-agent/) class that implements a conversational agent. In this guide, you will use an `AssistantAgent` (the LLM-backed agent) and a `ConversableAgent` (which executes tools on behalf of the user) to create an interactive assistant. Tools are dynamically retrieved from Arcade at runtime, so you can add or remove tools by changing a configuration variable. ## Integrate Arcade tools into an AG2 agent @@ -91,13 +91,19 @@ OPENAI_API_KEY=YOUR_OPENAI_API_KEY Create a new file called `main.py` and add the following code: ```python filename="main.py" +import inspect import os +from typing import Any + from arcadepy import Arcade +from arcadepy.types import ToolDefinition from autogen import AssistantAgent, ConversableAgent from dotenv import load_dotenv ``` +- `inspect`: Used to dynamically build function signatures from Arcade tool definitions. - `Arcade`: The Arcade client for tool authorization and execution. +- `ToolDefinition`: The type representing an Arcade tool definition. - `AssistantAgent`: The LLM-backed agent that decides which tools to call. - `ConversableAgent`: Executes tools on behalf of the user. @@ -106,21 +112,45 @@ from dotenv import load_dotenv imports come from the `autogen` module. -### Configure the clients +### Configure the agent ```python filename="main.py" load_dotenv() -# Initialize Arcade client -arcade = Arcade(api_key=os.environ["ARCADE_API_KEY"]) +# The Arcade user ID identifies who is authorizing each service ARCADE_USER_ID = os.environ["ARCADE_USER_ID"] +# All tools from the MCP servers defined in the array will be retrieved +MCP_SERVERS = ["Slack"] +# Individual tools to retrieve, useful when you don't need all tools from an MCP server +TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] +# LLM model to use inside the agent +MODEL = "gpt-4o" +# System message that provides context and personality to the agent +SYSTEM_MESSAGE = "You are a helpful assistant. Use the provided tools to help the user manage their email and Slack." +# The agent's name +AGENT_NAME = "Assistant" +``` -# LLM configuration for AG2 -llm_config = { - "config_list": [ - {"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]} - ], +### Map Arcade types to Python types + +This utility maps Arcade's type system to Python types, which AG2 uses to generate the function-calling schema for the LLM. + +```python filename="main.py" +TYPE_MAP: dict[str, type] = { + "string": str, + "number": float, + "integer": int, + "boolean": bool, + "array": list, + "json": dict, } + + +def _python_type(val_type: str) -> type: + t = TYPE_MAP.get(val_type) + if t is None: + raise ValueError(f"Unsupported Arcade value type: {val_type}") + return t ``` ### Write a helper function to call Arcade tools @@ -137,17 +167,21 @@ This function handles tool authorization and execution through the Arcade client ```python filename="main.py" -def call_arcade_tool(tool_name: str, inputs: dict) -> dict: +def call_arcade_tool( + client: Arcade, tool_name: str, inputs: dict, user_id: str +) -> dict: """Authorize (if needed) and execute an Arcade tool, returning the output.""" - auth = arcade.tools.authorize(tool_name=tool_name, user_id=ARCADE_USER_ID) + auth = client.tools.authorize(tool_name=tool_name, user_id=user_id) if auth.status != "completed": - print(f"\n[Auth required] Visit this URL to authorize {tool_name}:\n {auth.url}\n") - arcade.auth.wait_for_completion(auth.id) + print( + f"\n[Auth required] Visit this URL to authorize {tool_name}:\n {auth.url}\n" + ) + client.auth.wait_for_completion(auth) - result = arcade.tools.execute( + result = client.tools.execute( tool_name=tool_name, input=inputs, - user_id=ARCADE_USER_ID, + user_id=user_id, ) if not result.output: return {} @@ -155,54 +189,116 @@ def call_arcade_tool(tool_name: str, inputs: dict) -> dict: return data.get("value") or {} ``` -### Define tool wrapper functions +### Dynamically generate Python functions from Arcade tool definitions -AG2 registers tools as typed Python functions. Define a wrapper for each Arcade tool you want the agent to use. Each function calls the `call_arcade_tool` helper with the appropriate tool name and inputs. +AG2 registers tools as typed Python functions. Since tools are retrieved dynamically from Arcade, you need to build these functions at runtime. This helper creates a function for each tool definition, using `inspect.Parameter` and `inspect.Signature` to set the correct parameter names, types, and defaults so that AG2 can generate the correct function-calling schema. ```python filename="main.py" -def list_emails(max_results: int = 10, is_unread: bool = False) -> dict: - """List emails from Gmail inbox. - - max_results: maximum number of emails to return (default 10) - - is_unread: if True, return only unread emails - """ - return call_arcade_tool( - "Gmail.ListEmails", - {"max_results": max_results, "is_unread": is_unread}, - ) - +def _build_tool_function( + tool_def: ToolDefinition, + client: Arcade, + user_id: str, +) -> Any: + """Build a typed Python function from an Arcade tool definition.""" + tool_name = tool_def.qualified_name + sanitized_name = tool_name.replace(".", "_") + + # Build inspect.Parameter list from the Arcade definition + params = [] + annotations: dict[str, Any] = {} + for p in tool_def.input.parameters or []: + p_type = _python_type(p.value_schema.val_type) + if p_type is list and p.value_schema.inner_val_type: + inner = _python_type(p.value_schema.inner_val_type) + p_type = list[inner] # type: ignore[valid-type] + + default = inspect.Parameter.empty if p.required else None + params.append( + inspect.Parameter( + p.name, + kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, + default=default, + annotation=p_type, + ) + ) + annotations[p.name] = p_type + + annotations["return"] = dict + + # Create a closure that calls the Arcade tool + def tool_func(**kwargs) -> dict: + return call_arcade_tool(client, tool_name, kwargs, user_id) + + # Set function metadata so AG2 can introspect it + tool_func.__name__ = sanitized_name + tool_func.__qualname__ = sanitized_name + tool_func.__doc__ = tool_def.description + tool_func.__signature__ = inspect.Signature(params) + tool_func.__annotations__ = annotations + + return tool_func +``` -def send_email(to: str, subject: str, body: str) -> dict: - """Send a new email. - - to: recipient email address - - subject: email subject line - - body: plain-text email body - """ - return call_arcade_tool( - "Gmail.SendEmail", - {"recipient": to, "subject": subject, "body": body}, - ) +### Retrieve Arcade tools +This function retrieves tool definitions from the Arcade API and returns a list of dynamically-built Python functions ready to register with AG2. -def reply_to_email(email_id: str, body: str) -> dict: - """Reply to an email by message ID. - - email_id: ID of the message to reply to - - body: plain-text reply body - """ - return call_arcade_tool( - "Gmail.ReplyToEmail", - {"reply_to_message_id": email_id, "body": body, "reply_to_whom": "ONLY_THE_SENDER"}, - ) +```python filename="main.py" +def get_arcade_tools( + client: Arcade, + *, + tools: list[str] | None = None, + mcp_servers: list[str] | None = None, + user_id: str = "", +) -> list[Any]: + """Retrieve Arcade tool definitions and return dynamically-built Python functions.""" + if not tools and not mcp_servers: + raise ValueError("Provide at least one tool name or MCP server name") + + definitions: list[ToolDefinition] = [] + + if tools: + for name in tools: + definitions.append(client.tools.get(name=name)) + + if mcp_servers: + for server in mcp_servers: + page = client.tools.list(toolkit=server) + definitions.extend(page.items) + + return [ + _build_tool_function(defn, client, user_id) + for defn in definitions + ] ``` ### Create the agents and register the tools -Create the `AssistantAgent` and `ConversableAgent`, then register each tool function using the `register_for_llm` and `register_for_execution` pattern. This tells the assistant which tools it can call and the user proxy how to execute them. +Create the `AssistantAgent` and `ConversableAgent`, retrieve the Arcade tools, and register each dynamically-generated function. This tells the assistant which tools it can call and the user proxy how to execute them. ```python filename="main.py" +# Initialize the Arcade client +client = Arcade(api_key=os.environ["ARCADE_API_KEY"]) + +# Retrieve tools dynamically from Arcade +arcade_tools = get_arcade_tools( + client, + tools=TOOLS, + mcp_servers=MCP_SERVERS, + user_id=ARCADE_USER_ID, +) + +# LLM configuration for AG2 +llm_config = { + "config_list": [ + {"model": MODEL, "api_key": os.environ["OPENAI_API_KEY"]} + ], +} + assistant = AssistantAgent( - name="GmailAssistant", + name=AGENT_NAME, llm_config=llm_config, - system_message="You are a helpful Gmail assistant. Use the provided tools to help the user manage their email.", + system_message=SYSTEM_MESSAGE, ) user_proxy = ConversableAgent( @@ -211,21 +307,17 @@ user_proxy = ConversableAgent( is_termination_msg=lambda msg: "TERMINATE" in (msg.get("content") or ""), ) -# Register tools: each function is registered for both LLM (caller) and execution (executor) -for func, description in [ - (list_emails, "List emails from Gmail inbox. Filter by unread status and max results."), - (send_email, "Send a new email to a recipient with subject and body."), - (reply_to_email, "Reply to an email by message ID with a body."), -]: +# Register each dynamically-generated function with AG2 +for func in arcade_tools: user_proxy.register_for_execution()(func) - assistant.register_for_llm(description=description)(func) + assistant.register_for_llm(description=func.__doc__)(func) ``` ### Start the conversation ```python filename="main.py" if __name__ == "__main__": - print("Gmail Assistant ready. Type your request (or 'exit' to quit).") + print("Assistant ready. Type your request (or 'exit' to quit).") while True: message = input("\nYou: ").strip() if not message or message.lower() in ("exit", "quit"): @@ -248,6 +340,7 @@ You should see the agent responding to your prompts, handling tool calls and aut - "Show my unread emails" - "Send an email to someone@example.com about scheduling a demo" - "Summarize my latest 3 emails" +- "Send a message in the #general Slack channel" @@ -260,7 +353,7 @@ You should see the agent responding to your prompts, handling tool calls and aut Now that you have integrated Arcade tools into your AG2 agent, you can: -- Add more tools from different MCP servers, such as Slack or Notion. +- Add more tools by modifying the `MCP_SERVERS` and `TOOLS` variables. - Explore [AG2's group chat](https://docs.ag2.ai/latest/docs/user-guide/advanced-concepts/groupchat/groupchat/) patterns to coordinate multiple agents. - Check out the [build-with-ag2 examples](https://github.com/ag2ai/build-with-ag2/tree/main/arcade) for more integration patterns. @@ -270,36 +363,63 @@ Now that you have integrated Arcade tools into your AG2 agent, you can: **main.py** (full file) ```python filename="main.py" +import inspect import os +from typing import Any + from arcadepy import Arcade +from arcadepy.types import ToolDefinition from autogen import AssistantAgent, ConversableAgent from dotenv import load_dotenv load_dotenv() -# Initialize Arcade client -arcade = Arcade(api_key=os.environ["ARCADE_API_KEY"]) +# The Arcade user ID identifies who is authorizing each service ARCADE_USER_ID = os.environ["ARCADE_USER_ID"] - -# LLM configuration for AG2 -llm_config = { - "config_list": [ - {"model": "gpt-4o", "api_key": os.environ["OPENAI_API_KEY"]} - ], +# All tools from the MCP servers defined in the array will be retrieved +MCP_SERVERS = ["Slack"] +# Individual tools to retrieve, useful when you don't need all tools from an MCP server +TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] +# LLM model to use inside the agent +MODEL = "gpt-4o" +# System message that provides context and personality to the agent +SYSTEM_MESSAGE = "You are a helpful assistant. Use the provided tools to help the user manage their email and Slack." +# The agent's name +AGENT_NAME = "Assistant" + +# Mapping of Arcade value types to Python types +TYPE_MAP: dict[str, type] = { + "string": str, + "number": float, + "integer": int, + "boolean": bool, + "array": list, + "json": dict, } -def call_arcade_tool(tool_name: str, inputs: dict) -> dict: +def _python_type(val_type: str) -> type: + t = TYPE_MAP.get(val_type) + if t is None: + raise ValueError(f"Unsupported Arcade value type: {val_type}") + return t + + +def call_arcade_tool( + client: Arcade, tool_name: str, inputs: dict, user_id: str +) -> dict: """Authorize (if needed) and execute an Arcade tool, returning the output.""" - auth = arcade.tools.authorize(tool_name=tool_name, user_id=ARCADE_USER_ID) + auth = client.tools.authorize(tool_name=tool_name, user_id=user_id) if auth.status != "completed": - print(f"\n[Auth required] Visit this URL to authorize {tool_name}:\n {auth.url}\n") - arcade.auth.wait_for_completion(auth.id) + print( + f"\n[Auth required] Visit this URL to authorize {tool_name}:\n {auth.url}\n" + ) + client.auth.wait_for_completion(auth) - result = arcade.tools.execute( + result = client.tools.execute( tool_name=tool_name, input=inputs, - user_id=ARCADE_USER_ID, + user_id=user_id, ) if not result.output: return {} @@ -307,44 +427,101 @@ def call_arcade_tool(tool_name: str, inputs: dict) -> dict: return data.get("value") or {} -def list_emails(max_results: int = 10, is_unread: bool = False) -> dict: - """List emails from Gmail inbox. - - max_results: maximum number of emails to return (default 10) - - is_unread: if True, return only unread emails - """ - return call_arcade_tool( - "Gmail.ListEmails", - {"max_results": max_results, "is_unread": is_unread}, - ) - - -def send_email(to: str, subject: str, body: str) -> dict: - """Send a new email. - - to: recipient email address - - subject: email subject line - - body: plain-text email body - """ - return call_arcade_tool( - "Gmail.SendEmail", - {"recipient": to, "subject": subject, "body": body}, - ) - - -def reply_to_email(email_id: str, body: str) -> dict: - """Reply to an email by message ID. - - email_id: ID of the message to reply to - - body: plain-text reply body - """ - return call_arcade_tool( - "Gmail.ReplyToEmail", - {"reply_to_message_id": email_id, "body": body, "reply_to_whom": "ONLY_THE_SENDER"}, - ) +def _build_tool_function( + tool_def: ToolDefinition, + client: Arcade, + user_id: str, +) -> Any: + """Build a typed Python function from an Arcade tool definition.""" + tool_name = tool_def.qualified_name + sanitized_name = tool_name.replace(".", "_") + + # Build inspect.Parameter list from the Arcade definition + params = [] + annotations: dict[str, Any] = {} + for p in tool_def.input.parameters or []: + p_type = _python_type(p.value_schema.val_type) + if p_type is list and p.value_schema.inner_val_type: + inner = _python_type(p.value_schema.inner_val_type) + p_type = list[inner] # type: ignore[valid-type] + + default = inspect.Parameter.empty if p.required else None + params.append( + inspect.Parameter( + p.name, + kind=inspect.Parameter.POSITIONAL_OR_KEYWORD, + default=default, + annotation=p_type, + ) + ) + annotations[p.name] = p_type + + annotations["return"] = dict + + # Create a closure that calls the Arcade tool + def tool_func(**kwargs) -> dict: + return call_arcade_tool(client, tool_name, kwargs, user_id) + + # Set function metadata so AG2 can introspect it + tool_func.__name__ = sanitized_name + tool_func.__qualname__ = sanitized_name + tool_func.__doc__ = tool_def.description + tool_func.__signature__ = inspect.Signature(params) + tool_func.__annotations__ = annotations + + return tool_func + + +def get_arcade_tools( + client: Arcade, + *, + tools: list[str] | None = None, + mcp_servers: list[str] | None = None, + user_id: str = "", +) -> list[Any]: + """Retrieve Arcade tool definitions and return dynamically-built Python functions.""" + if not tools and not mcp_servers: + raise ValueError("Provide at least one tool name or MCP server name") + + definitions: list[ToolDefinition] = [] + + if tools: + for name in tools: + definitions.append(client.tools.get(name=name)) + + if mcp_servers: + for server in mcp_servers: + page = client.tools.list(toolkit=server) + definitions.extend(page.items) + + return [ + _build_tool_function(defn, client, user_id) + for defn in definitions + ] + + +# Initialize the Arcade client +client = Arcade(api_key=os.environ["ARCADE_API_KEY"]) + +# Retrieve tools dynamically from Arcade +arcade_tools = get_arcade_tools( + client, + tools=TOOLS, + mcp_servers=MCP_SERVERS, + user_id=ARCADE_USER_ID, +) +# LLM configuration for AG2 +llm_config = { + "config_list": [ + {"model": MODEL, "api_key": os.environ["OPENAI_API_KEY"]} + ], +} assistant = AssistantAgent( - name="GmailAssistant", + name=AGENT_NAME, llm_config=llm_config, - system_message="You are a helpful Gmail assistant. Use the provided tools to help the user manage their email.", + system_message=SYSTEM_MESSAGE, ) user_proxy = ConversableAgent( @@ -353,17 +530,13 @@ user_proxy = ConversableAgent( is_termination_msg=lambda msg: "TERMINATE" in (msg.get("content") or ""), ) -# Register tools: each function is registered for both LLM (caller) and execution (executor) -for func, description in [ - (list_emails, "List emails from Gmail inbox. Filter by unread status and max results."), - (send_email, "Send a new email to a recipient with subject and body."), - (reply_to_email, "Reply to an email by message ID with a body."), -]: +# Register each dynamically-generated function with AG2 +for func in arcade_tools: user_proxy.register_for_execution()(func) - assistant.register_for_llm(description=description)(func) + assistant.register_for_llm(description=func.__doc__)(func) if __name__ == "__main__": - print("Gmail Assistant ready. Type your request (or 'exit' to quit).") + print("Assistant ready. Type your request (or 'exit' to quit).") while True: message = input("\nYou: ").strip() if not message or message.lower() in ("exit", "quit"): From 7ded8be6d65c5f183af2c6f9bb94e921f4796f08 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 30 Mar 2026 01:15:28 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A4=96=20Regenerate=20LLMs.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/llms.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/llms.txt b/public/llms.txt index b332f5cb1..ed48b46ad 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -1,4 +1,4 @@ - + # Arcade @@ -151,6 +151,7 @@ Arcade delivers three core capabilities: Deploy agents even your security team w - [Use Arcade in Cursor](https://docs.arcade.dev/en/get-started/mcp-clients/cursor): This documentation page provides a step-by-step guide for connecting Cursor to an Arcade MCP Gateway, enabling users to utilize Arcade tools effectively. It outlines the prerequisites for setup, including creating an Arcade account and obtaining an API key, and offers instructions for configuring Cursor - [Use Arcade in Microsoft Copilot Studio](https://docs.arcade.dev/en/get-started/mcp-clients/copilot-studio): This documentation page provides a step-by-step guide for connecting Microsoft Copilot Studio to an Arcade MCP Gateway, enabling users to integrate and utilize Arcade tools within their agents. It outlines the prerequisites, configuration steps, and authorization process necessary for establishing the connection, - [Use Arcade in Visual Studio Code](https://docs.arcade.dev/en/get-started/mcp-clients/visual-studio-code): This documentation page provides a step-by-step guide for connecting Visual Studio Code to an Arcade MCP Gateway, enabling users to set up and run their MCP server within the IDE. It outlines prerequisites, setup instructions, and authentication processes to ensure a successful integration. +- [Use Arcade tools with AG2](https://docs.arcade.dev/en/get-started/agent-frameworks/ag2/use-arcade-tools): Documentation page - [Use Arcade tools with CrewAI](https://docs.arcade.dev/en/get-started/agent-frameworks/crewai/use-arcade-tools): This documentation page provides a comprehensive guide for integrating Arcade tools into CrewAI applications, enabling users to build task-oriented multi-agent systems that assist with platforms like Gmail and Slack. It outlines the prerequisites, learning outcomes, and step-by-step instructions for setting up - [What are tools?](https://docs.arcade.dev/en/guides/tool-calling): This documentation page provides an overview of tool calling in language models, explaining how users can leverage tools to enhance the capabilities of AI models for tasks such as data retrieval and scheduling. It outlines the process of integrating tools with language models using the Arcade SDK, - [Why evaluate tools?](https://docs.arcade.dev/en/guides/create-tools/evaluate-tools/why-evaluate): This documentation page explains the importance of evaluating tools used by AI models to ensure accurate tool selection and parameter accuracy in production environments. It outlines the evaluation process, scoring components, and potential issues that can arise without proper assessments. Users can learn how to create