Repository navigation
Expand file tree
/
Copy pathfunction_registry.py
More file actions
108 lines (89 loc) · 4.15 KB
/
Copy pathfunction_registry.py
File metadata and controls
108 lines (89 loc) · 4.15 KB
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
from abc import ABC, abstractmethod
from enum import Enum
import json
import inspect
import logging
import re
from typing import Callable
from parser import DocStringParser
decorated_ai_functions = {}
class AIFunctionParameter(object):
def __init__(self, name: str, enum: list[str] = None):
self._name = name
self._enum = enum
@property
def name(self) -> str:
return self._name
@property
def enum(self) -> list[str]:
return self._enum
def ai_function(params: list[AIFunctionParameter] = None):
def decorator(func):
decorated_ai_functions[func.__name__] = {
'callable': func,
'params': {p.name: p for p in params} if params is not None else {}
}
return func
return decorator
class FunctionRegistry:
def __init__(self, docstring_parser: DocStringParser):
self.logger = logging.getLogger(f"{self.__class__.__module__}.{self.__class__.__name__}")
self._docstring_parser = docstring_parser
self._functions = {}
self._type_mapping = {
int: "integer",
float: "number",
str: "string",
Enum: "string",
inspect.Parameter.empty: "any"
}
def register_function(self, function: Callable, params: dict = None):
if not callable(function):
raise ValueError("Function must be callable")
if function.__name__ in self._functions:
self.logger.warning(f"Function {function.__name__} already registered and will be overwritten")
self._functions[function.__name__] = {
"callable": function,
"description": self._get_tool_description(function, params)
}
def register_decorated_functions(self):
for name, func_desc in decorated_ai_functions.items():
self.logger.debug(f"registering function '{name}'")
self.register_function(func_desc["callable"], func_desc["params"])
def call_function(self, function_name, **kwargs):
self.logger.debug(f"Calling function '{function_name}'")
return self._functions[function_name]["callable"](**kwargs)
def _map_type(self, python_type) -> str:
for py_type, mapped_type in self._type_mapping.items():
if issubclass(python_type, py_type):
return mapped_type
raise ValueError("Function has unknown parameter type")
def _get_tool_description(self, func: Callable, params: dict[str, AIFunctionParameter] = None) -> dict:
tool = {}
signature = inspect.signature(func)
tool["name"] = func.__name__
tool["description"] = ""
tool["parameters"] = {}
tool["parameters"]["type"] = "object"
tool["parameters"]["properties"] = {}
tool["parameters"]["required"] = []
for param_name, param in inspect.signature(func).parameters.items():
tool["parameters"]["properties"][param_name] = {}
tool["parameters"]["properties"][param_name]["type"] = self._map_type(param.annotation)
if param_name in params and params[param_name].enum:
tool["parameters"]["properties"][param_name]["enum"] = params[param_name].enum
if issubclass(param.annotation, Enum):
tool["parameters"]["properties"][param_name]["enum"] = list(param.annotation.__members__.keys())
if param.default is inspect.Parameter.empty:
tool["parameters"]["required"].append(param_name)
if func.__doc__:
tool["description"] = self._docstring_parser.get_description(func)
for param_name, param in inspect.signature(func).parameters.items():
tool["parameters"]["properties"][param_name][
"description"] = self._docstring_parser.get_parameter_description(func, param_name)
else:
self.logger.warning(f"No docstring found for function {func.__name__}")
return {"type": "function", "function": tool}
def get_tools(self):
self.logger.debug(json.dumps([value["description"] for key, value in self._functions.items()], indent=4))
return [value["description"] for key, value in self._functions.items()]