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 <nate@arcade-ai.com>
This commit is contained in:
parent
4b469eaa66
commit
8964111023
71 changed files with 2697 additions and 1709 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,7 @@
|
|||
.DS_Store
|
||||
|
||||
*.lock
|
||||
|
||||
# example data
|
||||
examples/data
|
||||
scratch
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
from arcade.core.version import VERSION
|
||||
|
||||
__version__ = VERSION
|
||||
165
arcade/arcade/actor/base.py
Normal file
165
arcade/arcade/actor/base.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
54
arcade/arcade/actor/fastapi/actor.py
Normal file
54
arcade/arcade/actor/fastapi/actor.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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}")
|
||||
|
|
@ -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"])
|
||||
|
|
@ -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))
|
||||
45
arcade/arcade/actor/schema.py
Normal file
45
arcade/arcade/actor/schema.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
144
arcade/arcade/cli/new.py
Normal file
144
arcade/arcade/cli/new.py
Normal file
|
|
@ -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]")
|
||||
|
|
@ -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:
|
||||
"""
|
||||
184
arcade/arcade/core/client.py
Normal file
184
arcade/arcade/core/client.py
Normal file
|
|
@ -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
|
||||
64
arcade/arcade/core/config.py
Normal file
64
arcade/arcade/core/config.py
Normal file
|
|
@ -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()
|
||||
23
arcade/arcade/core/env.py
Normal file
23
arcade/arcade/core/env.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
42
arcade/arcade/core/parse.py
Normal file
42
arcade/arcade/core/parse.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
*,
|
||||
126
arcade/arcade/core/toolkit.py
Normal file
126
arcade/arcade/core/toolkit.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
1
arcade/arcade/core/version.py
Normal file
1
arcade/arcade/core/version.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
VERSION = "0.1.0"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
409
arcade/poetry.lock
generated
409
arcade/poetry.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ dictionaryDefinitions: []
|
|||
dictionaries: []
|
||||
words:
|
||||
- conlist
|
||||
- fastapi
|
||||
- openai
|
||||
- pydantic
|
||||
- pyproject
|
||||
- toolpack
|
||||
|
|
|
|||
1
examples/arcade_gmail/arcade_gmail/tools/__init__.py
Normal file
1
examples/arcade_gmail/arcade_gmail/tools/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
__all__ = ["gmail"]
|
||||
43
examples/example_nate/arcade_example_nate/main.py
Normal file
43
examples/example_nate/arcade_example_nate/main.py
Normal file
|
|
@ -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))
|
||||
|
|
@ -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)
|
||||
326
examples/example_nate/poetry.lock
generated
Normal file
326
examples/example_nate/poetry.lock
generated
Normal file
|
|
@ -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"
|
||||
27
examples/example_nate/pyproject.toml
Normal file
27
examples/example_nate/pyproject.toml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_example_nate"
|
||||
version = "0.1.0"
|
||||
description = "Nate's testing package for Arcade"
|
||||
authors = ["Nate <nate@arcade-ai.com>"]
|
||||
|
||||
|
||||
[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"]
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
1
examples/gmail/arcade_gmail/tools/__init__.py
Normal file
1
examples/gmail/arcade_gmail/tools/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
__all__ = ["gmail"]
|
||||
|
|
@ -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"]
|
||||
547
examples/gmail/poetry.lock
generated
Normal file
547
examples/gmail/poetry.lock
generated
Normal file
|
|
@ -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"
|
||||
22
examples/gmail/pyproject.toml
Normal file
22
examples/gmail/pyproject.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_gmail"
|
||||
version = "0.1.0"
|
||||
description = "LLM tools for interating with gmail"
|
||||
authors = ["Sam Partee <sam@arcade-ai.com>"]
|
||||
|
||||
[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"
|
||||
0
examples/websearch/arcade_websearch/__init__.py
Normal file
0
examples/websearch/arcade_websearch/__init__.py
Normal file
0
examples/websearch/arcade_websearch/tools/__init__.py
Normal file
0
examples/websearch/arcade_websearch/tools/__init__.py
Normal file
25
examples/websearch/arcade_websearch/tools/google.py
Normal file
25
examples/websearch/arcade_websearch/tools/google.py
Normal file
|
|
@ -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])
|
||||
291
examples/websearch/poetry.lock
generated
Normal file
291
examples/websearch/poetry.lock
generated
Normal file
|
|
@ -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"
|
||||
17
examples/websearch/pyproject.toml
Normal file
17
examples/websearch/pyproject.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[tool.poetry]
|
||||
name = "arcade_websearch"
|
||||
version = "0.1.0"
|
||||
description = "Tools for searching the web"
|
||||
authors = ["Sam Partee <sam@arcade-ai.com>"]
|
||||
|
||||
[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"
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
"required": ["name", "version"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"input": {
|
||||
"inputs": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue