Add examples (#136)

## PR Description
This PR adds 7 examples.

* `call_a_tool_directly_with_auth.py` - Simple example that uses Arcade
client to execute a tool that lists Gmail emails
* `call_a_tool_directly.py` - Simple example that uses Arcade client to
execute a tool that adds two numbers together
* `call_a_tool_with_llm.py` - Simple example that uses the LLM api to
star the arcade-ai repository
* `get_auth_token.py` - Simple example that gets a Google auth token and
then calls the Google API
* `call_multiple_tools_directly_with_auth.py` - A more involved example
that directly calls multiple spotify tools sequentially
* `call_multiple_tools_with_llm.py` - A more involved example that uses
an llm to call multiple spotify tools sequentially
* `simple_chatbot.py` - Simple chatbot that uses arcade tools and has
history

---------

Co-authored-by: Nate Barbettini <nathanaelb@gmail.com>
This commit is contained in:
Eric Gustin 2024-11-06 11:02:41 -08:00 committed by GitHub
parent 6d1bc6c084
commit 081865733a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 546 additions and 60 deletions

View file

@ -8,6 +8,11 @@
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.organizeImports": "explicit"
},
"editor.defaultFormatter": "charliermarsh.ruff"
},
"[jsonc]": {

View file

@ -1,26 +0,0 @@
from arcadepy import Arcade
client = Arcade(
base_url="http://localhost:9099",
)
user_id = "you@example.com"
# Start the authorization process
auth_response = client.tools.authorize(
tool_name="Google.ListEmails",
user_id=user_id,
)
if auth_response.status != "completed":
print(f"Click this link to authorize: {auth_response.authorization_url}")
input("After you have authorized, press Enter to continue...")
inputs = {"n_emails": 5}
response = client.tools.execute(
tool_name="Google.ListEmails",
inputs=inputs,
user_id=user_id,
)
print(response)

View file

@ -1,34 +0,0 @@
from arcadepy import Arcade
from arcadepy.types.auth_authorize_params import AuthRequirement, AuthRequirementOauth2
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
client = Arcade(
base_url="http://localhost:9099",
)
user_id = "you@example.com"
# Start the authorization process
auth_response = client.auth.authorize(
auth_requirement=AuthRequirement(
provider_id="google",
oauth2=AuthRequirementOauth2(
scopes=["https://www.googleapis.com/auth/gmail.readonly"],
),
),
user_id=user_id,
)
if auth_response.status != "completed":
print(f"Click this link to authorize: {auth_response.authorization_url}")
input("After you have authorized, press Enter to continue...")
# Use the token from the authorization response
creds = Credentials(auth_response.context.token)
service = build("gmail", "v1", credentials=creds)
# Now you can use the Google API
results = service.users().labels().list(userId="me").execute()
labels = results.get("labels", [])
print("Labels:", labels)

View file

@ -0,0 +1,40 @@
"""
This example demonstrates how to directly call a tool that does not require authorization.
"""
from arcadepy import Arcade # pip install arcade-py
def call_non_auth_tool(client: Arcade, user_id: str) -> None:
"""Directly call a prebuilt tool that does not require authorization.
In this example, we are
1. Preparing the inputs to the Math.Add tool
2. Executing the tool
3. Printing the output of the tool's execution, i.e., the result of adding 9001 and 42
This is a simple example of calling a non-auth tool. Next, try writing your own non-auth tool for your own use case.
"""
# Prepare the inputs to the tool as a dictionary where keys are the names of the parameters expected by the tool and the values are the actual values to pass to the tool
inputs = {"a": 9001, "b": 42}
# Execute the tool
response = client.tools.execute(
tool_name="Math.Add",
inputs=inputs,
user_id=user_id,
)
# Print the output of the tool execution
print(response.output.value)
if __name__ == "__main__":
cloud_host = "https://api.arcade-ai.com"
client = Arcade(
base_url=cloud_host, # Alternatively, use http://localhost:9099 if you are running Arcade Engine locally, or any base_url if you're hosting elsewhere
)
user_id = "you@example.com"
call_non_auth_tool(client, user_id)

View file

@ -0,0 +1,51 @@
"""
This example demonstrates how to directly call a tool that requires authorization.
"""
from arcadepy import Arcade # pip install arcade-py
def call_auth_tool(client: Arcade, user_id: str) -> None:
"""Directly call a prebuilt tool that requires authorization.
In this example, we are
1. Authorizing Arcade to read emails from the user's Gmail account with the user's permission to do so
2. Reading 5 emails from the user's Gmail account
3. Printing the emails
Try altering this example to call a tool that requires a different authorization.
"""
# Start the authorization process
auth_response = client.tools.authorize(
tool_name="Google.ListEmails",
user_id=user_id,
)
# If not already authorized, then wait for the user to authorize the permissions required by the tool
if auth_response.status != "completed":
print(f"Click this link to authorize: {auth_response.authorization_url}")
# Wait for the user to complete the auth flow, if necessary
client.auth.wait_for_completion(auth_response)
# Prepare the inputs to the tool as a dictionary where keys are the names of the parameters expected by the tool and the values are the actual values to pass to the tool
inputs = {"n_emails": 5}
# Execute the tool
response = client.tools.execute(
tool_name="Google.ListEmails",
inputs=inputs,
user_id=user_id,
)
# Print the output of the tool execution.
print(response)
if __name__ == "__main__":
client = Arcade(
base_url="https://api.arcade-ai.com", # Alternatively, use http://localhost:9099 if you are running Arcade Engine locally, or any base_url if you're hosting elsewhere
)
user_id = "you@example.com"
call_auth_tool(client, user_id)

View file

@ -0,0 +1,45 @@
"""
This example shows how to call a tool that requires authorization with an LLM using the OpenAI Python client.
"""
import os
from openai import OpenAI
def call_tool_with_openai(client: OpenAI) -> dict:
response = client.chat.completions.create(
messages=[
{"role": "user", "content": "Star the ArcadeAI/arcade-ai repository."},
],
model="gpt-4o-mini", # TODO: Try "claude-3-5-sonnet-20240620" or other models from our supported model providers. Checkout out our docs for a full list: https://docs.arcade-ai.com/integrations
user="you@example.com",
tools=["Github.SetStarred"],
tool_choice="generate", # TODO: Try "execute" and note any differences
)
return response
if __name__ == "__main__":
arcade_api_key = os.environ.get(
"ARCADE_API_KEY"
) # If you forget your Arcade API key, it is stored at ~/.arcade/credentials.yaml on `arcade login`
cloud_host = "https://api.arcade-ai.com" + "/v1"
openai_client = OpenAI(
api_key=arcade_api_key,
base_url=cloud_host, # Alternatively, use http://localhost:9099/v1 if you are running Arcade Engine locally
)
chat_result = call_tool_with_openai(openai_client)
# If the tool call requires authorization, then wait for the user to authorize and then call the tool again
if (
chat_result.choices[0].tool_authorizations
and chat_result.choices[0].tool_authorizations[0].get("status") == "pending"
):
print(chat_result.choices[0].message.content)
input("After you have authorized, press Enter to continue...")
chat_result = call_tool_with_openai(openai_client)
print(chat_result.choices[0].message.content)

View file

@ -0,0 +1,178 @@
"""Example script demonstrating how to call multiple tools directly with authentication.
For this example, we are using the prebuilt Spotify toolkit to start playing similar songs to the currently playing song.
Steps:
1. Get the currently playing song
2. Get audio features for the currently playing song
3. Get song recommendations that are similar to the currently playing song
4. Start playing the recommended songs
5. Inform the user which recommended song is now playing
"""
from typing import Any, Optional
from arcadepy import Arcade # pip install arcade-py
# Need to click on a link for every provider
def get_permissions(client: Arcade, provider_to_scopes: dict, user_id: str) -> None:
"""Prompt the user to authorize necessary permissions for each provider."""
for provider, scopes in provider_to_scopes.items():
auth_response = client.auth.start(
user_id=user_id,
provider=provider,
scopes=scopes,
)
if auth_response.status != "completed":
print(f"Click this link to authorize: {auth_response.authorization_url}")
input("After you have authorized, press Enter to continue...")
def call_tool(client: Arcade, tool_name: str, user_id: str, inputs: Optional[dict] = None) -> Any:
"""Call a single tool."""
if inputs is None:
inputs = {}
response = client.tools.execute(
tool_name=tool_name,
inputs=inputs,
user_id=user_id,
)
return response.output.value
def recommend_similar_songs(
client: Arcade, provider_to_scopes: dict, tools: list[str], user_id: str, user_country_code: str
) -> None:
"""Execute the sequence of tools to get recommendations and start playback."""
get_permissions(client, provider_to_scopes, user_id)
recommendation_params = {"seed_genres": []}
(
get_currently_playing_tool,
get_tracks_audio_features_tool,
get_recommendations_tool,
start_tracks_playback_tool,
) = tools
# Step 1: Get the currently playing song
while True:
response = call_tool(client, get_currently_playing_tool, user_id)
if response["is_playing"]:
break
print("Nothing is playing right now. Press Enter once you start playing a song...")
input()
# Step 1.5: Use the previous tool output to construct the inputs for the following tool calls
current_track_name = response["track_name"]
current_track_artists = response["track_artists"]
current_track_id = response["track_id"]
current_track_spotify_url = response["track_spotify_url"]
current_track_artists_ids = response["track_artists_ids"]
print(
f"\nYou are currently listening to '{current_track_name}' by {', '.join(current_track_artists)}"
)
recommendation_params.update({
"seed_tracks": [current_track_id],
"seed_artists": current_track_artists_ids,
})
# Step 2:Get audio features for the currently playing song
response = call_tool(
client,
get_tracks_audio_features_tool,
user_id,
inputs={"track_ids": [current_track_id]},
)
# Step 2.5: Use the previous tool output to construct the inputs for the following tool calls
audio_features = response["audio_features"][0]
recommendation_params.update({
"target_acousticness": float(audio_features["acousticness"]),
"target_danceability": float(audio_features["danceability"]),
"target_energy": float(audio_features["energy"]),
"target_instrumentalness": float(audio_features["instrumentalness"]),
"target_key": int(audio_features["key"]),
"target_liveness": float(audio_features["liveness"]),
"target_loudness": float(audio_features["loudness"]),
"target_mode": int(audio_features["mode"]),
"target_speechiness": float(audio_features["speechiness"]),
"target_tempo": float(audio_features["tempo"]),
"target_time_signature": int(audio_features["time_signature"]),
"target_valence": float(audio_features["valence"]),
})
# Step 3: Get song recommendations that are similar to the currently playingsong
print(
f"Getting recommendations similar to '{current_track_name}' by {', '.join(current_track_artists)} - {current_track_spotify_url}"
)
response = call_tool(client, get_recommendations_tool, user_id, inputs=recommendation_params)
# Step 3.5: Use the previous tool output to construct the inputs for the following tool calls
# Filter out remixes and the same song from the recommendations
tracks = [
track for track in response["tracks"] if not track["name"].startswith(current_track_name)
]
track_ids = [
track["id"] for track in tracks if (user_country_code in track["available_markets"])
]
track_names = [track["name"] for track in tracks]
tracks_artists = [[artist["name"] for artist in track["artists"]] for track in tracks]
track_spotify_urls = [track["external_urls"]["spotify"] for track in tracks]
if not track_ids:
print("I couldn't find any similar songs that are available in your country.")
return
print("\nHere are some recommendations:")
for track_name, track_artists, track_spotify_url in zip(
track_names, tracks_artists, track_spotify_urls
):
print(f"\t{track_name} by {', '.join(track_artists)} - {track_spotify_url}")
# Step 4: Start playing the recommended songs
response = call_tool(
client,
start_tracks_playback_tool,
user_id,
inputs={"track_ids": track_ids},
)
# Step 5: Inform the user which recommended song is now playing
response = call_tool(client, get_currently_playing_tool, user_id)
print(
f"\nNow playing: {response['track_name']} by {', '.join(response['track_artists'])} - {response['track_spotify_url']}"
)
if __name__ == "__main__":
client = Arcade(base_url="https://api.arcade-ai.com")
# Necessary scopes for the tools we are calling:
provider_to_scopes = {
"spotify": [
"user-read-currently-playing",
"user-read-playback-state",
"user-modify-playback-state",
],
}
tools = [
"Spotify.GetCurrentlyPlaying", # Get info about the current song
"Spotify.GetTracksAudioFeatures", # Get audio features for the current song
"Spotify.GetRecommendations", # Get recommendations similar to the current song
"Spotify.StartTracksPlaybackById", # Start playing the recommended songs
]
user_id = "you@example.com"
user_country_code = "US"
while True:
recommend_similar_songs(client, provider_to_scopes, tools, user_id, user_country_code)
print("\nPress Enter to get more recommendations...")
input()

View file

@ -0,0 +1,91 @@
"""
Example script demonstrating how to call multiple tools (sequentially) using an LLM with authentication.
For this example, we are using the prebuilt Spotify toolkit to start playing similar songs to the currently playing song.
Steps:
1. Get the currently playing song
2. Get audio features for the currently playing song
3. Get song recommendations that are similar to the currently playing song
4. Start playing the recommended songs
5. Inform the user which recommended song is now playing
"""
import os
from openai import OpenAI
def call_tool(client: OpenAI, user_id: str, tool: str, message: dict, history: list[dict]) -> str:
"""Make a tool call with a specific tool and message."""
response = client.chat.completions.create(
messages=[
*history,
message,
],
model="gpt-4o",
user=user_id,
tools=[tool],
tool_choice="generate",
)
return response
def call_tools_with_llm(client: OpenAI, user_id: str, user_country_code: str) -> list[dict]:
"""Use an LLM to execute the sequence of tools to get recommendations and start playback."""
tools = [
"Spotify.GetCurrentlyPlaying",
"Spotify.GetTracksAudioFeatures",
"Spotify.GetRecommendations",
"Spotify.StartTracksPlaybackById",
"Spotify.GetCurrentlyPlaying",
]
messages = [
{"role": "user", "content": "Get the currently playing song."},
{"role": "user", "content": "Retrieve its audio features."},
{
"role": "user",
"content": "Get song recommendations similar to it. "
"Do not include the currently playing song in the recommendations "
"or any remixed versions of the song. "
"Also only include tracks that are available in the user's country. "
f"The current user resides in {user_country_code}.",
},
{"role": "user", "content": "Start playing the recommended songs. Just one tool call."},
{"role": "user", "content": "Get the currently playing song."},
]
history = []
for i in range(len(messages)):
response = call_tool(client, user_id, tools[i], messages[i], history)
print("\n\n", response.choices[0].message.content)
if (
response.choices[0].tool_authorizations
and response.choices[0].tool_authorizations[0].get("status") == "pending"
):
input("\nPress Enter once you have authorized...")
response = call_tool(client, user_id, tools[i], messages[i], history)
history.append(messages[i])
history.append({"role": "assistant", "content": response.choices[0].message.content})
return history
if __name__ == "__main__":
arcade_api_key = os.environ.get("ARCADE_API_KEY")
cloud_host = "https://api.arcade-ai.com/v1"
openai_client = OpenAI(
api_key=arcade_api_key,
base_url=cloud_host,
)
user_id = "you@example.com"
user_country_code = "US"
while True:
history = call_tools_with_llm(openai_client, user_id, user_country_code)
print("\nPress Enter to get more recommendations...")
input()

View file

@ -0,0 +1,58 @@
"""
This example demonstrates how to get an authorization token for a user and then use it to make a request to the Google API on behalf of the user.
"""
from arcadepy import Arcade
from google.oauth2.credentials import Credentials # pip install google-auth
from googleapiclient.discovery import build # pip install google-api-python-client
def get_auth_token(client: Arcade, user_id: str) -> str:
"""Get an authorization token for a user.
In this example, we are
1. Starting the authorization process for the Gmail Readonly scope
2. Waiting for the user to authorize the scope
3. Getting the authorization token
4. Using the authorization token to make a request to the Google API on behalf of the user
"""
# Start the authorization process
auth_response = client.auth.start(
user_id, "google", scopes=["https://www.googleapis.com/auth/gmail.readonly"]
)
if auth_response.status != "completed":
print(f"Click this link to authorize: {auth_response.authorization_url}")
auth_response = client.auth.wait_for_completion(auth_response)
return auth_response.context.token
def use_auth_token(token: str) -> None:
"""Use an authorization token to make a request to the Google API on behalf of a user.
In this example, we are
1. Using the authorization token that we got from the authorization process to make a request to the Google API
client.auth.wait_for_completion(auth_response)
"""
# Use the token from the authorization response
creds = Credentials(token)
service = build("gmail", "v1", credentials=creds)
# Now you can use the Google API
results = service.users().labels().list(userId="me").execute()
labels = results.get("labels", [])
print("Labels:", labels)
if __name__ == "__main__":
cloud_host = "https://api.arcade-ai.com"
client = Arcade(
base_url=cloud_host, # Alternatively, use http://localhost:9099 if you are running Arcade locally, or any base_url if you're hosting elsewhere
)
user_id = "you@example.com"
token = get_auth_token(client, user_id)
use_auth_token(token)

View file

@ -0,0 +1,74 @@
"""
Example script demonstrating how to build a simple chatbot with Arcade AI.
For this example, we are using the prebuilt Google Docs toolkit to create and edit documents.
Try asking questions like:
- "Create a document with the title 'My New Document' and content 'Hello, World!'"
- "List my 2 most recently modified documents and tell me the title, document id, and document URL of each one and summarize them."
- "Edit the second document from the list you just returned and add the text 'Hello, World!' to the end of it."
"""
import os
from openai import OpenAI
def chat(openai_client: OpenAI, tool_names: list[str], user_id: str) -> None:
history = []
print("Hello! How can I help you today?")
while True:
message = {"role": "user", "content": input(">")}
history.append(message)
chat_result = call_tool_with_openai(openai_client, tool_names, user_id, history)
# If the tool call requires authorization, then wait for the user to authorize and then call the tool again
if (
chat_result.choices[0].tool_authorizations
and chat_result.choices[0].tool_authorizations[0].get("status") == "pending"
):
print("\n" + chat_result.choices[0].message.content)
input("\nAfter you have authorized, press Enter to continue...")
chat_result = call_tool_with_openai(openai_client, tool_names, user_id, history)
history.append({"role": "assistant", "content": chat_result.choices[0].message.content})
print(chat_result.choices[0].message.content)
def call_tool_with_openai(
client: OpenAI, tool_names: list[str], user_id: str, messages: list[dict]
) -> dict:
response = client.chat.completions.create(
messages=messages,
model="gpt-4o-mini",
user=user_id,
tools=tool_names,
tool_choice="generate",
)
return response
if __name__ == "__main__":
arcade_api_key = os.environ.get(
"ARCADE_API_KEY"
) # If you forget your Arcade API key, it is stored at ~/.arcade/credentials.yaml on `arcade login`
local_host = "http://localhost:9099/v1"
user_id = "user@example.com"
openai_client = OpenAI(
api_key=arcade_api_key,
base_url=local_host,
)
tool_names = [
"Google.GetDocumentById",
"Google.InsertTextAtEndOfDocument",
"Google.CreateBlankDocument",
"Google.CreateDocumentFromText",
"Google.ListDocuments",
]
chat(openai_client, tool_names, user_id)

View file

@ -18,7 +18,9 @@ class PlaybackState:
album_spotify_url: Optional[str] = None
track_id: Optional[str] = None
track_name: Optional[str] = None
track_spotify_url: Optional[str] = None
track_artists: list[str] = field(default_factory=list)
track_artists_ids: list[str] = field(default_factory=list)
show_id: Optional[str] = None
show_spotify_url: Optional[str] = None
episode_name: Optional[str] = None

View file

@ -75,7 +75,9 @@ def convert_to_playback_state(data: dict) -> PlaybackState:
playback_state.album_spotify_url = album.get("external_urls", {}).get("spotify")
playback_state.track_name = item.get("name")
playback_state.track_id = item.get("id")
playback_state.track_spotify_url = item.get("external_urls").get("spotify")
playback_state.track_artists = [artist.get("name") for artist in item.get("artists", [])]
playback_state.track_artists_ids = [artist.get("id") for artist in item.get("artists", [])]
elif data.get("currently_playing_type") == "episode":
item = data.get("item", {})
show = item.get("show", {})