From 90f114696860166c43e63421d31c9f627050af8c Mon Sep 17 00:00:00 2001 From: Sam Partee Date: Wed, 24 Jul 2024 09:10:31 -0700 Subject: [PATCH] Cleanup examples and README (#8) As the title states. Also added in a prompt/execute capabiltiy into the CLI for the ``arcade run`` command Committed by @nbarbettini Original work by @Spartee --- README.md | 93 +++++++++++++------ arcade/arcade/cli/main.py | 40 ++++---- examples/math/arcade_arithmetic/__init__.py | 0 examples/math/arcade_arithmetic/main.py | 43 +++++++++ .../math/arcade_arithmetic/tools/__init__.py | 0 .../arcade_arithmetic/tools/arithmetic.py | 44 +++++++++ examples/math/pyproject.toml | 17 ++++ 7 files changed, 192 insertions(+), 45 deletions(-) create mode 100644 examples/math/arcade_arithmetic/__init__.py create mode 100644 examples/math/arcade_arithmetic/main.py create mode 100644 examples/math/arcade_arithmetic/tools/__init__.py create mode 100644 examples/math/arcade_arithmetic/tools/arithmetic.py create mode 100644 examples/math/pyproject.toml diff --git a/README.md b/README.md index d9cb7753..631da5ae 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,76 @@ -[![Release](https://img.shields.io/github/v/release/spartee/arcade-ai)](https://img.shields.io/github/v/release/spartee/arcade-ai) -[![Build status](https://img.shields.io/github/actions/workflow/status/spartee/arcade-ai/main.yml?branch=main)](https://github.com/spartee/arcade-ai/actions/workflows/main.yml?query=branch%3Amain) -[![codecov](https://codecov.io/gh/spartee/arcade-ai/branch/main/graph/badge.svg)](https://codecov.io/gh/spartee/arcade-ai) -[![Commit activity](https://img.shields.io/github/commit-activity/m/spartee/arcade-ai)](https://img.shields.io/github/commit-activity/m/spartee/arcade-ai) -[![License](https://img.shields.io/github/license/spartee/arcade-ai)](https://img.shields.io/github/license/spartee/arcade-ai) +[![Release](https://img.shields.io/github/v/release/arcadeai/arcade-ai)](https://img.shields.io/github/v/release/arcadeai/arcade-ai) +[![Build status](https://img.shields.io/github/actions/workflow/status/arcadeai/arcade-ai/main.yml?branch=main)](https://github.com/arcadeai/arcade-ai/actions/workflows/main.yml?query=branch%3Amain) +[![codecov](https://codecov.io/gh/arcadeai/arcade-ai/branch/main/graph/badge.svg)](https://codecov.io/gh/arcadeai/arcade-ai) +[![Commit activity](https://img.shields.io/github/commit-activity/m/arcadeai/arcade-ai)](https://img.shields.io/github/commit-activity/m/arcadeai/arcade-ai) +[![License](https://img.shields.io/github/license/arcadeai/arcade-ai)](https://img.shields.io/github/license/arcadeai/arcade-ai) -# Arcade-AI -ToolServe is a framework specifically designed to manage and orchestrate Language Learning Models (LLMs) or "tools" with high efficiency. It distinctively separates the tools from the orchestration framework to improve dependency management, packaging, and execution. +# Arcade AI -The server enhances data management capabilities, enabling LLMs to interact with structured data efficiently without the need to return the entire dataset to the context window. +Arcade AI is the developer platform for building tools designed to be used with language models. With Arcade, developers can create, deploy, and easily integrate new tools with language models to enhance their capabilities. -This functionality is especially beneficial for agents tasked with performing actions or responding to queries based on extensive structured datasets. +## `arcade-ai` -## Components - -### 1. Command Line Interface (CLI) - -The CLI component, located at `toolserve/cli/main.py`, offers commands to package, serve, and inspect LLM "tools". It utilizes the Typer library to manage command-line arguments and options. - -### 2. Server - -The server component, which manages the storage of artifacts, data, and logs generated by the tools, is implemented using FastAPI and can be found at `toolserve/server/main.py`. The server configuration includes routes, middleware, and database connections. - -### 3. SDK - -Located at `toolserve/sdk`, the SDK streamlines the development of tools by providing decorators and helper functions that abstract routine tasks, allowing developers to concentrate on the logic of the tool rather than on repetitive code. - -### 4. Builtins - -Built-in tools for common tasks such as SQL queries are available at `toolserve/builtin/default`. These tools are ready to use and require no additional setup. +The `arcade-ai` package contains: + - `arcade` CLI + - `arcade.sdk` Tool SDK + - `arcade.actor` serving tools with FastAPI, Flask, or Django ## Installation -To install the ToolServe package, execute the following command: +To install the Arcade AI package, execute the following command: ```bash -pip install toolserve +pip install arcade-ai +``` + +or install from source: + +```bash +git clone https://github.com/arcadeai/arcade-ai.git +cd arcade-ai +pip install poetry +poetry install +``` + +## First steps + +Follow these steps if you've cloned the repo and installed the package from source: + +```bash +cd examples/websearch +poetry install + +arcade show arcade_websearch +``` +This will show an output that looks like + +┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┓ +┃ Name ┃ Description ┃ Toolkit ┃ Version ┃ +┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━┩ +│ SearchGoogle │ Search Google using SerpAPI and return organic search results. │ websearch │ 0.1.0 │ +└──────────────┴────────────────────────────────────────────────────────────────┴───────────┴─────────┘ + + +Predict the parameters with a model and run the tool with the predicted parameters. Arcade adds the `execute` choice to the tool, which allows you to run the tool with the predicted parameters in a single request. + +```bash +> arcade run arcade_websearch "who is Sam Partee?" --choice "execute" +Running tool: SearchGoogle with params: {'query': 'Sam Partee'} + +[{"position": 1, "title": "Sam Partee (@SamPartee) / X", "link": "https://twitter.com/sampartee", "redirect_link": +"https://www.google.com/url?sa=t&source=web&rct=j&opi=89978449&url=https://twitter.com/sampartee&ved=2ahUKEwjBwKiz3b6HAxV1VTABHXL8BZQQFnoECAYQAQ", +"displayed_link": "1.5K+ followers", "thumbnail": +..... +.. (truncated) +``` + +Arcade also adds the `predict` choice to the tool, which allows you to predict the parameters with a model. + +```bash +> arcade run arcade_websearch "who is Sam Partee?" --choice "predict" # also the default +Running tool: SearchGoogle with params: {'query': 'Sam Partee'} + +Sam Partee is a CTO, Co-founder of Arcade AI and former Machine Learning Engineer at companies like RedisInc and HPE_Cray. They have +expertise in AI/ML, vector search, Python, HPC, and are a sports fan. ``` diff --git a/arcade/arcade/cli/main.py b/arcade/arcade/cli/main.py index 04438645..b11930d3 100644 --- a/arcade/arcade/cli/main.py +++ b/arcade/arcade/cli/main.py @@ -90,13 +90,13 @@ def show( @cli.command(help="Run a tool using an LLM to predict the arguments") -def run( +def run( # noqa: C901 toolkit: str = typer.Argument(..., help="The toolkit to add to model calls"), prompt: str = typer.Argument(..., help="The prompt to use for context"), model: str = typer.Option("gpt-3.5-turbo", "-m", help="The model to use for prediction."), tool: str = typer.Option(None, "-t", "--tool", help="The name of the tool to run."), choice: str = typer.Option( - "required", "-c", "--choice", help="The value of the tool choice argument" + "prompt", "-c", "--choice", help="The value of the tool choice argument" ), stream: bool = typer.Option(True, "-s", "--stream", help="Stream the tool output."), actor: Optional[str] = typer.Option( @@ -137,7 +137,9 @@ def run( # TODO put in the engine url from config client = EngineClient() - calls = client.call_tool(tools, tool_choice=choice, prompt=prompt, model=model) + # TODO better way of doing this + tool_choice = "required" if choice in ["prompt", "execute"] else choice + calls = client.call_tool(tools, tool_choice=tool_choice, prompt=prompt, model=model) messages = [ {"role": "user", "content": prompt}, @@ -147,6 +149,7 @@ def run( called_tool = catalog[tool_name] console.print(f"Running tool: {tool_name} with params: {parameters}", style="bold blue") + # TODO async.gather instead of loop. output = asyncio.run( ToolExecutor.run( called_tool.tool, @@ -160,20 +163,23 @@ def run( if output.data: console.print(output.data.result, style="bold red") else: - # TODO: Add the tool results to the response in a safer way - messages += [ - { - "role": "assistant", - # TODO: escape the output and ensure serialization works - "content": f"Results of Tool {tool_name}: {output.data.result!s}", # type: ignore[union-attr] - }, - ] - if stream: - stream_response = client.stream_complete(model=model, messages=messages) - display_streamed_markdown(stream_response) - else: - response = client.complete(model=model, messages=messages) - console.print(response.choices[0].message.content, style="bold green") + if choice == "prompt": + # TODO: Add the tool results to the response in a safer way + messages += [ + { + "role": "assistant", + # TODO: escape the output and ensure serialization works + "content": f"Results of Tool {tool_name}: {output.data.result!s}", # type: ignore[union-attr] + }, + ] + if stream: + stream_response = client.stream_complete(model=model, messages=messages) + display_streamed_markdown(stream_response) + else: + response = client.complete(model=model, messages=messages) + console.print(response.choices[0].message.content, style="bold green") + elif choice == "execute": + console.print(output.data.result, style="green") # type: ignore[union-attr] except RuntimeError as e: error_message = f"❌ Failed to run tool{': '+ escape(str(e)) if str(e) else ''}" diff --git a/examples/math/arcade_arithmetic/__init__.py b/examples/math/arcade_arithmetic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/math/arcade_arithmetic/main.py b/examples/math/arcade_arithmetic/main.py new file mode 100644 index 00000000..cc1d844f --- /dev/null +++ b/examples/math/arcade_arithmetic/main.py @@ -0,0 +1,43 @@ +from arcade.actor.fastapi.actor import FastAPIActor +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from openai import AsyncOpenAI +from arcade_example_nate.tools import arithmetic + + +client = AsyncOpenAI(base_url="http://localhost:6901") + +app = FastAPI() + +actor = FastAPIActor(app) +actor.register_tool(arithmetic.add) +actor.register_tool(arithmetic.multiply) +actor.register_tool(arithmetic.divide) +actor.register_tool(arithmetic.sqrt) + + +class ChatRequest(BaseModel): + message: str + + +@app.post("/chat") +async def chat(request: ChatRequest): + try: + raw_response = await client.chat.completions.with_raw_response.create( + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": request.message}, + ], + model="gpt-4o-mini", + max_tokens=150, + tool_choice="execute", + ) + chat_completion = raw_response.parse() + + return { + "response": chat_completion.choices[0].message.content.strip(), + "tool_call_count": raw_response.headers["arcade-tool-calls"], + "tool_call_duration_ms": raw_response.headers["arcade-total-tool-duration"], + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) diff --git a/examples/math/arcade_arithmetic/tools/__init__.py b/examples/math/arcade_arithmetic/tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/math/arcade_arithmetic/tools/arithmetic.py b/examples/math/arcade_arithmetic/tools/arithmetic.py new file mode 100644 index 00000000..31c472bb --- /dev/null +++ b/examples/math/arcade_arithmetic/tools/arithmetic.py @@ -0,0 +1,44 @@ +import math +from typing import Annotated + +from arcade.sdk.tool import tool + + +@tool +def add( + a: Annotated[int, "The first number"], b: Annotated[int, "The second number"] +) -> Annotated[int, "The sum of the two numbers"]: + """ + Add two numbers together + """ + return a + b + + +@tool +def multiply( + a: Annotated[int, "The first number"], b: Annotated[int, "The second number"] +) -> Annotated[int, "The product of the two numbers"]: + """ + Multiply two numbers together + """ + return a * b + + +@tool +def divide( + a: Annotated[int, "The first number"], b: Annotated[int, "The second number"] +) -> Annotated[float, "The quotient of the two numbers"]: + """ + Divide two numbers + """ + return a / b + + +@tool +def sqrt( + a: Annotated[int, "The number to square root"], +) -> Annotated[float, "The square root of the number"]: + """ + Get the square root of a number + """ + return math.sqrt(a) diff --git a/examples/math/pyproject.toml b/examples/math/pyproject.toml new file mode 100644 index 00000000..295601ba --- /dev/null +++ b/examples/math/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "arcade_arithmetic" +version = "0.1.0" +description = "Nate's testing package for Arcade" +authors = ["Nate "] + + +[tool.poetry.dependencies] +python = "^3.10" +fastapi = "^0.110.3" + +[tool.poetry.dev-dependencies] +pytest = "^7.4" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api"