From 8964111023a70c5a3514e1931e14fec71992e085 Mon Sep 17 00:00:00 2001 From: Sam Partee Date: Tue, 23 Jul 2024 16:26:54 -0700 Subject: [PATCH] Refactor into library approach (#7) This PR makes a few sweeping changes to the actor, cli, and overall structure of the project. - CLI commands skeleton - ``arcade run``, ``arcade show``, and ``arcade new`` - Working package mangement solution (``arcade_`` packages) - Actor approach for using frameworks other than FastAPI - Client for calling Engine within ``arcade/core`` - beginning of the config interface. --------- Co-authored-by: Nate Barbettini --- .gitignore | 4 + arcade/arcade/__init__.py | 3 + arcade/arcade/actor/base.py | 165 ++++++ arcade/arcade/actor/common/log.py | 58 -- arcade/arcade/actor/common/response.py | 102 ---- arcade/arcade/actor/common/serializers.py | 13 - arcade/arcade/actor/core/conf.py | 68 --- arcade/arcade/actor/core/depends.py | 8 - arcade/arcade/actor/core/generate.py | 94 --- arcade/arcade/actor/core/registrar.py | 90 --- .../actor/{common => fastapi}/__init__.py | 0 arcade/arcade/actor/fastapi/actor.py | 54 ++ .../{core/__init__.py => fastapi/auth.py} | 0 arcade/arcade/actor/main.py | 21 - arcade/arcade/actor/routes/__init__.py | 7 - arcade/arcade/actor/routes/tool.py | 77 --- arcade/arcade/actor/schema.py | 45 ++ arcade/arcade/apm/base.py | 55 -- arcade/arcade/apm/pack.py | 61 -- arcade/arcade/apm/parse.py | 79 --- arcade/arcade/cli/main.py | 248 ++++++-- arcade/arcade/cli/new.py | 144 +++++ arcade/arcade/{tool => core}/catalog.py | 145 ++--- arcade/arcade/core/client.py | 184 ++++++ arcade/arcade/core/config.py | 64 ++ arcade/arcade/core/env.py | 23 + arcade/arcade/{tool => core}/errors.py | 0 arcade/arcade/{tool => core}/executor.py | 10 +- arcade/arcade/core/parse.py | 42 ++ arcade/arcade/{tool => core}/response.py | 22 +- .../{actor/common => core}/response_code.py | 0 .../arcade/{tool/schemas.py => core/tool.py} | 0 arcade/arcade/core/toolkit.py | 126 ++++ .../{utils/__init__.py => core/utils.py} | 7 +- arcade/arcade/core/version.py | 1 + arcade/arcade/sdk/tool.py | 7 +- arcade/arcade/tool/openai.py | 107 ---- arcade/poetry.lock | 409 +++++-------- arcade/pyproject.toml | 9 +- arcade/tests/sdk/test_tool_decorator.py | 2 +- .../tests/tool/test_create_tool_definition.py | 22 +- .../test_create_tool_definition_errors.py | 4 +- .../test_create_tool_definition_pydantic.py | 6 +- ..._create_tool_definition_pydantic_errors.py | 4 +- arcade/tests/utils/test_utils_casing.py | 2 +- cspell.config.yaml | 2 + .../arcade_gmail/tools/__init__.py | 1 + .../arcade_example_nate}/__init__.py | 0 .../example_nate/arcade_example_nate/main.py | 43 ++ .../arcade_example_nate/tools}/__init__.py | 0 .../arcade_example_nate/tools/arithmetic.py | 44 ++ examples/example_nate/poetry.lock | 326 +++++++++++ examples/example_nate/pyproject.toml | 27 + examples/generic/pack.lock.toml | 18 - examples/generic/pack.toml | 14 - examples/generic/tools/BM25.py | 158 ----- examples/generic/tools/products.py | 81 --- examples/generic/tools/read_sqlite.py | 38 -- .../tools => gmail/arcade_gmail}/__init__.py | 0 examples/gmail/arcade_gmail/tools/__init__.py | 1 + .../arcade_gmail}/tools/gmail.py | 88 +-- examples/gmail/poetry.lock | 547 ++++++++++++++++++ examples/gmail/pyproject.toml | 22 + .../websearch/arcade_websearch/__init__.py | 0 .../arcade_websearch/tools/__init__.py | 0 .../arcade_websearch/tools/google.py | 25 + examples/websearch/poetry.lock | 291 ++++++++++ examples/websearch/pyproject.toml | 17 + ...jsonc => invoke_tool_request.schema.jsonc} | 2 +- ...sonc => invoke_tool_response.schema.jsonc} | 4 + schemas/preview/tool_definition.schema.jsonc | 65 +-- 71 files changed, 2697 insertions(+), 1709 deletions(-) create mode 100644 arcade/arcade/actor/base.py delete mode 100644 arcade/arcade/actor/common/log.py delete mode 100644 arcade/arcade/actor/common/response.py delete mode 100644 arcade/arcade/actor/common/serializers.py delete mode 100644 arcade/arcade/actor/core/conf.py delete mode 100644 arcade/arcade/actor/core/depends.py delete mode 100644 arcade/arcade/actor/core/generate.py delete mode 100644 arcade/arcade/actor/core/registrar.py rename arcade/arcade/actor/{common => fastapi}/__init__.py (100%) create mode 100644 arcade/arcade/actor/fastapi/actor.py rename arcade/arcade/actor/{core/__init__.py => fastapi/auth.py} (100%) delete mode 100644 arcade/arcade/actor/main.py delete mode 100644 arcade/arcade/actor/routes/__init__.py delete mode 100644 arcade/arcade/actor/routes/tool.py create mode 100644 arcade/arcade/actor/schema.py delete mode 100644 arcade/arcade/apm/base.py delete mode 100644 arcade/arcade/apm/pack.py delete mode 100644 arcade/arcade/apm/parse.py create mode 100644 arcade/arcade/cli/new.py rename arcade/arcade/{tool => core}/catalog.py (85%) create mode 100644 arcade/arcade/core/client.py create mode 100644 arcade/arcade/core/config.py create mode 100644 arcade/arcade/core/env.py rename arcade/arcade/{tool => core}/errors.py (100%) rename arcade/arcade/{tool => core}/executor.py (88%) create mode 100644 arcade/arcade/core/parse.py rename arcade/arcade/{tool => core}/response.py (66%) rename arcade/arcade/{actor/common => core}/response_code.py (100%) rename arcade/arcade/{tool/schemas.py => core/tool.py} (100%) create mode 100644 arcade/arcade/core/toolkit.py rename arcade/arcade/{utils/__init__.py => core/utils.py} (88%) create mode 100644 arcade/arcade/core/version.py delete mode 100644 arcade/arcade/tool/openai.py create mode 100644 examples/arcade_gmail/arcade_gmail/tools/__init__.py rename {arcade/arcade/apm => examples/example_nate/arcade_example_nate}/__init__.py (100%) create mode 100644 examples/example_nate/arcade_example_nate/main.py rename {arcade/arcade/tool => examples/example_nate/arcade_example_nate/tools}/__init__.py (100%) create mode 100644 examples/example_nate/arcade_example_nate/tools/arithmetic.py create mode 100644 examples/example_nate/poetry.lock create mode 100644 examples/example_nate/pyproject.toml delete mode 100644 examples/generic/pack.lock.toml delete mode 100644 examples/generic/pack.toml delete mode 100644 examples/generic/tools/BM25.py delete mode 100644 examples/generic/tools/products.py delete mode 100644 examples/generic/tools/read_sqlite.py rename examples/{generic/tools => gmail/arcade_gmail}/__init__.py (100%) create mode 100644 examples/gmail/arcade_gmail/tools/__init__.py rename examples/{generic => gmail/arcade_gmail}/tools/gmail.py (67%) create mode 100644 examples/gmail/poetry.lock create mode 100644 examples/gmail/pyproject.toml create mode 100644 examples/websearch/arcade_websearch/__init__.py create mode 100644 examples/websearch/arcade_websearch/tools/__init__.py create mode 100644 examples/websearch/arcade_websearch/tools/google.py create mode 100644 examples/websearch/poetry.lock create mode 100644 examples/websearch/pyproject.toml rename schemas/preview/{tool_request.schema.jsonc => invoke_tool_request.schema.jsonc} (99%) rename schemas/preview/{tool_response.schema.jsonc => invoke_tool_response.schema.jsonc} (97%) diff --git a/.gitignore b/.gitignore index 7240ce5b..7911d48c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +.DS_Store + +*.lock + # example data examples/data scratch diff --git a/arcade/arcade/__init__.py b/arcade/arcade/__init__.py index e69de29b..cee50dc4 100644 --- a/arcade/arcade/__init__.py +++ b/arcade/arcade/__init__.py @@ -0,0 +1,3 @@ +from arcade.core.version import VERSION + +__version__ = VERSION diff --git a/arcade/arcade/actor/base.py b/arcade/arcade/actor/base.py new file mode 100644 index 00000000..7750e821 --- /dev/null +++ b/arcade/arcade/actor/base.py @@ -0,0 +1,165 @@ +import time +from abc import ABC, abstractmethod +from datetime import datetime +from typing import Any, Callable + +from arcade.actor.schema import ( + InvokeToolRequest, + InvokeToolResponse, + ToolOutput, + ToolOutputError, +) +from arcade.core.catalog import ToolCatalog, Toolkit +from arcade.core.executor import ToolExecutor +from arcade.core.tool import ToolDefinition + + +class ActorComponent(ABC): + @abstractmethod + def register(self, router: Any) -> None: + """ + Register the component with the given router. + """ + pass + + @abstractmethod + async def __call__(self, request: Any) -> Any: + """ + Handle the request. + """ + pass + + +class BaseActor: + base_path = "/actor" # By default, prefix all our routes with /actor + + def __init__(self) -> None: + """ + Initialize the BaseActor with an empty ToolCatalog. + """ + self.catalog = ToolCatalog() + + def get_catalog(self) -> list[ToolDefinition]: + """ + Get the catalog as a list of ToolDefinitions. + """ + return [tool.definition for tool in self.catalog] + + def register_tool(self, tool: Callable) -> None: + """ + Register a tool to the catalog. + """ + self.catalog.add_tool(tool) + + def register_toolkit(self, toolkit: Toolkit) -> None: + """ + Register a toolkit to the catalog. + """ + self.catalog.add_toolkit(toolkit) + + async def invoke_tool(self, tool_request: InvokeToolRequest) -> InvokeToolResponse: + """ + Invoke a tool using the ToolExecutor. + """ + tool_name = tool_request.tool.name + tool = self.catalog.get_tool(tool_name) + if not tool: + raise ValueError(f"Tool {tool_name} not found in catalog.") + + materialized_tool = self.catalog[tool_name] + + start_time = time.time() + + response = await ToolExecutor.run( + func=materialized_tool.tool, + input_model=materialized_tool.input_model, + output_model=materialized_tool.output_model, + **tool_request.inputs or {}, + ) + if response.code == 200: + # TODO remove ignore + output = ToolOutput(value=response.data.result) # type: ignore[union-attr] + else: + output = ToolOutput(error=ToolOutputError(message=response.msg)) + + end_time = time.time() # End time in seconds + duration_ms = (end_time - start_time) * 1000 # Convert to milliseconds + + return InvokeToolResponse( + invocation_id=tool_request.invocation_id, + duration=duration_ms, + finished_at=datetime.now().isoformat(), + success=response.code == 200, + output=output, + ) + + def health_check(self) -> dict[str, Any]: + """ + Provide a health check that serves as a heartbeat of actor health. + """ + return {"status": "ok", "tool_count": len(self.catalog.tools.keys())} + + def register_routes(self, router: Any) -> None: + """ + Register the necessary routes to the application. + """ + catalog_component = CatalogComponent(self) + invoke_tool_component = InvokeToolComponent(self) + health_check_component = HealthCheckComponent(self) + + catalog_component.register(router) + invoke_tool_component.register(router) + health_check_component.register(router) + + +class CatalogComponent(ActorComponent): + def __init__(self, actor: BaseActor) -> None: + self.actor = actor + + def register(self, router: Any) -> None: + """ + Register the catalog route with the router. + """ + router.add_route(f"{self.actor.base_path}/tools", self, methods=["GET"]) + + async def __call__(self, request: Any) -> list[ToolDefinition]: + """ + Handle the request to get the catalog. + """ + return self.actor.get_catalog() + + +class InvokeToolComponent(ActorComponent): + def __init__(self, actor: BaseActor) -> None: + self.actor = actor + + def register(self, router: Any) -> None: + """ + Register the invoke tool route with the router. + """ + router.add_route(f"{self.actor.base_path}/tools/invoke", self, methods=["POST"]) + + async def __call__(self, request: Any) -> InvokeToolResponse: + """ + Handle the request to invoke a tool. + """ + invoke_tool_request_data = await request.json() + invoke_tool_request = InvokeToolRequest.model_validate(invoke_tool_request_data) + return await self.actor.invoke_tool(invoke_tool_request) + + +class HealthCheckComponent(ActorComponent): + def __init__(self, actor: BaseActor) -> None: + self.actor = actor + + def register(self, router: Any) -> None: + """ + Register the health check route with the router. + """ + router.add_route(f"{self.actor.base_path}/health", self, methods=["GET"]) + + async def __call__(self, request: Any) -> dict[str, Any]: + """ + Handle the request for a health check. + """ + return self.actor.health_check() diff --git a/arcade/arcade/actor/common/log.py b/arcade/arcade/actor/common/log.py deleted file mode 100644 index 22c9c2ec..00000000 --- a/arcade/arcade/actor/common/log.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -import os -from typing import TYPE_CHECKING - -from loguru import logger - -from arcade.actor.core.conf import settings - -actor_log_path = os.path.join(settings.WORK_DIR, "actor_logs") - - -if TYPE_CHECKING: - import loguru - - -class Logger: - """Logger for the Actor server""" - - def __init__(self) -> None: - self.log_path = actor_log_path - - def log(self) -> loguru.Logger: - if not os.path.exists(self.log_path): - os.makedirs(self.log_path, exist_ok=True) - - log_stdout_file = os.path.join(self.log_path, settings.LOG_STDOUT_FILENAME) - log_stderr_file = os.path.join(self.log_path, settings.LOG_STDERR_FILENAME) - - log_config = { - "rotation": "10 MB", - "retention": "15 days", - "compression": "tar.gz", - "enqueue": True, - } - # stdout - logger.add( - log_stdout_file, - level="INFO", - filter=lambda record: record["level"].name == "INFO" or record["level"].no <= 25, # type: ignore[call-overload] - backtrace=False, - diagnose=False, - **log_config, - ) - # stderr - logger.add( - log_stderr_file, - level="ERROR", - filter=lambda record: record["level"].name == "ERROR" or record["level"].no >= 30, # type: ignore[call-overload] - backtrace=True, - diagnose=True, - **log_config, - ) - - return logger - - -log = Logger().log() diff --git a/arcade/arcade/actor/common/response.py b/arcade/arcade/actor/common/response.py deleted file mode 100644 index 6738d14f..00000000 --- a/arcade/arcade/actor/common/response.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -from datetime import datetime -from typing import Any - -from pydantic import BaseModel, ConfigDict - -from arcade.actor.common.response_code import CustomResponse, CustomResponseCode -from arcade.actor.core.conf import settings - -_ExcludeData = set[int | str] | dict[int | str, Any] - -__all__ = ["ResponseModel", "response_base"] - - -class ResponseModel(BaseModel): - """ - # Unified return model - E.g. :: - - @router.get('/test', response_model=ResponseModel) - def test(): - return ResponseModel(data={'test': 'test'}) - - @router.get('/test') - def test() -> ResponseModel: - return ResponseModel(data={'test': 'test'}) - - @router.get('/test') - def test() -> ResponseModel: - res = CustomResponseCode.HTTP_200 - return ResponseModel(code=res.code, msg=res.msg, data={'test': 'test'}) - """ - - # TODO: json_encoders: https://github.com/tiangolo/fastapi/discussions/10252 - model_config = ConfigDict( - json_encoders={datetime: lambda x: x.strftime(settings.DATETIME_FORMAT)} - ) - - code: int = CustomResponseCode.HTTP_200.code - msg: str = CustomResponseCode.HTTP_200.msg - data: Any | None = None - - -class ResponseBase: - """ - Unified return method - - .. tip:: - - The methods in this class will return the ResponseModel model, existing as a coding style; - - E.g. :: - - @router.get('/test') - def test() -> ResponseModel: - return await response_base.success(data={'test': 'test'}) - """ - - @staticmethod - async def __response( - *, - res: CustomResponseCode | CustomResponse = CustomResponseCode.HTTP_200, - msg: str | None = None, - data: Any | None = None, - ) -> ResponseModel: - """ - General method for successful response - - :param res: Response information - :param data: Response data - :return: - """ - msg = msg if msg else res.msg - return ResponseModel(code=res.code, msg=msg, data=data) - - async def success( - self, - *, - res: CustomResponseCode | CustomResponse = CustomResponseCode.HTTP_200, - data: Any | None = None, - ) -> ResponseModel: - return await self.__response(res=res, data=data) - - async def fail( - self, - *, - res: CustomResponseCode | CustomResponse = CustomResponseCode.HTTP_400, - data: Any = None, - ) -> ResponseModel: - return await self.__response(res=res, data=data) - - async def error( - self, - *, - res: CustomResponseCode | CustomResponse = CustomResponseCode.HTTP_400, - msg: str = CustomResponseCode.HTTP_400.msg, - data: Any = None, - ) -> ResponseModel: - return await self.__response(res=res, msg=msg, data=data) - - -response_base = ResponseBase() diff --git a/arcade/arcade/actor/common/serializers.py b/arcade/arcade/actor/common/serializers.py deleted file mode 100644 index e4bea3d3..00000000 --- a/arcade/arcade/actor/common/serializers.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Any - -import msgspec -from starlette.responses import JSONResponse - - -class MsgSpecJSONResponse(JSONResponse): - """ - JSON response using the high-performance msgspec library to serialize data to JSON. - """ - - def render(self, content: Any) -> bytes: - return msgspec.json.encode(content) diff --git a/arcade/arcade/actor/core/conf.py b/arcade/arcade/actor/core/conf.py deleted file mode 100644 index 29c63c06..00000000 --- a/arcade/arcade/actor/core/conf.py +++ /dev/null @@ -1,68 +0,0 @@ -import os -from functools import lru_cache -from pathlib import Path -from typing import Literal - -from pydantic_settings import BaseSettings, SettingsConfigDict - - -# https://docs.pydantic.dev/latest/concepts/pydantic_settings/ -class Settings(BaseSettings): - model_config = SettingsConfigDict(env_file=".env") - - WORK_DIR: Path = Path.home() / ".arcade" - TOOLS_DIR: Path = Path(os.getcwd()) - - # Env Config - ENVIRONMENT: Literal["dev", "pro"] = "dev" - - # FastAPI - API_V1_STR: str = "/v1" - API_ACTION_STR: str = "/tool" - TITLE: str = "Arcade AI Toolserver" - VERSION: str = "0.1.0" - DESCRIPTION: str = "Arcade AI Toolserver API" - DOCS_URL: str | None = f"{API_V1_STR}/docs" - REDOCS_URL: str | None = f"{API_V1_STR}/redocs" - OPENAPI_URL: str | None = f"{API_V1_STR}/openapi" - - # @model_validator(mode='before') - # @classmethod - # def validate_openapi_url(cls, values): - # if values['ENVIRONMENT'] == 'pro': - # values['OPENAPI_URL'] = None - # return values - - # Uvicorn - UVICORN_HOST: str = "127.0.0.1" - UVICORN_PORT: int = 8000 - UVICORN_RELOAD: bool = True - - # Static Server - STATIC_FILES: bool = False - - # Logs - LOG_STDOUT_FILENAME: str = "actor.log" - LOG_STDERR_FILENAME: str = "actor.err" - - # DateTime - DATETIME_TIMEZONE: str = "US/Pacific" - DATETIME_FORMAT: str = "%Y-%m-%d %H:%M:%S" - - # Middleware - MIDDLEWARE_CORS: bool = True - MIDDLEWARE_GZIP: bool = True - MIDDLEWARE_ACCESS: bool = False - - # these should be set in .env - TOKEN_SECRET_KEY: str = "secret" - OPERA_LOG_ENCRYPT_SECRET_KEY: str = "secret" - - -@lru_cache -def get_settings() -> Settings: - # TODO allow user to specify env file path as a Env Var - return Settings() - - -settings = get_settings() diff --git a/arcade/arcade/actor/core/depends.py b/arcade/arcade/actor/core/depends.py deleted file mode 100644 index b24feb4e..00000000 --- a/arcade/arcade/actor/core/depends.py +++ /dev/null @@ -1,8 +0,0 @@ -from starlette.requests import Request - -from arcade.tool.catalog import ToolCatalog - - -def get_catalog(request: Request) -> ToolCatalog: - # TODO figure out why this says return type is Any - return request.app.state.catalog # type: ignore[no-any-return] diff --git a/arcade/arcade/actor/core/generate.py b/arcade/arcade/actor/core/generate.py deleted file mode 100644 index f8781bf7..00000000 --- a/arcade/arcade/actor/core/generate.py +++ /dev/null @@ -1,94 +0,0 @@ -import traceback -from typing import Callable - -from fastapi import APIRouter, Body, Depends, Request -from pydantic import BaseModel, ValidationError - -from arcade.actor.core.conf import settings -from arcade.tool.catalog import MaterializedTool -from arcade.tool.executor import ToolExecutor -from arcade.tool.response import ToolResponse, tool_response - - -def create_endpoint_function( - name: str, - description: str, - func: Callable, - input_model: type[BaseModel], - output_model: type[BaseModel], -) -> Callable[..., ToolResponse]: - """ - Factory function to create endpoint functions with 'frozen' schema and input_model values. - """ - - # dummy function to signal the parameters should be in the - # body of the request - def get_input_model(inputs: BaseModel = Body(...)) -> BaseModel: - return inputs - - async def run(request: Request, inputs: BaseModel = Depends(get_input_model)) -> ToolResponse: - """ - The function that will be executed when a user sends a POST request - to a tool endpoint - """ - try: - # get the body of the request without parsing and validating it - # as the executor will do that - body = await request.json() - response = await ToolExecutor.run(func, input_model, output_model, **body) - - # TODO: Does this catch validation errors on output? - except ValidationError as e: - return await tool_response.fail(msg=str(e)) - - except Exception as e: - return await tool_response.fail( - msg=str(e), - data=traceback.format_exc(), - ) - return response - - run.__name__ = name - run.__doc__ = description - - # TODO investigate this - return run # type: ignore[return-value] - - -def generate_endpoint(schemas: list[MaterializedTool]) -> APIRouter: - """ - Generate a HTTP endpoint for each tool definition passed. - """ - routers = [] - top_level_router = APIRouter(prefix=settings.API_ACTION_STR) - - for schema in schemas: - router = APIRouter(prefix="/" + schema.meta.module) - - define = schema.definition - - # Create the endpoint function - run = create_endpoint_function( - name=define.name, - description=define.description, - func=schema.tool, - input_model=schema.input_model, - output_model=schema.output_model, - ) - - # Add the endpoint to the FastAPI app - router.post( - f"/{define.name}", # Note: Names from the ToolCatalog are already in PascalCase - name=define.name, - summary=define.description, - tags=[schema.meta.module], - # TODO investigate this - response_model=ToolResponse[schema.output_model], # type: ignore[name-defined] - response_model_exclude_unset=True, - response_model_exclude_none=True, - )(run) - - routers.append(router) - for router in routers: - top_level_router.include_router(router) - return top_level_router diff --git a/arcade/arcade/actor/core/registrar.py b/arcade/arcade/actor/core/registrar.py deleted file mode 100644 index c7a81b85..00000000 --- a/arcade/arcade/actor/core/registrar.py +++ /dev/null @@ -1,90 +0,0 @@ -from fastapi import FastAPI - -from arcade.actor.common.serializers import MsgSpecJSONResponse -from arcade.actor.core.conf import settings -from arcade.actor.core.generate import generate_endpoint -from arcade.actor.routes import v1 -from arcade.tool.catalog import ToolCatalog - - -def register_app() -> FastAPI: - # FastAPI - app = FastAPI( - title=settings.TITLE, - version=settings.VERSION, - description=settings.DESCRIPTION, - docs_url=settings.DOCS_URL, - redoc_url=settings.REDOCS_URL, - openapi_url=settings.OPENAPI_URL, - default_response_class=MsgSpecJSONResponse, - ) - - register_static_file(app) - - register_middleware(app) - - register_router(app) - - generate_tool_routes(app) - - return app - - -def register_static_file(app: FastAPI) -> None: - """ - Register static files - """ - - if settings.STATIC_FILES: - import os - - from fastapi.staticfiles import StaticFiles - - if not os.path.exists("./static"): - os.mkdir("./static") - app.mount("/static", StaticFiles(directory="static"), name="static") - - -def register_middleware(app: FastAPI) -> None: - """ - Register middleware for the FastAPI app - """ - # Gzip: Always at the top - if settings.MIDDLEWARE_GZIP: - from fastapi.middleware.gzip import GZipMiddleware - - app.add_middleware(GZipMiddleware) - - # CORS: Always at the end - if settings.MIDDLEWARE_CORS: - from fastapi.middleware.cors import CORSMiddleware - - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - - -def register_router(app: FastAPI) -> None: - """ - Register routers for the FastAPI app - """ - dependencies = None - - # API - app.include_router(v1, dependencies=dependencies) - - -def generate_tool_routes(app: FastAPI) -> None: - """ - Generate tool routes for each tool in the catalog - Add the routes to the FastAPI app and the tool - definitions to the catalog - """ - catalog = ToolCatalog() - router = generate_endpoint(list(catalog.tools.values())) - app.include_router(router) - app.state.catalog = catalog diff --git a/arcade/arcade/actor/common/__init__.py b/arcade/arcade/actor/fastapi/__init__.py similarity index 100% rename from arcade/arcade/actor/common/__init__.py rename to arcade/arcade/actor/fastapi/__init__.py diff --git a/arcade/arcade/actor/fastapi/actor.py b/arcade/arcade/actor/fastapi/actor.py new file mode 100644 index 00000000..8346d5f6 --- /dev/null +++ b/arcade/arcade/actor/fastapi/actor.py @@ -0,0 +1,54 @@ +import asyncio +from typing import Any, Callable + +from fastapi import FastAPI, Request + +from arcade.actor.base import BaseActor + + +class FastAPIActor(BaseActor): + def __init__(self, app: FastAPI) -> None: + """ + Initialize the FastAPIActor with a FastAPI app + instance and an empty ToolCatalog. + """ + super().__init__() + self.app = app + self.router = FastAPIRouter(app) + self.register_routes(self.router) + + +class FastAPIRouter: # TODO create an interface for this + def __init__(self, app: FastAPI) -> None: + self.app = app + + def add_route(self, path: str, handler: Callable, methods: str) -> None: + """ + Add a route to the FastAPI application. + """ + for method in methods: + if method == "GET": + self.app.get(path)(self.wrap_handler(handler)) + elif method == "POST": + self.app.post(path)(self.wrap_handler(handler)) + elif method == "PUT": + self.app.put(path)(self.wrap_handler(handler)) + elif method == "DELETE": + self.app.delete(path)(self.wrap_handler(handler)) + else: + raise ValueError(f"Unsupported HTTP method: {method}") + + def wrap_handler(self, handler: Callable) -> Callable: + """ + Wrap the handler to handle FastAPI-specific request and response. + """ + + async def wrapped_handler(request: Request) -> Any: + if asyncio.iscoroutinefunction(handler) or ( + callable(handler) and asyncio.iscoroutinefunction(handler.__call__) # type: ignore[operator] + ): + return await handler(request) + else: + return handler(request) + + return wrapped_handler diff --git a/arcade/arcade/actor/core/__init__.py b/arcade/arcade/actor/fastapi/auth.py similarity index 100% rename from arcade/arcade/actor/core/__init__.py rename to arcade/arcade/actor/fastapi/auth.py diff --git a/arcade/arcade/actor/main.py b/arcade/arcade/actor/main.py deleted file mode 100644 index 4e30ea7a..00000000 --- a/arcade/arcade/actor/main.py +++ /dev/null @@ -1,21 +0,0 @@ -from pathlib import Path - -import uvicorn - -from arcade.actor.common.log import log -from arcade.actor.core.conf import settings -from arcade.actor.core.registrar import register_app - -app = register_app() - -if __name__ == "__main__": - try: - log.info("Arcade AI Toolserve is starting...") - uvicorn.run( - app=f"{Path(__file__).stem}:app", - host=settings.UVICORN_HOST, - port=settings.UVICORN_PORT, - reload=settings.UVICORN_RELOAD, - ) - except Exception as e: - log.error(f"FastAPI start filed: {e}") diff --git a/arcade/arcade/actor/routes/__init__.py b/arcade/arcade/actor/routes/__init__.py deleted file mode 100644 index bca6b037..00000000 --- a/arcade/arcade/actor/routes/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from fastapi import APIRouter - -from arcade.actor.core.conf import settings -from arcade.actor.routes.tool import router as tool_router - -v1 = APIRouter(prefix=settings.API_V1_STR) -v1.include_router(tool_router, prefix="/tools", tags=["Tool Catalog"]) diff --git a/arcade/arcade/actor/routes/tool.py b/arcade/arcade/actor/routes/tool.py deleted file mode 100644 index 800dadbe..00000000 --- a/arcade/arcade/actor/routes/tool.py +++ /dev/null @@ -1,77 +0,0 @@ -from typing import TYPE_CHECKING - -from fastapi import APIRouter, Body, Depends, Query -from pydantic import ValidationError - -from arcade.actor.common.response import ResponseModel, response_base -from arcade.actor.common.response_code import CustomResponseCode -from arcade.actor.core.depends import get_catalog -from arcade.tool.openai import schema_to_openai_tool - -if TYPE_CHECKING: - from arcade.tool.catalog import ToolCatalog - -router = APIRouter() - - -@router.get( - "/list", - summary="List available tools", -) -async def list_tools(catalog: "ToolCatalog" = Depends(get_catalog)) -> ResponseModel: - """List all available tools""" - - tools = catalog.list_tools() - return await response_base.success(data=tools) - - -@router.get("/json", summary="Get the JSON (openai) format of a tool") -async def get_oai_function( - tool_name: str = Query(..., title="Tool Name", description="The name of the tool"), - catalog: "ToolCatalog" = Depends(get_catalog), -) -> ResponseModel: - """Get the OpenAI function format of an tool""" - - try: - tool = catalog[tool_name] - json_data = schema_to_openai_tool(tool) - - return await response_base.success(data=json_data) - - except KeyError: - return await response_base.fail( - res=CustomResponseCode.HTTP_404, - data=f"Tool '{tool_name}' not found in the catalog", - ) - except ValidationError as e: - return await response_base.fail(res=CustomResponseCode.HTTP_400, data=str(e)) - except Exception as e: - return await response_base.fail(res=CustomResponseCode.HTTP_500, data=str(e)) - - -@router.post("/execute", summary="Execute a tool") -async def execute_tool( - tool_name: str = Query(..., title="Tool Name", description="The name of the tool"), - data: dict[str, str] = Body( - ..., title="Tool Data", description="The data to execute the tool with" - ), - catalog: "ToolCatalog" = Depends(get_catalog), -) -> ResponseModel: - """Execute a tool""" - - try: - # TODO use executor and error handling - tool = catalog.get_tool(tool_name) - except ValueError: - return await response_base.fail( - res=CustomResponseCode.HTTP_404, - data=f"Tool '{tool_name}' not found in the catalog", - ) - - try: - result = await tool(**data) # type: ignore[misc] - return await response_base.success(data=result) - except ValidationError as e: - return await response_base.fail(res=CustomResponseCode.HTTP_400, data=str(e)) - except Exception as e: - return await response_base.fail(res=CustomResponseCode.HTTP_500, data=str(e)) diff --git a/arcade/arcade/actor/schema.py b/arcade/arcade/actor/schema.py new file mode 100644 index 00000000..cd1bff17 --- /dev/null +++ b/arcade/arcade/actor/schema.py @@ -0,0 +1,45 @@ +from typing import ClassVar, Union + +from pydantic import BaseModel + + +class ToolVersion(BaseModel): + name: str + version: str + + +class InvokeToolRequest(BaseModel): + run_id: str + invocation_id: str + created_at: str + tool: ToolVersion + inputs: dict | None + context: dict | None + + +class ToolOutputError(BaseModel): + message: str + developer_message: str | None = None + + +class ToolOutput(BaseModel): + value: Union[str, int, float, bool, dict] | None = None + error: ToolOutputError | None = None + + class Config: + json_schema_extra: ClassVar[dict] = { + "oneOf": [ + {"required": ["value"]}, + {"required": ["error"]}, + {"required": ["requires_authorization"]}, + {"required": ["artifact"]}, + ] + } + + +class InvokeToolResponse(BaseModel): + invocation_id: str + finished_at: str + duration: float + success: bool + output: ToolOutput | None = None diff --git a/arcade/arcade/apm/base.py b/arcade/arcade/apm/base.py deleted file mode 100644 index eb376bd5..00000000 --- a/arcade/arcade/apm/base.py +++ /dev/null @@ -1,55 +0,0 @@ -import os -from pathlib import Path -from typing import Optional, Union - -import toml -import tomlkit -from pydantic import BaseModel, EmailStr - - -class PackInfo(BaseModel): - """Package Manager-esk info about a pack of tools.""" - - name: str - description: str - version: str - author: Optional[str] - email: Optional[EmailStr] - - -class ToolPack(BaseModel): - """A package of tools and their dependencies.""" - - pack: PackInfo - depends: Optional[dict[str, str]] = None - tools: dict[str, str] = {} - - def write_lock_file(self, pack_dir: Union[str, os.PathLike]) -> None: - """Write the pack definition to a lock file.""" - - lock_file = Path(pack_dir) / "pack.lock.toml" - pack_dict = self.dict(by_alias=True, exclude_none=True) - pack_ordered_dict = { - "pack": pack_dict.get("pack"), - "depends": pack_dict.get("depends"), - "tools": pack_dict.get("tools"), - } - - # Create a tomlkit document from the ordered dictionary - doc = tomlkit.document() - for key, value in pack_ordered_dict.items(): - doc[key] = value - - # Write the tomlkit document to file - with open(lock_file, "w") as f: - f.write(tomlkit.dumps(doc)) - - @classmethod - def from_lock_file(cls, pack_dir: Union[str, os.PathLike]) -> "ToolPack": - """Create a ToolPack object from a lock file.""" - - pack_dir = Path(pack_dir).resolve() - lock_file = pack_dir / "pack.lock.toml" - with open(lock_file) as f: - data = toml.load(f) - return cls(**data) diff --git a/arcade/arcade/apm/pack.py b/arcade/arcade/apm/pack.py deleted file mode 100644 index f2fe52d5..00000000 --- a/arcade/arcade/apm/pack.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -from pathlib import Path -from typing import Union - -import toml -from pydantic import ValidationError - -from arcade.apm.base import PackInfo, ToolPack -from arcade.apm.parse import get_tools_from_file -from arcade.utils import snake_to_pascal_case - - -class Packer: - def __init__(self, pack_dir: Union[str, os.PathLike]): - self.pack_dir = Path(pack_dir).resolve() - self.tools_dir = self.pack_dir / "tools" - # Load the action pack configuration from a TOML file - try: - with open(self.pack_dir / "pack.toml") as f: - pack_data = toml.load(f) - - self.pack = PackInfo(**pack_data["pack"]) - self.modules = pack_data["modules"] - - except FileNotFoundError: - raise FileNotFoundError(f"No 'pack.toml' found in {self.tools_dir}") - except (toml.TomlDecodeError, ValidationError) as e: - raise ValueError(f"Invalid 'pack.toml' format: {e}") - - self.tools = self.load_tools() - self.depends: dict[str, str] = {} # TODO - # self.packs = [] # TODO - - def load_tools(self) -> dict[str, str]: - """ - Find and load the from the tools defined within directory - """ - tools = {} - for tool_file in self.tools_dir.rglob("*.py"): - if "__init__.py" in tool_file.name: - continue - try: - module = tool_file.stem - version = self.modules.get(module, "latest") - - found_tools = get_tools_from_file(tool_file) - for tool in found_tools: - tool_name = module + "." + tool + "@" + version - tools[snake_to_pascal_case(tool)] = tool_name - except Exception as e: - print(f"Error loading tool from {tool_file}: {e}") - return tools - - def create_pack(self) -> None: - """ - Create a tool pack - """ - if not self.tools: - raise ValueError("No tools found in the tools directory") - pack = ToolPack(pack=self.pack, depends=self.depends, tools=self.tools) - pack.write_lock_file(self.pack_dir) diff --git a/arcade/arcade/apm/parse.py b/arcade/arcade/apm/parse.py deleted file mode 100644 index f684b759..00000000 --- a/arcade/arcade/apm/parse.py +++ /dev/null @@ -1,79 +0,0 @@ -import ast -import importlib.metadata -import importlib.util -import sys -from pathlib import Path -from typing import Optional, Union - -from stdlib_list import stdlib_list - - -def load_ast_tree(filepath: str | Path) -> ast.AST: - """ - Load and parse the Abstract Syntax Tree (AST) from a Python file. - - """ - try: - with open(filepath) as file: - return ast.parse(file.read(), filename=filepath) - except FileNotFoundError: - raise FileNotFoundError(f"File {filepath} not found") - - -def get_python_version() -> str: - """ - Get the current Python version. - """ - return f"{sys.version_info.major}.{sys.version_info.minor}" - - -def retrieve_imported_libraries(tree: ast.AST) -> dict[str, Optional[str]]: - """ - Retrieve non-standard libraries imported in the AST. - """ - libraries = {} - python_version = get_python_version() - stdlib_modules = stdlib_list(python_version) - - for node in ast.walk(tree): - if isinstance(node, ast.ImportFrom): - package_name = node.module.split(".")[0] if node.module else None - if package_name: - if package_name in stdlib_modules: - continue - else: - try: - package_version = importlib.metadata.version(package_name) - except importlib.metadata.PackageNotFoundError: - package_version = None - else: - continue - libraries[package_name] = package_version - return libraries - - -def get_function_name_if_decorated( - node: Union[ast.FunctionDef, ast.AsyncFunctionDef] -) -> Optional[str]: - """ - Check if a function has a decorator - """ - decorator_ids = {"ar.tool", "tool"} - for decorator in node.decorator_list: - if isinstance(decorator, ast.Name) and decorator.id in decorator_ids: - return node.name - return None - - -def get_tools_from_file(filepath: str | Path) -> list[str]: - """ - Retrieve tools from a Python file. - """ - tree = load_ast_tree(filepath) - tools = [] - for node in ast.walk(tree): - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - tool_name = get_function_name_if_decorated(node) - if tool_name: - tools.append(tool_name) - return tools diff --git a/arcade/arcade/cli/main.py b/arcade/arcade/cli/main.py index 7f413fd4..04438645 100644 --- a/arcade/arcade/cli/main.py +++ b/arcade/arcade/cli/main.py @@ -1,63 +1,233 @@ +import asyncio import os +from typing import Optional import typer -import uvicorn +from openai.resources.chat.completions import ChatCompletionChunk, Stream from rich.console import Console +from rich.markdown import Markdown from rich.markup import escape +from rich.table import Table +from typer.core import TyperGroup +from typer.models import Context + + +class OrderCommands(TyperGroup): + def list_commands(self, ctx: Context) -> list[str]: # type: ignore[override] + """Return list of commands in the order appear.""" + return list(self.commands) # get commands using self.commands -from arcade.actor.core.conf import settings -cli = typer.Typer() console = Console() +cli = typer.Typer( + cls=OrderCommands, +) -@cli.command(help="Starts the ToolServer with specified configurations.") -def serve( - host: str = typer.Option( - settings.UVICORN_HOST, - help="Host for the app, from settings by default.", - show_default=True, +@cli.command(help="Log in to Arcade Cloud") +def login( + username: str = typer.Option(..., prompt="Username", help="Your Arcade Cloud username"), + api_key: str = typer.Option(None, prompt="API Key", help="Your Arcade Cloud API Key"), +) -> None: + """ + Logs the user into Arcade Cloud. + """ + # Here you would add the logic to authenticate the user with Arcade Cloud + pass + + +@cli.command(help="Create a new toolkit package directory") +def new( + directory: str = typer.Option(os.getcwd(), "--dir", help="tools directory path"), +) -> None: + """ + Creates a new toolkit with the given name, description, and result type. + """ + from arcade.cli.new import create_new_toolkit + + try: + create_new_toolkit(directory) + except Exception as e: + error_message = f"❌ Failed to create new Toolkit: {escape(str(e))}" + console.print(error_message, style="bold red") + + +@cli.command(help="Show the available tools in an actor or toolkit directory") +def show( + toolkit: str = typer.Argument(..., help="The toolkit to show the tools of"), + actor: Optional[str] = typer.Option(None, help="A running actor address to list tools from"), +) -> None: + """ + Show the available tools in an actor or toolkit + """ + from arcade.core.catalog import ToolCatalog + from arcade.core.toolkit import Toolkit + + try: + # load the toolkit from python package + loaded_toolkit = Toolkit.from_package(toolkit) + + # create a tool catalog and add the toolkit + catalog = ToolCatalog() + catalog.add_toolkit(loaded_toolkit) + + # Create a table with Rich library + table = Table(show_header=True, header_style="bold magenta") + table.add_column("Name") + table.add_column("Description") + table.add_column("Toolkit") + table.add_column("Version") + + for tool in catalog: + table.add_row(tool.name, tool.description, tool.meta.toolkit, tool.version) + + console.print(table) + + except Exception as e: + # better error message here + error_message = f"❌ Failed to List tools: {escape(str(e))}" + console.print(error_message, style="bold red") + + +@cli.command(help="Run a tool using an LLM to predict the arguments") +def run( + 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" ), - port: int = typer.Option( - settings.UVICORN_PORT, - help="Port for the app, settings default.", - show_default=True, + stream: bool = typer.Option(True, "-s", "--stream", help="Stream the tool output."), + actor: Optional[str] = typer.Option( + None, "-a", "--actor", help="The actor to use for prediction." ), ) -> None: """ - Starts the actor with host, port, and reload options. Uses - Uvicorn as ASGI actor. Parameters allow runtime configuration. + Run a tool using an LLM to predict the arguments. """ - from arcade.actor.main import app + from arcade.core.catalog import ToolCatalog + from arcade.core.client import EngineClient + from arcade.core.executor import ToolExecutor + from arcade.core.toolkit import Toolkit try: - uvicorn.run( - app=app, - host=host, - port=port, - ) - except KeyboardInterrupt: - console.print("actor stopped by user.", style="bold red") - typer.Exit() - except Exception as e: - error_message = f"❌ Failed to start Toolserver: {escape(str(e))}" + # load the toolkit from python package + loaded_toolkit = Toolkit.from_package(toolkit) + + # create a tool catalog and add the toolkit + catalog = ToolCatalog() + catalog.add_toolkit(loaded_toolkit) + + # if user specified a tool + if tool: + # check if the tool is in the catalog/toolkit + if tool not in catalog: + console.print(f"❌ Tool not found in toolkit: {toolkit}", style="bold red") + raise typer.Exit(code=1) + else: + tools = [catalog[tool]] + else: + # use all the tools in the catalog + tools = list(catalog) + + if catalog.is_empty(): + console.print(f"❌ No tools found in toolkit: {toolkit}", style="bold red") + raise typer.Exit(code=1) + + # TODO put in the engine url from config + client = EngineClient() + calls = client.call_tool(tools, tool_choice=choice, prompt=prompt, model=model) + + messages = [ + {"role": "user", "content": prompt}, + ] + + for tool_name, parameters in calls.items(): + called_tool = catalog[tool_name] + console.print(f"Running tool: {tool_name} with params: {parameters}", style="bold blue") + + output = asyncio.run( + ToolExecutor.run( + called_tool.tool, + called_tool.input_model, + called_tool.output_model, + **parameters, + ) + ) + if output.code != 200: + console.print(output.msg, style="bold red") + 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") + + except RuntimeError as e: + error_message = f"❌ Failed to run tool{': '+ escape(str(e)) if str(e) else ''}" console.print(error_message, style="bold red") - raise typer.Exit(code=1) -@cli.command(help="Build a new Tool Pack") -def pack( - directory: str = typer.Option(os.getcwd(), "--dir", help="tools directory path with pack.toml"), +@cli.command(help="Manage the Arcade Engine (start/stop/restart)") +def engine( + action: str = typer.Argument("start", help="The action to take (start/stop/restart)"), + host: str = typer.Option("localhost", "--host", "-h", help="The host of the engine"), + port: int = typer.Option(6901, "--port", "-p", help="The port of the engine"), ) -> None: """ - Creates a new tool pack with the given name, description, and result type. + Manage the Arcade Engine (start/stop/restart) """ - from arcade.apm.pack import Packer + pass - try: - pack = Packer(directory) - pack.create_pack() - except Exception as e: - error_message = f"❌ Failed to build Tool Pack: {escape(str(e))}" - console.print(error_message, style="bold red") - raise typer.Exit(code=1) + +@cli.command(help="Manage credientials stored in the Arcade Engine") +def credentials( + action: str = typer.Argument("show", help="The action to take (add/remove/show)"), + name: str = typer.Option(None, "--name", "-n", help="The name of the credential to add/remove"), + val: str = typer.Option(None, "--val", "-v", help="The value of the credential to add/remove"), +) -> None: + """ + Manage credientials stored in the Arcade Engine + """ + pass + + +@cli.command(help="Show/edit configuration details of the Arcade Engine") +def config( + action: str = typer.Argument("show", help="The action to take (show/edit)"), + name: str = typer.Option(None, "--name", "-n", help="The name of the configuration to edit"), + val: str = typer.Option(None, "--val", "-v", help="The value of the configuration to edit"), +) -> None: + """ + Show/edit configuration details of the Arcade Engine + """ + pass + + +def display_streamed_markdown(stream: Stream[ChatCompletionChunk]) -> None: + """ + Display the streamed markdown chunks as a single line. + """ + from rich.live import Live + + full_message = "" + with Live(console=console, refresh_per_second=10) as live: + for chunk in stream: + choice = chunk.choices[0] + chunk_message = choice.delta.content + if chunk_message: + full_message += chunk_message + markdown_chunk = Markdown(full_message) + live.update(markdown_chunk) diff --git a/arcade/arcade/cli/new.py b/arcade/arcade/cli/new.py new file mode 100644 index 00000000..c1faa73f --- /dev/null +++ b/arcade/arcade/cli/new.py @@ -0,0 +1,144 @@ +import os +import re +from textwrap import dedent +from typing import Optional + +import typer +from rich.console import Console + +from arcade.core.version import VERSION + +console = Console() + +DEFAULT_VERSIONS = { + "python": "^3.10", + "arcade-ai": f"^{VERSION}", + "pytest": "^7.4.0", +} + + +def ask_question(question: str, default: Optional[str] = None) -> str: + """ + Ask a question via input() and return the answer. + """ + if default: + question = f"{question} [{default}]" + answer = typer.prompt(question) + if not answer and default: + return default + return str(answer) + + +def create_directory(path: str) -> None: + """ + Create a directory if it doesn't exist. + """ + try: + os.makedirs(path, exist_ok=True) + except Exception as e: + console.print(f"[red]Failed to create directory {path}: {e}[/red]") + + +def create_file(path: str, content: str) -> None: + """ + Create a file with the given content. + """ + try: + with open(path, "w") as f: + f.write(content) + except Exception as e: + console.print(f"[red]Failed to create file {path}: {e}[/red]") + + +def create_pyproject_toml(directory: str, toolkit_name: str, author: str, description: str) -> None: + """ + Create a pyproject.toml file for the new toolkit. + """ + + content = f""" +[tool.poetry] +name = "{toolkit_name}" +version = "0.1.0" +description = "{description}" +authors = ["{author}"] + +[tool.poetry.dependencies] +python = "{DEFAULT_VERSIONS["python"]}" +arcade-ai = "{DEFAULT_VERSIONS["arcade-ai"]}" + +[tool.poetry.dev-dependencies] +pytest = "{DEFAULT_VERSIONS["pytest"]}" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" +""" + create_file(os.path.join(directory, "pyproject.toml"), content.strip()) + + +def create_new_toolkit(directory: str) -> None: + """Generate a new Toolkit package based on user input.""" + name = ask_question("Name of the new toolkit?") + toolkit_name = f"arcade_{name}" + + # Check for illegal characters in the toolkit name + if not re.match(r"^[\w_]+$", toolkit_name): + console.print( + dedent( + "[red]Toolkit name contains illegal characters. \ + Only alphanumeric characters and underscores are allowed.[/red]" + ) + ) + return + + description = ask_question("Description of the toolkit?") + author_name = ask_question("Author's name?") + author_email = ask_question("Author's email?") + author = f"{author_name} <{author_email}>" + + generate_test_dir = ask_question("Generate test directory? (yes/no)", "yes") == "yes" + generate_eval_dir = ask_question("Generate eval directory? (yes/no)", "yes") == "yes" + + top_level_dir = os.path.join(directory, name) + toolkit_dir = os.path.join(directory, name, toolkit_name) + + # Create the top level toolkit directory + create_directory(top_level_dir) + # Create the toolkit directory + create_directory(toolkit_dir) + + # Create the tools directory + create_directory(os.path.join(toolkit_dir, "tools")) + + # Create the __init__.py file in the tools directory + create_file(os.path.join(toolkit_dir, "tools", "__init__.py"), "") + + # Create the hello.py file in the tools directory + docstring = '"""Say a greeting!"""' + create_file( + os.path.join(toolkit_dir, "tools", "hello.py"), + dedent( + f""" + from arcade.sdk.tool import tool + + @tool + def hello() -> str: + {docstring} + + return "Hello, World!" + """ + ).strip(), + ) + + # Create the pyproject.toml file + create_pyproject_toml(top_level_dir, toolkit_name, author, description) + + # If the user wants to generate a test directory + if generate_test_dir: + create_directory(os.path.join(top_level_dir, "tests")) + + # If the user wants to generate an eval directory + if generate_eval_dir: + create_directory(os.path.join(top_level_dir, "evals")) + + console.print(f"[green]Toolkit {toolkit_name} has been created.[/green]") diff --git a/arcade/arcade/tool/catalog.py b/arcade/arcade/core/catalog.py similarity index 85% rename from arcade/arcade/tool/catalog.py rename to arcade/arcade/core/catalog.py index 81058b47..3343bf8c 100644 --- a/arcade/arcade/tool/catalog.py +++ b/arcade/arcade/core/catalog.py @@ -1,11 +1,10 @@ import asyncio import inspect -import sys from collections.abc import Iterator from dataclasses import dataclass from datetime import datetime from importlib import import_module -from pathlib import Path +from types import ModuleType from typing import ( Annotated, Any, @@ -22,11 +21,8 @@ from pydantic import BaseModel, Field, create_model from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined -from arcade.actor.core.conf import settings -from arcade.apm.base import ToolPack -from arcade.sdk.annotations import Inferrable -from arcade.tool.errors import ToolDefinitionError -from arcade.tool.schemas import ( +from arcade.core.errors import ToolDefinitionError +from arcade.core.tool import ( InputParameter, ToolDefinition, ToolInputs, @@ -34,12 +30,14 @@ from arcade.tool.schemas import ( ToolRequirements, ValueSchema, ) -from arcade.utils import ( +from arcade.core.toolkit import Toolkit +from arcade.core.utils import ( does_function_return_value, first_or_none, is_string_literal, snake_to_pascal_case, ) +from arcade.sdk.annotations import Inferrable WireType = Literal["string", "integer", "float", "boolean", "json"] @@ -50,6 +48,8 @@ class ToolMeta(BaseModel): """ module: str + toolkit: Optional[str] = None + package: Optional[str] = None path: Optional[str] = None date_added: datetime = Field(default_factory=datetime.now) date_updated: datetime = Field(default_factory=datetime.now) @@ -81,54 +81,95 @@ class MaterializedTool(BaseModel): return self.definition.description -# TODO make a generate for catalog type - - -class ToolCatalog: +class ToolCatalog(BaseModel): """Singleton class that holds all tools for a given actor""" - def __init__(self, tools_dir: Path = settings.TOOLS_DIR): - self.tools: dict[str, MaterializedTool] = self.read_tools(tools_dir) + tools: dict[str, MaterializedTool] = {} - @staticmethod - def read_tools(directory: Path) -> dict[str, MaterializedTool]: + def add_tool( + self, + tool_func: Callable, + module: ModuleType | None = None, + toolkit: Toolkit | None = None, + ) -> None: """ - Create tool definitions from a directory of python files + Add a function to the catalog as a tool. """ - toolpack = ToolPack.from_lock_file(directory) - sys.path.append(str(Path(directory).resolve() / "tools")) + input_model, output_model = create_func_models(tool_func) + definition = ToolCatalog.create_tool_definition( + tool_func, toolkit.version if toolkit else "latest" + ) - tools: dict[str, MaterializedTool] = {} - for name, tool_spec in toolpack.tools.items(): - module_name, versioned_tool = tool_spec.split(".", 1) - func_name, version = versioned_tool.split("@") + self.tools[definition.name] = MaterializedTool( + definition=definition, + tool=tool_func, + meta=ToolMeta( + module=module.__name__ if module else tool_func.__module__, + toolkit=toolkit.name if toolkit else None, + package=toolkit.package_name if toolkit else None, + path=module.__file__ if module else None, + ), + input_model=input_model, + output_model=output_model, + ) - module = import_module(module_name) - tool_func = getattr(module, func_name) - input_model, output_model = create_func_models(tool_func) - tool_name = name - tools[tool_name] = MaterializedTool( - definition=ToolCatalog.create_tool_definition(tool_func, version), - tool=tool_func, - meta=ToolMeta(module=module_name, path=module.__file__), - input_model=input_model, - output_model=output_model, - ) + def add_toolkit(self, toolkit: Toolkit) -> None: + """ + Add the tools from a loaded toolkit to the catalog. + """ - return tools + for module_name, tool_names in toolkit.tools.items(): + for tool_name in tool_names: + try: + module = import_module(module_name) + tool_func = getattr(module, tool_name) + + except AttributeError: + raise ToolDefinitionError( + f"Could not find tool {tool_name} in module {module_name}" + ) + except ImportError: + raise ToolDefinitionError(f"Could not import module {module_name}") + + self.add_tool(tool_func, module, toolkit) + + def __getitem__(self, name: str) -> MaterializedTool: + for tool_name, tool in self.tools.items(): + if tool_name == name: + return tool + raise KeyError(f"Tool {name} not found.") + + def __contains__(self, name: str) -> bool: + return name in self.tools + + def __iter__(self) -> Iterator[MaterializedTool]: # type: ignore[override] + yield from self.tools.values() + + def __len__(self) -> int: + return len(self.tools) + + def is_empty(self) -> bool: + return len(self.tools) == 0 + + def get_tool(self, name: str) -> Optional[Callable]: + for tool in self.tools.values(): + if tool.definition.name == name: + return tool.tool + raise ValueError(f"Tool {name} not found.") @staticmethod def create_tool_definition(tool: Callable, version: str) -> ToolDefinition: """ Given a tool function, create a ToolDefinition + # TODO: (sam) Make this a function? """ tool_name = getattr(tool, "__tool_name__", tool.__name__) # Hard requirement: tools must have descriptions tool_description = getattr(tool, "__tool_description__", None) - if tool_description is None: + if not tool_description: raise ToolDefinitionError(f"Tool {tool_name} is missing a description") # If the function returns a value, it must have a type annotation @@ -136,7 +177,7 @@ class ToolCatalog: raise ToolDefinitionError(f"Tool {tool_name} must have a return type annotation") return ToolDefinition( - name=tool_name, + name=snake_to_pascal_case(tool_name), description=tool_description, version=version, inputs=create_input_definition(tool), @@ -146,36 +187,6 @@ class ToolCatalog: ), ) - def __getitem__(self, name: str) -> MaterializedTool: - # TODO error handling - for tool_name, tool in self.tools.items(): - if tool_name == name: - return tool - raise KeyError(f"Tool {name} not found.") - - def __iter__(self) -> Iterator[MaterializedTool]: - yield from self.tools.values() - - def get_tool(self, name: str) -> Optional[Callable]: - for tool in self.tools.values(): - if tool.definition.name == name: - return tool.tool - raise ValueError(f"Tool {name} not found.") - - def list_tools(self) -> list[dict[str, str]]: - def get_tool_endpoint(t: MaterializedTool) -> str: - return f"/tool/{t.meta.module}/{t.definition.name}" - - return [ - { - "name": t.definition.name, - "description": t.definition.description, - "version": t.version, - "endpoint": get_tool_endpoint(t), - } - for t in self.tools.values() - ] - def create_input_definition(func: Callable) -> ToolInputs: """ diff --git a/arcade/arcade/core/client.py b/arcade/arcade/core/client.py new file mode 100644 index 00000000..9a8f9e37 --- /dev/null +++ b/arcade/arcade/core/client.py @@ -0,0 +1,184 @@ +import json +import os +from enum import Enum +from typing import Any, Optional + +from openai import OpenAI +from openai.resources.chat.completions import ChatCompletion, ChatCompletionChunk, Stream +from pydantic import BaseModel +from pydantic_core import PydanticUndefined + +from arcade.core.catalog import MaterializedTool + +PYTHON_TO_JSON_TYPES: dict[type, str] = { + str: "string", + int: "integer", + float: "number", + bool: "boolean", + list: "array", + dict: "object", +} + +ToolCalls = dict[str, dict[str, Any]] + + +def python_type_to_json_type(python_type: type[Any]) -> dict[str, Any] | str: + """ + Map Python types to JSON Schema types, including handling of + complex types such as lists and dictionaries. + """ + if hasattr(python_type, "__origin__"): + origin = python_type.__origin__ + + if origin is list: + item_type = python_type_to_json_type(python_type.__args__[0]) + return {"type": "array", "items": item_type} + elif origin is dict: + value_type = python_type_to_json_type(python_type.__args__[1]) + return {"type": "object", "additionalProperties": value_type} + + elif issubclass(python_type, BaseModel): + return model_to_json_schema(python_type) + + return PYTHON_TO_JSON_TYPES.get(python_type, "string") + + +def model_to_json_schema(model: type[BaseModel]) -> dict[str, Any]: + """ + Convert a Pydantic model to a JSON schema. + """ + properties = {} + required = [] + for field_name, model_field in model.model_fields.items(): + type_json = python_type_to_json_type(model_field.annotation) # type: ignore[arg-type] + if isinstance(type_json, dict): + field_schema = type_json + else: + field_schema = { + "type": type_json, + "description": model_field.description or "", + } + if model_field.default not in [None, PydanticUndefined]: + if isinstance(model_field.default, Enum): + field_schema["default"] = model_field.default.value + else: + field_schema["default"] = model_field.default + if model_field.is_required(): + required.append(field_name) + properties[field_name] = field_schema + return { + "type": "object", + "properties": properties, + "required": required, + } + + +def schema_to_openai_tool(tool: MaterializedTool) -> dict[str, Any]: + """ + Convert a ToolDefinition object to a JSON schema dictionary in the specified function format. + """ + input_model_schema = model_to_json_schema(tool.input_model) + function_schema = { + "type": "function", + "function": { + "name": tool.definition.name, + "description": tool.definition.description, + "parameters": input_model_schema, + }, + } + return function_schema + + +def called_tool(chat_completion: ChatCompletion) -> bool: + """ + Return true if the chat completion called a tool. + """ + choice = chat_completion.choices[0] + if choice.message.tool_calls: + return True + return False + + +def get_tool_args(chat_completion: ChatCompletion) -> ToolCalls: + """ + Returns the tool arguments from the chat completion object. + """ + tool_args_list = {} + message = chat_completion.choices[0].message + if message.tool_calls: + for tool_call in message.tool_calls: + tool_args_list[tool_call.function.name] = json.loads(tool_call.function.arguments) + return tool_args_list + + +class EngineClient: + def __init__(self, api_key: str | None = None, base_url: str | None = None): + api_key = os.environ["OPENAI_API_KEY"] if api_key is None else api_key + self.client = OpenAI(api_key=api_key, base_url=base_url) + + def __getattr__(self, name: str) -> Any: + return getattr(self.client, name) + + def call_tool( + self, + tools: list[MaterializedTool], + model: str, + messages: Optional[list[dict[str, Any]]] = None, + tool_choice: Optional[str] = "required", + parallel_tool_calls: Optional[bool] = False, + prompt: Optional[str] = "", + **kwargs: Any, + ) -> ToolCalls: + """ + Infer the arguments for a given tool and call the OpenAI API. + """ + specs = [schema_to_openai_tool(tool) for tool in tools] + + if messages is None: + messages = [{"role": "user", "content": prompt}] + try: + completion = self.complete( + model=model, + messages=messages, + tools=specs, + tool_choice=tool_choice, + parallel_tool_calls=parallel_tool_calls, + **kwargs, + ) + if not called_tool(completion): + raise ValueError("No tool call was made.") + + except (KeyError, IndexError) as e: + raise ValueError("Invalid response format from OpenAI API.") from e + + return get_tool_args(completion) + + def complete( + self, + model: str, + messages: list[dict[str, Any]], + **kwargs: Any, + ) -> ChatCompletion: + """ + Call the OpenAI API with the given messages. + """ + completion = self.client.chat.completions.create( + model=model, + messages=messages, # type: ignore[arg-type] + **kwargs, + ) + return completion + + def stream_complete( # type: ignore[misc] + self, + model: str, + messages: list[dict[str, Any]], + **kwargs: Any, + ) -> Stream[ChatCompletionChunk]: + stream = self.client.chat.completions.create( + model=model, + messages=messages, # type: ignore[arg-type] + stream=True, + **kwargs, + ) + yield from stream diff --git a/arcade/arcade/core/config.py b/arcade/arcade/core/config.py new file mode 100644 index 00000000..b163e62f --- /dev/null +++ b/arcade/arcade/core/config.py @@ -0,0 +1,64 @@ +from pathlib import Path + +import toml +from pydantic import BaseModel + +from arcade.core.env import settings + + +class Config(BaseModel): + api_key: str | None = None + user_email: str | None = None + engine_key: str | None = None + engine_host: str = "localhost" + engine_port: str = "6901" + + config_dir: Path = settings.WORK_DIR if settings.WORK_DIR else Path.home() / ".arcade" + config_file: Path = config_dir / "arcade.toml" + + @property + def arcade_api_key(self) -> str: + if not self.api_key: + raise ValueError("Arcade API Key not set") + return self.api_key + + @property + def engine_url(self, tls: bool = False) -> str: + if tls: + return f"https://{self.engine_host}:{self.engine_port}" + return f"http://{self.engine_host}:{self.engine_port}" + + @staticmethod + def create_config_directory() -> None: + """ + Create the configuration directory if it does not exist. + """ + config_dir = Config.config_dir + if not config_dir.exists(): + config_dir.mkdir(parents=True, exist_ok=True) + + def save_to_file(self) -> None: + """ + Save the configuration to the TOML file in the configuration directory. + """ + self.create_config_directory() + config_file_path = self.config_file + with config_file_path.open("w") as config_file: + toml.dump(self.dict(), config_file) + + @classmethod + def load_from_file(cls) -> "Config": + """ + Load the configuration from the TOML file in the configuration directory. + """ + cls.create_config_directory() + config_file_path = cls.config_file + if config_file_path.exists(): + with config_file_path.open("r") as config_file: + config_data = toml.load(config_file) + return cls(**config_data) + return cls() + + +# Singleton instance of Config +config = Config.load_from_file() diff --git a/arcade/arcade/core/env.py b/arcade/arcade/core/env.py new file mode 100644 index 00000000..da5330ed --- /dev/null +++ b/arcade/arcade/core/env.py @@ -0,0 +1,23 @@ +import os +from functools import lru_cache +from pathlib import Path + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=".env") + + WORK_DIR: Path = Path.home() / ".arcade" + TOOLS_DIR: Path = Path(os.getcwd()) + TOOLKIT_DIR: Path = Path(os.getcwd()) + + +@lru_cache +def get_settings() -> Settings: + # env_file = os.getenv("ARCADE_ENV_FILE") + # TODO allow env override + return Settings() + + +settings = get_settings() diff --git a/arcade/arcade/tool/errors.py b/arcade/arcade/core/errors.py similarity index 100% rename from arcade/arcade/tool/errors.py rename to arcade/arcade/core/errors.py diff --git a/arcade/arcade/tool/executor.py b/arcade/arcade/core/executor.py similarity index 88% rename from arcade/arcade/tool/executor.py rename to arcade/arcade/core/executor.py index 19b090b1..1ab52a81 100644 --- a/arcade/arcade/tool/executor.py +++ b/arcade/arcade/core/executor.py @@ -1,14 +1,15 @@ +import asyncio from typing import Any, Callable from pydantic import BaseModel, ValidationError -from arcade.tool.errors import ( +from arcade.core.errors import ( ToolExecutionError, ToolInputError, ToolOutputError, ToolSerializationError, ) -from arcade.tool.response import ToolResponse, tool_response +from arcade.core.response import ToolResponse, tool_response class ToolExecutor: @@ -28,7 +29,10 @@ class ToolExecutor: inputs = await ToolExecutor._serialize_input(input_model, **kwargs) # execute the tool function - results = await func(**inputs.dict()) + if asyncio.iscoroutinefunction(func): + results = await func(**inputs.model_dump()) + else: + results = func(**inputs.model_dump()) # serialize the output model output = await ToolExecutor._serialize_output(output_model, results) diff --git a/arcade/arcade/core/parse.py b/arcade/arcade/core/parse.py new file mode 100644 index 00000000..5476ccc7 --- /dev/null +++ b/arcade/arcade/core/parse.py @@ -0,0 +1,42 @@ +import ast +from pathlib import Path +from typing import Optional, Union + + +def load_ast_tree(filepath: str | Path) -> ast.AST: + """ + Load and parse the Abstract Syntax Tree (AST) from a Python file. + + """ + try: + with open(filepath) as file: + return ast.parse(file.read(), filename=filepath) + except FileNotFoundError: + raise FileNotFoundError(f"File {filepath} not found") + + +def get_function_name_if_decorated( + node: Union[ast.FunctionDef, ast.AsyncFunctionDef], +) -> Optional[str]: + """ + Check if a function has a decorator + """ + decorator_ids = {"ar.tool", "tool"} + for decorator in node.decorator_list: + if isinstance(decorator, ast.Name) and decorator.id in decorator_ids: + return node.name + return None + + +def get_tools_from_file(filepath: str | Path) -> list[str]: + """ + Retrieve tools from a Python file. + """ + tree = load_ast_tree(filepath) + tools = [] + for node in ast.walk(tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + tool_name = get_function_name_if_decorated(node) + if tool_name: + tools.append(tool_name) + return tools diff --git a/arcade/arcade/tool/response.py b/arcade/arcade/core/response.py similarity index 66% rename from arcade/arcade/tool/response.py rename to arcade/arcade/core/response.py index 2d9a03b6..b415b446 100644 --- a/arcade/arcade/tool/response.py +++ b/arcade/arcade/core/response.py @@ -1,13 +1,11 @@ -from datetime import datetime from typing import Any, Generic, TypeVar -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel -from arcade.actor.common.response import ( +from arcade.core.response_code import ( CustomResponse, CustomResponseCode, ) -from arcade.actor.core.conf import settings _ExcludeData = set[int | str] | dict[int | str, Any] T = TypeVar("T") @@ -22,11 +20,6 @@ class ToolResponse(BaseModel, Generic[T]): """ - # TODO: json_encoders configuration failure: https://github.com/tiangolo/fastapi/discussions/10252 - model_config = ConfigDict( - json_encoders={datetime: lambda x: x.strftime(settings.DATETIME_FORMAT)} - ) - code: int = CustomResponseCode.HTTP_200.code msg: str = CustomResponseCode.HTTP_200.msg @@ -61,17 +54,6 @@ class ToolResponseFactory: ) -> ToolResponse: return await self.__response(res=res, data=data) - async def retry( - self, - *, - res: CustomResponseCode | CustomResponse = CustomResponseCode.HTTP_200, - msg: str = CustomResponseCode.HTTP_200.msg, - data: T | None = None, - ) -> ToolResponse: - # TODO: Implement retry logic and ability to add messages to the response for - # the LLM - return await self.__response(res=res, msg=msg, data=data) - async def fail( self, *, diff --git a/arcade/arcade/actor/common/response_code.py b/arcade/arcade/core/response_code.py similarity index 100% rename from arcade/arcade/actor/common/response_code.py rename to arcade/arcade/core/response_code.py diff --git a/arcade/arcade/tool/schemas.py b/arcade/arcade/core/tool.py similarity index 100% rename from arcade/arcade/tool/schemas.py rename to arcade/arcade/core/tool.py diff --git a/arcade/arcade/core/toolkit.py b/arcade/arcade/core/toolkit.py new file mode 100644 index 00000000..650d9a02 --- /dev/null +++ b/arcade/arcade/core/toolkit.py @@ -0,0 +1,126 @@ +import importlib.metadata +import importlib.util +import os +import types +from collections import defaultdict +from pathlib import Path + +from pydantic import BaseModel, ConfigDict, field_validator + +from arcade.core.parse import get_tools_from_file + + +class Toolkit(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + name: str + """Name of the toolkit""" + + package_name: str + """Name of the package holding the toolkit""" + + tools: dict[str, list[str]] = defaultdict(list) + """Mapping of module names to tools""" + + # Other python package metadata + version: str + description: str + author: list[str] = [] + repository: str | None = None + homepage: str | None = None + + @field_validator("name", mode="before") + def strip_arcade_prefix(cls, value: str) -> str: + """ + Validator to strip the 'arcade_' prefix from the name if it exists. + """ + if value.startswith("arcade_"): + return value[len("arcade_") :] + return value + + @classmethod + def from_module(cls, module: types.ModuleType) -> "Toolkit": + """ + Load a toolkit from an imported python module + + >>> import arcade_arithmetic + >>> toolkit = Toolkit.from_module(arcade_arithmetic) + """ + return cls.from_package(module.__name__) + + @classmethod + def from_package(cls, package: str) -> "Toolkit": + """ + Load a Toolkit from a Python package + """ + try: + metadata = importlib.metadata.metadata(package) + name = metadata["Name"] + package_name = package + version = metadata["Version"] + description = metadata["Summary"] if "Summary" in metadata else "" + author = metadata.get_all("Author-email") + homepage = metadata["Home-page"] if "Home-page" in metadata else None + repo = metadata["Repository"] if "Repository" in metadata else None + + except importlib.metadata.PackageNotFoundError as e: + raise ValueError(f"Package {package} not found.") from e + except KeyError as e: + raise ValueError(f"Metadata key error for package {package}.") from e + except Exception as e: + raise ValueError(f"Failed to load metadata for package {package}.") from e + + # Get the package directory + try: + package_dir = Path(get_package_directory(package)) + except AttributeError as e: + raise ValueError(f"Failed to locate package directory for {package}.") from e + + # Get all python files in the package directory + try: + modules = [f for f in package_dir.glob("**/*.py") if f.is_file()] + except OSError as e: + raise ValueError( + f"Failed to locate Python files in package directory for {package}." + ) from e + + toolkit = cls( + name=name, + package_name=package_name, + version=version, + description=description, + author=author if author else [], + homepage=homepage, + repository=repo, + ) + + for module_path in modules: + relative_path = module_path.relative_to(package_dir) + import_path = ".".join(relative_path.with_suffix("").parts) + import_path = f"{package_name}.{import_path}" + toolkit.tools[import_path] = get_tools_from_file(str(module_path)) + + if not toolkit.tools: + raise ValueError(f"No tools found in package {package}") + + return toolkit + + +def get_package_directory(package_name: str) -> str: + """ + Get the directory of a Python package + """ + + spec = importlib.util.find_spec(package_name) + if spec is None: + raise ImportError(f"Cannot find package named {package_name}") + if not spec.origin: + raise ImportError(f"Package {package_name} does not have a file path associated with it") + + package_path = spec.origin + + if spec.submodule_search_locations: + # It's a package, get the directory + package_path = os.path.dirname(package_path) + + return package_path diff --git a/arcade/arcade/utils/__init__.py b/arcade/arcade/core/utils.py similarity index 88% rename from arcade/arcade/utils/__init__.py rename to arcade/arcade/core/utils.py index 4d2ce7fd..42a84480 100644 --- a/arcade/arcade/utils/__init__.py +++ b/arcade/arcade/core/utils.py @@ -29,7 +29,12 @@ def snake_to_pascal_case(name: str) -> str: """ Converts a snake_case name to PascalCase. """ - return "".join(x.capitalize() or "_" for x in name.split("_")) + if "_" in name: + return "".join(x.capitalize() or "_" for x in name.split("_")) + # check if first letter is uppercase + if name[0].isupper(): + return name + return name.capitalize() def is_string_literal(_type: type) -> bool: diff --git a/arcade/arcade/core/version.py b/arcade/arcade/core/version.py new file mode 100644 index 00000000..1cf6267a --- /dev/null +++ b/arcade/arcade/core/version.py @@ -0,0 +1 @@ +VERSION = "0.1.0" diff --git a/arcade/arcade/sdk/tool.py b/arcade/arcade/sdk/tool.py index fe10f5ac..a80d865c 100644 --- a/arcade/arcade/sdk/tool.py +++ b/arcade/arcade/sdk/tool.py @@ -1,8 +1,9 @@ +import inspect import os from typing import Any, Callable, Optional, TypeVar, Union -from arcade.tool.schemas import ToolAuthorizationRequirement -from arcade.utils import snake_to_pascal_case +from arcade.core.tool import ToolAuthorizationRequirement +from arcade.core.utils import snake_to_pascal_case T = TypeVar("T") @@ -19,7 +20,7 @@ def tool( tool_name = name or snake_to_pascal_case(func_name) setattr(func, "__tool_name__", tool_name) # noqa: B010 (Do not call `setattr` with a constant attribute value) - setattr(func, "__tool_description__", desc or func.__doc__) # noqa: B010 + setattr(func, "__tool_description__", desc or inspect.cleandoc(func.__doc__ or "")) # noqa: B010 setattr(func, "__tool_requires_auth__", requires_auth) # noqa: B010 return func diff --git a/arcade/arcade/tool/openai.py b/arcade/arcade/tool/openai.py deleted file mode 100644 index 003bbf41..00000000 --- a/arcade/arcade/tool/openai.py +++ /dev/null @@ -1,107 +0,0 @@ -import json -from enum import Enum -from typing import Any - -from pydantic import BaseModel -from pydantic_core import PydanticUndefined - -from arcade.tool.catalog import MaterializedTool - -PYTHON_TO_JSON_TYPES: dict[type, str] = { - str: "string", - int: "integer", - float: "number", - bool: "boolean", - list: "array", - dict: "object", -} - - -def python_type_to_json_type(python_type: type[Any]) -> dict[str, Any]: - """ - Map Python types to JSON Schema types, including handling of - complex types such as lists and dictionaries. - """ - if hasattr(python_type, "__origin__"): - origin = python_type.__origin__ - - if origin is list: - item_type = python_type_to_json_type(python_type.__args__[0]) - return {"type": "array", "items": item_type} - elif origin is dict: - value_type = python_type_to_json_type(python_type.__args__[1]) - return {"type": "object", "additionalProperties": value_type} - - elif issubclass(python_type, BaseModel): - return model_to_json_schema(python_type) - - raise ValueError(f"Unsupported type: {python_type}") - - -def model_to_json_schema(model: type[BaseModel]) -> dict[str, Any]: - """ - Convert a Pydantic model to a JSON schema. - """ - properties = {} - required = [] - for field_name, model_field in model.model_fields.items(): - # TODO: remove type ignore - type_json = python_type_to_json_type(model_field.annotation) # type: ignore[arg-type] - if isinstance(type_json, dict): - field_schema = type_json - else: - field_schema = { - "type": type_json, - "description": model_field.description or "", - } - if model_field.default not in [None, PydanticUndefined]: - if isinstance(model_field.default, Enum): - field_schema["default"] = model_field.default.value - else: - field_schema["default"] = model_field.default - if model_field.is_required(): - required.append(field_name) - properties[field_name] = field_schema - return { - "type": "object", - "properties": properties, - "required": required, - } - - -def schema_to_openai_tool(tool: "MaterializedTool") -> str: - """Convert an ToolDefinition object to a JSON schema string in the specified function format. - - Example output format: - { - "type": "function", - "function": { - "name": "get_current_weather", - "description": "Get the current weather in a given location", - "parameters": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA" - }, - "unit": { - "type": "string", - "enum": ["celsius", "fahrenheit"] - } - }, - "required": ["location"] - } - } - } - """ - input_model_schema = model_to_json_schema(tool.input_model) - function_schema = { - "type": "function", - "function": { - "name": tool.definition.name, - "description": tool.definition.description, - "parameters": input_model_schema, - }, - } - return json.dumps(function_schema, indent=2) diff --git a/arcade/poetry.lock b/arcade/poetry.lock index b10fed30..e31da5c4 100644 --- a/arcade/poetry.lock +++ b/arcade/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -33,17 +33,6 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - [[package]] name = "babel" version = "2.15.0" @@ -334,40 +323,16 @@ files = [ ] [[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" optional = false -python-versions = ">=3.8" +python-versions = ">=3.6" files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - -[[package]] -name = "email-validator" -version = "2.2.0" -description = "A robust email address syntax and deliverability validation library." -optional = false -python-versions = ">=3.8" -files = [ - {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, - {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, -] - -[package.dependencies] -dnspython = ">=2.0.0" -idna = ">=2.0.0" - [[package]] name = "exceptiongroup" version = "1.2.2" @@ -460,6 +425,51 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "httpcore" +version = "1.0.5" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, + {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, +] + +[package.dependencies] +certifi = "*" +h11 = ">=0.13,<0.15" + +[package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<0.26.0)"] + +[[package]] +name = "httpx" +version = "0.27.0" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.8" +files = [ + {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, +] + +[package.dependencies] +anyio = "*" +certifi = "*" +httpcore = "==1.*" +idna = "*" +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + [[package]] name = "identify" version = "2.6.0" @@ -513,24 +523,6 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "loguru" -version = "0.7.2" -description = "Python logging made (stupidly) simple" -optional = false -python-versions = ">=3.5" -files = [ - {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, - {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, -] - -[package.dependencies] -colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} -win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} - -[package.extras] -dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] - [[package]] name = "markdown" version = "3.6" @@ -803,163 +795,46 @@ files = [ griffe = ">=0.37" mkdocstrings = ">=0.20" -[[package]] -name = "msgpack" -version = "1.0.8" -description = "MessagePack serializer" -optional = false -python-versions = ">=3.8" -files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, -] - -[[package]] -name = "msgspec" -version = "0.18.6" -description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." -optional = false -python-versions = ">=3.8" -files = [ - {file = "msgspec-0.18.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77f30b0234eceeff0f651119b9821ce80949b4d667ad38f3bfed0d0ebf9d6d8f"}, - {file = "msgspec-0.18.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a76b60e501b3932782a9da039bd1cd552b7d8dec54ce38332b87136c64852dd"}, - {file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06acbd6edf175bee0e36295d6b0302c6de3aaf61246b46f9549ca0041a9d7177"}, - {file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40a4df891676d9c28a67c2cc39947c33de516335680d1316a89e8f7218660410"}, - {file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6896f4cd5b4b7d688018805520769a8446df911eb93b421c6c68155cdf9dd5a"}, - {file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ac4dd63fd5309dd42a8c8c36c1563531069152be7819518be0a9d03be9788e4"}, - {file = "msgspec-0.18.6-cp310-cp310-win_amd64.whl", hash = "sha256:fda4c357145cf0b760000c4ad597e19b53adf01382b711f281720a10a0fe72b7"}, - {file = "msgspec-0.18.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f"}, - {file = "msgspec-0.18.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa"}, - {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b"}, - {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07"}, - {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c"}, - {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492"}, - {file = "msgspec-0.18.6-cp311-cp311-win_amd64.whl", hash = "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4"}, - {file = "msgspec-0.18.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c"}, - {file = "msgspec-0.18.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1"}, - {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466"}, - {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad237100393f637b297926cae1868b0d500f764ccd2f0623a380e2bcfb2809ca"}, - {file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db1d8626748fa5d29bbd15da58b2d73af25b10aa98abf85aab8028119188ed57"}, - {file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d70cb3d00d9f4de14d0b31d38dfe60c88ae16f3182988246a9861259c6722af6"}, - {file = "msgspec-0.18.6-cp312-cp312-win_amd64.whl", hash = "sha256:1003c20bfe9c6114cc16ea5db9c5466e49fae3d7f5e2e59cb70693190ad34da0"}, - {file = "msgspec-0.18.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7d9faed6dfff654a9ca7d9b0068456517f63dbc3aa704a527f493b9200b210a"}, - {file = "msgspec-0.18.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9da21f804c1a1471f26d32b5d9bc0480450ea77fbb8d9db431463ab64aaac2cf"}, - {file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46eb2f6b22b0e61c137e65795b97dc515860bf6ec761d8fb65fdb62aa094ba61"}, - {file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8355b55c80ac3e04885d72db515817d9fbb0def3bab936bba104e99ad22cf46"}, - {file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9080eb12b8f59e177bd1eb5c21e24dd2ba2fa88a1dbc9a98e05ad7779b54c681"}, - {file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc001cf39becf8d2dcd3f413a4797c55009b3a3cdbf78a8bf5a7ca8fdb76032c"}, - {file = "msgspec-0.18.6-cp38-cp38-win_amd64.whl", hash = "sha256:fac5834e14ac4da1fca373753e0c4ec9c8069d1fe5f534fa5208453b6065d5be"}, - {file = "msgspec-0.18.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:974d3520fcc6b824a6dedbdf2b411df31a73e6e7414301abac62e6b8d03791b4"}, - {file = "msgspec-0.18.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd62e5818731a66aaa8e9b0a1e5543dc979a46278da01e85c3c9a1a4f047ef7e"}, - {file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7481355a1adcf1f08dedd9311193c674ffb8bf7b79314b4314752b89a2cf7f1c"}, - {file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aa85198f8f154cf35d6f979998f6dadd3dc46a8a8c714632f53f5d65b315c07"}, - {file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e24539b25c85c8f0597274f11061c102ad6b0c56af053373ba4629772b407be"}, - {file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c61ee4d3be03ea9cd089f7c8e36158786cd06e51fbb62529276452bbf2d52ece"}, - {file = "msgspec-0.18.6-cp39-cp39-win_amd64.whl", hash = "sha256:b5c390b0b0b7da879520d4ae26044d74aeee5144f83087eb7842ba59c02bc090"}, - {file = "msgspec-0.18.6.tar.gz", hash = "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e"}, -] - -[package.extras] -dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli-w"] -doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"] -test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli", "tomli-w"] -toml = ["tomli", "tomli-w"] -yaml = ["pyyaml"] - [[package]] name = "mypy" -version = "1.10.1" +version = "1.11.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, - {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, - {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, - {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, - {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, - {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, - {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, - {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, - {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, - {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, - {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, - {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, - {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, - {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, - {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, - {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, - {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, - {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, - {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, - {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, - {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, - {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, - {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, + {file = "mypy-1.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3824187c99b893f90c845bab405a585d1ced4ff55421fdf5c84cb7710995229"}, + {file = "mypy-1.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96f8dbc2c85046c81bcddc246232d500ad729cb720da4e20fce3b542cab91287"}, + {file = "mypy-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a5d8d8dd8613a3e2be3eae829ee891b6b2de6302f24766ff06cb2875f5be9c6"}, + {file = "mypy-1.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72596a79bbfb195fd41405cffa18210af3811beb91ff946dbcb7368240eed6be"}, + {file = "mypy-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:35ce88b8ed3a759634cb4eb646d002c4cef0a38f20565ee82b5023558eb90c00"}, + {file = "mypy-1.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:98790025861cb2c3db8c2f5ad10fc8c336ed2a55f4daf1b8b3f877826b6ff2eb"}, + {file = "mypy-1.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25bcfa75b9b5a5f8d67147a54ea97ed63a653995a82798221cca2a315c0238c1"}, + {file = "mypy-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bea2a0e71c2a375c9fa0ede3d98324214d67b3cbbfcbd55ac8f750f85a414e3"}, + {file = "mypy-1.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2b3d36baac48e40e3064d2901f2fbd2a2d6880ec6ce6358825c85031d7c0d4d"}, + {file = "mypy-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8e2e43977f0e09f149ea69fd0556623919f816764e26d74da0c8a7b48f3e18a"}, + {file = "mypy-1.11.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d44c1e44a8be986b54b09f15f2c1a66368eb43861b4e82573026e04c48a9e20"}, + {file = "mypy-1.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cea3d0fb69637944dd321f41bc896e11d0fb0b0aa531d887a6da70f6e7473aba"}, + {file = "mypy-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a83ec98ae12d51c252be61521aa5731f5512231d0b738b4cb2498344f0b840cd"}, + {file = "mypy-1.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7b73a856522417beb78e0fb6d33ef89474e7a622db2653bc1285af36e2e3e3d"}, + {file = "mypy-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:f2268d9fcd9686b61ab64f077be7ffbc6fbcdfb4103e5dd0cc5eaab53a8886c2"}, + {file = "mypy-1.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:940bfff7283c267ae6522ef926a7887305945f716a7704d3344d6d07f02df850"}, + {file = "mypy-1.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:14f9294528b5f5cf96c721f231c9f5b2733164e02c1c018ed1a0eff8a18005ac"}, + {file = "mypy-1.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7b54c27783991399046837df5c7c9d325d921394757d09dbcbf96aee4649fe9"}, + {file = "mypy-1.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:65f190a6349dec29c8d1a1cd4aa71284177aee5949e0502e6379b42873eddbe7"}, + {file = "mypy-1.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbe286303241fea8c2ea5466f6e0e6a046a135a7e7609167b07fd4e7baf151bf"}, + {file = "mypy-1.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:104e9c1620c2675420abd1f6c44bab7dd33cc85aea751c985006e83dcd001095"}, + {file = "mypy-1.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f006e955718ecd8d159cee9932b64fba8f86ee6f7728ca3ac66c3a54b0062abe"}, + {file = "mypy-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:becc9111ca572b04e7e77131bc708480cc88a911adf3d0239f974c034b78085c"}, + {file = "mypy-1.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6801319fe76c3f3a3833f2b5af7bd2c17bb93c00026a2a1b924e6762f5b19e13"}, + {file = "mypy-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:c1a184c64521dc549324ec6ef7cbaa6b351912be9cb5edb803c2808a0d7e85ac"}, + {file = "mypy-1.11.0-py3-none-any.whl", hash = "sha256:56913ec8c7638b0091ef4da6fcc9136896914a9d60d54670a75880c3e5b99ace"}, + {file = "mypy-1.11.0.tar.gz", hash = "sha256:93743608c7348772fdc717af4aeee1997293a1ad04bc0ea6efa15bf65385c538"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" +typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] @@ -989,6 +864,29 @@ files = [ {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] +[[package]] +name = "openai" +version = "1.37.0" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.7.1" +files = [ + {file = "openai-1.37.0-py3-none-any.whl", hash = "sha256:a903245c0ecf622f2830024acdaa78683c70abb8e9d37a497b851670864c9f73"}, + {file = "openai-1.37.0.tar.gz", hash = "sha256:dc8197fc40ab9d431777b6620d962cc49f4544ffc3011f03ce0a805e6eb54adb"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.7,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] + [[package]] name = "packaging" version = "24.1" @@ -1083,7 +981,6 @@ files = [ [package.dependencies] annotated-types = ">=0.4.0" -email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""} pydantic-core = "2.20.1" typing-extensions = [ {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, @@ -1288,13 +1185,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "pytest-asyncio" -version = "0.23.7" +version = "0.23.8" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, - {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, + {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"}, + {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"}, ] [package.dependencies] @@ -1375,6 +1272,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1423,24 +1321,6 @@ files = [ [package.dependencies] pyyaml = "*" -[[package]] -name = "redis" -version = "5.0.7" -description = "Python client for Redis database and key-value store" -optional = false -python-versions = ">=3.7" -files = [ - {file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"}, - {file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"}, -] - -[package.dependencies] -async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} - -[package.extras] -hiredis = ["hiredis (>=1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] - [[package]] name = "regex" version = "2024.5.15" @@ -1607,24 +1487,6 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] -[[package]] -name = "stdlib-list" -version = "0.10.0" -description = "A list of Python Standard Libraries (2.7 through 3.12)." -optional = false -python-versions = ">=3.7" -files = [ - {file = "stdlib_list-0.10.0-py3-none-any.whl", hash = "sha256:b3a911bc441d03e0332dd1a9e7d0870ba3bb0a542a74d7524f54fb431256e214"}, - {file = "stdlib_list-0.10.0.tar.gz", hash = "sha256:6519c50d645513ed287657bfe856d527f277331540691ddeaf77b25459964a14"}, -] - -[package.extras] -dev = ["build", "stdlib-list[doc,lint,test]"] -doc = ["furo", "sphinx"] -lint = ["black", "mypy", "ruff"] -support = ["sphobjinv"] -test = ["coverage[toml]", "pytest", "pytest-cov"] - [[package]] name = "toml" version = "0.10.2" @@ -1685,6 +1547,26 @@ virtualenv = ">=20.26.3" docs = ["furo (>=2024.5.6)", "sphinx (>=7.3.7)", "sphinx-argparse-cli (>=1.16)", "sphinx-autodoc-typehints (>=2.2.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"] testing = ["build[virtualenv] (>=1.2.1)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=9.1)", "distlib (>=0.3.8)", "flaky (>=3.8.1)", "hatch-vcs (>=0.4)", "hatchling (>=1.25)", "psutil (>=6)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-xdist (>=3.6.1)", "re-assert (>=1.1)", "setuptools (>=70.2)", "time-machine (>=2.14.2)", "wheel (>=0.43)"] +[[package]] +name = "tqdm" +version = "4.66.4" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, + {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "typer" version = "0.9.4" @@ -1745,25 +1627,6 @@ h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] -[[package]] -name = "uvicorn" -version = "0.28.1" -description = "The lightning-fast ASGI server." -optional = false -python-versions = ">=3.8" -files = [ - {file = "uvicorn-0.28.1-py3-none-any.whl", hash = "sha256:5162f6d652f545be91b1feeaee8180774af143965ca9dc8a47ff1dc6bafa4ad5"}, - {file = "uvicorn-0.28.1.tar.gz", hash = "sha256:08103e79d546b6cf20f67c7e5e434d2cf500a6e29b28773e407250c54fc4fa3c"}, -] - -[package.dependencies] -click = ">=7.0" -h11 = ">=0.8" -typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} - -[package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] - [[package]] name = "virtualenv" version = "20.26.3" @@ -1828,21 +1691,7 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] -[[package]] -name = "win32-setctime" -version = "1.1.0" -description = "A small Python utility to set file creation time on Windows" -optional = false -python-versions = ">=3.5" -files = [ - {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, - {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, -] - -[package.extras] -dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] - [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "70c2ff77e49aa92b28bad0d96b994e9d22ec3288feadeee6295329961f7efc8a" +content-hash = "8ae884652ca236023f88f4e4074cb831259b562ae634c62d298c39d1c47c5999" diff --git a/arcade/pyproject.toml b/arcade/pyproject.toml index 3a35783c..ca761eff 100644 --- a/arcade/pyproject.toml +++ b/arcade/pyproject.toml @@ -14,21 +14,16 @@ build-backend = "poetry.core.masonry.api" [tool.poetry.dependencies] python = ">=3.10,<4.0" -pydantic = {extras = ["email"], version = "^2.7.0"} +pydantic = "^2.7.0" fastapi = "^0.110.0" -redis = "^5.0.3" -uvicorn = "^0.28.0" -loguru = "^0.7.2" pydantic-settings = "^2.2.1" -msgspec = "^0.18.6" -msgpack = "^1.0.8" typer = "^0.9.0" rich = "^13.7.1" toml = "^0.10.2" tomlkit = "^0.12.4" -stdlib-list = "^0.10.0" requests = "^2.26.0" +openai = "^1.36.0" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" pytest-cov = "^4.0.0" diff --git a/arcade/tests/sdk/test_tool_decorator.py b/arcade/tests/sdk/test_tool_decorator.py index 79cbb2ac..e0e6f676 100644 --- a/arcade/tests/sdk/test_tool_decorator.py +++ b/arcade/tests/sdk/test_tool_decorator.py @@ -2,8 +2,8 @@ import asyncio import pytest +from arcade.core.tool import OAuth2AuthorizationRequirement from arcade.sdk.tool import tool -from arcade.tool.schemas import OAuth2AuthorizationRequirement def test_sync_function(): diff --git a/arcade/tests/tool/test_create_tool_definition.py b/arcade/tests/tool/test_create_tool_definition.py index 2734f902..c15111fc 100644 --- a/arcade/tests/tool/test_create_tool_definition.py +++ b/arcade/tests/tool/test_create_tool_definition.py @@ -2,10 +2,8 @@ from typing import Annotated, Literal, Optional import pytest -from arcade.sdk.annotations import Inferrable -from arcade.sdk.tool import tool -from arcade.tool.catalog import ToolCatalog -from arcade.tool.schemas import ( +from arcade.core.catalog import ToolCatalog +from arcade.core.tool import ( InputParameter, OAuth2AuthorizationRequirement, ToolInputs, @@ -13,6 +11,8 @@ from arcade.tool.schemas import ( ToolRequirements, ValueSchema, ) +from arcade.sdk.annotations import Inferrable +from arcade.sdk.tool import tool ### Tests on @tool decorator @@ -27,6 +27,15 @@ def func_with_docstring_description(): pass +@tool +def func_with_multiline_docstring_description(): + """ + Docstring description + on multiple lines + """ + pass + + @tool(name="MyCustomTool", desc="A function with a very cool description") def func_with_name_and_description(): pass @@ -163,6 +172,11 @@ def func_with_complex_return() -> list[dict[str, str]]: {"description": "Docstring description"}, id="func_with_docstring_description", ), + pytest.param( + func_with_multiline_docstring_description, + {"description": "Docstring description\non multiple lines"}, + id="func_with_multiline_docstring_description", + ), pytest.param( func_with_name_and_description, {"name": "MyCustomTool", "description": "A function with a very cool description"}, diff --git a/arcade/tests/tool/test_create_tool_definition_errors.py b/arcade/tests/tool/test_create_tool_definition_errors.py index f4d4386a..4102ef22 100644 --- a/arcade/tests/tool/test_create_tool_definition_errors.py +++ b/arcade/tests/tool/test_create_tool_definition_errors.py @@ -1,8 +1,8 @@ import pytest +from arcade.core.catalog import ToolCatalog +from arcade.core.errors import ToolDefinitionError from arcade.sdk.tool import tool -from arcade.tool.catalog import ToolCatalog -from arcade.tool.errors import ToolDefinitionError @tool diff --git a/arcade/tests/tool/test_create_tool_definition_pydantic.py b/arcade/tests/tool/test_create_tool_definition_pydantic.py index 7fb1c5f4..7f0f5f24 100644 --- a/arcade/tests/tool/test_create_tool_definition_pydantic.py +++ b/arcade/tests/tool/test_create_tool_definition_pydantic.py @@ -3,14 +3,14 @@ from typing import Annotated, Optional, Union import pytest from pydantic import BaseModel, Field -from arcade.sdk.tool import tool -from arcade.tool.catalog import ToolCatalog -from arcade.tool.schemas import ( +from arcade.core.catalog import ToolCatalog +from arcade.core.tool import ( InputParameter, ToolInputs, ToolOutput, ValueSchema, ) +from arcade.sdk.tool import tool class ProductOutput(BaseModel): diff --git a/arcade/tests/tool/test_create_tool_definition_pydantic_errors.py b/arcade/tests/tool/test_create_tool_definition_pydantic_errors.py index 405b3d66..a875d407 100644 --- a/arcade/tests/tool/test_create_tool_definition_pydantic_errors.py +++ b/arcade/tests/tool/test_create_tool_definition_pydantic_errors.py @@ -3,9 +3,9 @@ from typing import Annotated import pytest from pydantic import Field +from arcade.core.catalog import ToolCatalog +from arcade.core.errors import ToolDefinitionError from arcade.sdk.tool import tool -from arcade.tool.catalog import ToolCatalog -from arcade.tool.errors import ToolDefinitionError @tool diff --git a/arcade/tests/utils/test_utils_casing.py b/arcade/tests/utils/test_utils_casing.py index e4075017..481da9ea 100644 --- a/arcade/tests/utils/test_utils_casing.py +++ b/arcade/tests/utils/test_utils_casing.py @@ -1,6 +1,6 @@ import pytest -from arcade.utils import pascal_to_snake_case, snake_to_pascal_case +from arcade.core.utils import pascal_to_snake_case, snake_to_pascal_case @pytest.mark.parametrize( diff --git a/cspell.config.yaml b/cspell.config.yaml index 1c973ce8..cc24d7ce 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -5,6 +5,8 @@ dictionaryDefinitions: [] dictionaries: [] words: - conlist + - fastapi + - openai - pydantic - pyproject - toolpack diff --git a/examples/arcade_gmail/arcade_gmail/tools/__init__.py b/examples/arcade_gmail/arcade_gmail/tools/__init__.py new file mode 100644 index 00000000..dc57b3dd --- /dev/null +++ b/examples/arcade_gmail/arcade_gmail/tools/__init__.py @@ -0,0 +1 @@ +__all__ = ["gmail"] diff --git a/arcade/arcade/apm/__init__.py b/examples/example_nate/arcade_example_nate/__init__.py similarity index 100% rename from arcade/arcade/apm/__init__.py rename to examples/example_nate/arcade_example_nate/__init__.py diff --git a/examples/example_nate/arcade_example_nate/main.py b/examples/example_nate/arcade_example_nate/main.py new file mode 100644 index 00000000..cc1d844f --- /dev/null +++ b/examples/example_nate/arcade_example_nate/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/arcade/arcade/tool/__init__.py b/examples/example_nate/arcade_example_nate/tools/__init__.py similarity index 100% rename from arcade/arcade/tool/__init__.py rename to examples/example_nate/arcade_example_nate/tools/__init__.py diff --git a/examples/example_nate/arcade_example_nate/tools/arithmetic.py b/examples/example_nate/arcade_example_nate/tools/arithmetic.py new file mode 100644 index 00000000..31c472bb --- /dev/null +++ b/examples/example_nate/arcade_example_nate/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/example_nate/poetry.lock b/examples/example_nate/poetry.lock new file mode 100644 index 00000000..60835465 --- /dev/null +++ b/examples/example_nate/poetry.lock @@ -0,0 +1,326 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.4.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, + {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastapi" +version = "0.110.3" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"}, + {file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.37.2,<0.38.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "starlette" +version = "0.37.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.8" +files = [ + {file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"}, + {file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"}, +] + +[package.dependencies] +anyio = ">=3.4.0,<5" + +[package.extras] +full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "8032fc5c60f6baa14a741935f75d06586fe1bf67804b258469600e0051a1997a" diff --git a/examples/example_nate/pyproject.toml b/examples/example_nate/pyproject.toml new file mode 100644 index 00000000..77532d43 --- /dev/null +++ b/examples/example_nate/pyproject.toml @@ -0,0 +1,27 @@ +[tool.poetry] +name = "arcade_example_nate" +version = "0.1.0" +description = "Nate's testing package for Arcade" +authors = ["Nate "] + + +[tool.poetry.metadata] +license = "MIT" +repository = "https://github.com/nate/arcade_example_nate" +homepage = "https://arcade-ai.com" +tools = ["arcade", "example", "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" + +[tool.arcade] +modules = ["arcade_example_nate.tools.arithmetic"] diff --git a/examples/generic/pack.lock.toml b/examples/generic/pack.lock.toml deleted file mode 100644 index cc997ab6..00000000 --- a/examples/generic/pack.lock.toml +++ /dev/null @@ -1,18 +0,0 @@ -[pack] -name = "Routines for Jarvis" -description = "Jarvis Chatbot routines" -version = "0.1.0" -author = "Sam Partee" -email = "sam@partee.io" - -[depends] - -[tools] -TextSearch = "BM25.text_search@0.0.1" -ReadProducts = "products.read_products@latest" -ReadSqlite = "read_sqlite.read_sqlite@0.0.1" -SendEmail = "gmail.send_email@0.0.1" -ReadEmail = "gmail.read_email@0.0.1" -OauthReadEmail = "gmail.oauth_read_email@0.0.1" -ListDriveFiles = "gmail.list_drive_files@0.0.1" -SearchEmployee = "people.search_employee@0.1.0" diff --git a/examples/generic/pack.toml b/examples/generic/pack.toml deleted file mode 100644 index 0779ac4a..00000000 --- a/examples/generic/pack.toml +++ /dev/null @@ -1,14 +0,0 @@ - - -[pack] -name = "Routines for Jarvis" -description = "Jarvis Chatbot routines" -version = "0.1.0" -author = "Sam Partee" -email = "sam@partee.io" - -[modules] -gmail = "0.0.1" -read_sqlite = "0.0.1" -BM25 = "0.0.1" -people = "0.1.0" diff --git a/examples/generic/tools/BM25.py b/examples/generic/tools/BM25.py deleted file mode 100644 index 5a38958a..00000000 --- a/examples/generic/tools/BM25.py +++ /dev/null @@ -1,158 +0,0 @@ -import math -import numpy as np - -from typing import Annotated -from multiprocessing import Pool, cpu_count - -from arcade.sdk.tool import tool - - -class BM25: - def __init__(self, corpus, tokenizer=None): - self.corpus_size = 0 - self.avgdl = 0 - self.doc_freqs = [] - self.idf = {} - self.doc_len = [] - self.tokenizer = tokenizer - - if tokenizer: - corpus = self._tokenize_corpus(corpus) - else: - corpus = self._tokenize(corpus) - - nd = self._initialize(corpus) - self._calc_idf(nd) - - @staticmethod - def _tokenize(texts: list[str]) -> list[list[str]]: - return [text.split() for text in texts] - - def _initialize(self, corpus): - nd = {} # word -> number of documents with word - num_doc = 0 - for document in corpus: - self.doc_len.append(len(document)) - num_doc += len(document) - - frequencies = {} - for word in document: - if word not in frequencies: - frequencies[word] = 0 - frequencies[word] += 1 - self.doc_freqs.append(frequencies) - - for word, freq in frequencies.items(): - try: - nd[word] += 1 - except KeyError: - nd[word] = 1 - - self.corpus_size += 1 - - self.avgdl = num_doc / self.corpus_size - return nd - - def _tokenize_corpus(self, corpus): - pool = Pool(cpu_count()) - tokenized_corpus = pool.map(self.tokenizer, corpus) - return tokenized_corpus - - def _calc_idf(self, nd): - raise NotImplementedError() - - def get_scores(self, query): - raise NotImplementedError() - - def get_batch_scores(self, query, doc_ids): - raise NotImplementedError() - - def get_top_n(self, query, documents, n=5): - assert self.corpus_size == len( - documents - ), "The documents given don't match the index corpus!" - query = self._tokenize([query])[0] # tokenize the query - scores = self.get_scores(query) - top_n = np.argsort(scores)[::-1][:n] - return [documents[i] for i in top_n] - - -class Okapi(BM25): - def __init__(self, corpus, tokenizer=None, k1=1.5, b=0.75, epsilon=0.25): - self.k1 = k1 - self.b = b - self.epsilon = epsilon - super().__init__(corpus, tokenizer) - - def _calc_idf(self, nd): - """ - Calculates frequencies of terms in documents and in corpus. - This algorithm sets a floor on the idf values to eps * average_idf - """ - # collect idf sum to calculate an average idf for epsilon value - idf_sum = 0 - # collect words with negative idf to set them a special epsilon value. - # idf can be negative if word is contained in more than half of documents - negative_idfs = [] - for word, freq in nd.items(): - idf = math.log(self.corpus_size - freq + 0.5) - math.log(freq + 0.5) - self.idf[word] = idf - idf_sum += idf - if idf < 0: - negative_idfs.append(word) - self.average_idf = idf_sum / len(self.idf) - - eps = self.epsilon * self.average_idf - for word in negative_idfs: - self.idf[word] = eps - - def get_scores(self, query): - """ - The ATIRE BM25 variant uses an idf function which uses a log(idf) score. To prevent negative idf scores, - this algorithm also adds a floor to the idf value of epsilon. - See [Trotman, A., X. Jia, M. Crane, Towards an Efficient and Effective Search Engine] for more info - :param query: - :return: - """ - score = np.zeros(self.corpus_size) - doc_len = np.array(self.doc_len) - for q in query: - q_freq = np.array([(doc.get(q) or 0) for doc in self.doc_freqs]) - score += (self.idf.get(q) or 0) * ( - q_freq - * (self.k1 + 1) - / (q_freq + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)) - ) - return score - - def get_batch_scores(self, query, doc_ids): - """ - Calculate bm25 scores between query and subset of all docs - """ - assert all(di < len(self.doc_freqs) for di in doc_ids) - score = np.zeros(len(doc_ids)) - doc_len = np.array(self.doc_len)[doc_ids] - for q in query: - q_freq = np.array([(self.doc_freqs[di].get(q) or 0) for di in doc_ids]) - score += (self.idf.get(q) or 0) * ( - q_freq - * (self.k1 + 1) - / (q_freq + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)) - ) - return score.tolist() - - -@tool -async def text_search( - query: Annotated[str, "The search query"], - texts: Annotated[list[str], "The texts through which to search"], - num_results: Annotated[int, "Number of texts to return"] = 5, -) -> Annotated[list[str], "Most similar texts"]: - """Use the BM25 algorithm to search through texts - - This should only be used for smaller datasets where the number - of texts is less than 100. - """ - - bm25 = Okapi(texts) - return bm25.get_top_n(query, texts, num_results) diff --git a/examples/generic/tools/products.py b/examples/generic/tools/products.py deleted file mode 100644 index 7ba2aef7..00000000 --- a/examples/generic/tools/products.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import Union, Annotated, Literal -from arcade.sdk.tool import tool, get_secret -import pandas as pd -from pydantic import BaseModel, Field - - -class ProductFilter(BaseModel): - column: str = Field(..., description="The column to filter on") - - -class FilterRating(ProductFilter): - greater_than: int = Field( - ..., description="The rating to filter greater than", gt=0, lt=5 - ) - - -class FilterPriceGreaterThan(ProductFilter): - price: int = Field(..., description="The price to filter greater than", gt=0) - - -class FilterPriceLessThan(ProductFilter): - price: int = Field(..., description="The price to filter less than", gt=0) - - -class ProductSearch(BaseModel): - """The search action to perform""" - - column: str = Field("Product Name", description="The column to search in") - """the column to search in""" - - query: str = Field(..., description="The query to search for") - """the query to search for""" - - filter_operation: Union[ - FilterRating, FilterPriceGreaterThan, FilterPriceLessThan - ] = None - """The filter operation to perform""" - - -class ProductOutput(BaseModel): - product_name: str = Field(..., description="The name of the product") - price: int = Field(..., description="The price of the product") - stock_quantity: int = Field(..., description="The stock quantity of the product") - - -@tool -def read_products( - action: Annotated[ProductSearch, "The search action to perform"], - cols: Annotated[ - Literal["Product Name", "Price", "Stock Quantity"], "The columns to return" - ] = ["Product Name", "Price", "Stock Quantity"], -) -> Annotated[list[ProductOutput], "The list of products matching the search"]: - """Used to search through products by name and filter by rating or price.""" - - file_path = get_secret( - "PRODUCTS_PATH", - "/Users/spartee/Dropbox/Arcade/platform/toolserver/examples/data/Sample_Products_Info.csv", - ) - try: - df = pd.read_csv(file_path) - df = df[cols] - - if action.filter_operation: - if isinstance(action.filter_operation, FilterRating): - df = df[ - df[action.filter_operation.column] - > action.filter_operation.greater_than - ] - elif isinstance(action.filter_operation, FilterPriceGreaterThan): - df = df[ - df[action.filter_operation.column] > action.filter_operation.price - ] - elif isinstance(action.filter_operation, FilterPriceLessThan): - df = df[ - df[action.filter_operation.column] < action.filter_operation.price - ] - - except Exception as e: - # TODO what to do here? - print(e) - return df.to_json() diff --git a/examples/generic/tools/read_sqlite.py b/examples/generic/tools/read_sqlite.py deleted file mode 100644 index 7058c864..00000000 --- a/examples/generic/tools/read_sqlite.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Annotated - -from arcade.sdk.tool import tool -import pandas as pd - -from sqlite3 import connect - - -@tool -async def read_sqlite( - file_path: Annotated[str, "Path to the SQLite database file"], - table_name: Annotated[str, "Name of the table to read from"], - cols: Annotated[str, "Columns to read from the table"] = "*", -) -> str: - """Read data from a SQLite database table and save it as a DataFrame. - - Columns to choose from are: - - *: All columns - - column_name: Single column - - column_name1, column_name2, ...: Multiple columns - """ - # Connect to the SQLite database - conn = connect(file_path) - cursor = conn.cursor() - - # Read the data from the table - query = f"SELECT * FROM {table_name}" - cursor.execute(query) - rows = cursor.fetchall() - - # Get the column names - cursor.execute(f"PRAGMA table_info({table_name})") - columns = [col[1] for col in cursor.fetchall()] - - # Create a DataFrame from the data - df = pd.DataFrame(rows, columns=columns) - - return df.json() diff --git a/examples/generic/tools/__init__.py b/examples/gmail/arcade_gmail/__init__.py similarity index 100% rename from examples/generic/tools/__init__.py rename to examples/gmail/arcade_gmail/__init__.py diff --git a/examples/gmail/arcade_gmail/tools/__init__.py b/examples/gmail/arcade_gmail/tools/__init__.py new file mode 100644 index 00000000..dc57b3dd --- /dev/null +++ b/examples/gmail/arcade_gmail/tools/__init__.py @@ -0,0 +1 @@ +__all__ = ["gmail"] diff --git a/examples/generic/tools/gmail.py b/examples/gmail/arcade_gmail/tools/gmail.py similarity index 67% rename from examples/generic/tools/gmail.py rename to examples/gmail/arcade_gmail/tools/gmail.py index 712de6a6..da0e583f 100644 --- a/examples/generic/tools/gmail.py +++ b/examples/gmail/arcade_gmail/tools/gmail.py @@ -1,11 +1,6 @@ import os import re -import email -import smtplib -import imaplib -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText from base64 import urlsafe_b64decode from bs4 import BeautifulSoup from google.auth.transport.requests import Request @@ -14,88 +9,7 @@ from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.exceptions import RefreshError from googleapiclient.discovery import build from typing import Dict, List, Annotated -from arcade.sdk.tool import tool, get_secret - - -@tool -async def send_email( - recipient_email: Annotated[str, "Email address of the recipient"], - subject: Annotated[str, "Subject of the email"], - body: Annotated[str, "Body of the email"], -): - """Send an email via gmail SMTP server""" - - sender_email = get_secret("gmail_email") - sender_password = get_secret("gmail_password") - server = get_secret("gmail_stmp_server", "smtp.gmail.com") - port = get_secret("gmail_stmp_port", 587) - - message = MIMEMultipart() - message["From"] = sender_email - message["To"] = recipient_email - message["Subject"] = subject - message.attach(MIMEText(body, "plain")) - - server = smtplib.SMTP(server, port) - server.starttls() - server.login(sender_email, sender_password) - print(f"Logged in to SMTP server at {':'.join((server, port))}", "DEBUG") - - server.send_message(message) - server.quit() - - print(f"Email sent from {sender_email} to {recipient_email}", "INFO") - - -@tool -async def read_email( - n_emails: Annotated[int, "Number of emails to read"] = 5, -) -> Annotated[str, "emails"]: - """Read emails from a Gmail account and extract plain text content, removing any HTML.""" - - email_address = get_secret("gmail_email") - password = get_secret("gmail_password") - server = get_secret("gmail_stmp_server", "smtp.gmail.com") - - # Connect to the Gmail IMAP server - mail = imaplib.IMAP4_SSL(server) - mail.login(email_address, password) - mail.select("inbox") # connect to inbox. - - result, data = mail.search(None, "ALL") - email_ids = data[0].split() - email_ids.reverse() # Reverse to get the most recent emails first - - emails = [] - - for email_id in email_ids[:n_emails]: - try: - result, data = mail.fetch(email_id, "(RFC822)") - raw_email = data[0][1] - msg = email.message_from_bytes(raw_email) - - email_details = {"from": msg["From"], "to": msg["To"], "date": msg["Date"]} - - if msg.is_multipart(): - for part in msg.walk(): - if part.get_content_type() == "text/plain": - body = part.get_payload(decode=True).decode("utf-8") - email_details["body"] = clean_email_body(body) - else: - body = msg.get_payload(decode=True).decode("utf-8") - email_details["body"] = clean_email_body(body) - except Exception as e: - print(f"Error reading email {email_id}: {e}", "ERROR") - continue - - emails.append(email_details) - - mail.close() - mail.logout() - data = "\n".join( - [f"{email['from']} - {email['date']}\n{email['body']}\n" for email in emails] - ) - return data +from arcade.sdk.tool import tool SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"] diff --git a/examples/gmail/poetry.lock b/examples/gmail/poetry.lock new file mode 100644 index 00000000..f1212b35 --- /dev/null +++ b/examples/gmail/poetry.lock @@ -0,0 +1,547 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "arcade-ai" +version = "0.1.0" +description = "" +optional = false +python-versions = "*" +files = [] +develop = true + +[package.source] +type = "directory" +url = "../../arcade" + +[[package]] +name = "cachetools" +version = "5.4.0" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.4.0-py3-none-any.whl", hash = "sha256:3ae3b49a3d5e28a77a0be2b37dbcb89005058959cb2323858c2657c4a8cab474"}, + {file = "cachetools-5.4.0.tar.gz", hash = "sha256:b8adc2e7c07f105ced7bc56dbb6dfbe7c4a00acce20e2227b3f355be89bc6827"}, +] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "google-api-core" +version = "2.19.1" +description = "Google API client core library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-api-core-2.19.1.tar.gz", hash = "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd"}, + {file = "google_api_core-2.19.1-py3-none-any.whl", hash = "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125"}, +] + +[package.dependencies] +google-auth = ">=2.14.1,<3.0.dev0" +googleapis-common-protos = ">=1.56.2,<2.0.dev0" +proto-plus = ">=1.22.3,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" +requests = ">=2.18.0,<3.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] +grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] + +[[package]] +name = "google-api-python-client" +version = "2.137.0" +description = "Google API Client Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_api_python_client-2.137.0-py2.py3-none-any.whl", hash = "sha256:a8b5c5724885e5be9f5368739aa0ccf416627da4ebd914b410a090c18f84d692"}, + {file = "google_api_python_client-2.137.0.tar.gz", hash = "sha256:e739cb74aac8258b1886cb853b0722d47c81fe07ad649d7f2206f06530513c04"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0.dev0" +google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0.dev0" +google-auth-httplib2 = ">=0.2.0,<1.0.0" +httplib2 = ">=0.19.0,<1.dev0" +uritemplate = ">=3.0.1,<5" + +[[package]] +name = "google-auth" +version = "2.32.0" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google_auth-2.32.0-py2.py3-none-any.whl", hash = "sha256:53326ea2ebec768070a94bee4e1b9194c9646ea0c2bd72422785bd0f9abfad7b"}, + {file = "google_auth-2.32.0.tar.gz", hash = "sha256:49315be72c55a6a37d62819e3573f6b416aca00721f7e3e31a008d928bf64022"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = ">=3.1.4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0.dev0)"] + +[[package]] +name = "google-auth-httplib2" +version = "0.2.0" +description = "Google Authentication Library: httplib2 transport" +optional = false +python-versions = "*" +files = [ + {file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"}, + {file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"}, +] + +[package.dependencies] +google-auth = "*" +httplib2 = ">=0.19.0" + +[[package]] +name = "google-auth-oauthlib" +version = "1.2.1" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "google_auth_oauthlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f"}, + {file = "google_auth_oauthlib-1.2.1.tar.gz", hash = "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263"}, +] + +[package.dependencies] +google-auth = ">=2.15.0" +requests-oauthlib = ">=0.7.0" + +[package.extras] +tool = ["click (>=6.0.0)"] + +[[package]] +name = "googleapis-common-protos" +version = "1.63.2" +description = "Common protobufs used in Google APIs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "googleapis-common-protos-1.63.2.tar.gz", hash = "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87"}, + {file = "googleapis_common_protos-1.63.2-py2.py3-none-any.whl", hash = "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945"}, +] + +[package.dependencies] +protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" + +[package.extras] +grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] + +[[package]] +name = "httplib2" +version = "0.22.0" +description = "A comprehensive HTTP client library." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"}, + {file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"}, +] + +[package.dependencies] +pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""} + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "proto-plus" +version = "1.24.0" +description = "Beautiful, Pythonic protocol buffers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, + {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, +] + +[package.dependencies] +protobuf = ">=3.19.0,<6.0.0dev" + +[package.extras] +testing = ["google-api-core (>=1.31.5)"] + +[[package]] +name = "protobuf" +version = "5.27.2" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-5.27.2-cp310-abi3-win32.whl", hash = "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38"}, + {file = "protobuf-5.27.2-cp310-abi3-win_amd64.whl", hash = "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505"}, + {file = "protobuf-5.27.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5"}, + {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b"}, + {file = "protobuf-5.27.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e"}, + {file = "protobuf-5.27.2-cp38-cp38-win32.whl", hash = "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863"}, + {file = "protobuf-5.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6"}, + {file = "protobuf-5.27.2-cp39-cp39-win32.whl", hash = "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca"}, + {file = "protobuf-5.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce"}, + {file = "protobuf-5.27.2-py3-none-any.whl", hash = "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470"}, + {file = "protobuf-5.27.2.tar.gz", hash = "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714"}, +] + +[[package]] +name = "pyasn1" +version = "0.6.0" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"}, + {file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.0" +description = "A collection of ASN.1-based protocols modules" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyasn1_modules-0.4.0-py3-none-any.whl", hash = "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b"}, + {file = "pyasn1_modules-0.4.0.tar.gz", hash = "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.7.0" + +[[package]] +name = "pyparsing" +version = "3.1.2" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=3.4" +files = [ + {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, + {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "uritemplate" +version = "4.1.1" +description = "Implementation of RFC 6570 URI Templates" +optional = false +python-versions = ">=3.6" +files = [ + {file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"}, + {file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "67276ca5a354bfd657cea93ecea1ac54f8b1bc9e3d8ed2d2519e5836e93a6da3" diff --git a/examples/gmail/pyproject.toml b/examples/gmail/pyproject.toml new file mode 100644 index 00000000..20cedd97 --- /dev/null +++ b/examples/gmail/pyproject.toml @@ -0,0 +1,22 @@ +[tool.poetry] +name = "arcade_gmail" +version = "0.1.0" +description = "LLM tools for interating with gmail" +authors = ["Sam Partee "] + +[tool.poetry.dependencies] +python = "^3.10" +arcade-ai = "^0.1.0" +google-api-core = "2.19.1" +google-api-python-client = "2.137.0" +google-auth = "2.32.0" +google-auth-httplib2 = "0.2.0" +google-auth-oauthlib = "1.2.1" +googleapis-common-protos = "1.63.2" + +[tool.poetry.dev-dependencies] +pytest = "^7.4.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/examples/websearch/arcade_websearch/__init__.py b/examples/websearch/arcade_websearch/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/websearch/arcade_websearch/tools/__init__.py b/examples/websearch/arcade_websearch/tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/websearch/arcade_websearch/tools/google.py b/examples/websearch/arcade_websearch/tools/google.py new file mode 100644 index 00000000..82c195ca --- /dev/null +++ b/examples/websearch/arcade_websearch/tools/google.py @@ -0,0 +1,25 @@ +import json +import serpapi +from typing import Annotated +from arcade.sdk.tool import tool, get_secret + + +@tool +async def search_google( + query: Annotated[str, "Search query"], + n_results: Annotated[int, "Number of results to retrieve"] = 5, +) -> str: + """Search Google using SerpAPI and return organic search results.""" + + api_key = get_secret("SERP_API_KEY") + if not api_key: + raise ValueError("SERP_API_KEY is not set") + + client = serpapi.Client(api_key=api_key) + params = {"engine": "google", "q": query} + + search = client.search(params) + results = search.as_dict() + organic_results = results.get("organic_results", []) + + return json.dumps(organic_results[:n_results]) diff --git a/examples/websearch/poetry.lock b/examples/websearch/poetry.lock new file mode 100644 index 00000000..5c9daeea --- /dev/null +++ b/examples/websearch/poetry.lock @@ -0,0 +1,291 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "arcade-ai" +version = "0.1.0" +description = "" +optional = false +python-versions = "*" +files = [] +develop = true + +[package.source] +type = "directory" +url = "../../arcade" + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "serpapi" +version = "0.1.5" +description = "The official Python client for SerpApi.com." +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "serpapi-0.1.5-py2.py3-none-any.whl", hash = "sha256:6467b6adec1231059f754ccaa952b229efeaa8b9cae6e71f879703ec9e5bb3d1"}, + {file = "serpapi-0.1.5.tar.gz", hash = "sha256:b9707ed54750fdd2f62dc3a17c6a3fb7fa421dc37902fd65b2263c0ac765a1a5"}, +] + +[package.dependencies] +requests = "*" + +[package.extras] +color = ["pygments"] +test = ["pytest"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "177b838d81c7f4b81756f9ff98caa1849c4e25f5a725c38be8de507aaa93ba24" diff --git a/examples/websearch/pyproject.toml b/examples/websearch/pyproject.toml new file mode 100644 index 00000000..0723a1fd --- /dev/null +++ b/examples/websearch/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "arcade_websearch" +version = "0.1.0" +description = "Tools for searching the web" +authors = ["Sam Partee "] + +[tool.poetry.dependencies] +python = "^3.10" +arcade-ai = "^0.1.0" +serpapi = "^0.1.5" + +[tool.poetry.dev-dependencies] +pytest = "^7.4.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/schemas/preview/tool_request.schema.jsonc b/schemas/preview/invoke_tool_request.schema.jsonc similarity index 99% rename from schemas/preview/tool_request.schema.jsonc rename to schemas/preview/invoke_tool_request.schema.jsonc index 0599e8bc..0ad76197 100644 --- a/schemas/preview/tool_request.schema.jsonc +++ b/schemas/preview/invoke_tool_request.schema.jsonc @@ -34,7 +34,7 @@ "required": ["name", "version"], "additionalProperties": false }, - "input": { + "inputs": { "type": "object", "additionalProperties": true }, diff --git a/schemas/preview/tool_response.schema.jsonc b/schemas/preview/invoke_tool_response.schema.jsonc similarity index 97% rename from schemas/preview/tool_response.schema.jsonc rename to schemas/preview/invoke_tool_response.schema.jsonc index 6dc44dd4..1e752dcf 100644 --- a/schemas/preview/tool_response.schema.jsonc +++ b/schemas/preview/invoke_tool_response.schema.jsonc @@ -11,6 +11,10 @@ "type": "string", "description": "ID of this specific tool call" }, + "duration": { + "type": "number", + "description": "The duration of the tool call, in milliseconds" + }, "finished_at": { "type": "string", "format": "date-time" diff --git a/schemas/preview/tool_definition.schema.jsonc b/schemas/preview/tool_definition.schema.jsonc index 38797326..57c4b197 100644 --- a/schemas/preview/tool_definition.schema.jsonc +++ b/schemas/preview/tool_definition.schema.jsonc @@ -7,33 +7,26 @@ "enum": ["string", "integer", "float", "boolean", "json"] }, "value_schema": { - // Represents a value schema (e.g. function input parameter) + // Represents the schema of a value (e.g. function input parameter value) "type": "object", "properties": { - "type": { + "val_type": { "$ref": "#/$defs/primitives" - } - }, - "required": ["type"], - "additionalProperties": false, - "if": { - "properties": { - "type": { - "const": "string" - } - } - }, - "then": { - // String values can optionally be constrained to a known list - "properties": { - "enum": { - "type": "array", - "items": { - "type": "string" + }, + "enum": { + "oneOf": [ + { "type": "null" }, // Can be unset + { + "type": "array", + "items": { + "type": "string" + } } - } + ] } - } + }, + "required": ["val_type"], + "additionalProperties": false } }, "type": "object", @@ -53,10 +46,9 @@ }, "version": { "type": "string", - "pattern": "^[a-f0-9]{7,40}$", // SHA version pattern (7-40 hexadecimal characters) "description": "An identifier for this version of the tool" }, - "input": { + "inputs": { "type": "object", "properties": { "parameters": { @@ -77,7 +69,7 @@ "description": "A descriptive, human-readable explanation of the parameter.", "type": "string" }, - "schema": { + "value_schema": { "$ref": "#/$defs/value_schema" }, "inferrable": { @@ -86,7 +78,7 @@ "default": true } }, - "required": ["name", "required", "schema"], + "required": ["name", "required", "value_schema"], "additionalProperties": false } } @@ -105,18 +97,12 @@ "enum": ["value", "error", "null", "artifact", "requires_authorization"] } }, - "value": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "schema": { - "$ref": "#/$defs/value_schema" - } - }, - "required": ["schema"], - "additionalProperties": false + "description": { + "description": "A descriptive, human-readable explanation of the function's output.", + "type": "string" + }, + "value_schema": { + "$ref": "#/$defs/value_schema" } }, "required": ["available_modes"], @@ -127,6 +113,7 @@ "properties": { "authorization": { "oneOf": [ + { "type": "null" }, // Can be unset { "type": "string", "enum": ["none", "token"] @@ -157,6 +144,6 @@ } } }, - "required": ["name", "version", "input", "output"], + "required": ["name", "version", "inputs", "output"], "additionalProperties": false }