arcade-mcp/arcade/arcade/tool/openai.py
Sam Partee 28fe56cfc1
MyPy Compliant (#5)
MyPy compliance for the whole codebase

- systematic way of executing tools (`executor.py`)
- support for using pydantic models in tool inputs and outputs
- mypy compliance (most of the changes)
- removal of unused code (from previous iterations)

Co-authored-by: Nate Barbettini <nate@arcade-ai.com>
2024-07-16 17:01:38 -07:00

107 lines
3.4 KiB
Python

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)