Spaces:
Running
Running
from abc import ABC, abstractmethod | |
import json | |
from google.generativeai.types import CallableFunctionDeclaration | |
import google.generativeai.types.content_types as content_types | |
from utils import add_params_and_annotations | |
class Parameter: | |
def __init__(self, name, description, required): | |
self.name = name | |
self.description = description | |
self.required = required | |
def as_openai_info(self): | |
pass | |
def as_standard_api(self): | |
pass | |
class StringParameter(Parameter): | |
def __init__(self, name, description, required): | |
super().__init__(name, description, required) | |
def as_openai_info(self): | |
return { | |
"type": "string", | |
"name": self.name, | |
"description": self.description | |
} | |
def as_standard_api(self): | |
return { | |
"type": "string", | |
"name": self.name, | |
"description": self.description, | |
"required": self.required | |
} | |
def as_natural_language(self): | |
return f'{self.name} (string{", required" if self.required else ""}): {self.description}.' | |
def as_documented_python(self): | |
return f'{self.name} (str{", required" if self.required else ""}): {self.description}.' | |
def as_gemini_tool(self): | |
return { | |
'type': 'string', | |
'description': self.description | |
} | |
def from_standard_api(api_info): | |
return StringParameter(api_info["name"], api_info["description"], api_info["required"]) | |
class EnumParameter(Parameter): | |
def __init__(self, name, description, values, required): | |
super().__init__(name, description, required) | |
self.values = values | |
def as_openai_info(self): | |
return { | |
"type": "string", | |
"description": self.description, | |
"values": self.values | |
} | |
def as_standard_api(self): | |
return { | |
"type": "enum", | |
"name": self.name, | |
"description": self.description, | |
"values": self.values, | |
"required": self.required | |
} | |
def as_natural_language(self): | |
return f'{self.name} (enum{", required" if self.required else ""}): {self.description}. Possible values: {", ".join(self.values)}' | |
def as_documented_python(self): | |
return f'{self.name} (str{", required" if self.required else ""}): {self.description}. Possible values: {", ".join(self.values)}' | |
def as_gemini_tool(self): | |
return { | |
'description': self.description, | |
'type': 'string', | |
'enum': self.values | |
} | |
def from_standard_api(api_info): | |
return EnumParameter(api_info["name"], api_info["description"], api_info["values"], api_info["required"]) | |
class NumberParameter(Parameter): | |
def __init__(self, name, description, required): | |
super().__init__(name, description, required) | |
def as_openai_info(self): | |
return { | |
"type": "number", | |
"description": self.description | |
} | |
def as_standard_api(self): | |
return { | |
"type": "number", | |
"name": self.name, | |
"description": self.description, | |
"required": self.required | |
} | |
def as_natural_language(self): | |
return f'{self.name} (number): {self.description}' | |
def as_documented_python(self): | |
return f'{self.name} (number): {self.description}' | |
def as_gemini_tool(self): | |
return { | |
'description': self.description, | |
'type': 'number' | |
} | |
class ArrayParameter(Parameter): | |
def __init__(self, name, description, required, item_schema): | |
super().__init__(name, description, required) | |
self.item_schema = item_schema | |
def as_openai_info(self): | |
return { | |
"type": "array", | |
"description": self.description, | |
"items": self.item_schema | |
} | |
def as_standard_api(self): | |
return { | |
"type": "array", | |
"name": self.name, | |
"description": self.description, | |
"required": self.required, | |
"item_schema": self.item_schema | |
} | |
def as_natural_language(self): | |
return f'{self.name} (array): {self.description}. Each item should follow the JSON schema: {json.dumps(self.item_schema)}' | |
def as_documented_python(self): | |
return f'{self.name} (list): {self.description}. Each item should follow the JSON schema: {json.dumps(self.item_schema)}' | |
def as_gemini_tool(self): | |
return { | |
'description': self.description, | |
'type': 'array', | |
'items': self.item_schema | |
} | |
def parameter_from_openai_api(parameter_name, schema, required): | |
if 'enum' in schema: | |
return EnumParameter(parameter_name, schema['description'], schema['enum'], required) | |
elif schema['type'] == 'string': | |
return StringParameter(parameter_name, schema['description'], required) | |
elif schema['type'] == 'number': | |
return NumberParameter(parameter_name, schema['description'], required) | |
elif schema['type'] == 'array': | |
return ArrayParameter(parameter_name, schema['description'], required, schema['items']) | |
else: | |
raise ValueError(f'Unknown parameter type: {schema["type"]}') | |
class Tool: | |
def __init__(self, name, description, parameters, function, output_schema=None): | |
self.name = name | |
self.description = description | |
self.parameters = parameters | |
self.function = function | |
self.output_schema = output_schema | |
def call_tool_for_toolformer(self, *args, **kwargs): | |
print(f'Toolformer called tool {self.name} with args {args} and kwargs {kwargs}') | |
# Unlike a call from a routine, this call catches exceptions and returns them as strings | |
try: | |
tool_reply = self.function(*args, **kwargs) | |
print(f'Tool {self.name} returned: {tool_reply}') | |
return tool_reply | |
except Exception as e: | |
print(f'Tool {self.name} failed with exception: {e}') | |
return 'Tool call failed: ' + str(e) | |
def as_openai_info(self): | |
return { | |
"type": "function", | |
"function": { | |
"name": self.name, | |
"description": self.description, | |
"parameters": { | |
"type" : "object", | |
"properties": {parameter.name : parameter.as_openai_info() for parameter in self.parameters}, | |
"required": [parameter.name for parameter in self.parameters if parameter.required] | |
} | |
} | |
} | |
def as_gemini_tool(self) -> CallableFunctionDeclaration: | |
if len(self.parameters) == 0: | |
parameters = None | |
else: | |
parameters = { | |
'type': 'object', | |
'properties': {parameter.name: parameter.as_gemini_tool() for parameter in self.parameters}, | |
'required': [parameter.name for parameter in self.parameters if parameter.required] | |
} | |
return content_types.Tool([CallableFunctionDeclaration( | |
name=self.name, | |
description=self.description, | |
parameters=parameters, | |
function=self.call_tool_for_toolformer | |
)]) | |
def as_llama_schema(self): | |
schema = { | |
'name': self.name, | |
'description': self.description, | |
'parameters': {parameter.name : parameter.as_openai_info() for parameter in self.parameters}, | |
'required': [parameter.name for parameter in self.parameters if parameter.required] | |
} | |
if self.output_schema is not None: | |
schema['output_schema'] = self.output_schema | |
return schema | |
def as_natural_language(self): | |
print('Converting to natural language') | |
print('Number of parameters:', len(self.parameters)) | |
nl = f'Function {self.name}: {self.description}. Parameters:\n' | |
if len(self.parameters) == 0: | |
nl += 'No parameters.' | |
else: | |
for parameter in self.parameters: | |
nl += '\t' + parameter.as_natural_language() + '\n' | |
if self.output_schema is not None: | |
nl += f'\Returns a dictionary with schema: {json.dumps(self.output_schema, indent=2)}' | |
return nl | |
def as_standard_api(self): | |
return { | |
"name": self.name, | |
"description": self.description, | |
"parameters": [parameter.as_standard_api() for parameter in self.parameters] | |
} | |
def as_documented_python(self): | |
documented_python = f'Tool {self.name}:\n\n{self.description}\nParameters:\n' | |
if len(self.parameters) == 0: | |
documented_python += 'No parameters.' | |
else: | |
for parameter in self.parameters: | |
documented_python += '\t' + parameter.as_documented_python() + '\n' | |
if self.output_schema is not None: | |
documented_python += f'\Returns a dictionary with schema: {json.dumps(self.output_schema, indent=2)}' | |
return documented_python | |
def as_executable_function(self): | |
# Create an actual function that can be called | |
def f(*args, **kwargs): | |
print('Routine called tool', self.name, 'with args', args, 'and kwargs', kwargs) | |
response = self.function(*args, **kwargs) | |
print('Tool', self.name, 'returned:', response) | |
return response | |
return f | |
def as_annotated_function(self): | |
def wrapped_fn(*args, **kwargs): | |
return self.call_tool_for_toolformer(*args, **kwargs) | |
parsed_parameters = {} | |
description = self.description | |
for parameter_name, parameter_schema in self.as_openai_info()['function']['parameters']['properties'].items(): | |
if parameter_schema['type'] == 'string': | |
parsed_parameters[parameter_name] = (str, parameter_schema['description']) | |
elif parameter_schema['type'] == 'number': | |
parsed_parameters[parameter_name] = (float, parameter_schema['description']) | |
elif parameter_schema['type'] == 'object': | |
parsed_parameters[parameter_name] = (dict, parameter_schema['description']) | |
description += f'\n{parameter_name} has the schema:\n' + json.dumps(parameter_schema) + '\n' | |
else: | |
raise ValueError(f'Unknown parameter type: {parameter_schema["type"]}') | |
return_type = type(None) | |
if self.output_schema is not None: | |
#description += '\nOutput schema:\n' + json.dumps(self.output_schema) | |
if self.output_schema['type'] == 'string': | |
return_type = str | |
elif self.output_schema['type'] == 'number': | |
return_type = float | |
elif self.output_schema['type'] == 'object': | |
return_type = dict | |
else: | |
raise ValueError(f'Unknown output type: {self.output_schema["type"]}') | |
return add_params_and_annotations( | |
self.name, description, parsed_parameters, return_type)(wrapped_fn) | |
def from_openai_info(info, func): | |
parameters = [parameter_from_openai_api(name, schema, name in info['function']['parameters']['required']) for name, schema in info['function']['parameters']['properties'].items()] | |
return Tool(info['function']['name'], info['function']['description'], parameters, func) | |
class Conversation(ABC): | |
def chat(self, message, role='user', print_output=True): | |
pass | |
class Toolformer(ABC): | |
def new_conversation(self, prompt, tools, category=None) -> Conversation: | |
pass | |