tooluniverse 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tooluniverse might be problematic. Click here for more details.
- tooluniverse/__init__.py +17 -0
- tooluniverse/base_tool.py +32 -0
- tooluniverse/data/__init__.py +0 -0
- tooluniverse/data/fda_drug_labeling_tools.json +6529 -0
- tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +201670 -0
- tooluniverse/data/monarch_tools.json +118 -0
- tooluniverse/data/opentarget_tools.json +1415 -0
- tooluniverse/data/special_tools.json +48 -0
- tooluniverse/execute_function.py +216 -0
- tooluniverse/graphql_tool.py +122 -0
- tooluniverse/openfda_tool.py +421 -0
- tooluniverse/restful_tool.py +95 -0
- tooluniverse/utils.py +172 -0
- tooluniverse-0.1.0.dist-info/METADATA +54 -0
- tooluniverse-0.1.0.dist-info/RECORD +17 -0
- tooluniverse-0.1.0.dist-info/WHEEL +5 -0
- tooluniverse-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "Finish",
|
|
4
|
+
"description": "Indicate the end of multi-step reasoning.",
|
|
5
|
+
"parameter": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": null
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "Tool_RAG",
|
|
12
|
+
"description": "Retrieve related tools from the toolbox based on the provided description",
|
|
13
|
+
"parameter": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"properties": {
|
|
16
|
+
"description": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "The description of the tool capability required.",
|
|
19
|
+
"required": true
|
|
20
|
+
},
|
|
21
|
+
"limit": {
|
|
22
|
+
"type": "integer",
|
|
23
|
+
"description": "The number of tools to retrieve",
|
|
24
|
+
"required": true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": [
|
|
29
|
+
"description",
|
|
30
|
+
"limit"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "CallAgent",
|
|
35
|
+
"description": "Give a solution plan to the agent and let it solve the problem. Solution plan should reflect a distinct method, approach, or viewpoint to solve the given question. Call these function multiple times, and each solution plan should start with different aspects of the question, for example, genes, phenotypes, diseases, or drugs, etc. The CallAgent will achieve the task based on the plan, so only give the plan instead of unverified information.",
|
|
36
|
+
"parameter": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties":
|
|
39
|
+
{
|
|
40
|
+
"solution": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "A feasible and concise solution plan that address the question.",
|
|
43
|
+
"required": true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
]
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from .utils import read_json_list, evaluate_function_call, extract_function_call_json
|
|
2
|
+
import copy
|
|
3
|
+
import json
|
|
4
|
+
import random
|
|
5
|
+
import string
|
|
6
|
+
from .graphql_tool import OpentargetTool, OpentargetGeneticsTool, OpentargetToolDrugNameMatch
|
|
7
|
+
from .openfda_tool import FDADrugLabelTool, FDADrugLabelSearchTool, FDADrugLabelSearchIDTool, FDADrugLabelGetDrugGenericNameTool
|
|
8
|
+
from .restful_tool import MonarchTool, MonarchDiseasesForMultiplePhenoTool
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
# Determine the directory where the current file is located
|
|
13
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
14
|
+
|
|
15
|
+
default_tool_files = {
|
|
16
|
+
'opentarget': os.path.join(current_dir, 'data', 'opentarget_tools.json'),
|
|
17
|
+
'fda_drug_label': os.path.join(current_dir, 'data', 'fda_drug_labeling_tools.json'),
|
|
18
|
+
'special_tools': os.path.join(current_dir, 'data', 'special_tools.json'),
|
|
19
|
+
'monarch': os.path.join(current_dir, 'data', 'monarch_tools.json')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
tool_type_mappings = {
|
|
23
|
+
'OpenTarget': OpentargetTool,
|
|
24
|
+
'OpenTargetGenetics': OpentargetGeneticsTool,
|
|
25
|
+
'FDADrugLabel': FDADrugLabelTool,
|
|
26
|
+
'FDADrugLabelSearchTool': FDADrugLabelSearchTool,
|
|
27
|
+
'Monarch': MonarchTool,
|
|
28
|
+
'MonarchDiseasesForMultiplePheno': MonarchDiseasesForMultiplePhenoTool,
|
|
29
|
+
'FDADrugLabelSearchIDTool': FDADrugLabelSearchIDTool,
|
|
30
|
+
'FDADrugLabelGetDrugGenericNameTool': FDADrugLabelGetDrugGenericNameTool,
|
|
31
|
+
'OpentargetToolDrugNameMatch': OpentargetToolDrugNameMatch,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ToolUniverse:
|
|
36
|
+
def __init__(self, tool_files=default_tool_files):
|
|
37
|
+
# Initialize any necessary attributes here
|
|
38
|
+
self.all_tools = []
|
|
39
|
+
self.all_tool_dict = {}
|
|
40
|
+
self.tool_category_dicts = {}
|
|
41
|
+
if tool_files is None:
|
|
42
|
+
tool_files = default_tool_files
|
|
43
|
+
self.tool_files = tool_files
|
|
44
|
+
self.callable_functions = {}
|
|
45
|
+
|
|
46
|
+
def load_tools(self, tool_type=None):
|
|
47
|
+
print(f"Number of tools before load tools: {len(self.all_tools)}")
|
|
48
|
+
if tool_type is None:
|
|
49
|
+
for each in self.tool_files:
|
|
50
|
+
loaded_tool_list = read_json_list(self.tool_files[each])
|
|
51
|
+
self.all_tools += loaded_tool_list
|
|
52
|
+
self.tool_category_dicts[each] = loaded_tool_list
|
|
53
|
+
else:
|
|
54
|
+
for each in tool_type:
|
|
55
|
+
loaded_tool_list = read_json_list(self.tool_files[each])
|
|
56
|
+
self.all_tools += loaded_tool_list
|
|
57
|
+
self.tool_category_dicts[each] = loaded_tool_list
|
|
58
|
+
# Deduplication of tools
|
|
59
|
+
tool_name_list = []
|
|
60
|
+
dedup_all_tools = []
|
|
61
|
+
for each in self.all_tools:
|
|
62
|
+
if each['name'] not in tool_name_list:
|
|
63
|
+
tool_name_list.append(each['name'])
|
|
64
|
+
dedup_all_tools.append(each)
|
|
65
|
+
self.all_tools = dedup_all_tools
|
|
66
|
+
self.refresh_tool_name_desc()
|
|
67
|
+
|
|
68
|
+
print(f"Number of tools after load tools: {len(self.all_tools)}")
|
|
69
|
+
|
|
70
|
+
def return_all_loaded_tools(self):
|
|
71
|
+
return copy.deepcopy(self.all_tools)
|
|
72
|
+
|
|
73
|
+
def refresh_tool_name_desc(self, enable_full_desc=False):
|
|
74
|
+
tool_name_list = []
|
|
75
|
+
tool_desc_list = []
|
|
76
|
+
for tool in self.all_tools:
|
|
77
|
+
tool_name_list.append(tool['name'])
|
|
78
|
+
if enable_full_desc:
|
|
79
|
+
tool_desc_list.append(json.dumps(tool))
|
|
80
|
+
else:
|
|
81
|
+
tool_desc_list.append(tool['name']+': '+tool['description'])
|
|
82
|
+
self.all_tool_dict[tool['name']] = tool
|
|
83
|
+
return tool_name_list, tool_desc_list
|
|
84
|
+
|
|
85
|
+
def prepare_one_tool_prompt(self, tool):
|
|
86
|
+
valid_keys = ['name', 'description', 'parameter', 'required']
|
|
87
|
+
tool = copy.deepcopy(tool)
|
|
88
|
+
for key in list(tool.keys()):
|
|
89
|
+
if key not in valid_keys:
|
|
90
|
+
del tool[key]
|
|
91
|
+
return tool
|
|
92
|
+
|
|
93
|
+
def prepare_tool_prompts(self, tool_list):
|
|
94
|
+
copied_list = []
|
|
95
|
+
for tool in tool_list:
|
|
96
|
+
copied_list.append(self.prepare_one_tool_prompt(tool))
|
|
97
|
+
return copied_list
|
|
98
|
+
|
|
99
|
+
def remove_keys(self, tool_list, invalid_keys):
|
|
100
|
+
copied_list = copy.deepcopy(tool_list)
|
|
101
|
+
for tool in copied_list:
|
|
102
|
+
# Create a list of keys to avoid modifying the dictionary during iteration
|
|
103
|
+
for key in list(tool.keys()):
|
|
104
|
+
if key in invalid_keys:
|
|
105
|
+
del tool[key]
|
|
106
|
+
return copied_list
|
|
107
|
+
|
|
108
|
+
def prepare_tool_examples(self, tool_list):
|
|
109
|
+
valid_keys = ['name', 'description',
|
|
110
|
+
'parameter', 'required', 'query_schema', 'fields', 'label', 'type']
|
|
111
|
+
copied_list = copy.deepcopy(tool_list)
|
|
112
|
+
for tool in copied_list:
|
|
113
|
+
# Create a list of keys to avoid modifying the dictionary during iteration
|
|
114
|
+
for key in list(tool.keys()):
|
|
115
|
+
if key not in valid_keys:
|
|
116
|
+
del tool[key]
|
|
117
|
+
return copied_list
|
|
118
|
+
|
|
119
|
+
def get_tool_by_name(self, tool_names):
|
|
120
|
+
picked_tool_list = []
|
|
121
|
+
for each_name in tool_names:
|
|
122
|
+
if each_name in self.all_tool_dict:
|
|
123
|
+
picked_tool_list.append(self.all_tool_dict[each_name])
|
|
124
|
+
else:
|
|
125
|
+
print(f"Tool name {each_name} not found in the loaded tools.")
|
|
126
|
+
return picked_tool_list
|
|
127
|
+
|
|
128
|
+
def get_one_tool_by_one_name(self, tool_name):
|
|
129
|
+
if tool_name in self.all_tool_dict:
|
|
130
|
+
return self.all_tool_dict[tool_name]
|
|
131
|
+
else:
|
|
132
|
+
print(f"Tool name {tool_name} not found in the loaded tools.")
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def get_tool_type_by_name(self, tool_name):
|
|
136
|
+
return self.all_tool_dict[tool_name]['type']
|
|
137
|
+
|
|
138
|
+
def tool_to_str(self, tool_list):
|
|
139
|
+
return '\n\n'.join(json.dumps(obj, indent=4) for obj in tool_list)
|
|
140
|
+
|
|
141
|
+
def extract_function_call_json(self, lst, return_message=False, verbose=True):
|
|
142
|
+
return extract_function_call_json(lst, return_message=return_message, verbose=verbose)
|
|
143
|
+
|
|
144
|
+
def call_id_gen(self):
|
|
145
|
+
return "".join(random.choices(string.ascii_letters + string.digits, k=9))
|
|
146
|
+
|
|
147
|
+
def run(self, fcall_str, return_message=False, verbose=True):
|
|
148
|
+
if return_message:
|
|
149
|
+
function_call_json, message = self.extract_function_call_json(
|
|
150
|
+
fcall_str, return_message=return_message, verbose=verbose)
|
|
151
|
+
else:
|
|
152
|
+
function_call_json = self.extract_function_call_json(
|
|
153
|
+
fcall_str, return_message=return_message, verbose=verbose)
|
|
154
|
+
if function_call_json is not None:
|
|
155
|
+
if isinstance(function_call_json, list):
|
|
156
|
+
# return the function call+result message with call id.
|
|
157
|
+
call_results = []
|
|
158
|
+
for i in range(len(function_call_json)):
|
|
159
|
+
call_result = self.run_one_function(function_call_json[i])
|
|
160
|
+
call_id = self.call_id_gen()
|
|
161
|
+
function_call_json[i]["call_id"] = call_id
|
|
162
|
+
call_results.append({"role": "tool", "content": json.dumps(
|
|
163
|
+
{"content": call_result, "call_id": call_id})})
|
|
164
|
+
revised_messages = [{"role": "assistant", "content": message,
|
|
165
|
+
"tool_calls": json.dumps(function_call_json)}]+call_results
|
|
166
|
+
return revised_messages
|
|
167
|
+
else:
|
|
168
|
+
return self.run_one_function(function_call_json)
|
|
169
|
+
else:
|
|
170
|
+
print("\033[91mNot a function call\033[0m")
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
def run_one_function(self, function_call_json):
|
|
174
|
+
check_status, check_message = self.check_function_call(function_call_json)
|
|
175
|
+
if check_status is False:
|
|
176
|
+
return "Invalid function call: "+check_message# + " You must correct your invalid function call!"
|
|
177
|
+
function_name = function_call_json["name"]
|
|
178
|
+
arguments = function_call_json["arguments"]
|
|
179
|
+
if function_name in self.callable_functions:
|
|
180
|
+
return self.callable_functions[function_name].run(arguments)
|
|
181
|
+
else:
|
|
182
|
+
if function_name in self.all_tool_dict:
|
|
183
|
+
print(
|
|
184
|
+
"\033[92mInitiating callable_function from loaded tool dicts.\033[0m")
|
|
185
|
+
tool = self.init_tool(
|
|
186
|
+
self.all_tool_dict[function_name], add_to_cache=True)
|
|
187
|
+
return tool.run(arguments)
|
|
188
|
+
|
|
189
|
+
def init_tool(self, tool=None, tool_name=None, add_to_cache=True):
|
|
190
|
+
if tool_name is not None:
|
|
191
|
+
new_tool = tool_type_mappings[tool_name]()
|
|
192
|
+
else:
|
|
193
|
+
tool_type = tool['type']
|
|
194
|
+
tool_name = tool['name']
|
|
195
|
+
if 'OpentargetToolDrugNameMatch' == tool_type:
|
|
196
|
+
if 'FDADrugLabelGetDrugGenericNameTool' not in self.callable_functions:
|
|
197
|
+
self.callable_functions['FDADrugLabelGetDrugGenericNameTool'] = tool_type_mappings['FDADrugLabelGetDrugGenericNameTool']()
|
|
198
|
+
new_tool = tool_type_mappings[tool_type](tool_config=tool, drug_generic_tool=self.callable_functions['FDADrugLabelGetDrugGenericNameTool'])
|
|
199
|
+
else:
|
|
200
|
+
new_tool = tool_type_mappings[tool_type](tool_config=tool)
|
|
201
|
+
if add_to_cache:
|
|
202
|
+
self.callable_functions[tool_name] = new_tool
|
|
203
|
+
return new_tool
|
|
204
|
+
|
|
205
|
+
def check_function_call(self, fcall_str, function_config=None):
|
|
206
|
+
function_call_json = self.extract_function_call_json(fcall_str)
|
|
207
|
+
print("loaded function call json", function_call_json)
|
|
208
|
+
if function_call_json is not None:
|
|
209
|
+
if function_config is not None:
|
|
210
|
+
return evaluate_function_call(function_config, function_call_json)
|
|
211
|
+
function_name = function_call_json['name']
|
|
212
|
+
if not function_name in self.all_tool_dict:
|
|
213
|
+
return False, f"Function name {function_name} not found in loaded tools."
|
|
214
|
+
return evaluate_function_call(self.all_tool_dict[function_name], function_call_json)
|
|
215
|
+
else:
|
|
216
|
+
return False, "\033[91mInvalid JSON string of function call\033[0m"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from graphql import build_schema
|
|
2
|
+
from graphql.language import parse
|
|
3
|
+
from graphql.validation import validate
|
|
4
|
+
from .base_tool import BaseTool
|
|
5
|
+
import requests
|
|
6
|
+
import copy
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def validate_query(query_str, schema_str):
|
|
11
|
+
try:
|
|
12
|
+
# Build the GraphQL schema object from the provided schema string
|
|
13
|
+
schema = build_schema(schema_str)
|
|
14
|
+
|
|
15
|
+
# Parse the query string into an AST (Abstract Syntax Tree)
|
|
16
|
+
query_ast = parse(query_str)
|
|
17
|
+
|
|
18
|
+
# Validate the query AST against the schema
|
|
19
|
+
validation_errors = validate(schema, query_ast)
|
|
20
|
+
|
|
21
|
+
if not validation_errors:
|
|
22
|
+
return True
|
|
23
|
+
else:
|
|
24
|
+
# Collect and return the validation errors
|
|
25
|
+
error_messages = '\n'.join(str(error)
|
|
26
|
+
for error in validation_errors)
|
|
27
|
+
return f"Query validation errors:\n{error_messages}"
|
|
28
|
+
except Exception as e:
|
|
29
|
+
return f"An error occurred during validation: {str(e)}"
|
|
30
|
+
|
|
31
|
+
def remove_none_and_empty_values(json_obj):
|
|
32
|
+
"""Remove all key-value pairs where the value is None or an empty list"""
|
|
33
|
+
if isinstance(json_obj, dict):
|
|
34
|
+
return {k: remove_none_and_empty_values(v) for k, v in json_obj.items() if v is not None and v != []}
|
|
35
|
+
elif isinstance(json_obj, list):
|
|
36
|
+
return [remove_none_and_empty_values(item) for item in json_obj if item is not None and item != []]
|
|
37
|
+
else:
|
|
38
|
+
return json_obj
|
|
39
|
+
|
|
40
|
+
def execute_query(endpoint_url, query, variables=None):
|
|
41
|
+
response = requests.post(
|
|
42
|
+
endpoint_url, json={'query': query, 'variables': variables})
|
|
43
|
+
try:
|
|
44
|
+
result = response.json()
|
|
45
|
+
# result = json.dumps(result, ensure_ascii=False)
|
|
46
|
+
result = remove_none_and_empty_values(result)
|
|
47
|
+
# Check if the response contains errors
|
|
48
|
+
if 'errors' in result:
|
|
49
|
+
print("Invalid Query: ", result['errors'])
|
|
50
|
+
return None
|
|
51
|
+
# Check if the data field is empty
|
|
52
|
+
elif not result.get('data') or all(not v for v in result['data'].values()):
|
|
53
|
+
print("No data returned")
|
|
54
|
+
return None
|
|
55
|
+
else:
|
|
56
|
+
return result
|
|
57
|
+
except requests.exceptions.JSONDecodeError as e:
|
|
58
|
+
print("JSONDecodeError: Could not decode the response as JSON")
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class GraphQLTool(BaseTool):
|
|
63
|
+
def __init__(self, tool_config, endpoint_url):
|
|
64
|
+
super().__init__(tool_config)
|
|
65
|
+
self.endpoint_url = endpoint_url
|
|
66
|
+
self.query_schema = tool_config['query_schema']
|
|
67
|
+
self.parameters = tool_config['parameter']['properties']
|
|
68
|
+
self.default_size = 5
|
|
69
|
+
|
|
70
|
+
def run(self, arguments):
|
|
71
|
+
arguments = copy.deepcopy(arguments)
|
|
72
|
+
if 'size' in self.parameters and 'size' not in arguments:
|
|
73
|
+
arguments['size'] = 5
|
|
74
|
+
return execute_query(endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class OpentargetTool(GraphQLTool):
|
|
78
|
+
def __init__(self, tool_config):
|
|
79
|
+
endpoint_url = 'https://api.platform.opentargets.org/api/v4/graphql'
|
|
80
|
+
super().__init__(tool_config, endpoint_url)
|
|
81
|
+
|
|
82
|
+
def run(self, arguments):
|
|
83
|
+
for each_arg, arg_value in arguments.items(): # opentarget api cannot handle '-' in the arguments
|
|
84
|
+
if isinstance(arg_value, str):
|
|
85
|
+
if '-' in arg_value:
|
|
86
|
+
arguments[each_arg] = arg_value.replace('-', ' ')
|
|
87
|
+
return super().run(arguments)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class OpentargetToolDrugNameMatch(GraphQLTool):
|
|
91
|
+
def __init__(self, tool_config, drug_generic_tool=None):
|
|
92
|
+
endpoint_url = 'https://api.platform.opentargets.org/api/v4/graphql'
|
|
93
|
+
self.drug_generic_tool = drug_generic_tool
|
|
94
|
+
self.possible_drug_name_args = ['drugName']
|
|
95
|
+
super().__init__(tool_config, endpoint_url)
|
|
96
|
+
|
|
97
|
+
def run(self, arguments):
|
|
98
|
+
arguments = copy.deepcopy(arguments)
|
|
99
|
+
results = execute_query(endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments)
|
|
100
|
+
if results is None:
|
|
101
|
+
print("No results found for the drug brand name. Trying with the generic name.")
|
|
102
|
+
name_arguments = {}
|
|
103
|
+
for each_args in self.possible_drug_name_args:
|
|
104
|
+
if each_args in arguments:
|
|
105
|
+
name_arguments['drug_name'] = arguments[each_args]
|
|
106
|
+
break
|
|
107
|
+
if len(name_arguments)==0:
|
|
108
|
+
print("No drug name found in the arguments.")
|
|
109
|
+
return None
|
|
110
|
+
drug_name_results = self.drug_generic_tool.run(name_arguments)
|
|
111
|
+
if 'openfda.generic_name' in drug_name_results:
|
|
112
|
+
arguments[each_args] = drug_name_results['openfda.generic_name']
|
|
113
|
+
print("Found generic name. Trying with the generic name: ", arguments[each_args])
|
|
114
|
+
results = execute_query(endpoint_url=self.endpoint_url, query=self.query_schema, variables=arguments)
|
|
115
|
+
return results
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class OpentargetGeneticsTool(GraphQLTool):
|
|
120
|
+
def __init__(self, tool_config):
|
|
121
|
+
endpoint_url = 'https://api.genetics.opentargets.org/graphql'
|
|
122
|
+
super().__init__(tool_config, endpoint_url)
|