Remove client.chat resource (#105)

This was always temporary, but we are finally removing the chat resource
from the ``Arcade`` and ``AsyncArcade`` clients.
This commit is contained in:
Sam Partee 2024-10-10 18:03:30 -07:00 committed by GitHub
parent 6b716d6dde
commit ff092ac303
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 30 additions and 38 deletions

View file

@ -8,7 +8,7 @@ from typing import Any, Optional
from urllib.parse import urlencode from urllib.parse import urlencode
import typer import typer
from openai import OpenAIError from openai import OpenAI, OpenAIError
from rich.console import Console from rich.console import Console
from rich.markup import escape from rich.markup import escape
from rich.text import Text from rich.text import Text
@ -256,7 +256,10 @@ def chat(
history.append({"role": "user", "content": user_input}) history.append({"role": "user", "content": user_input})
try: 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: except OpenAIError as e:
console.print(f"❌ Arcade Chat failed with error: {e!s}", style="bold red") console.print(f"❌ Arcade Chat failed with error: {e!s}", style="bold red")
continue continue
@ -273,7 +276,7 @@ def chat(
try: try:
history.pop() history.pop()
chat_result = handle_chat_interaction( chat_result = handle_chat_interaction(
client, model, history, user_email, stream openai_client, model, history, user_email, stream
) )
except OpenAIError as e: except OpenAIError as e:
console.print(f"❌ Arcade Chat failed with error: {e!s}", style="bold red") console.print(f"❌ Arcade Chat failed with error: {e!s}", style="bold red")

View file

@ -4,6 +4,7 @@ from pathlib import Path
from typing import Callable, Union from typing import Callable, Union
import typer import typer
from openai import OpenAI
from openai.resources.chat.completions import ChatCompletionChunk, Stream from openai.resources.chat.completions import ChatCompletionChunk, Stream
from openai.types.chat.chat_completion import Choice as ChatCompletionChoice from openai.types.chat.chat_completion import Choice as ChatCompletionChoice
from openai.types.chat.chat_completion_chunk import Choice as ChatCompletionChunkChoice from openai.types.chat.chat_completion_chunk import Choice as ChatCompletionChunkChoice
@ -247,7 +248,7 @@ class ChatInteractionResult:
def handle_chat_interaction( 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: ) -> ChatInteractionResult:
""" """
Handle a single chat-request/chat-response interaction for both streamed and non-streamed responses. Handle a single chat-request/chat-response interaction for both streamed and non-streamed responses.

View file

@ -13,7 +13,6 @@ from arcade.client.errors import (
RateLimitError, RateLimitError,
UnauthorizedError, UnauthorizedError,
) )
from arcade.client.schema import OPENAI_API_VERSION
T = TypeVar("T") T = TypeVar("T")
ResponseT = TypeVar("ResponseT") ResponseT = TypeVar("ResponseT")
@ -22,11 +21,15 @@ ResponseT = TypeVar("ResponseT")
class BaseResource(Generic[T]): class BaseResource(Generic[T]):
"""Base class for all resources.""" """Base class for all resources."""
_path: str _path: str = ""
_version: str = "v1"
def __init__(self, client: T) -> None: def __init__(self, client: T) -> None:
self._client = client 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: class BaseArcadeClient:
@ -37,8 +40,8 @@ class BaseArcadeClient:
base_url: str | None = None, base_url: str | None = None,
api_key: str | None = None, api_key: str | None = None,
headers: dict[str, str] | None = None, headers: dict[str, str] | None = None,
timeout: float | Timeout = 10.0, timeout: float | Timeout = 30.0,
retries: int = 3, retries: int = 1,
): ):
""" """
Initialize the BaseArcadeClient. Initialize the BaseArcadeClient.
@ -70,12 +73,6 @@ class BaseArcadeClient:
""" """
return urljoin(self._base_url, path) 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: def _handle_http_error(self, e: httpx.HTTPStatusError) -> None:
error_map = { error_map = {
400: BadRequestError, 400: BadRequestError,

View file

@ -1,8 +1,7 @@
import json
from typing import Any, TypeVar, Union from typing import Any, TypeVar, Union
from httpx import Timeout from httpx import Timeout
from openai import AsyncOpenAI, OpenAI
from openai.resources.chat import AsyncChat, Chat
from arcade.client.base import ( from arcade.client.base import (
AsyncArcadeClient, AsyncArcadeClient,
@ -120,7 +119,7 @@ class ToolResource(BaseResource[ClientT]):
tool_name: str, tool_name: str,
user_id: str, user_id: str,
tool_version: str | None = None, tool_version: str | None = None,
inputs: dict[str, Any] | None = None, inputs: dict[str, Any] | str | None = None,
) -> ExecuteToolResponse: ) -> ExecuteToolResponse:
""" """
Send a request to execute a tool and return the response. 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). tool_version: The version of the tool to execute (if not provided, the latest version will be used).
inputs: The inputs for the tool. 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 = { request_data = {
"tool_name": tool_name, "tool_name": tool_name,
"user_id": user_id, "user_id": user_id,
@ -399,12 +404,6 @@ class Arcade(SyncArcadeClient):
self.auth: AuthResource = AuthResource(self) self.auth: AuthResource = AuthResource(self)
self.tools: ToolResource = ToolResource(self) self.tools: ToolResource = ToolResource(self)
self.health: HealthResource = HealthResource(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: def _execute_request(self, method: str, url: str, **kwargs: Any) -> Any:
""" """
@ -422,12 +421,6 @@ class AsyncArcade(AsyncArcadeClient):
self.auth: AsyncAuthResource = AsyncAuthResource(self) self.auth: AsyncAuthResource = AsyncAuthResource(self)
self.tools: AsyncToolResource = AsyncToolResource(self) self.tools: AsyncToolResource = AsyncToolResource(self)
self.health: AsyncHealthResource = AsyncHealthResource(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: async def _execute_request(self, method: str, url: str, **kwargs: Any) -> Any:
""" """

View file

@ -1,12 +1,9 @@
import os
from enum import Enum from enum import Enum
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from arcade.core.schema import ToolAuthorizationContext, ToolCallOutput from arcade.core.schema import ToolAuthorizationContext, ToolCallOutput
OPENAI_API_VERSION = os.getenv("OPENAI_API_VERSION", "v1")
class AuthProvider(str, Enum): class AuthProvider(str, Enum):
google = "google" google = "google"

View file

@ -16,7 +16,8 @@ except ImportError:
"Use `pip install arcade-ai[evals]` to install the required dependencies for evaluation." "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 from arcade.sdk.error import WeightError
if TYPE_CHECKING: if TYPE_CHECKING:
@ -520,12 +521,12 @@ class EvalSuite:
) )
self.cases.append(new_case) 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. Run the evaluation suite.
Args: Args:
client: The AsyncArcade client instance. client: The AsyncOpenAI client instance.
model: The model to evaluate. model: The model to evaluate.
Returns: Returns:
@ -651,11 +652,11 @@ def tool_eval() -> Callable[[Callable], Callable]:
raise TypeError("Eval function must return an EvalSuite") raise TypeError("Eval function must return an EvalSuite")
suite.max_concurrent = max_concurrency suite.max_concurrent = max_concurrency
results = [] results = []
async with AsyncArcade( async with AsyncOpenAI(
api_key=config.api.key, api_key=config.api.key,
base_url=config.engine_url, base_url=config.engine_url,
) as client: ) as client:
result = await suite.run(client, model) # type: ignore[arg-type] result = await suite.run(client, model)
results.append(result) results.append(result)
return results return results