Spaces:
Runtime error
Runtime error
File size: 5,541 Bytes
105b369 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
from typing import Any, Dict, Optional, Callable, get_type_hints
from pydantic import BaseModel, validate_call
from phi.utils.log import logger
class Function(BaseModel):
"""Model for Functions"""
# The name of the function to be called.
# Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64.
name: str
# A description of what the function does, used by the model to choose when and how to call the function.
description: Optional[str] = None
# The parameters the functions accepts, described as a JSON Schema object.
# To describe a function that accepts no parameters, provide the value {"type": "object", "properties": {}}.
parameters: Dict[str, Any] = {"type": "object", "properties": {}}
entrypoint: Optional[Callable] = None
# If True, the arguments are sanitized before being passed to the function.
sanitize_arguments: bool = True
def to_dict(self) -> Dict[str, Any]:
return self.model_dump(exclude_none=True, include={"name", "description", "parameters"})
@classmethod
def from_callable(cls, c: Callable) -> "Function":
from inspect import getdoc
from phi.utils.json_schema import get_json_schema
parameters = {"type": "object", "properties": {}}
try:
# logger.info(f"Getting type hints for {c}")
type_hints = get_type_hints(c)
# logger.info(f"Type hints for {c}: {type_hints}")
# logger.info(f"Getting JSON schema for {type_hints}")
parameters = get_json_schema(type_hints)
# logger.info(f"JSON schema for {c}: {parameters}")
# logger.debug(f"Type hints for {c.__name__}: {type_hints}")
except Exception as e:
logger.warning(f"Could not parse args for {c.__name__}: {e}")
return cls(
name=c.__name__,
description=getdoc(c),
parameters=parameters,
entrypoint=validate_call(c),
)
def get_type_name(self, t):
name = str(t)
if "list" in name or "dict" in name:
return name
else:
return t.__name__
def get_definition_for_prompt(self) -> Optional[str]:
"""Returns a function definition that can be used in a prompt."""
import json
if self.entrypoint is None:
return None
type_hints = get_type_hints(self.entrypoint)
return_type = type_hints.get("return", None)
returns = None
if return_type is not None:
returns = self.get_type_name(return_type)
function_info = {
"name": self.name,
"description": self.description,
"arguments": self.parameters.get("properties", {}),
"returns": returns,
}
return json.dumps(function_info, indent=2)
def get_definition_for_prompt_dict(self) -> Optional[Dict[str, Any]]:
"""Returns a function definition that can be used in a prompt."""
if self.entrypoint is None:
return None
type_hints = get_type_hints(self.entrypoint)
return_type = type_hints.get("return", None)
returns = None
if return_type is not None:
returns = self.get_type_name(return_type)
function_info = {
"name": self.name,
"description": self.description,
"arguments": self.parameters.get("properties", {}),
"returns": returns,
}
return function_info
class FunctionCall(BaseModel):
"""Model for Function Calls"""
# The function to be called.
function: Function
# The arguments to call the function with.
arguments: Optional[Dict[str, Any]] = None
# The result of the function call.
result: Optional[Any] = None
# The ID of the function call.
call_id: Optional[str] = None
# Error while parsing arguments or running the function.
error: Optional[str] = None
def get_call_str(self) -> str:
"""Returns a string representation of the function call."""
if self.arguments is None:
return f"{self.function.name}()"
trimmed_arguments = {}
for k, v in self.arguments.items():
if isinstance(v, str) and len(v) > 100:
trimmed_arguments[k] = "..."
else:
trimmed_arguments[k] = v
call_str = f"{self.function.name}({', '.join([f'{k}={v}' for k, v in trimmed_arguments.items()])})"
return call_str
def execute(self) -> bool:
"""Runs the function call.
@return: True if the function call was successful, False otherwise.
"""
if self.function.entrypoint is None:
return False
logger.debug(f"Running: {self.get_call_str()}")
# Call the function with no arguments if none are provided.
if self.arguments is None:
try:
self.result = self.function.entrypoint()
return True
except Exception as e:
logger.warning(f"Could not run function {self.get_call_str()}")
logger.exception(e)
self.result = str(e)
return False
try:
self.result = self.function.entrypoint(**self.arguments)
return True
except Exception as e:
logger.warning(f"Could not run function {self.get_call_str()}")
logger.exception(e)
self.result = str(e)
return False
|