diff --git a/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx b/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx index 8455a60fd..21aa99066 100644 --- a/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx +++ b/app/en/get-started/agent-frameworks/crewai/use-arcade-tools/page.mdx @@ -7,7 +7,9 @@ import { Steps, Tabs, Callout } from "nextra/components"; # Use Arcade tools with CrewAI -[CrewAI](https://www.crewai.com/) is an agentic framework optimized for building task-oriented multi-agent systems. This guide explains how to integrate Arcade tools into your CrewAI applications. +This guide explains how to integrate Arcade tools into your CrewAI applications. + +[CrewAI](https://www.crewai.com/) is an agentic framework optimized for building task-oriented multi-agent systems. You'll learn to retrieve Arcade tools, convert them to CrewAI format, and build an agent that uses Arcade tools for Gmail and Slack integration. This integration enables just-in-time tool authorization and provides a complete solution for CrewAI applications that need external service connectivity. @@ -120,7 +122,7 @@ This includes many imports, here's a breakdown: ### Configure the agent -The rest of the code uses these variables to customize the agent and manage the tools. Feel free to configure them to your liking. Here, the `EventListener` class is used to suppress CrewAI's rich panel output, which is useful for debugging but verbose for an interactive session like the one you're building. +The rest of the code uses these variables to customize the agent and manage the tools. Feel free to configure them to your liking. Here, the `EventListener` class suppresses CrewAI's rich panel output, which is useful for debugging but verbose for an interactive session like the one you're building. ```python filename="main.py" # Load environment variables from the .env file @@ -128,13 +130,13 @@ load_dotenv() # The Arcade User ID identifies who is authorizing each service. ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") -# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. +# This determines which MCP server provides the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will work. MCP_SERVERS = ["Slack"] # This determines individual tools. Useful to pick specific tools when you don't need all of them. TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] # This determines the maximum number of tool definitions Arcade will return per MCP server TOOL_LIMIT = 30 -# This determines which LLM model will be used inside the agent +# This determines which LLM model will work inside the agent MODEL = "openai/gpt-5-mini" # The individual objective that guides the agent's decision-making AGENT_GOAL = "Help the user with all their requests" @@ -186,7 +188,7 @@ def _build_args_model(tool_def: ToolDefinition) -> type[BaseModel]: ### Write a custom class that extends the CrewAI BaseTool class -Here, you define the `ArcadeTool` class that extends the CrewAI `BaseTool` class to add the following capability: +Here, you define the `ArcadeTool` class that extends the CrewAI `BaseTool` class to add the following feature: - Authorize the tool with the Arcade client with the `_auth_tool` helper function - Execute the tool with the Arcade client with the `_run` method @@ -245,7 +247,7 @@ class ArcadeTool(BaseTool): ### Retrieve Arcade tools and transform them into CrewAI tools -Here you get the Arcade tools you want the agent to utilize, and transform them into CrewAI tools. The first step is to initialize the Arcade client, and get the tools you want to work with. +Here you get the Arcade tools you want the agent to use, and transform them into CrewAI tools. The first step involves initializing the Arcade client, and getting the tools you want to work with. Here's a breakdown of what it does for clarity: @@ -352,7 +354,7 @@ You should see the agent responding to your prompts like any model, as well as h ## 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. +- **Avoid conflicts**: Be mindful of duplicate or overlapping capability. ## Next steps @@ -383,13 +385,13 @@ load_dotenv() # The Arcade User ID identifies who is authorizing each service. ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") -# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. +# This determines which MCP server provides the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will work. MCP_SERVERS = ["Slack"] # This determines individual tools. Useful to pick specific tools when you don't need all of them. TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] # This determines the maximum number of tool definitions Arcade will return per MCP server TOOL_LIMIT = 30 -# This determines which LLM model will be used inside the agent +# This determines which LLM model will work inside the agent MODEL = "openai/gpt-5-mini" # The individual objective that guides the agent's decision-making AGENT_GOAL = "Help the user with all their requests" @@ -549,4 +551,4 @@ if __name__ == "__main__": ``` - + \ No newline at end of file diff --git a/app/en/get-started/agent-frameworks/google-adk/setup-python/page.mdx b/app/en/get-started/agent-frameworks/google-adk/setup-python/page.mdx index 22dd27025..e8a58ee08 100644 --- a/app/en/get-started/agent-frameworks/google-adk/setup-python/page.mdx +++ b/app/en/get-started/agent-frameworks/google-adk/setup-python/page.mdx @@ -7,6 +7,8 @@ import { Steps, Tabs, Callout } from "nextra/components"; # Setup Arcade with Google ADK (Python) +Learn how to integrate Arcade tools using Google ADK primitives to build an AI agent. + Google ADK is a modular framework for building and deploying AI agents. It optimizes for Gemini and the Google Ecosystem, but supports any model. @@ -145,7 +147,7 @@ This includes multiple imports, here's a breakdown: ### Configure the agent -These variables set the configuration for the rest of the code to customize the agent and manage the tools. Feel free to configure them to your liking. Set the `google_genai.types` logging level to `ERROR` to avoid a lot of noise in the console. Load the environment variables from the `.env` file using `load_dotenv()`. +These variables set the configuration for the rest of the code to customize the agent and manage the tools. Configure them to your liking. Set the `google_genai.types` logging level to `ERROR` to avoid a lot of noise in the console. Load the environment variables from the `.env` file using `load_dotenv()`. ```python filename="main.py" logging.getLogger("google_genai.types").setLevel(logging.ERROR) @@ -154,13 +156,13 @@ load_dotenv() # The Arcade User ID identifies who is authorizing each service. ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") -# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. +# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be utilized. MCP_SERVERS = ["Slack"] # This determines individual tools. Useful to pick specific tools when you don't need all of them. TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] # This prompt defines the behavior of the agent. MODEL = "gemini-2.5-flash" -# This determines which LLM model will be used inside the agent +# This determines which LLM model will be applied inside the agent SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack." # This determines the name of the agent. AGENT_NAME = "AwesomeAgent" @@ -469,7 +471,7 @@ You should see the agent responding to your prompts like any model, as well as h ## Tips for selecting tools - **Relevance**: Pick only the tools you need. Avoid utilizing all tools at once. -- **User identification**: Always provide a unique and consistent `user_id` for each user. Apply your internal or database user ID, not something entered by the user. +- **User identification**: Always provide a unique and consistent `user_id` for each user. Use your internal or database user ID, not something entered by the user. ## Next steps @@ -510,13 +512,13 @@ load_dotenv() # The Arcade User ID identifies who is authorizing each service. ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") -# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. +# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be utilized. MCP_SERVERS = ["Slack"] # This determines individual tools. Useful to pick specific tools when you don't need all of them. TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] # This prompt defines the behavior of the agent. MODEL = "gemini-2.5-flash" -# This determines which LLM model will be used inside the agent +# This determines which LLM model will be applied inside the agent SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack." # This determines the name of the agent. AGENT_NAME = "AwesomeAgent" @@ -759,4 +761,4 @@ if __name__ == '__main__': asyncio.run(main()) ``` - + \ No newline at end of file diff --git a/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py/page.mdx b/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py/page.mdx index 2669dedc0..1d3c6b440 100644 --- a/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py/page.mdx +++ b/app/en/get-started/agent-frameworks/langchain/use-arcade-with-langchain-py/page.mdx @@ -7,7 +7,9 @@ import { Steps, Tabs, Callout } from "nextra/components"; # Setup Arcade with LangChain -LangChain is a popular agentic framework that abstracts a lot of the complexity of building AI agents. It is built on top of LangGraph, a lower level orchestration framework that offers more control over the inner flow of the agent. +Learn how to integrate Arcade tools with LangChain agents using interrupts and checkpointers. + +LangChain is a popular agentic framework that abstracts a lot of the complexity when building AI agents. LangGraph, a lower level orchestration framework that offers more control over the inner flow of the agent, forms the foundation of LangChain. @@ -38,8 +40,8 @@ Learn how to integrate Arcade tools using LangChain primitives LangChain provides multiple abstractions for building AI agents, and it's useful to internalize how some of these primitives work, so you can understand and extend the different agentic patterns LangChain supports. - [_Agents_](https://docs.langchain.com/oss/python/langchain/agents): Most agentic frameworks, including LangChain, provide an abstraction for a ReAct agent. -- [_Interrupts_](https://docs.langchain.com/oss/python/langgraph/interrupts): Interrupts in LangChain are a way to control the flow of the agentic loop when something needs to be done outside of the normal ReAct flow. For example, if a tool requires authorization, you can interrupt the agent and ask the user to authorize the tool before continuing. -- [_Checkpointers_](https://docs.langchain.com/oss/python/langgraph/persistence): Checkpointers are how LangChain implements persistence. A checkpointer stores the agent's state in a "checkpoint" that you can resume later. You save those checkpoints to a _thread_, which you can access after the agent's execution, making it simple for long-running agents and for handling interruptions and more sophisticated flows such as branching, time travel, and more. +- [_Interrupts_](https://docs.langchain.com/oss/python/langgraph/interrupts): Interrupts in LangChain control the flow of the agentic loop when you need to complete something outside of the normal ReAct flow. For example, if a tool requires authorization, you can interrupt the agent and ask the user to authorize the tool before continuing. +- [_Checkpointers_](https://docs.langchain.com/oss/python/langgraph/persistence): Checkpointers are how LangChain implements persistence. A checkpointer stores the agent's state in a "checkpoint" that you can resume later. You save those checkpoints to a _thread_, which you can access after the agent's execution, making it straightforward for long-running agents and for handling interruptions and more sophisticated flows such as branching, time travel, and more. ## Integrate Arcade tools into a LangChain agent @@ -104,7 +106,7 @@ from langgraph.types import Command, interrupt from pydantic import BaseModel, Field, create_model ``` -This is quite a number of imports, let's break them down: +This includes several imports, break them down: - Arcade imports: - `AsyncArcade`: The Arcade client, interacts with the Arcade API. @@ -121,9 +123,9 @@ This is quite a number of imports, let's break them down: - `interrupt`: Interrupts the ReAct flow and asks the user for input. - Other imports: - `load_dotenv`: Loads the environment variables from the `.env` file. - - `os`: The operating system module, used to interact with the operating system. - - `typing` imports: Used for type hints, which are not required but recommended for type safety. - - `pydantic` imports: Used for data validation and model creation when converting Arcade tools to LangChain tools. + - `os`: The operating system module, interacts with the operating system. + - `typing` imports: Type hints provide type safety. + - `pydantic` imports: Data validation and model creation when converting Arcade tools to LangChain tools. ### Configure the agent @@ -134,9 +136,9 @@ The rest of the code uses these variables to customize the agent and manage the load_dotenv() # Configure your own values to customize your agent -# The Arcade User ID identifies who is authorizing each service. +# The Arcade User ID identifies who authorizes each service. ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") -# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. +# This determines which MCP server provides the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be in use. MCP_SERVERS = ["Slack"] # This determines individual tools. Useful to pick specific tools when you don't need all of them. TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] @@ -144,15 +146,15 @@ TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] TOOL_LIMIT = 30 # This prompt defines the behavior of the agent. SYSTEM_PROMPT = "You are a helpful assistant that can use Gmail tools. Your main task is to help the user with anything they may need." -# This determines which LLM will be used inside the agent +# This determines which LLM the agent will use MODEL = "gpt-5-nano" ``` ### Write a helper function to convert Arcade tools to LangChain tools -Here you convert the Arcade tools to LangChain tools. You use the `arcade_schema_to_pydantic` function to convert the Arcade tool definition to a Pydantic model, and then use the moddel to define a `StructuredTool` and create a LangChain tool. +Here you convert the Arcade tools to LangChain tools. You use the `arcade_schema_to_pydantic` function to convert the Arcade tool definition to a Pydantic model, and then use the model to define a `StructuredTool` and create a LangChain tool. -The `arcade_to_langchain` function wraps the Arcade client and dynamically creates a `tool_function` that executes the tool and handles the authorization flow using the `interrupt` function. Once the tool is authorized, the `tool_function` will use the Arcade client to execute the tool with the provided arguments, and handle any errors that may occur. +The `arcade_to_langchain` function wraps the Arcade client and dynamically creates a `tool_function` that executes the tool and handles the authorization flow using the `interrupt` function. Once you authorize the tool, the `tool_function` will use the Arcade client to execute the tool with the provided arguments, and handle any errors that may occur. ```python filename="main.py" TYPE_MAPPING = { @@ -178,7 +180,7 @@ def arcade_schema_to_pydantic(tool_def: ToolDefinition) -> type[BaseModel]: if param_type is list and param.value_schema.inner_val_type: inner_type: type[Any] = get_python_type(param.value_schema.inner_val_type) param_type = list[inner_type] - param_description = param.description or "No description provided." + param_description = param.description or "The system provides no description." default = ... if param.required else None fields[param.name] = ( param_type, @@ -201,7 +203,7 @@ async def arcade_to_langchain( async def tool_function(config: RunnableConfig, **kwargs: Any) -> Any: user_id = config.get("configurable", {}).get("user_id") if config else None if not user_id: - raise ValueError("User ID is required to execute Arcade tools") + raise ValueError("You must provide a User ID to execute Arcade tools") auth_response = await arcade_client.tools.authorize( tool_name=arcade_tool.qualified_name, @@ -223,7 +225,7 @@ async def arcade_to_langchain( authorized = interrupt_result.get("authorized") if not authorized: raise RuntimeError( - f"Authorization was not completed for tool {arcade_tool.name}" + f"You did not complete authorization for tool {arcade_tool.name}" ) # Filter out None values to avoid passing unset optional parameters @@ -266,7 +268,7 @@ async def arcade_to_langchain( ### Write a helper function to get Arcade tools in LangChain format -In this helper function you use the Arcade client to retrieved the tools you configured at the beginning of the `main.py` file. You will use a dictionary to store the tools and avoid possible duplicates that may occur if you retrieve the same tool in the `TOOLS` and `MCP_SERVERS` variables. After retrieving all the tools, you will call the `arcade_to_langchain` function to convert the Arcade tools to LangChain tools. +In this helper function you use the Arcade client to retrieve the tools you configured at the beginning of the `main.py` file. You will use a dictionary to store the tools and avoid possible duplicates that may occur if you retrieve the same tool in the `TOOLS` and `MCP_SERVERS` variables. After retrieving all the tools, you will call the `arcade_to_langchain` function to convert the Arcade tools to LangChain tools. ```python filename="main.py" async def get_arcade_tools( @@ -278,10 +280,10 @@ async def get_arcade_tools( if not arcade_client: arcade_client = AsyncArcade(api_key=os.getenv("ARCADE_API_KEY")) - # if no tools or MCP servers are provided, raise an error + # if no tools or MCP servers exist, raise an error if not tools and not mcp_servers: raise ValueError( - "No tools or MCP servers provided to retrieve tool definitions") + "No tools or MCP servers exist to retrieve tool definitions") # Collect tool definitions, using qualified name as key to avoid duplicates tool_definitions: dict[str, ToolDefinition] = {} @@ -310,7 +312,7 @@ async def get_arcade_tools( ### Write the interrupt handler -In LangChain, each interrupt needs to be "resolved" for the flow to continue. In response to an interrupt, you need to return a decision object with the information needed to resolve the interrupt. In this case, the decision is whether the authorization was successful, in which case the tool call will be retried, or if the authorization failed, the flow will be interrupted with an error, and the agent will decide what to do next. +In LangChain, you must "resolve" each interrupt for the flow to continue. In response to an interrupt, you need to return a decision object with the information needed to resolve the interrupt. In this case, the decision is whether the authorization was successful, in which case the system will retry the tool call, or if the authorization failed, the flow will interrupt with an error, and the agent will decide what to do next. This helper function receives an interrupt and returns a decision object. Decision objects can be of any serializable type (convertible to JSON). In this case, you return a dictionary with a boolean flag indicating if the authorization was successful. @@ -362,7 +364,7 @@ async def handle_authorization_interrupt( ### Write the invoke helper -This last helper function handles the streaming of the agent's response, and captures the interrupts. When an interrupt is detected, it is added to the `interrupts` array, and the flow is interrupted. If there are no interrupts, it will just stream the agent's to your console. +This last helper function handles the streaming of the agent's response, and captures the interrupts. When the system detects an interrupt, it adds the interrupt to the `interrupts` array, and the flow stops. If there are no interrupts, it will just stream the agent's response to your console. ```python filename="main.py" async def stream_agent_response(agent, input_data, config) -> List[Any]: @@ -400,7 +402,7 @@ async def stream_agent_response(agent, input_data, config) -> List[Any]: Finally, write the main function that will create the agent, initialize the conversation, and handle the user input. -Here the `config` object is used to configure the `thread_id`, which tells the agent to store the state of the conversation into that specific thread. In the main function you will also initialize the checkpointer, and handle route the interrupts to the handles you wrote earlier. Notice how a single turn of the agentic loop may have multiple interrupts, and you need to handle them all before continuing to the next turn. +Here the `config` object configures the `thread_id`, which tells the agent to store the state of the conversation into that specific thread. In the main function you will also initialize the checkpointer, and handle route the interrupts to the handlers you wrote earlier. Notice how a single turn of the agentic loop may have multiple interrupts, and you need to handle them all before continuing to the next turn. ```python filename="main.py" async def main(): @@ -467,7 +469,7 @@ async def main(): # Handle interrupts if any occurred if interrupts: - print(f"\n⚠️ Detected {len(interrupts)} interrupt(s)\n") + print(f"\n⚠️ Detected {len(interrupts)} interrupt\n") # Process each interrupt for interrupt_obj in interrupts: @@ -482,7 +484,7 @@ async def main(): # Resume agent with authorization decision current_input = Command(resume=decision) - break # Continue to next iteration + break else: print(f"❌ Unknown interrupt type: {interrupt_type}") break @@ -516,11 +518,11 @@ You should see the agent responding to your prompts like any model, as well as h ## Key takeaways -- Arcade tools can be integrated into any agentic framework like LangChain, all you need is to transform the Arcade tools into LangChain tools and handle the authorization flow. +- You can integrate Arcade tools into any agentic framework like LangChain, all you need is to transform the Arcade tools into LangChain tools and handle the authorization flow. - Context isolation: By handling the authorization flow outside of the agent's context, 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. - You can leverage the interrupts mechanism to handle human intervention in the agent's flow, useful for authorization flows, policy enforcement, or anything else that requires input from the user. -## Next Steps +## Next steps 1. Try adding additional tools to the agent or modifying the tools in the catalog for a different use case by modifying the `MCP_SERVERS` and `TOOLS` variables. 2. Try refactoring the `handle_authorization_interrupt` function to handle more complex flows, such as human-in-the-loop. @@ -552,9 +554,9 @@ from pydantic import BaseModel, Field, create_model load_dotenv() # Configure your own values to customize your agent -# The Arcade User ID identifies who is authorizing each service. +# The Arcade User ID identifies who authorizes each service. ARCADE_USER_ID = os.getenv("ARCADE_USER_ID") -# This determines which MCP server is providing the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be used. +# This determines which MCP server provides the tools, you can customize this to make a Notion agent. All tools from the MCP servers defined in the array will be in use. MCP_SERVERS = ["Slack"] # This determines individual tools. Useful to pick specific tools when you don't need all of them. TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] @@ -562,7 +564,7 @@ TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] TOOL_LIMIT = 30 # This prompt defines the behavior of the agent. SYSTEM_PROMPT = "You are a helpful assistant that can use Gmail tools. Your main task is to help the user with anything they may need." -# This determines which LLM will be used inside the agent +# This determines which LLM the agent will use MODEL = "gpt-4o-mini" TYPE_MAPPING = { @@ -588,7 +590,7 @@ def arcade_schema_to_pydantic(tool_def: ToolDefinition) -> type[BaseModel]: if param_type is list and param.value_schema.inner_val_type: inner_type: type[Any] = get_python_type(param.value_schema.inner_val_type) param_type = list[inner_type] - param_description = param.description or "No description provided." + param_description = param.description or "The system provides no description." default = ... if param.required else None fields[param.name] = ( param_type, @@ -612,7 +614,7 @@ async def arcade_to_langchain( async def tool_function(config: RunnableConfig, **kwargs: Any) -> Any: user_id = config.get("configurable", {}).get("user_id") if config else None if not user_id: - raise ValueError("User ID is required to execute Arcade tools") + raise ValueError("You must provide a User ID to execute Arcade tools") auth_response = await arcade_client.tools.authorize( tool_name=arcade_tool.qualified_name, @@ -634,7 +636,7 @@ async def arcade_to_langchain( authorized = interrupt_result.get("authorized") if not authorized: raise RuntimeError( - f"Authorization was not completed for tool {arcade_tool.name}" + f"You did not complete authorization for tool {arcade_tool.name}" ) # Filter out None values to avoid passing unset optional parameters @@ -685,10 +687,10 @@ async def get_arcade_tools( if not arcade_client: arcade_client = AsyncArcade(api_key=os.getenv("ARCADE_API_KEY")) - # if no tools or MCP servers are provided, raise an error + # if no tools or MCP servers exist, raise an error if not tools and not mcp_servers: raise ValueError( - "No tools or MCP servers provided to retrieve tool definitions") + "No tools or MCP servers exist to retrieve tool definitions") # Collect tool definitions, using qualified name as key to avoid duplicates tool_definitions: dict[str, ToolDefinition] = {} @@ -762,123 +764,4 @@ async def stream_agent_response(agent, input_data, config) -> List[Any]: # Display agent actions for node_name, node_output in chunk.items(): - if node_name == "__interrupt__": - continue - - if "messages" in node_output: - for msg in node_output["messages"]: - # Tool calls from the AI - if isinstance(msg, AIMessage) and msg.tool_calls: - for tool_call in msg.tool_calls: - print(f"🔧 Calling tool: {tool_call['name']}") - - # Tool response - just acknowledge it, don't dump the content - elif isinstance(msg, ToolMessage): - print(f" ✓ {msg.name} completed, processing output...") - - # Final AI response text - elif isinstance(msg, AIMessage) and msg.content: - print(f"\n🤖 Assistant:\n{msg.content}") - - return interrupts - - -async def main(): - # Initialize Arcade client - arcade = AsyncArcade() - - # Get tools - all_tools = await get_arcade_tools(arcade_client=arcade, - mcp_servers=MCP_SERVERS, tools=TOOLS) - - # Initialize LLM - model = ChatOpenAI( - model=MODEL, - api_key=os.getenv("OPENAI_API_KEY") - ) - - # Create agent with memory checkpointer - memory = MemorySaver() - agent = create_agent( - system_prompt=SYSTEM_PROMPT, - model=model, - tools=all_tools, - checkpointer=memory - ) - - print(f"\n🤖 Agent created with {len(all_tools)} tools") - print("Type 'quit' or 'exit' to end the conversation.\n") - print("="*70) - - # Configuration for agent execution - config = { - "configurable": { - "thread_id": "conversation_thread", - "user_id": ARCADE_USER_ID - } - } - - # Interactive conversation loop - while True: - # Get user input - try: - user_message = input("\n💬 You: ").strip() - except (EOFError, KeyboardInterrupt): - print("\n\n👋 Goodbye!") - break - - # Check for exit commands - if not user_message: - continue - if user_message.lower() in ("quit", "exit", "q"): - print("\n👋 Goodbye!") - break - - print("="*70) - - # Start with user message - current_input = {"messages": [{"role": "user", "content": user_message}]} - - # Agent execution loop with interrupt handling - while True: - print("\n🔄 Running agent...\n") - - interrupts = await stream_agent_response(agent, current_input, config) - - # Handle interrupts if any occurred - if interrupts: - print(f"\n⚠️ Detected {len(interrupts)} interrupt(s)\n") - - # Process each interrupt - for interrupt_obj in interrupts: - interrupt_type = interrupt_obj.value.get("type") - - if interrupt_type == "authorization_required": - # Handle authorization interrupt - decision = await handle_authorization_interrupt( - interrupt_obj.value, - arcade - ) - - # Resume agent with authorization decision - current_input = Command(resume=decision) - break # Continue to next iteration - else: - print(f"❌ Unknown interrupt type: {interrupt_type}") - break - else: - # All interrupts processed without break - break - else: - # No interrupts - agent completed successfully - print("\n✅ Response complete!") - break - - print("\n" + "="*70) - - -if __name__ == "__main__": - asyncio.run(main()) -``` - - + if node_name == "__interrupt__": \ No newline at end of file diff --git a/app/en/get-started/agent-frameworks/openai-agents/setup-python/page.mdx b/app/en/get-started/agent-frameworks/openai-agents/setup-python/page.mdx index 2f5029e65..7cbaa6e3d 100644 --- a/app/en/get-started/agent-frameworks/openai-agents/setup-python/page.mdx +++ b/app/en/get-started/agent-frameworks/openai-agents/setup-python/page.mdx @@ -14,7 +14,7 @@ The [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) is a pop -You will implement a CLI agent that can use Arcade tools to help the user with their requests. The harness handles tools that require authorization automatically, so users don't need to worry about it. +You will implement a CLI agent that can use Arcade tools to help the user with their requests. The harness handles tools that require user authorization automatically, so users don't need to worry about it. @@ -31,7 +31,7 @@ You will implement a CLI agent that can use Arcade tools to help the user with t - How to retrieve Arcade tools and transform them into OpenAI Agents tools - How to build an OpenAI Agents agent - How to integrate Arcade tools into the OpenAI Agents flow -- How to implement "just in time" (JIT) tool authorization using Arcade's client +- How to implement "just in time" (JIT) tool user authorization using Arcade's client @@ -146,7 +146,7 @@ TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] TOOL_LIMIT = 30 # This prompt defines the behavior of the agent. SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack." -# This determines which LLM model will be used inside the agent +# This determines which LLM model will handle the agent MODEL = "gpt-4o-mini" ``` @@ -183,20 +183,20 @@ def convert_output_to_json(output: Any) -> str: ### Write a helper function to authorize Arcade tools -This helper function implements "just in time" (JIT) tool authorization using Arcade's client. When the agent tries to execute a tool that requires authorization, the `result` object's `status` will be `"pending"`, and you can use the `authorize` method to get an authorization URL. You then wait for the user to complete the authorization and retry the tool call. If the user has already authorized the tool, the `status` will be `"completed"`, and the OAuth dance skips silently, which improves the user experience. +This helper function implements "just in time" (JIT) tool user authorization using Arcade's client. When the agent tries to execute a tool that requires user authorization, the `result` object's `status` will have the value `"pending"`, and you can use the `authorize` method to get a user authorization URL. You then wait for the user to complete the user authorization and retry the tool call. If the user has already authorized the tool, the `status` will have the value `"completed"`, and the OAuth dance skips silently, which improves the user experience. - This function captures the authorization flow outside of the agent's context, + This function captures the user authorization flow outside of the agent's context, which is a good practice for security and context engineering. By handling everything in the harness, 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. + user authorization URL or leaking it, and you keep the context free from any + user authorization-related traces, which reduces the risk of hallucinations. ```python filename="main.py" async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str): if not context.context.get("user_id"): - raise ToolError("No user ID and authorization required for tool") + raise ToolError("No user ID and user authorization required for tool") result = await client.tools.authorize( tool_name=tool_name, @@ -204,14 +204,14 @@ async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_n ) if result.status != "completed": - print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}") + print(f"{tool_name} requires user authorization to run, please open the following URL to authorize: {result.url}") await client.auth.wait_for_completion(result) ``` ### Write a helper function to execute Arcade tools -This helper function shows how the OpenAI Agents framework invokes the Arcade tools. It handles the authorization flow, and then calls the tool using the `execute` method. It handles the conversion of the arguments from JSON to a dictionary (expected by Arcade) and the conversion of the output from the Arcade tool to a JSON string (expected by the OpenAI Agents framework). Here is where you call the helper functions defined earlier to authorize the tool and convert the output to a JSON string. +This helper function shows how the OpenAI Agents framework invokes the Arcade tools. It handles the user authorization flow, and then calls the tool using the `execute` method. It handles the conversion of the arguments from JSON to a dictionary (expected by Arcade) and the conversion of the output from the Arcade tool to a JSON string (expected by the OpenAI Agents framework). Here is where you call the helper functions defined earlier to authorize the tool and convert the output to a JSON string. ```python filename="main.py" async def invoke_arcade_tool( @@ -239,7 +239,7 @@ async def invoke_arcade_tool( ### Retrieve Arcade tools and transform them into LangChain tools -Here you get the Arcade tools you want the agent to use, and transform them into OpenAI Agents tools. The first step is to initialize the Arcade client, and get the tools you want. Since OpenAI is itself an inference provider, the Arcade API provides a convenient endpoint to get the tools in the OpenAI format, which is also the format expected by the OpenAI Agents framework. +Here you get the Arcade tools you want the agent to use, and transform them into OpenAI Agents tools. The first step is to initialize the Arcade client, and get the tools you want. Since OpenAI is itself an inference provider, the Arcade API offers a convenient endpoint to get the tools in the OpenAI format, which is also the format expected by the OpenAI Agents framework. This helper function is long, here's a breakdown of what it does for clarity: @@ -258,10 +258,10 @@ async def get_arcade_tools( if not client: client = AsyncArcade() - # if no tools or MCP servers are provided, raise an error + # if no tools or MCP servers have values, raise an error if not tools and not mcp_servers: raise ValueError( - "No tools or MCP servers provided to retrieve tool definitions") + "No tools or MCP servers specified to retrieve tool definitions") # Use the Arcade Client to get OpenAI-formatted tool definitions tool_formats = [] @@ -370,7 +370,7 @@ if __name__ == "__main__": uv run main.py ``` -You should see the agent responding to your prompts like any model, as well as handling any tool calls and authorization requests. Here are some example prompts you can try: +You should see the agent responding to your prompts like any model, as well as handling any tool calls and user authorization requests. Here are some example prompts you can try: - "Send me an email with a random haiku about OpenAI Agents" - "Summarize my latest 3 emails" @@ -379,8 +379,8 @@ You should see the agent responding to your prompts like any model, as well as h ## Key takeaways -- You can integrate Arcade tools into any agentic framework like OpenAI Agents, all you need is to transform the Arcade tools into OpenAI Agents tools and handle the authorization flow. -- Context isolation: By handling the authorization flow outside of the agent's context, 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. +- You can integrate Arcade tools into any agentic framework like OpenAI Agents, all you need is to transform the Arcade tools into OpenAI Agents tools and handle the user authorization flow. +- Context isolation: By handling the user authorization flow outside of the agent's context, you remove the risk of the LLM replacing the user authorization URL or leaking it, and you keep the context free from any user authorization-related traces, which reduces the risk of hallucinations. ## Next steps @@ -389,7 +389,7 @@ You should see the agent responding to your prompts like any model, as well as h ## Example code -The team provides example code for you to reference: +Arcade offers example code for you to reference: ```python filename="main.py" from agents import Agent, Runner, TResponseInputItem @@ -418,7 +418,7 @@ TOOLS = ["Gmail_ListEmails", "Gmail_SendEmail", "Gmail_WhoAmI"] TOOL_LIMIT = 30 # This prompt defines the behavior of the agent. SYSTEM_PROMPT = "You are a helpful assistant that can assist with Gmail and Slack." -# This determines which LLM model will be used inside the agent +# This determines which LLM model will handle the agent MODEL = "gpt-4o-mini" # Arcade to OpenAI agent exception classes @@ -446,7 +446,7 @@ def convert_output_to_json(output: Any) -> str: async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_name: str): if not context.context.get("user_id"): - raise ToolError("No user ID and authorization required for tool") + raise ToolError("No user ID and user authorization required for tool") result = await client.tools.authorize( tool_name=tool_name, @@ -454,7 +454,7 @@ async def authorize_tool(client: AsyncArcade, context: RunContextWrapper, tool_n ) if result.status != "completed": - print(f"{tool_name} requires authorization to run, please open the following URL to authorize: {result.url}") + print(f"{tool_name} requires user authorization to run, please open the following URL to authorize: {result.url}") await client.auth.wait_for_completion(result) @@ -489,10 +489,10 @@ async def get_arcade_tools( if not client: client = AsyncArcade() - # if no tools or MCP servers are provided, raise an error + # if no tools or MCP servers have values, raise an error if not tools and not mcp_servers: raise ValueError( - "No tools or MCP servers provided to retrieve tool definitions") + "No tools or MCP servers specified to retrieve tool definitions") # Use the Arcade Client to get OpenAI-formatted tool definitions tool_formats = [] @@ -577,4 +577,4 @@ async def main(): # Run the main function as the entry point of the script if __name__ == "__main__": - asyncio.run(main()) + asyncio.run(main()) \ No newline at end of file diff --git a/app/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python/page.mdx b/app/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python/page.mdx index 6365c3c38..9d8e417ee 100644 --- a/app/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python/page.mdx +++ b/app/en/get-started/agent-frameworks/setup-arcade-with-your-llm-python/page.mdx @@ -8,7 +8,9 @@ import { SignupLink } from "@/app/_components/analytics"; # Connect Arcade to your LLM -Arcade tools work alongside an LLM. To make that work, you need a small piece of glue code called a "harness." The harness orchestrates the back-and-forth between the user, the model, and the tools. In this guide, you'll build one so you can wire Arcade into your LLM-powered app. +Build a harness to orchestrate communication between your user, an LLM, and Arcade tools in Python. + +Arcade tools work alongside an LLM. To make that work, you need a piece of glue code called a "harness." The harness orchestrates the back-and-forth between the user, the model, and the tools. In this guide, you'll build one so you can wire Arcade into your LLM-powered app. @@ -198,11 +200,9 @@ def authorize_and_run_tool(tool_name: str, input: str): return json.dumps(result.output.value) ``` -This helper function adapts to any tool in the catalog and will make sure that the authorization requirements are met before executing the tool. For more complex agentic patterns, this is generally the best place to handle interruptions that may require user interaction, such as when the tool requires a user to approve a request, or to provide additional context. - -### Write a helper function that handles the LLM's invocation +This helper function adapts to any tool in the catalog and makes sure that the authorization requirements meet authorization criteria before executing the tool. For more complex agentic patterns, this is generally the best place to handle interruptions that may require user interaction, such as when the tool requires a user to approve a request, or to provide additional context. -There are many orchestration patterns that can be used to handle the LLM invocation. A common pattern is a ReAct architecture, where the user prompt will result in a loop of messages between the LLM and the tools, until the LLM provides a final response (no tool calls). This is the pattern we will implement in this example. +Several options exist for orchestration patterns that can handle the LLM invocation. A common pattern is a ReAct architecture, where the user prompt will result in a loop of messages between the LLM and the tools, until the LLM provides a final response (no tool calls). This is the pattern implemented in this example. To avoid the risk of infinite loops, limit the number of turns (in this case, a maximum of 5). This is a parameter that you can tune to your needs. Set it to a value that is high enough to allow the LLM to complete its task but low enough to prevent infinite loops. @@ -260,7 +260,7 @@ def invoke_llm( return history ``` -These two helper functions form the core of your agentic loop. Notice that authorization is handled outside the agentic context, and the tool execution is passed back to the LLM in every case. Depending on your needs, you may want to handle tool orchestration within the harness and pass only the final result of multiple tool calls to the LLM. +These two helper functions form the core of your agentic loop. Notice that authorization occurs outside the agentic context, and the harness returns tool execution to the LLM in every case. Depending on your needs, you may want to handle tool orchestration within the harness and the harness returns only the final result of multiple tool calls to the LLM. ### Write the main agentic loop @@ -327,10 +327,10 @@ With the selection of tools above, you should be able to get the agent to effect -## Next Steps +## Next steps - Learn more about using Arcade with a [framework](/get-started/agent-frameworks) or [MCP client](/get-started/mcp-clients). -- Learn more about how to [build your own MCP Servers](/guides/create-tools/tool-basics/build-mcp-server). +- Learn more about how to [build your own MCP servers](/guides/create-tools/tool-basics/build-mcp-server). ## Example code @@ -484,4 +484,4 @@ def chat(): if __name__ == "__main__": chat() ``` - + \ No newline at end of file