Langchain arcade (#125)

Co-authored-by: Eric Gustin <eric@arcade-ai.com>
Co-authored-by: Nate Barbettini <nathanaelb@gmail.com>
Co-authored-by: Nate Barbettini <nate@arcade-ai.com>
This commit is contained in:
Sam Partee 2024-10-25 16:59:21 -07:00 committed by GitHub
parent 5b64404839
commit 4d2786935a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1132 additions and 196 deletions

View file

@ -9,7 +9,7 @@ You can contribute in many ways:
## Report Bugs ## Report Bugs
Report bugs at https://github.com/spartee/arcade-ai/issues Report bugs at https://github.com/ArcadeAI/arcade-ai/issues
If you are reporting a bug, please include: If you are reporting a bug, please include:
@ -29,11 +29,11 @@ Anything tagged with "enhancement" and "help wanted" is open to whoever wants to
## Write Documentation ## Write Documentation
Cookiecutter PyPackage could always use more documentation, whether as part of the official docs, in docstrings, or even on the web in blog posts, articles, and such. Arcade AI could always use more documentation, whether as part of the official docs, in docstrings, or even on the web in blog posts, articles, and such.
## Submit Feedback ## Submit Feedback
The best way to send feedback is to file an issue at https://github.com/spartee/arcade-ai/issues. The best way to send feedback is to file an issue at https://github.com/ArcadeAI/arcade-ai/issues.
If you are proposing a new feature: If you are proposing a new feature:

View file

@ -41,13 +41,13 @@
<a href="https://github.com/ArcadeAI/arcade-js" target="_blank">JavaScript Client</a> <a href="https://github.com/ArcadeAI/arcade-js" target="_blank">JavaScript Client</a>
</p> </p>
# <img src="https://docs.arcade-ai.com/images/logo/arcadeai-title-dark.png" alt="" width="139.98" height="27.84" style="vertical-align: bottom;" />
Arcade AI empowers any developer to seamlessly integrate large language models (LLMs) with real-world systems, enabling secure, user-authenticated interactions with data and services.
## What is Arcade AI? ## What is Arcade AI?
[Arcade AI](https://arcade-ai.com?ref=github) bridges the gap between powerful AI models and practical applications by making it easy for developers to build tools that perform real-world actions on behalf of users. With Arcade AI, unlock the true potential of AI in your applications. Check out our [documentation](https://docs.arcade-ai.com). [Arcade AI](https://arcade-ai.com?ref=github) offers developer-focused tooling and APIs designed to improve the capabilities of LLM applications and agents.
By providing an authentication and authorization layer for agents and the tools agents use, Arcade AI connects agentic applications with your users' data and services - like accessing their Gmail, GitHub, Zoom, Spotify, LinkedIn, and more.
To learn more, check out our [documentation](https://docs.arcade-ai.com).
_Pst. hey, you, join our stargazers! It's free!_ _Pst. hey, you, join our stargazers! It's free!_
@ -55,14 +55,13 @@ _Pst. hey, you, join our stargazers! It's free!_
<img src="https://img.shields.io/github/stars/arcadeai/arcade-ai.svg?style=social&label=Star&maxAge=2592000" alt="GitHub stars"> <img src="https://img.shields.io/github/stars/arcadeai/arcade-ai.svg?style=social&label=Star&maxAge=2592000" alt="GitHub stars">
</a> </a>
## How to use it? ## Quickstart
We provide a hosted version of Arcade AI that you can use immediately.
### Requirements ### Requirements
1. A free **[Arcade AI account](https://arcade-ai.com/signup)**
2. **Python 3.10+** verify your Python version by running `python --version` or `python3 --version` in your terminal 1. An **[Arcade AI account](https://arcade-ai.typeform.com/early-access)** (current a waitlist)
3. **pip** the Python package installer that is typically included with Python 2. **Python 3.10+**. Verify your Python version by running `python --version` or `python3 --version` in your terminal
3. **pip**, the Python package installer that is typically included with Python
### Installation ### Installation
@ -70,11 +69,19 @@ We provide a hosted version of Arcade AI that you can use immediately.
pip install 'arcade-ai[fastapi]' pip install 'arcade-ai[fastapi]'
``` ```
Then login to your account (we're working through the waitlist as fast as we can!)
```bash ```bash
arcade login arcade login
``` ```
### Verify Installation This will open a browser window to login.
### Verify Installation using `arcade chat`
The `arcade-ai` package comes with a CLI app called `arcade chat` that is used to test tools as you develop them.
By default, `arcade chat` will connect to the hosted version of Arcade AI with built-in tools (found in `toolkits`).
```bash ```bash
arcade chat arcade chat
@ -99,23 +106,24 @@ I starred the ArcadeAI/arcade-ai repo on Github for you!
You can use Ctrl-C to exit the chat at any time. You can use Ctrl-C to exit the chat at any time.
### Arcade Engine APIs
## Features - **`/auth`**: Generic OAuth 2.0 flow for authorizing agents across many services
Arcade AI integrates with a variety of services to provide a seamless experience for developers and users. - **`/tools`**: Manage, authorize, and execute tools. Tool-calling where the tools are **actually called**
- **`/chat`**: An OpenAI-compatible LLM API that enables tool execution with new `tool_choice` options:
1. `tool_choice='execute'`: Return the predicted tool call's output as content in the response
2. `tool_choice='generate'`: Generate a response informed by predicted tool call(s) execution.
1. **Hosted Tools**: Arcade AI offers a number of prebuilt toolkits that are ready to use out of the box. These toolkits can be used to interact with a variety of services. See the full API spec [here](https://reference.arcade-ai.com).
1. **Custom Tools**: Developers can build their own tools to integrate with Arcade AI.
1. **Auth Providers**: Arcade AI integrates with a variety of auth providers to enable users to seamlessly and securely allow Arcade AI tools to access their data.
### Arcade Cloud Engine
You can find all of Arcade AI's capabilities and how to use them in our [documentation](https://docs.arcade-ai.com).
### Arcade AI Hosted Tools
<img src="https://docs.arcade-ai.com/images/icons/github.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/gmail.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/google_calendar.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/google_docs.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/google_drive.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/serpapi.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/slack.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/web.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/twitter.png" alt="" width="30" height="30" style="vertical-align: top;" /> <img src="https://docs.arcade-ai.com/images/icons/github.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/gmail.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/google_calendar.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/google_docs.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/google_drive.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/serpapi.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/slack.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/web.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/twitter.png" alt="" width="30" height="30" style="vertical-align: top;" />
<br><br> <br><br>
Arcade AI offers a number of prebuilt toolkits that can be used to interact with a variety of services. Arcade AI offers a number of prebuilt toolkits that can be used to interact with a variety of services.
#### Calling tools directly #### Calling tools directly
```python ```python
from arcadepy import Arcade from arcadepy import Arcade
@ -145,6 +153,7 @@ print(response)
``` ```
#### Calling tools with the LLM API #### Calling tools with the LLM API
```python ```python
import os import os
from openai import OpenAI from openai import OpenAI
@ -182,6 +191,7 @@ Arcade AI enables you to evaluate your custom tools to ensure they function corr
Learn how to evaluate your tools by following our [evaluating tools guide](https://docs.arcade-ai.com/home/evaluate-tools/create-an-evaluation-suite). Learn how to evaluate your tools by following our [evaluating tools guide](https://docs.arcade-ai.com/home/evaluate-tools/create-an-evaluation-suite).
### Auth ### Auth
<img src="https://docs.arcade-ai.com/images/icons/github.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/google.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/linkedin.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/msft.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/slack.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/spotify.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/twitter.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/zoom.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/oauth2.png" alt="" width="30" height="30" style="vertical-align: top;" /> <img src="https://docs.arcade-ai.com/images/icons/github.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/google.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/linkedin.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/msft.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/slack.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/spotify.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/twitter.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/zoom.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/oauth2.png" alt="" width="30" height="30" style="vertical-align: top;" />
<br><br> <br><br>
@ -192,6 +202,7 @@ Learn how to use Arcade AI's auth providers to enable tools and agents to call o
To see all available auth providers, refer to the [auth providers documentation](https://docs.arcade-ai.com/integrations). To see all available auth providers, refer to the [auth providers documentation](https://docs.arcade-ai.com/integrations).
### Models ### Models
<img src="https://docs.arcade-ai.com/images/icons/openai.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/anthropic.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/ollama.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/groq.png" alt="" width="30" height="30" style="vertical-align: top;" /> <img src="https://docs.arcade-ai.com/images/icons/openai.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/anthropic.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/ollama.png" alt="" width="30" height="30" style="vertical-align: top;" /><img src="https://docs.arcade-ai.com/images/icons/groq.png" alt="" width="30" height="30" style="vertical-align: top;" />
<br><br> <br><br>
Arcade AI supports a variety of model providers when using the Arcade AI LLM API. Arcade AI supports a variety of model providers when using the Arcade AI LLM API.

View file

@ -139,7 +139,7 @@ def show(
None, "-t", "--tool", help="The specific tool to show details for" None, "-t", "--tool", help="The specific tool to show details for"
), ),
host: str = typer.Option( host: str = typer.Option(
None, DEFAULT_ENGINE_HOST,
"-h", "-h",
"--host", "--host",
help="The Arcade Engine address to send chat requests to.", help="The Arcade Engine address to send chat requests to.",
@ -165,9 +165,9 @@ def show(
""" """
Show the available toolkits or detailed information about a specific tool. Show the available toolkits or detailed information about a specific tool.
""" """
local_hosts = ["localhost", "127.0.0.1", "0.0.0.0"] # noqa: S104
try: try:
if not host: if host in local_hosts:
catalog = create_cli_catalog(toolkit=toolkit) catalog = create_cli_catalog(toolkit=toolkit)
tools = [t.definition for t in list(catalog)] tools = [t.definition for t in list(catalog)]
else: else:

175
contrib/langchain/.gitignore vendored Normal file
View file

@ -0,0 +1,175 @@
.DS_Store
arcade.toml
docker/arcade.toml
*.lock
# example data
examples/data
scratch
docs/source
# From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

21
contrib/langchain/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024, Arcade AI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,62 @@
VERSION ?= "0.1.0"
.PHONY: install
install: ## Install the poetry environment and install the pre-commit hooks
@if ! command -v poetry >/dev/null 2>&1; then \
echo "🚫 Poetry is not installed. Please install poetry before proceeding."; \
exit 1; \
fi
@echo "🚀 Creating virtual environment using pyenv and poetry"
@poetry install --all-extras
@poetry run pre-commit install
.PHONY: check
check: ## Run code quality tools.
@echo "🚀 Checking Poetry lock file consistency with 'pyproject.toml': Running poetry check --lock"
@poetry check --lock
@echo "🚀 Linting code: Running pre-commit"
@poetry run pre-commit run -a
@echo "🚀 Static type checking: Running mypy"
@poetry run mypy $(git ls-files '*.py')
.PHONY: test
test: ## Test the code with pytest
@echo "🚀 Testing code: Running pytest"
@poetry run pytest -W ignore -v --cov --cov-config=pyproject.toml --cov-report=xml
.PHONY: set-version
set-version: ## Set the version in the pyproject.toml file
@echo "🚀 Setting version in pyproject.toml"
@poetry version $(VERSION)
.PHONY: unset-version
unset-version: ## Set the version in the pyproject.toml file
@echo "🚀 Setting version in pyproject.toml"
@poetry version 0.1.0
.PHONY: build
build: clean-build ## Build wheel file using poetry
@echo "🚀 Creating wheel file"
@poetry build
.PHONY: clean-build
clean-build: ## clean build artifacts
@rm -rf dist
.PHONY: publish
publish: ## publish a release to pypi.
@echo "🚀 Publishing: Dry run."
@poetry config pypi-token.pypi $(PYPI_TOKEN)
@poetry publish --dry-run
@echo "🚀 Publishing."
@poetry publish
.PHONY: build-and-publish
build-and-publish: build publish ## Build and publish.
.PHONY: help
help:
@echo "🛠️ Arcade AI Dev Commands:\n"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.DEFAULT_GOAL := help

View file

@ -0,0 +1,38 @@
<h3 align="center">
<a name="readme-top"></a>
<img
src="https://docs.arcade-ai.com/images/logo/arcade-ai-logo.png"
>
</h3>
<div align="center">
<h3>LangChain Integration</h3>
<a href="https://github.com/arcadeai/arcade-ai/blob/main/LICENSE">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License">
</a>
<a href="https://pepy.tech/project/langchain-arcade">
<img src="https://static.pepy.tech/badge/langchain-arcade" alt="Downloads">
</a>
</div>
<p align="center">
<a href="https://docs.arcade-ai.com" target="_blank">Docs</a>
<a href="https://docs.arcade-ai.com/integrations" target="_blank">Integrations</a>
<a href="https://github.com/ArcadeAI/cookbook" target="_blank">Cookbook</a>
<a href="https://github.com/ArcadeAI/arcade-py" target="_blank">Python Client</a>
<a href="https://github.com/ArcadeAI/arcade-js" target="_blank">JavaScript Client</a>
</p>
## Overview
`langchain-arcade` allows you to use Arcade AI tools in your LangChain and LangGraph applications.
## Installation
```bash
pip install langchain-arcade
```
## Usage
See the [examples](https://github.com/ArcadeAI/arcade-ai/tree/main/examples/langchain) for usage examples

View file

@ -0,0 +1,3 @@
from .manager import ArcadeToolManager
__all__ = ["ArcadeToolManager"]

View file

@ -0,0 +1,182 @@
from typing import Any, Callable
from arcadepy import NOT_GIVEN, Arcade
from arcadepy.types.shared import ToolDefinition
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field, create_model
# Check if LangGraph is enabled
LANGGRAPH_ENABLED = True
try:
from langgraph.errors import NodeInterrupt
except ImportError:
LANGGRAPH_ENABLED = False
# Mapping of Arcade value types to Python types
TYPE_MAPPING = {
"string": str,
"number": float,
"integer": int,
"boolean": bool,
"array": list,
"json": dict,
}
def get_python_type(val_type: str) -> Any:
"""Map Arcade value types to Python types.
Args:
val_type: The value type as a string.
Returns:
Corresponding Python type.
"""
_type = TYPE_MAPPING.get(val_type)
if _type is None:
raise ValueError(f"Invalid value type: {val_type}")
return _type
def tool_definition_to_pydantic_model(tool_def: ToolDefinition) -> type[BaseModel]:
"""Convert a ToolDefinition's inputs into a Pydantic BaseModel.
Args:
tool_def: The ToolDefinition object to convert.
Returns:
A Pydantic BaseModel class representing the tool's input schema.
"""
try:
fields: dict[str, Any] = {}
for param in tool_def.inputs.parameters or []:
param_type = get_python_type(param.value_schema.val_type)
if param_type == list and param.value_schema.inner_val_type: # noqa: E721
inner_type: type[Any] = get_python_type(param.value_schema.inner_val_type)
param_type = list[inner_type] # type: ignore[valid-type]
param_description = param.description or "No description provided."
default = ... if param.required else None
fields[param.name] = (
param_type,
Field(default=default, description=param_description),
)
return create_model(f"{tool_def.name}Args", **fields)
except ValueError as e:
raise ValueError(
f"Error converting {tool_def.name} parameters into pydantic model for langchain: {e}"
)
def create_tool_function(
client: Arcade,
tool_name: str,
tool_def: ToolDefinition,
args_schema: type[BaseModel],
langgraph: bool = False,
) -> Callable:
"""Create a callable function to execute an Arcade tool.
Args:
client: The Arcade client instance.
tool_name: The name of the tool to wrap.
tool_def: The ToolDefinition of the tool to wrap.
args_schema: The Pydantic model representing the tool's arguments.
langgraph: Whether to enable LangGraph-specific behavior.
Returns:
A callable function that executes the tool.
"""
if langgraph and not LANGGRAPH_ENABLED:
raise ImportError("LangGraph is not installed. Please install it to use this feature.")
requires_authorization = (
tool_def.requirements is not None and tool_def.requirements.authorization is not None
)
def tool_function(config: RunnableConfig, **kwargs: Any) -> Any:
"""Execute the Arcade tool with the given parameters.
Args:
config: RunnableConfig containing execution context.
**kwargs: Tool input arguments.
Returns:
The output from the tool execution.
"""
user_id = config.get("configurable", {}).get("user_id") if config else None
if requires_authorization:
if user_id is None:
error_message = f"user_id is required to run {tool_name}"
if langgraph:
raise NodeInterrupt(error_message)
return {"error": error_message}
# Authorize the user for the tool
auth_response = client.tools.authorize(tool_name=tool_name, user_id=user_id)
if auth_response.status != "completed":
auth_message = (
"Please use the following link to authorize: "
f"{auth_response.authorization_url}"
)
if langgraph:
raise NodeInterrupt(auth_message)
return {"error": auth_message}
# Execute the tool with provided inputs
execute_response = client.tools.execute(
tool_name=tool_name,
inputs=kwargs,
user_id=user_id if user_id is not None else NOT_GIVEN,
)
if execute_response.success:
return execute_response.output.value # type: ignore[union-attr]
error_message = str(execute_response.output.error) # type: ignore[union-attr]
if langgraph:
raise NodeInterrupt(error_message)
return {"error": error_message}
return tool_function
def wrap_arcade_tool(
client: Arcade,
tool_name: str,
tool_def: ToolDefinition,
langgraph: bool = False,
) -> StructuredTool:
"""Wrap an Arcade `ToolDefinition` as a LangChain `StructuredTool`.
Args:
client: The Arcade client instance.
tool_name: The name of the tool to wrap.
tool_def: The ToolDefinition object to wrap.
langgraph: Whether to enable LangGraph-specific behavior.
Returns:
A StructuredTool instance representing the Arcade tool.
"""
description = tool_def.description or "No description provided."
# Create a Pydantic model for the tool's input arguments
args_schema = tool_definition_to_pydantic_model(tool_def)
# Create the action function
action_func = create_tool_function(
client=client,
tool_name=tool_name,
tool_def=tool_def,
args_schema=args_schema,
langgraph=langgraph,
)
# Create the StructuredTool instance
return StructuredTool.from_function(
func=action_func,
name=tool_name,
description=description,
args_schema=args_schema,
inject_kwargs={"user_id"},
)

View file

@ -0,0 +1,184 @@
import os
from collections.abc import Iterator
from typing import Any, Optional
from arcadepy import Arcade
from arcadepy.types.shared import AuthorizationResponse, ToolDefinition
from langchain_core.tools import StructuredTool
from langchain_arcade._utilities import (
wrap_arcade_tool,
)
class ArcadeToolManager:
"""
Arcade tool manager for LangChain framework.
This class wraps Arcade tools as LangChain `StructuredTool`
objects for integration.
"""
def __init__(
self,
client: Optional[Arcade] = None,
**kwargs: dict[str, Any],
) -> None:
"""Initialize the ArcadeToolManager.
Example:
>>> manager = ArcadeToolManager(api_key="...")
>>>
>>> # retrieve a specific tool as a langchain tool
>>> manager.get_tools(tools=["Search.SearchGoogle"])
>>>
>>> # retrieve all tools in a toolkit as langchain tools
>>> manager.get_tools(toolkits=["Search"])
>>>
>>> # clear and initialize new tools in the manager
>>> manager.init_tools(tools=["Search.SearchGoogle"], toolkits=["Search"])
Args:
client: Optional Arcade client instance.
"""
if not client:
api_key = kwargs.get("api_key", os.getenv("ARCADE_API_KEY", None))
client = Arcade(api_key=api_key) # type: ignore[arg-type]
self.client = client
self._tools: dict[str, ToolDefinition] = {}
@property
def tools(self) -> list[str]:
return list(self._tools.keys())
def __iter__(self) -> Iterator[tuple[str, ToolDefinition]]:
yield from self._tools.items()
def __len__(self) -> int:
return len(self._tools)
def __getitem__(self, tool_name: str) -> ToolDefinition:
return self._tools[tool_name]
def init_tools(
self,
tools: Optional[list[str]] = None,
toolkits: Optional[list[str]] = None,
) -> None:
"""Initialize the tools in the manager.
This will clear any existing tools in the manager.
Example:
>>> manager = ArcadeToolManager(api_key="...")
>>> manager.init_tools(tools=["Search.SearchGoogle"])
>>> manager.get_tools()
Args:
tools: Optional list of tool names to include.
toolkits: Optional list of toolkits to include.
"""
self._tools = self._retrieve_tool_definitions(tools, toolkits)
def get_tools(
self,
tools: Optional[list[str]] = None,
toolkits: Optional[list[str]] = None,
langgraph: bool = False,
) -> list[StructuredTool]:
"""Return the tools in the manager as LangChain StructuredTool objects.
Note: if tools/toolkits are provided, the manager will update it's
internal tools using a dictionary update by tool name.
Example:
>>> manager = ArcadeToolManager(api_key="...")
>>>
>>> # retrieve a specific tool as a langchain tool
>>> manager.get_tools(tools=["Search.SearchGoogle"])
Args:
tools: Optional list of tool names to include.
toolkits: Optional list of toolkits to include.
langgraph: Whether to use LangGraph-specific behavior
such as NodeInterrupts for auth.
Returns:
List of StructuredTool instances.
"""
# TODO account for versioning
if tools or toolkits:
new_tools = self._retrieve_tool_definitions(tools, toolkits)
self._tools.update(new_tools)
elif len(self) == 0:
self.init_tools()
langchain_tools: list[StructuredTool] = []
for tool_name, definition in self:
lc_tool = wrap_arcade_tool(self.client, tool_name, definition, langgraph)
langchain_tools.append(lc_tool)
return langchain_tools
def authorize(self, tool_name: str, user_id: str) -> AuthorizationResponse:
"""Authorize a user for a tool.
Example:
>>> manager = ArcadeToolManager(api_key="...")
>>> manager.authorize("X.PostTweet", "user_123")
Args:
tool_name: The name of the tool to authorize.
user_id: The user ID to authorize.
Returns:
AuthorizationResponse
"""
return self.client.tools.authorize(tool_name=tool_name, user_id=user_id)
def is_authorized(self, authorization_id: str) -> bool:
"""Check if a tool authorization is complete.
Example:
>>> manager = ArcadeToolManager(api_key="...")
>>> manager.init_tools(toolkits=["Search"])
>>> manager.is_authorized("auth_123")
"""
return self.client.auth.status(authorization_id=authorization_id).status == "completed"
def requires_auth(self, tool_name: str) -> bool:
"""Check if a tool requires authorization."""
tool_def = self._get_tool_definition(tool_name)
if tool_def.requirements is None:
return False
return tool_def.requirements.authorization is not None
def _get_tool_definition(self, tool_name: str) -> ToolDefinition:
try:
return self._tools[tool_name]
except KeyError:
raise ValueError(f"Tool '{tool_name}' not found in this ArcadeToolManager instance")
def _retrieve_tool_definitions(
self, tools: Optional[list[str]] = None, toolkits: Optional[list[str]] = None
) -> dict[str, ToolDefinition]:
all_tools: list[ToolDefinition] = []
if tools is not None or toolkits is not None:
if tools:
single_tools = [self.client.tools.get(tool_id=tool_id) for tool_id in tools]
all_tools.extend(single_tools)
if toolkits:
for tk in toolkits:
all_tools.extend(self.client.tools.list(toolkit=tk))
else:
# retrieve all tools
page_iterator = self.client.tools.list()
all_tools.extend(page_iterator)
tool_definitions: dict[str, ToolDefinition] = {}
for tool in all_tools:
full_tool_name = f"{tool.toolkit.name}_{tool.name}"
tool_definitions[full_tool_name] = tool
return tool_definitions

View file

@ -0,0 +1,49 @@
[tool.poetry]
name = "langchain-arcade"
version = "0.1.1"
description = "An integration package connecting Arcade AI and LangChain/LangGraph"
authors = ["Arcade AI <dev@arcade-ai.com>"]
readme = "README.md"
repository = "https://github.com/arcadeai/arcade-ai/tree/main/contrib/langchain"
license = "MIT"
[tool.poetry.dependencies]
python = ">=3.10,<3.13"
langchain-core = "^0.3.0"
arcadepy = "~0.2.0"
langgraph = {version = ">=0.2.32,<0.3.0", optional = true}
[tool.poetry.extras]
langgraph = ["langgraph"]
[tool.poetry.group.dev.dependencies]
pytest = "^8.1.2"
pytest-cov = "^4.0.0"
mypy = "^1.5.1"
pre-commit = "^3.4.0"
tox = "^4.11.1"
pytest-asyncio = "^0.23.7"
[tool.mypy]
files = ["langchain_arcade"]
python_version = "3.10"
disallow_untyped_defs = "True"
disallow_any_unimported = "True"
no_implicit_optional = "True"
check_untyped_defs = "True"
warn_return_any = "True"
warn_unused_ignores = "True"
show_error_codes = "True"
ignore_missing_imports = "True"
[tool.pytest.ini_options]
testpaths = ["tests"]
[tool.coverage.run]
branch = true
source = ["langchain_arcade"]
[tool.coverage.report]
skip_empty = true

View file

@ -0,0 +1,59 @@
import os
from arcadepy import Arcade
from google.oauth2.credentials import Credentials
from langchain_google_community import GmailToolkit
from langchain_google_community.gmail.utils import (
build_resource_service,
)
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
# Get the API key from the environment variable
api_key = os.getenv("ARCADE_API_KEY")
# Initialize the Arcade client
client = Arcade(api_key=api_key)
# Start the authorization process for Gmail
# see all possible gmail scopes here:
# https://developers.google.com/gmail/api/auth/scopes
user_id = "user@example.com"
auth_response = client.auth.start(
user_id=user_id, provider="google", scopes=["https://www.googleapis.com/auth/gmail.readonly"]
)
# Prompt the user to authorize if not already completed
if auth_response.status != "completed":
print("Please authorize the application in your browser:")
print(auth_response.authorization_url)
# Wait for the user to complete the authorization process, if necessary...
auth_response = client.auth.wait_for_completion(auth_response)
# Obtain credentials using the authorization context
creds = Credentials(auth_response.context.token)
api_resource = build_resource_service(credentials=creds)
# Initialize the Gmail toolkit with the authorized API resource
toolkit = GmailToolkit(api_resource=api_resource)
# Retrieve the tools from the langchain gmail toolkit
tools = toolkit.get_tools()
# Initialize the language model and create an agent
llm = ChatOpenAI(model="gpt-4o")
agent_executor = create_react_agent(llm, tools)
# Define the user query
example_query = "Read my latest emails and summarize them."
# Execute the agent with the user query
events = agent_executor.stream(
{"messages": [("user", example_query)]},
stream_mode="values",
)
# Display the agent's response
for event in events:
event["messages"][-1].pretty_print()

View file

@ -0,0 +1,101 @@
import os
import time
# Import necessary classes and modules
from langchain_arcade import ArcadeToolManager
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode
arcade_api_key = os.environ["ARCADE_API_KEY"]
openai_api_key = os.environ["OPENAI_API_KEY"]
# Initialize the tool manager and fetch tools compatible with langgraph
tool_manager = ArcadeToolManager(api_key=arcade_api_key)
tools = tool_manager.get_tools(
toolkits=["Github"],
langgraph=True, # use langgraph-specific behavior
)
tool_node = ToolNode(tools)
# Create a language model instance and bind it with the tools
model = ChatOpenAI(model="gpt-4o", api_key=openai_api_key)
model_with_tools = model.bind_tools(tools)
# Function to invoke the model and get a response
def call_agent(state):
messages = state["messages"]
response = model_with_tools.invoke(messages)
# Return the updated message history
return {"messages": [*messages, response]}
# Function to determine the next step in the workflow based on the last message
def should_continue(state: MessagesState):
last_message = state["messages"][-1]
if last_message.tool_calls:
tool_name = last_message.tool_calls[0]["name"]
if tool_manager.requires_auth(tool_name):
return "authorization" # Proceed to authorization if required
else:
return "tools" # Proceed to tool execution if no authorization is needed
return END # End the workflow if no tool calls are present
# Function to handle authorization for tools that require it
def authorize(state: MessagesState, config: dict):
user_id = config["configurable"].get("user_id")
tool_name = state["messages"][-1].tool_calls[0]["name"]
auth_response = tool_manager.authorize(tool_name, user_id)
if auth_response.status == "completed":
# Authorization completed successfully; continue
return {"messages": state["messages"]}
else:
# Prompt the user to visit the authorization URL
print(f"Visit the following URL to authorize: {auth_response.authorization_url}")
# Wait until authorization is completed
while not tool_manager.is_authorized(auth_response.authorization_id):
time.sleep(1)
return {"messages": state["messages"]}
# Build the workflow graph using StateGraph
workflow = StateGraph(MessagesState)
# Add nodes (steps) to the graph
workflow.add_node("agent", call_agent)
workflow.add_node("tools", tool_node)
workflow.add_node("authorization", authorize)
# Define the edges and control flow between nodes
workflow.add_edge(START, "agent")
workflow.add_conditional_edges("agent", should_continue, ["authorization", "tools", END])
workflow.add_edge("authorization", "tools")
workflow.add_edge("tools", "agent")
# Set up memory for checkpointing the state
memory = MemorySaver()
# Compile the graph with the checkpointer
graph = workflow.compile(checkpointer=memory)
# Define the input messages from the user
inputs = {
"messages": [HumanMessage(content="Star arcadeai/arcade-ai on GitHub!")],
}
# Configuration with thread and user IDs for authorization purposes
config = {
"configurable": {
"thread_id": "4",
"user_id": "user@example.com",
}
}
# Run the graph and stream the outputs
for chunk in graph.stream(inputs, config=config, stream_mode="values"):
# Pretty-print the last message in the chunk
chunk["messages"][-1].pretty_print()

View file

@ -1,65 +0,0 @@
from typing import cast
from arcadepy import NOT_GIVEN, Arcade
from arcadepy.types.auth_authorize_params import AuthRequirement, AuthRequirementOauth2
from google.oauth2.credentials import Credentials
from langchain_google_community import GmailToolkit
from langchain_google_community.gmail.utils import (
build_resource_service,
)
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
# Step 1: Install required packages
# Run the following in your terminal:
# %pip install -qU langchain-google-community[gmail]
# %pip install -qU langchain-openai
# %pip install -qU langgraph
client = Arcade()
# Start the authorization process for the tool "ListEmails"
auth_response = client.auth.authorize(
auth_requirement=AuthRequirement(
provider_id="google",
oauth2=AuthRequirementOauth2(
scopes=["https://www.googleapis.com/auth/gmail.readonly"],
),
),
user_id="sam@arcade-ai.com",
)
# If authorization is not completed, prompt the user and poll for status
if auth_response.status != "completed":
print("Please complete the authorization challenge in your browser before continuing:")
print(auth_response.authorization_url)
input("Press Enter to continue...")
# Poll for authorization status using the auth polling method
while auth_response.status != "completed":
auth_response = client.auth.status(
authorization_id=cast(str, auth_response.authorization_id),
scopes=" ".join(auth_response.scopes) if auth_response.scopes else NOT_GIVEN,
wait=30, # Long poll
)
# Authorization is completed; proceed with obtaining credentials
creds = Credentials(auth_response.context.token)
api_resource = build_resource_service(credentials=creds)
toolkit = GmailToolkit(api_resource=api_resource)
# Step 4: Get available tools
tools = toolkit.get_tools()
# Step 5: Initialize the LLM and create an agent
llm = ChatOpenAI(model="gpt-4o")
agent_executor = create_react_agent(llm, tools)
# Step 6: Draft an email using the agent
example_query = "Read my latest emails to me and summarize them."
events = agent_executor.stream(
{"messages": [("user", example_query)]},
stream_mode="values",
)
for event in events:
event["messages"][-1].pretty_print()

View file

@ -1,60 +0,0 @@
import os
from typing import Any, TypedDict
from arcadepy import Arcade
from langgraph.checkpoint.memory import MemorySaver
from langgraph.errors import NodeInterrupt
from langgraph.graph import END, START, StateGraph
client = Arcade(api_key=os.environ["ARCADE_API_KEY"])
class State(TypedDict):
emails: Any
def step_1(state: State, config) -> State:
user_id = config["configurable"]["user_id"]
challenge = client.tools.authorize(
tool_name="ListEmails",
user_id=user_id,
)
if challenge.status != "completed":
raise NodeInterrupt(f"Please visit this URL to authorize: {challenge.auth_url}")
result = client.tools.execute(
tool_name="ListEmails",
user_id=user_id,
inputs={"n_emails": 5},
)
return {"emails": result}
builder = StateGraph(State)
builder.add_node("step_1", step_1)
builder.add_edge(START, "step_1")
builder.add_edge("step_1", END)
# Set up memory
memory = MemorySaver()
# Compile the graph with memory
graph = builder.compile(checkpointer=memory)
config = {"configurable": {"thread_id": "2", "user_id": "sam@arcade-ai.com"}}
result = graph.invoke({"emails": None}, config=config)
state = graph.get_state({"configurable": {"thread_id": "2"}})
print("interrupted state\n----------")
print(state)
print("----------")
input()
result = graph.invoke({"emails": None}, config=config)
state = graph.get_state({"configurable": {"thread_id": "2"}})
print("final state\n----------")
print(state)
print("----------")
print("final result\n----------")
print(result)
print("----------")

View file

@ -0,0 +1,5 @@
langchain>=0.3.0
arcadepy>=0.2.0
langchain-google-community[gmail]>=0.1.1
langchain-openai>=0.1.1
langgraph>=0.1.1

View file

@ -0,0 +1,37 @@
import os
from langchain import hub
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_arcade import ArcadeToolManager
from langchain_openai import ChatOpenAI
arcade_api_key = os.environ["ARCADE_API_KEY"]
openai_api_key = os.environ["OPENAI_API_KEY"]
# Pull relevant agent model.
prompt = hub.pull("hwchase17/openai-functions-agent")
# Get all the tools available in Arcade
manager = ArcadeToolManager(api_key=arcade_api_key)
# Tool names follow the format "ToolkitName.ToolName"
tools = manager.get_tools(tools=["Web.ScrapeUrl"])
print(manager.tools)
# clear and init new tools from a toolkit
manager.init_tools(toolkits=["Search"])
print(manager.tools)
# get more tools
tools = manager.get_tools(toolkits=["Math"])
print(manager.tools)
# init the LLM
llm = ChatOpenAI(api_key=openai_api_key)
# Define agent
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# Try a few examples
agent_executor.invoke({"input": "Lookup Seymour Cray on Google"})
agent_executor.invoke({"input": "What is 1234567890 * 9876543210?"})

View file

@ -0,0 +1,42 @@
import os
# Import necessary modules and classes
from langchain_arcade import ArcadeToolManager
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
arcade_api_key = os.environ["ARCADE_API_KEY"]
openai_api_key = os.environ["OPENAI_API_KEY"]
# Initialize the tool manager that fetches
# tools from arcade and wraps them as langgraph tools
tool_manager = ArcadeToolManager(api_key=arcade_api_key)
tools = tool_manager.get_tools(langgraph=True)
# Create an instance of the AI language model
model = ChatOpenAI(model="gpt-4o", api_key=openai_api_key)
# Init a prebuilt agent that can use tools
# in a REACT style langgraph
graph = create_react_agent(model, tools=tools)
# Define the initial input message from the user
inputs = {
"messages": [HumanMessage(content="Star arcadeai/arcade-ai on GitHub!")],
}
# Configuration parameters for the agent and tools
config = {
"configurable": {
"thread_id": "2",
"user_id": "user@example.com",
}
}
# Stream the assistant's responses by executing the graph
for chunk in graph.stream(inputs, stream_mode="values", config=config):
# Access the latest message from the conversation
last_message = chunk["messages"][-1]
# Print the assistant's message content
print(last_message.content)

View file

@ -0,0 +1,21 @@
## Setup
Follow [these instructions](https://arcade-ai.com/home/quickstart) to Install Arcade AI and create an API key.
This example is using OpenAI, as the LLM provider. Ensure you have an [OpenAI API key](https://platform.openai.com/docs/quickstart).
Copy the `env.example` file to `.env` and supply your API keys for **at least** `OPENAI_API_KEY` and `ARCADE_API_KEY`.
## Usage with LangGraph API
### Local testing with LangGraph Studio
For testing locally (e.g., currently supported only on MacOS), you can use the LangGraph Studio desktop application.
[Download LangGraph Studio](https://github.com/langchain-ai/langgraph-studio?tab=readme-ov-file#download) and open this directory in the Studio application.
The `langgraph.json` file in this directory specifies the graph that will be loaded in Studio.
### Deploying to LangGraph Cloud
Follow [these instructions](https://langchain-ai.github.io/langgraph/cloud/quick_start/#deploy-to-cloud) to deploy your graph to LangGraph Cloud.

View file

@ -0,0 +1,8 @@
from dataclasses import dataclass
@dataclass(kw_only=True)
class AgentConfigurable:
"""The configurable fields for the chatbot."""
user_id: str = "default-user"

View file

@ -0,0 +1,9 @@
# To separate your traces from other application
LANGSMITH_PROJECT=arcade-graph
# LANGCHAIN_API_KEY=...
# Arcade API key
# ARCADE_API_KEY=...
# LLM choice:
# OPENAI_API_KEY=...

View file

@ -0,0 +1,84 @@
import os
import time
from configuration import AgentConfigurable
from langchain_arcade import ArcadeToolManager
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode
# Initialize the Arcade Tool Manager with your API key
arcade_api_key = os.getenv("ARCADE_API_KEY")
openai_api_key = os.getenv("OPENAI_API_KEY")
toolkit = ArcadeToolManager(api_key=arcade_api_key)
# Retrieve tools compatible with LangGraph
tools = toolkit.get_tools(langgraph=True)
tool_node = ToolNode(tools)
# Initialize the language model with your OpenAI API key
model = ChatOpenAI(model="gpt-4o", api_key=openai_api_key)
# make the model aware of the tools
model_with_tools = model.bind_tools(tools)
# Define the agent function that invokes the model
def call_agent(state):
messages = state["messages"]
response = model_with_tools.invoke(messages)
# Return the updated message history
return {"messages": [*messages, response]}
# Function to determine the next step based on the model's response
def should_continue(state: MessagesState):
last_message = state["messages"][-1]
if last_message.tool_calls:
tool_name = last_message.tool_calls[0]["name"]
if toolkit.requires_auth(tool_name):
# If the tool requires authorization, proceed to the authorization step
return "authorization"
else:
# If no authorization is needed, proceed to execute the tool
return "tools"
# If no tool calls are present, end the workflow
return END
# Function to handle tool authorization
def authorize(state: MessagesState, config: dict):
user_id = config["configurable"].get("user_id")
tool_name = state["messages"][-1].tool_calls[0]["name"]
auth_response = toolkit.authorize(tool_name, user_id)
if auth_response.status == "completed":
# Authorization is complete; proceed to the next step
return {"messages": state["messages"]}
else:
# Prompt the user to complete authorization
print("Please authorize the application in your browser:")
print(auth_response.authorization_url)
input("Press Enter after completing authorization...")
# Poll for authorization status
while not toolkit.is_authorized(auth_response.authorization_id):
time.sleep(3)
return {"messages": state["messages"]}
# Build the workflow graph
workflow = StateGraph(MessagesState, AgentConfigurable)
# Add nodes to the graph
workflow.add_node("agent", call_agent)
workflow.add_node("tools", tool_node)
workflow.add_node("authorization", authorize)
# Define the edges and control flow
workflow.add_edge(START, "agent")
workflow.add_conditional_edges("agent", should_continue, ["authorization", "tools", END])
workflow.add_edge("authorization", "tools")
workflow.add_edge("tools", "agent")
# Compile the graph
graph = workflow.compile()

View file

@ -0,0 +1,11 @@
{
"dockerfile_lines": [],
"graphs": {
"graph": "./graph.py:graph"
},
"env": ".env",
"python_version": "3.11",
"dependencies": [
"."
]
}

View file

@ -0,0 +1,4 @@
langchain>=0.3.0
langchain-openai>=0.1.1
langgraph>=0.1.1
langchain-arcade>=0.1.0

View file

@ -1,45 +0,0 @@
import os
from modal import App, Image, asgi_app
os.environ["ARCADE_WORK_DIR"] = "/root"
# Define the FastAPI app
app = App("arcade-ai-actor")
image = (
Image.debian_slim()
.copy_local_dir("./dist", "/root/dist")
.pip_install("/root/dist/arcade_ai-0.1.0-py3-none-any.whl")
.pip_install("/root/dist/arcade_gmail-0.1.0-py3-none-any.whl")
.pip_install("/root/dist/arcade_search-0.1.0-py3-none-any.whl")
.pip_install("/root/dist/arcade_slack-0.1.0-py3-none-any.whl")
.pip_install("/root/dist/arcade_x-0.1.0-py3-none-any.whl")
.pip_install("fastapi>=0.110.0")
.pip_install("uvicorn>=0.24.0")
.pip_install("pydantic>=2.7.0")
.copy_local_file("./arcade.toml", "/root/arcade.toml")
)
@app.function(image=image)
@asgi_app()
def fastapi_app():
from fastapi import FastAPI
from arcade.actor.fastapi.actor import FastAPIActor
from arcade.sdk import Toolkit
web_app = FastAPI()
# Initialize app and Arcade FastAPIActor
actor_secret = os.environ.get("ARCADE_ACTOR_SECRET")
actor = FastAPIActor(web_app, secret=actor_secret)
# Register toolkits we've installed
toolkits = Toolkit.find_all_arcade_toolkits()
for toolkit in toolkits:
actor.register_toolkit(toolkit)
return web_app