From ff092ac303db8c36a7087942e0b686adf4a85140 Mon Sep 17 00:00:00 2001 From: Sam Partee Date: Thu, 10 Oct 2024 18:03:30 -0700 Subject: [PATCH] Remove client.chat resource (#105) This was always temporary, but we are finally removing the chat resource from the ``Arcade`` and ``AsyncArcade`` clients. --- arcade/arcade/cli/main.py | 9 ++++++--- arcade/arcade/cli/utils.py | 3 ++- arcade/arcade/client/base.py | 19 ++++++++----------- arcade/arcade/client/client.py | 23 ++++++++--------------- arcade/arcade/client/schema.py | 3 --- arcade/arcade/sdk/eval/eval.py | 11 ++++++----- 6 files changed, 30 insertions(+), 38 deletions(-) diff --git a/arcade/arcade/cli/main.py b/arcade/arcade/cli/main.py index 98ed1523..6217e80c 100644 --- a/arcade/arcade/cli/main.py +++ b/arcade/arcade/cli/main.py @@ -8,7 +8,7 @@ from typing import Any, Optional from urllib.parse import urlencode import typer -from openai import OpenAIError +from openai import OpenAI, OpenAIError from rich.console import Console from rich.markup import escape from rich.text import Text @@ -256,7 +256,10 @@ def chat( history.append({"role": "user", "content": user_input}) try: - chat_result = handle_chat_interaction(client, model, history, user_email, stream) + openai_client = OpenAI(api_key=config.api.key, base_url=config.engine_url) + chat_result = handle_chat_interaction( + openai_client, model, history, user_email, stream + ) except OpenAIError as e: console.print(f"❌ Arcade Chat failed with error: {e!s}", style="bold red") continue @@ -273,7 +276,7 @@ def chat( try: history.pop() chat_result = handle_chat_interaction( - client, model, history, user_email, stream + openai_client, model, history, user_email, stream ) except OpenAIError as e: console.print(f"❌ Arcade Chat failed with error: {e!s}", style="bold red") diff --git a/arcade/arcade/cli/utils.py b/arcade/arcade/cli/utils.py index def7ef32..d78b3beb 100644 --- a/arcade/arcade/cli/utils.py +++ b/arcade/arcade/cli/utils.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import Callable, Union import typer +from openai import OpenAI from openai.resources.chat.completions import ChatCompletionChunk, Stream from openai.types.chat.chat_completion import Choice as ChatCompletionChoice from openai.types.chat.chat_completion_chunk import Choice as ChatCompletionChunkChoice @@ -247,7 +248,7 @@ class ChatInteractionResult: def handle_chat_interaction( - client: Arcade, model: str, history: list[dict], user_email: str | None, stream: bool = False + client: OpenAI, model: str, history: list[dict], user_email: str | None, stream: bool = False ) -> ChatInteractionResult: """ Handle a single chat-request/chat-response interaction for both streamed and non-streamed responses. diff --git a/arcade/arcade/client/base.py b/arcade/arcade/client/base.py index f9f3359e..9d19069e 100644 --- a/arcade/arcade/client/base.py +++ b/arcade/arcade/client/base.py @@ -13,7 +13,6 @@ from arcade.client.errors import ( RateLimitError, UnauthorizedError, ) -from arcade.client.schema import OPENAI_API_VERSION T = TypeVar("T") ResponseT = TypeVar("ResponseT") @@ -22,11 +21,15 @@ ResponseT = TypeVar("ResponseT") class BaseResource(Generic[T]): """Base class for all resources.""" - _path: str + _path: str = "" + _version: str = "v1" def __init__(self, client: T) -> None: self._client = client - self._resource_path = self._client._base_url + self._path # type: ignore[attr-defined] + self._resource_path = urljoin( + self._client._base_url, # type: ignore[attr-defined] + f"{self._version}/{self._path}", + ) class BaseArcadeClient: @@ -37,8 +40,8 @@ class BaseArcadeClient: base_url: str | None = None, api_key: str | None = None, headers: dict[str, str] | None = None, - timeout: float | Timeout = 10.0, - retries: int = 3, + timeout: float | Timeout = 30.0, + retries: int = 1, ): """ Initialize the BaseArcadeClient. @@ -70,12 +73,6 @@ class BaseArcadeClient: """ return urljoin(self._base_url, path) - def _chat_url(self, base_url: str) -> str: - chat_url = str(base_url) - if not base_url.endswith(OPENAI_API_VERSION): - chat_url = f"{base_url}/{OPENAI_API_VERSION}" - return chat_url - def _handle_http_error(self, e: httpx.HTTPStatusError) -> None: error_map = { 400: BadRequestError, diff --git a/arcade/arcade/client/client.py b/arcade/arcade/client/client.py index 612f6286..71b1d388 100644 --- a/arcade/arcade/client/client.py +++ b/arcade/arcade/client/client.py @@ -1,8 +1,7 @@ +import json from typing import Any, TypeVar, Union from httpx import Timeout -from openai import AsyncOpenAI, OpenAI -from openai.resources.chat import AsyncChat, Chat from arcade.client.base import ( AsyncArcadeClient, @@ -120,7 +119,7 @@ class ToolResource(BaseResource[ClientT]): tool_name: str, user_id: str, tool_version: str | None = None, - inputs: dict[str, Any] | None = None, + inputs: dict[str, Any] | str | None = None, ) -> ExecuteToolResponse: """ Send a request to execute a tool and return the response. @@ -131,6 +130,12 @@ class ToolResource(BaseResource[ClientT]): tool_version: The version of the tool to execute (if not provided, the latest version will be used). inputs: The inputs for the tool. """ + if not isinstance(inputs, str): + try: + inputs = json.dumps(inputs) + except Exception: + raise ValueError("Inputs must be a valid JSON object or serializable dictionary") + request_data = { "tool_name": tool_name, "user_id": user_id, @@ -399,12 +404,6 @@ class Arcade(SyncArcadeClient): self.auth: AuthResource = AuthResource(self) self.tools: ToolResource = ToolResource(self) self.health: HealthResource = HealthResource(self) - chat_url = self._chat_url(self._base_url) - self._openai_client = OpenAI(base_url=chat_url, api_key=self._api_key) - - @property - def chat(self) -> Chat: - return self._openai_client.chat def _execute_request(self, method: str, url: str, **kwargs: Any) -> Any: """ @@ -422,12 +421,6 @@ class AsyncArcade(AsyncArcadeClient): self.auth: AsyncAuthResource = AsyncAuthResource(self) self.tools: AsyncToolResource = AsyncToolResource(self) self.health: AsyncHealthResource = AsyncHealthResource(self) - chat_url = self._chat_url(self._base_url) - self._openai_client = AsyncOpenAI(base_url=chat_url, api_key=self._api_key) - - @property - def chat(self) -> AsyncChat: - return self._openai_client.chat async def _execute_request(self, method: str, url: str, **kwargs: Any) -> Any: """ diff --git a/arcade/arcade/client/schema.py b/arcade/arcade/client/schema.py index f255f22d..27658f8c 100644 --- a/arcade/arcade/client/schema.py +++ b/arcade/arcade/client/schema.py @@ -1,12 +1,9 @@ -import os from enum import Enum from pydantic import BaseModel, Field from arcade.core.schema import ToolAuthorizationContext, ToolCallOutput -OPENAI_API_VERSION = os.getenv("OPENAI_API_VERSION", "v1") - class AuthProvider(str, Enum): google = "google" diff --git a/arcade/arcade/sdk/eval/eval.py b/arcade/arcade/sdk/eval/eval.py index ae51a89c..78bc6f65 100644 --- a/arcade/arcade/sdk/eval/eval.py +++ b/arcade/arcade/sdk/eval/eval.py @@ -16,7 +16,8 @@ except ImportError: "Use `pip install arcade-ai[evals]` to install the required dependencies for evaluation." ) -from arcade.client.client import AsyncArcade +from openai import AsyncOpenAI + from arcade.sdk.error import WeightError if TYPE_CHECKING: @@ -520,12 +521,12 @@ class EvalSuite: ) self.cases.append(new_case) - async def run(self, client: AsyncArcade, model: str) -> dict[str, Any]: + async def run(self, client: AsyncOpenAI, model: str) -> dict[str, Any]: """ Run the evaluation suite. Args: - client: The AsyncArcade client instance. + client: The AsyncOpenAI client instance. model: The model to evaluate. Returns: @@ -651,11 +652,11 @@ def tool_eval() -> Callable[[Callable], Callable]: raise TypeError("Eval function must return an EvalSuite") suite.max_concurrent = max_concurrency results = [] - async with AsyncArcade( + async with AsyncOpenAI( api_key=config.api.key, base_url=config.engine_url, ) as client: - result = await suite.run(client, model) # type: ignore[arg-type] + result = await suite.run(client, model) results.append(result) return results