dhisana 0.0.1.dev1__tar.gz → 0.0.1.dev2__tar.gz
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.
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/PKG-INFO +1 -1
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/setup.py +1 -1
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/ui/components.py +3 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/agent_tools.py +53 -25
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/assistant_tool_tag.py +1 -1
- dhisana-0.0.1.dev2/src/dhisana/utils/openai_helpers.py +485 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/openapi_spec_to_tools.py +5 -3
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana.egg-info/PKG-INFO +1 -1
- dhisana-0.0.1.dev1/src/dhisana/utils/openai_helpers.py +0 -272
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/README.md +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/pyproject.toml +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/setup.cfg +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/__init__.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/cli/__init__.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/cli/cli.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/cli/datasets.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/cli/models.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/cli/predictions.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/ui/__init__.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/__init__.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana/utils/tools_json.py +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana.egg-info/SOURCES.txt +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana.egg-info/dependency_links.txt +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana.egg-info/entry_points.txt +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana.egg-info/requires.txt +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/src/dhisana.egg-info/top_level.txt +0 -0
- {dhisana-0.0.1.dev1 → dhisana-0.0.1.dev2}/tests/test_agent_tools.py +0 -0
|
@@ -224,11 +224,13 @@ class Tab(Component):
|
|
|
224
224
|
class ModalDialog(Component):
|
|
225
225
|
def __init__(
|
|
226
226
|
self,
|
|
227
|
+
name: str,
|
|
227
228
|
title: str,
|
|
228
229
|
content: List[Component],
|
|
229
230
|
visible: bool = False,
|
|
230
231
|
on_close: Optional[str] = None,
|
|
231
232
|
):
|
|
233
|
+
self.name = name
|
|
232
234
|
self.title = title
|
|
233
235
|
self.content = content
|
|
234
236
|
self.visible = visible
|
|
@@ -238,6 +240,7 @@ class ModalDialog(Component):
|
|
|
238
240
|
return {
|
|
239
241
|
'type': 'modal-dialog',
|
|
240
242
|
'properties': {
|
|
243
|
+
'name': self.name,
|
|
241
244
|
'title': self.title,
|
|
242
245
|
'visible': self.visible,
|
|
243
246
|
'onClose': self.on_close,
|
|
@@ -23,12 +23,9 @@ from googleapiclient.errors import HttpError
|
|
|
23
23
|
GLOBAL_DATA_MODELS = []
|
|
24
24
|
GLOBAL_TOOLS_FUNCTIONS = {}
|
|
25
25
|
|
|
26
|
+
|
|
26
27
|
@assistant_tool
|
|
27
28
|
async def get_html_content_from_url(url):
|
|
28
|
-
# Check and replace http with https
|
|
29
|
-
if url.startswith("http://"):
|
|
30
|
-
url = url.replace("http://", "https://", 1)
|
|
31
|
-
|
|
32
29
|
async with async_playwright() as playwright:
|
|
33
30
|
browser = await playwright.chromium.launch(headless=True)
|
|
34
31
|
context = await browser.new_context()
|
|
@@ -45,6 +42,7 @@ async def get_html_content_from_url(url):
|
|
|
45
42
|
finally:
|
|
46
43
|
await browser.close()
|
|
47
44
|
|
|
45
|
+
|
|
48
46
|
async def parse_html_content(html_content):
|
|
49
47
|
if not html_content:
|
|
50
48
|
return ""
|
|
@@ -53,6 +51,7 @@ async def parse_html_content(html_content):
|
|
|
53
51
|
element.decompose()
|
|
54
52
|
return soup.get_text(separator=' ', strip=True)
|
|
55
53
|
|
|
54
|
+
|
|
56
55
|
def convert_base_64_json(base64_string):
|
|
57
56
|
"""
|
|
58
57
|
Convert a base64 encoded string to a JSON string.
|
|
@@ -65,12 +64,13 @@ def convert_base_64_json(base64_string):
|
|
|
65
64
|
"""
|
|
66
65
|
# Decode the base64 string to bytes
|
|
67
66
|
decoded_bytes = base64.b64decode(base64_string)
|
|
68
|
-
|
|
67
|
+
|
|
69
68
|
# Convert bytes to JSON string
|
|
70
69
|
json_string = decoded_bytes.decode('utf-8')
|
|
71
|
-
|
|
70
|
+
|
|
72
71
|
return json_string
|
|
73
72
|
|
|
73
|
+
|
|
74
74
|
@assistant_tool
|
|
75
75
|
async def get_file_content_from_googledrive_by_name(file_name: str = None) -> str:
|
|
76
76
|
"""
|
|
@@ -101,12 +101,13 @@ async def get_file_content_from_googledrive_by_name(file_name: str = None) -> st
|
|
|
101
101
|
|
|
102
102
|
# Search for the file by name
|
|
103
103
|
query = f"name = '{file_name}'"
|
|
104
|
-
results = service.files().list(q=query, pageSize=1,
|
|
104
|
+
results = service.files().list(q=query, pageSize=1,
|
|
105
|
+
fields="files(id, name)").execute()
|
|
105
106
|
items = results.get('files', [])
|
|
106
107
|
|
|
107
108
|
if not items:
|
|
108
109
|
raise FileNotFoundError(f"No file found with the name: {file_name}")
|
|
109
|
-
|
|
110
|
+
|
|
110
111
|
# Get the file ID of the first matching file
|
|
111
112
|
file_id = items[0]['id']
|
|
112
113
|
file_name = items[0]['name']
|
|
@@ -119,7 +120,7 @@ async def get_file_content_from_googledrive_by_name(file_name: str = None) -> st
|
|
|
119
120
|
|
|
120
121
|
# Request the file content from Google Drive
|
|
121
122
|
request = service.files().get_media(fileId=file_id)
|
|
122
|
-
|
|
123
|
+
|
|
123
124
|
# Create a file-like object in memory to hold the downloaded data
|
|
124
125
|
fh = io.FileIO(local_file_path, 'wb')
|
|
125
126
|
|
|
@@ -137,14 +138,15 @@ async def get_file_content_from_googledrive_by_name(file_name: str = None) -> st
|
|
|
137
138
|
# Return the local file path
|
|
138
139
|
return local_file_path
|
|
139
140
|
|
|
141
|
+
|
|
140
142
|
@assistant_tool
|
|
141
|
-
async def write_content_to_googledrive(
|
|
143
|
+
async def write_content_to_googledrive(cloud_file_path: str, local_file_path: str) -> str:
|
|
142
144
|
"""
|
|
143
145
|
Writes content from a local file to a file in Google Drive using a service account.
|
|
144
|
-
If the file does not exist in Google Drive, it creates it.
|
|
146
|
+
If the file does not exist in Google Drive, it creates it along with any necessary intermediate directories.
|
|
145
147
|
|
|
146
|
-
:param
|
|
147
|
-
:param
|
|
148
|
+
:param cloud_file_path: The path of the file to create or update on Google Drive.
|
|
149
|
+
:param local_file_path: The path to the local file whose content will be uploaded.
|
|
148
150
|
:return: The file ID of the uploaded or updated file.
|
|
149
151
|
"""
|
|
150
152
|
|
|
@@ -167,13 +169,35 @@ async def write_content_to_googledrive(cloud_file_name: str, local_file_name: st
|
|
|
167
169
|
# Build the Google Drive service object
|
|
168
170
|
service = build('drive', 'v3', credentials=credentials)
|
|
169
171
|
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
# Split the cloud file path into components
|
|
173
|
+
path_components = cloud_file_path.split('/')
|
|
174
|
+
parent_id = 'root'
|
|
175
|
+
|
|
176
|
+
# Create intermediate directories if they don't exist
|
|
177
|
+
for component in path_components[:-1]:
|
|
178
|
+
query = f"'{parent_id}' in parents and name = '{component}' and mimeType = 'application/vnd.google-apps.folder'"
|
|
179
|
+
results = service.files().list(q=query, pageSize=1, fields="files(id, name)").execute()
|
|
180
|
+
items = results.get('files', [])
|
|
181
|
+
|
|
182
|
+
if items:
|
|
183
|
+
parent_id = items[0]['id']
|
|
184
|
+
else:
|
|
185
|
+
file_metadata = {
|
|
186
|
+
'name': component,
|
|
187
|
+
'mimeType': 'application/vnd.google-apps.folder',
|
|
188
|
+
'parents': [parent_id]
|
|
189
|
+
}
|
|
190
|
+
folder = service.files().create(body=file_metadata, fields='id').execute()
|
|
191
|
+
parent_id = folder.get('id')
|
|
174
192
|
|
|
175
193
|
# Prepare the file for upload
|
|
176
|
-
media_body = MediaFileUpload(
|
|
194
|
+
media_body = MediaFileUpload(local_file_path, resumable=True)
|
|
195
|
+
file_name = path_components[-1]
|
|
196
|
+
|
|
197
|
+
# Check if the file exists in the specified directory
|
|
198
|
+
query = f"'{parent_id}' in parents and name = '{file_name}'"
|
|
199
|
+
results = service.files().list(q=query, pageSize=1, fields="files(id, name)").execute()
|
|
200
|
+
items = results.get('files', [])
|
|
177
201
|
|
|
178
202
|
if items:
|
|
179
203
|
# File exists, update its content
|
|
@@ -184,7 +208,10 @@ async def write_content_to_googledrive(cloud_file_name: str, local_file_name: st
|
|
|
184
208
|
).execute()
|
|
185
209
|
else:
|
|
186
210
|
# File does not exist, create a new one
|
|
187
|
-
file_metadata = {
|
|
211
|
+
file_metadata = {
|
|
212
|
+
'name': file_name,
|
|
213
|
+
'parents': [parent_id]
|
|
214
|
+
}
|
|
188
215
|
created_file = service.files().create(
|
|
189
216
|
body=file_metadata,
|
|
190
217
|
media_body=media_body,
|
|
@@ -194,8 +221,6 @@ async def write_content_to_googledrive(cloud_file_name: str, local_file_name: st
|
|
|
194
221
|
|
|
195
222
|
return file_id
|
|
196
223
|
|
|
197
|
-
|
|
198
|
-
|
|
199
224
|
@assistant_tool
|
|
200
225
|
async def list_files_in_drive_folder_by_name(folder_path: str = None) -> List[str]:
|
|
201
226
|
"""
|
|
@@ -230,11 +255,13 @@ async def list_files_in_drive_folder_by_name(folder_path: str = None) -> List[st
|
|
|
230
255
|
|
|
231
256
|
if folder_path:
|
|
232
257
|
# Split the folder path into individual folder names
|
|
233
|
-
folder_names = [name for name in folder_path.strip(
|
|
258
|
+
folder_names = [name for name in folder_path.strip(
|
|
259
|
+
'/').split('/') if name]
|
|
234
260
|
for folder_name in folder_names:
|
|
235
261
|
# Search for the folder by name under the current folder_id
|
|
236
262
|
query = (
|
|
237
|
-
f"name = '{
|
|
263
|
+
f"name = '{
|
|
264
|
+
folder_name}' and mimeType = 'application/vnd.google-apps.folder' "
|
|
238
265
|
f"and '{folder_id}' in parents and trashed = false"
|
|
239
266
|
)
|
|
240
267
|
try:
|
|
@@ -246,7 +273,7 @@ async def list_files_in_drive_folder_by_name(folder_path: str = None) -> List[st
|
|
|
246
273
|
items = results.get('files', [])
|
|
247
274
|
if not items:
|
|
248
275
|
raise FileNotFoundError(
|
|
249
|
-
f"Folder '{folder_name}' not found under parent folder ID '{folder_id}'"
|
|
276
|
+
f"Folder '{folder_name}' not found under parent folder ID '{folder_id}'"
|
|
250
277
|
)
|
|
251
278
|
# Update folder_id to the ID of the found folder
|
|
252
279
|
folder_id = items[0]['id']
|
|
@@ -424,4 +451,5 @@ async def get_calendar_events_using_service_account_async(
|
|
|
424
451
|
|
|
425
452
|
return events
|
|
426
453
|
|
|
427
|
-
GLOBAL_TOOLS_FUNCTIONS = {name: func for name, func in globals().items(
|
|
454
|
+
GLOBAL_TOOLS_FUNCTIONS = {name: func for name, func in globals().items(
|
|
455
|
+
) if callable(func) and getattr(func, 'is_assistant_tool', False)}
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
# Helper functions to call OpenAI Assistant
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import json
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Dict, List
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from openai import OpenAI
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from fastapi import HTTPException
|
|
13
|
+
from openai import LengthFinishReasonError, OpenAI, OpenAIError
|
|
14
|
+
import csv
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from .agent_tools import GLOBAL_DATA_MODELS, GLOBAL_TOOLS_FUNCTIONS, get_file_content_from_googledrive_by_name, write_content_to_googledrive
|
|
18
|
+
from .tools_json import GLOBAL_ASSISTANT_TOOLS
|
|
19
|
+
from .openapi_spec_to_tools import (
|
|
20
|
+
OPENAPI_TOOL_CONFIGURATIONS,
|
|
21
|
+
OPENAPI_GLOBAL_ASSISTANT_TOOLS,
|
|
22
|
+
OPENAPI_CALLABALE_FUNCTIONS,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def read_from_google_drive(path):
|
|
27
|
+
return await get_file_content_from_googledrive_by_name(file_name=path)
|
|
28
|
+
|
|
29
|
+
# Function to get headers for OpenAPI tools
|
|
30
|
+
def get_headers(toolname):
|
|
31
|
+
headers = OPENAPI_TOOL_CONFIGURATIONS.get(toolname, {}).get("headers", {})
|
|
32
|
+
return headers
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_params(toolname):
|
|
36
|
+
params = OPENAPI_TOOL_CONFIGURATIONS.get(toolname, {}).get("params", {})
|
|
37
|
+
return params
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def run_assistant(client, assistant, thread, prompt, response_type, allowed_tools):
|
|
41
|
+
"""
|
|
42
|
+
Runs the assistant with the given parameters.
|
|
43
|
+
"""
|
|
44
|
+
await send_initial_message(client, thread, prompt)
|
|
45
|
+
allowed_tool_items = get_allowed_tool_items(allowed_tools)
|
|
46
|
+
response_format = get_response_format(response_type)
|
|
47
|
+
|
|
48
|
+
run = client.beta.threads.runs.create_and_poll(
|
|
49
|
+
thread_id=thread.id,
|
|
50
|
+
assistant_id=assistant.id,
|
|
51
|
+
response_format=response_format,
|
|
52
|
+
tools=allowed_tool_items,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
max_iterations = 5
|
|
56
|
+
iteration_count = 0
|
|
57
|
+
|
|
58
|
+
while run.status == 'requires_action':
|
|
59
|
+
if iteration_count >= max_iterations:
|
|
60
|
+
print("Exceeded maximum number of iterations for requires_action.")
|
|
61
|
+
return "Error: Exceeded maximum number of iterations for requires_action."
|
|
62
|
+
|
|
63
|
+
tool_outputs = await handle_required_action(run)
|
|
64
|
+
if tool_outputs:
|
|
65
|
+
run = await submit_tool_outputs(client, thread, run, tool_outputs)
|
|
66
|
+
|
|
67
|
+
iteration_count += 1
|
|
68
|
+
|
|
69
|
+
return await handle_run_completion(client, thread, run)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def send_initial_message(client, thread, prompt):
|
|
73
|
+
client.beta.threads.messages.create(
|
|
74
|
+
thread_id=thread.id,
|
|
75
|
+
role="user",
|
|
76
|
+
content=prompt,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_allowed_tool_items(allowed_tools):
|
|
81
|
+
allowed_tool_items = [
|
|
82
|
+
tool for tool in GLOBAL_ASSISTANT_TOOLS
|
|
83
|
+
if tool['type'] == 'function' and tool['function']['name'] in allowed_tools
|
|
84
|
+
]
|
|
85
|
+
allowed_tool_items.extend([
|
|
86
|
+
tool for tool in OPENAPI_GLOBAL_ASSISTANT_TOOLS
|
|
87
|
+
if tool['type'] == 'function' and tool['function']['name'] in allowed_tools
|
|
88
|
+
])
|
|
89
|
+
return allowed_tool_items
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_response_format(response_type):
|
|
93
|
+
return {
|
|
94
|
+
'type': 'json_schema',
|
|
95
|
+
'json_schema': {
|
|
96
|
+
"name": response_type.__name__,
|
|
97
|
+
"schema": response_type.model_json_schema()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def handle_required_action(run):
|
|
103
|
+
tool_outputs = []
|
|
104
|
+
current_batch_size = 0
|
|
105
|
+
max_batch_size = 256 * 1024 # 256 KB
|
|
106
|
+
|
|
107
|
+
if hasattr(run, 'required_action') and hasattr(run.required_action, 'submit_tool_outputs'):
|
|
108
|
+
for tool in run.required_action.submit_tool_outputs.tool_calls:
|
|
109
|
+
function, openai_function = get_function(tool.function.name)
|
|
110
|
+
if function:
|
|
111
|
+
output_str, output_size = await invoke_function(function, tool, openai_function)
|
|
112
|
+
if current_batch_size + output_size > max_batch_size:
|
|
113
|
+
tool_outputs.append(
|
|
114
|
+
{"tool_call_id": tool.id, "output": ""})
|
|
115
|
+
else:
|
|
116
|
+
tool_outputs.append(
|
|
117
|
+
{"tool_call_id": tool.id, "output": output_str})
|
|
118
|
+
current_batch_size += output_size
|
|
119
|
+
else:
|
|
120
|
+
print(f"Function {tool.function.name} not found.")
|
|
121
|
+
tool_outputs.append(
|
|
122
|
+
{"tool_call_id": tool.id, "output": "No results found"})
|
|
123
|
+
|
|
124
|
+
return tool_outputs
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def get_function(function_name):
|
|
128
|
+
function = GLOBAL_TOOLS_FUNCTIONS.get(function_name)
|
|
129
|
+
openai_function = False
|
|
130
|
+
if not function:
|
|
131
|
+
function = OPENAPI_CALLABALE_FUNCTIONS.get(function_name)
|
|
132
|
+
openai_function = True
|
|
133
|
+
return function, openai_function
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def invoke_function(function, tool, openai_function):
|
|
137
|
+
try:
|
|
138
|
+
function_args = json.loads(tool.function.arguments)
|
|
139
|
+
print(f"Invoking function {tool.function.name} with args: {function_args}\n")
|
|
140
|
+
|
|
141
|
+
if openai_function:
|
|
142
|
+
output = await invoke_openai_function(function, function_args, tool.function.name)
|
|
143
|
+
else:
|
|
144
|
+
if asyncio.iscoroutinefunction(function):
|
|
145
|
+
output = await function(**function_args)
|
|
146
|
+
else:
|
|
147
|
+
output = function(**function_args)
|
|
148
|
+
output_str = json.dumps(output)
|
|
149
|
+
output_size = len(output_str.encode('utf-8'))
|
|
150
|
+
print(f"\nOutput from function {tool.function.name}: {output_str[:256]}\n")
|
|
151
|
+
|
|
152
|
+
return output_str, output_size
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"Error invoking function {tool.function.name}: {e}")
|
|
155
|
+
return "No results found", 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def invoke_openai_function(function, function_args, function_name):
|
|
159
|
+
|
|
160
|
+
json_body = function_args.get("json", None)
|
|
161
|
+
path_params = function_args.get("path_params", None)
|
|
162
|
+
fn_args = {"path_params": path_params, "data": json_body}
|
|
163
|
+
headers = get_headers(function_name)
|
|
164
|
+
|
|
165
|
+
query_params = function_args.get("params", {})
|
|
166
|
+
params = get_params(function_name)
|
|
167
|
+
query_params.update(params)
|
|
168
|
+
if asyncio.iscoroutinefunction(function):
|
|
169
|
+
output_fn = await function(
|
|
170
|
+
name=function_name,
|
|
171
|
+
fn_args=fn_args,
|
|
172
|
+
headers=headers,
|
|
173
|
+
params=query_params,
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
output_fn = function(
|
|
177
|
+
name=function_name,
|
|
178
|
+
fn_args=fn_args,
|
|
179
|
+
headers=headers,
|
|
180
|
+
params=query_params,
|
|
181
|
+
)
|
|
182
|
+
print(f"\nOutput from function {function_name}: {output_fn.status_code} {output_fn.reason}\n")
|
|
183
|
+
return {
|
|
184
|
+
"status_code": output_fn.status_code,
|
|
185
|
+
"text": output_fn.text,
|
|
186
|
+
"reason": output_fn.reason,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def submit_tool_outputs(client, thread, run, tool_outputs):
|
|
191
|
+
try:
|
|
192
|
+
return client.beta.threads.runs.submit_tool_outputs_and_poll(
|
|
193
|
+
thread_id=thread.id,
|
|
194
|
+
run_id=run.id,
|
|
195
|
+
tool_outputs=tool_outputs
|
|
196
|
+
)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
print("Failed to submit tool outputs:", e)
|
|
199
|
+
return run
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def handle_run_completion(client, thread, run):
|
|
203
|
+
if run.status == 'completed':
|
|
204
|
+
messages = client.beta.threads.messages.list(thread_id=thread.id)
|
|
205
|
+
return messages.data[0].content[0].text.value
|
|
206
|
+
else:
|
|
207
|
+
print("Failed to run assistant:", run.status)
|
|
208
|
+
return run.status
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def extract_and_structure_data(client, assistant, thread, prompt, task_inputs, response_type, allowed_tools):
|
|
212
|
+
# Replace placeholders in the prompt with task inputs
|
|
213
|
+
formatted_prompt = prompt
|
|
214
|
+
for key, value in task_inputs.items():
|
|
215
|
+
placeholder = "{{ inputs." + key + " }}"
|
|
216
|
+
formatted_prompt = formatted_prompt.replace(placeholder, str(value))
|
|
217
|
+
output = await run_assistant(client, assistant, thread, formatted_prompt, response_type, allowed_tools)
|
|
218
|
+
return output
|
|
219
|
+
|
|
220
|
+
class RowItem(BaseModel):
|
|
221
|
+
column_value: str
|
|
222
|
+
|
|
223
|
+
class ResponseList(BaseModel):
|
|
224
|
+
rows: List[RowItem]
|
|
225
|
+
|
|
226
|
+
def lookup_response_type(name: str):
|
|
227
|
+
for model in GLOBAL_DATA_MODELS:
|
|
228
|
+
if model.__name__ == name:
|
|
229
|
+
return model
|
|
230
|
+
return ResponseList # Default response type
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def process_agent_request(row_batch: List[Dict], workflow: Dict, custom_instructions: str) -> List[Dict]:
|
|
234
|
+
"""
|
|
235
|
+
Process agent request using the OpenAI client.
|
|
236
|
+
"""
|
|
237
|
+
try:
|
|
238
|
+
client = OpenAI()
|
|
239
|
+
assistant = client.beta.assistants.create(
|
|
240
|
+
name="AI Assistant",
|
|
241
|
+
instructions=f"Hi, You are an AI Assistant. Help the user with their tasks.\n\n{custom_instructions}\n\n",
|
|
242
|
+
tools=[],
|
|
243
|
+
model="gpt-4o-2024-08-06"
|
|
244
|
+
)
|
|
245
|
+
thread = client.beta.threads.create()
|
|
246
|
+
|
|
247
|
+
parsed_outputs = []
|
|
248
|
+
for row in row_batch:
|
|
249
|
+
try:
|
|
250
|
+
task_outputs = {} # Dictionary to store outputs of tasks
|
|
251
|
+
input_list = {}
|
|
252
|
+
input_list['initial_input_list'] = {
|
|
253
|
+
"data": [row],
|
|
254
|
+
"format": "list"
|
|
255
|
+
}
|
|
256
|
+
task_outputs['initial_input'] = input_list
|
|
257
|
+
for task in workflow['tasks']:
|
|
258
|
+
# Process each task
|
|
259
|
+
task_outputs = await process_task(client, assistant, thread, row, task, task_outputs)
|
|
260
|
+
# Collect the final output
|
|
261
|
+
parsed_outputs.append(task_outputs)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
print(f"Error processing row {row}: {e}")
|
|
264
|
+
return parsed_outputs
|
|
265
|
+
except Exception as e:
|
|
266
|
+
print(f"An error occurred: {e}")
|
|
267
|
+
return "Error Processing Leads"
|
|
268
|
+
finally:
|
|
269
|
+
try:
|
|
270
|
+
client.beta.assistants.delete(assistant.id)
|
|
271
|
+
except Exception as e:
|
|
272
|
+
print(f"Error deleting assistant: {e}")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
async def process_task(client, assistant, thread, row, task, task_outputs):
|
|
276
|
+
"""
|
|
277
|
+
Process a single task in the workflow.
|
|
278
|
+
"""
|
|
279
|
+
try:
|
|
280
|
+
# Prepare inputs
|
|
281
|
+
task_inputs = await prepare_task_inputs(row, task, task_outputs)
|
|
282
|
+
|
|
283
|
+
# Run the operation
|
|
284
|
+
output = await run_task_operation(client, assistant, thread, task, task_inputs)
|
|
285
|
+
|
|
286
|
+
# Store outputs
|
|
287
|
+
await store_task_outputs(task, output, task_outputs)
|
|
288
|
+
|
|
289
|
+
return task_outputs
|
|
290
|
+
except Exception as e:
|
|
291
|
+
print(f"Error processing task {task['id']}: {e}")
|
|
292
|
+
return task_outputs
|
|
293
|
+
|
|
294
|
+
async def read_csv_rows(file_path):
|
|
295
|
+
rows = []
|
|
296
|
+
with open(file_path, mode='r') as file:
|
|
297
|
+
csv_reader = csv.reader(file)
|
|
298
|
+
for row in csv_reader:
|
|
299
|
+
rows.append(row)
|
|
300
|
+
return rows
|
|
301
|
+
|
|
302
|
+
async def prepare_task_inputs(row, task, task_outputs):
|
|
303
|
+
"""
|
|
304
|
+
Prepare the inputs for a task based on its input specifications.
|
|
305
|
+
"""
|
|
306
|
+
inputs = task.get('inputs', {})
|
|
307
|
+
task_inputs = {}
|
|
308
|
+
for input_name, input_spec in inputs.items():
|
|
309
|
+
source = input_spec.get('source', {})
|
|
310
|
+
source_type = source.get('type', '')
|
|
311
|
+
format = input_spec.get('format', 'list')
|
|
312
|
+
if source_type == 'external':
|
|
313
|
+
# External source, get from initial input
|
|
314
|
+
input_data = row.get(input_name, row)
|
|
315
|
+
elif source_type == 'task_output':
|
|
316
|
+
# Get from previous task output
|
|
317
|
+
task_id = source.get('task_id')
|
|
318
|
+
output_key = source.get('output_key')
|
|
319
|
+
previous_task_output = task_outputs.get(task_id, {})
|
|
320
|
+
print(f"Previous task output: {previous_task_output} Output key: {output_key}")
|
|
321
|
+
if isinstance(previous_task_output, dict):
|
|
322
|
+
output_item = previous_task_output.get(output_key)
|
|
323
|
+
input_data = output_item['data']
|
|
324
|
+
else:
|
|
325
|
+
input_data = previous_task_output
|
|
326
|
+
|
|
327
|
+
# Ensure input_data is a list
|
|
328
|
+
if not isinstance(input_data, list):
|
|
329
|
+
input_data = [input_data]
|
|
330
|
+
elif source_type == 'google_drive':
|
|
331
|
+
# Handle Google Drive source
|
|
332
|
+
path = source.get('location')
|
|
333
|
+
input_data_path = await read_from_google_drive(path)
|
|
334
|
+
input_data = await read_csv_rows(input_data_path)
|
|
335
|
+
elif source_type == 'local_path':
|
|
336
|
+
# Handle local path source
|
|
337
|
+
input_data_path = source.get('location')
|
|
338
|
+
input_data = await read_csv_rows(input_data_path)
|
|
339
|
+
else:
|
|
340
|
+
input_data = None
|
|
341
|
+
if input_data:
|
|
342
|
+
task_inputs[input_name] = {
|
|
343
|
+
"format": format,
|
|
344
|
+
"data" : input_data
|
|
345
|
+
}
|
|
346
|
+
return task_inputs
|
|
347
|
+
|
|
348
|
+
async def run_task_operation(client, assistant, thread, task, task_inputs):
|
|
349
|
+
"""
|
|
350
|
+
Execute the operation defined in the task.
|
|
351
|
+
"""
|
|
352
|
+
operation = task.get('operation', {})
|
|
353
|
+
operation_type = operation.get('type', '')
|
|
354
|
+
allowed_tools = operation.get('allowed_tools', [])
|
|
355
|
+
response_type_name = operation.get('response_type', 'ResponseList')
|
|
356
|
+
response_type = lookup_response_type(response_type_name)
|
|
357
|
+
outputs = []
|
|
358
|
+
|
|
359
|
+
if operation_type == 'ai_assistant_call':
|
|
360
|
+
prompt_template = operation.get('prompt', '')
|
|
361
|
+
args = operation.get('args', [])
|
|
362
|
+
# Prepare prompt by substituting inputs
|
|
363
|
+
|
|
364
|
+
formatted_prompt = prompt_template
|
|
365
|
+
for key, value in task_inputs.items():
|
|
366
|
+
format = value.get('format', 'list')
|
|
367
|
+
if format == 'list':
|
|
368
|
+
for item in value.get('data'):
|
|
369
|
+
formatted_prompt = formatted_prompt.replace(
|
|
370
|
+
"{{ inputs." + key + " }}", json.dumps(item))
|
|
371
|
+
# Run assistant with prompt
|
|
372
|
+
output = await extract_and_structure_data(
|
|
373
|
+
client, assistant, thread, formatted_prompt, task_inputs, response_type, allowed_tools)
|
|
374
|
+
outputs.append(output)
|
|
375
|
+
else:
|
|
376
|
+
pass # TODO: Handle other formats
|
|
377
|
+
elif operation_type == 'python_callable':
|
|
378
|
+
function_name = operation.get('function', '')
|
|
379
|
+
args = operation.get('args', [])
|
|
380
|
+
function = globals().get(function_name)
|
|
381
|
+
if function is None:
|
|
382
|
+
raise Exception(f"Function {function_name} not found.")
|
|
383
|
+
# Prepare function arguments
|
|
384
|
+
function_args = [task_inputs.get(arg) for arg in args]
|
|
385
|
+
# Call the function
|
|
386
|
+
if asyncio.iscoroutinefunction(function):
|
|
387
|
+
output = await function(*function_args)
|
|
388
|
+
else:
|
|
389
|
+
output = function(*function_args)
|
|
390
|
+
outputs.append(output)
|
|
391
|
+
else:
|
|
392
|
+
# Handle other operation types
|
|
393
|
+
output = None
|
|
394
|
+
return_val = {
|
|
395
|
+
"data": outputs,
|
|
396
|
+
"format": "list"
|
|
397
|
+
}
|
|
398
|
+
return return_val
|
|
399
|
+
|
|
400
|
+
async def store_task_outputs(task, output, task_outputs):
|
|
401
|
+
"""
|
|
402
|
+
Store the outputs of a task for use in subsequent tasks.
|
|
403
|
+
"""
|
|
404
|
+
outputs = task.get('outputs', {})
|
|
405
|
+
if outputs:
|
|
406
|
+
for output_name, output_spec in outputs.items():
|
|
407
|
+
# Store output in task_outputs using task id and output_name
|
|
408
|
+
if task['id'] not in task_outputs:
|
|
409
|
+
task_outputs[task['id']] = {}
|
|
410
|
+
|
|
411
|
+
destination = output_spec.get('destination', {})
|
|
412
|
+
if destination:
|
|
413
|
+
dest_type = destination.get('type')
|
|
414
|
+
path_template = destination.get('path_template')
|
|
415
|
+
if path_template:
|
|
416
|
+
current_timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
|
|
417
|
+
path = path_template.replace('{timestamp}', current_timestamp)
|
|
418
|
+
path = path.replace('{task_id}', task['id'])
|
|
419
|
+
local_path = path
|
|
420
|
+
|
|
421
|
+
if dest_type == 'google_drive':
|
|
422
|
+
local_path = os.path.join('/tmp', path)
|
|
423
|
+
|
|
424
|
+
if dest_type == 'google_drive' or dest_type == 'local_path':
|
|
425
|
+
directory = os.path.dirname(local_path)
|
|
426
|
+
if directory and not os.path.exists(directory):
|
|
427
|
+
os.makedirs(directory)
|
|
428
|
+
with open(local_path, 'w') as file:
|
|
429
|
+
if output.get("format", "") == 'list':
|
|
430
|
+
data_list = [json.loads(item) for item in output.get("data", [])]
|
|
431
|
+
if data_list:
|
|
432
|
+
# Filter headers to include only simple types
|
|
433
|
+
headers = [key for key, value in data_list[0].items() if isinstance(value, (str, int, float, bool))]
|
|
434
|
+
writer = csv.DictWriter(file, fieldnames=headers)
|
|
435
|
+
writer.writeheader()
|
|
436
|
+
for data in data_list:
|
|
437
|
+
filtered_data = {key: value for key, value in data.items() if key in headers}
|
|
438
|
+
writer.writerow(filtered_data)
|
|
439
|
+
else:
|
|
440
|
+
file.write(str(output))
|
|
441
|
+
else:
|
|
442
|
+
# Ignore if destination type is not google_drive or local_path
|
|
443
|
+
pass
|
|
444
|
+
|
|
445
|
+
if dest_type == 'google_drive':
|
|
446
|
+
await write_to_google_drive(path, local_path)
|
|
447
|
+
|
|
448
|
+
task_outputs[task['id']][output_name] = output
|
|
449
|
+
else:
|
|
450
|
+
# If no outputs are defined, store the output under the task id
|
|
451
|
+
task_outputs[task['id']] = output
|
|
452
|
+
|
|
453
|
+
async def write_to_google_drive(cloud_path, local_path):
|
|
454
|
+
# Placeholder function for writing to Google Drive
|
|
455
|
+
await write_content_to_googledrive(cloud_path, local_path)
|
|
456
|
+
print(f"Writing to Google Drive at {cloud_path} {local_path}")
|
|
457
|
+
|
|
458
|
+
def get_structured_output(message: str, response_type):
|
|
459
|
+
try:
|
|
460
|
+
client = OpenAI()
|
|
461
|
+
completion = client.beta.chat.completions.parse(
|
|
462
|
+
model="gpt-4o-2024-08-06",
|
|
463
|
+
messages=[
|
|
464
|
+
{"role": "system", "content": "Extract structured content from input. Output is in JSON Format."},
|
|
465
|
+
{"role": "user", "content": message},
|
|
466
|
+
],
|
|
467
|
+
response_format=response_type,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
response = completion.choices[0].message
|
|
471
|
+
if response.parsed:
|
|
472
|
+
return response.parsed, 'SUCCESS'
|
|
473
|
+
elif response.refusal:
|
|
474
|
+
logging.warning("ERROR: Refusal response: %s", response.refusal)
|
|
475
|
+
return response.refusal, 'FAIL'
|
|
476
|
+
|
|
477
|
+
except LengthFinishReasonError as e:
|
|
478
|
+
logging.error(f"Too many tokens: {e}")
|
|
479
|
+
raise HTTPException(status_code=502, detail="The request exceeded the maximum token limit.")
|
|
480
|
+
except OpenAIError as e:
|
|
481
|
+
logging.error(f"OpenAI API error: {e}")
|
|
482
|
+
raise HTTPException(status_code=502, detail="Error communicating with the OpenAI API.")
|
|
483
|
+
except Exception as e:
|
|
484
|
+
logging.error(f"Unexpected error: {e}")
|
|
485
|
+
raise HTTPException(status_code=500, detail="An unexpected error occurred while processing your request.")
|
|
@@ -12,6 +12,7 @@ OPENAPI_GLOBAL_ASSISTANT_TOOLS = []
|
|
|
12
12
|
OPENAPI_CALLABALE_FUNCTIONS = {}
|
|
13
13
|
OPENAPI_TOOL_CONFIGURATIONS = {}
|
|
14
14
|
|
|
15
|
+
|
|
15
16
|
def convert_spec_to_tools(file_path: str):
|
|
16
17
|
# Open the file and load spec from there
|
|
17
18
|
with open(file_path, 'r') as file:
|
|
@@ -20,7 +21,9 @@ def convert_spec_to_tools(file_path: str):
|
|
|
20
21
|
openai_fns, call_api_fn = openapi_spec_to_openai_fn(spec)
|
|
21
22
|
return openai_fns, call_api_fn
|
|
22
23
|
|
|
23
|
-
# Parse and save the OpenAI Tools parsed and the corresponding callable functions
|
|
24
|
+
# Parse and save the OpenAI Tools parsed and the corresponding callable functions
|
|
25
|
+
|
|
26
|
+
|
|
24
27
|
def add_openapi_spec_to_tools_list(file_path: str):
|
|
25
28
|
openai_fns, call_api_fn = convert_spec_to_tools(file_path)
|
|
26
29
|
if (len(openai_fns) > 0):
|
|
@@ -28,6 +31,5 @@ def add_openapi_spec_to_tools_list(file_path: str):
|
|
|
28
31
|
for fn in openai_fns:
|
|
29
32
|
name = fn["function"]["name"]
|
|
30
33
|
if name:
|
|
31
|
-
OPENAPI_CALLABALE_FUNCTIONS[name]= call_api_fn
|
|
34
|
+
OPENAPI_CALLABALE_FUNCTIONS[name] = call_api_fn
|
|
32
35
|
return OPENAPI_GLOBAL_ASSISTANT_TOOLS
|
|
33
|
-
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
# Helper functions to call OpenAI Assistant
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
import json
|
|
6
|
-
from typing import Dict, List
|
|
7
|
-
|
|
8
|
-
from fastapi import HTTPException
|
|
9
|
-
from openai import LengthFinishReasonError, OpenAI, OpenAIError
|
|
10
|
-
from pydantic import BaseModel
|
|
11
|
-
|
|
12
|
-
from dhisana.utils.agent_tools import GLOBAL_DATA_MODELS, GLOBAL_TOOLS_FUNCTIONS
|
|
13
|
-
from .tools_json import GLOBAL_ASSISTANT_TOOLS
|
|
14
|
-
from .openapi_spec_to_tools import OPENAPI_TOOL_CONFIGURATIONS, OPENAPI_GLOBAL_ASSISTANT_TOOLS, OPENAPI_CALLABALE_FUNCTIONS
|
|
15
|
-
|
|
16
|
-
def get_headers(toolname):
|
|
17
|
-
headers = OPENAPI_TOOL_CONFIGURATIONS.get(toolname, {}).get("headers", {})
|
|
18
|
-
return headers
|
|
19
|
-
|
|
20
|
-
def get_params(toolname):
|
|
21
|
-
headers = OPENAPI_TOOL_CONFIGURATIONS.get(toolname, {}).get("params", {})
|
|
22
|
-
return headers
|
|
23
|
-
|
|
24
|
-
async def run_assistant(client, assistant, thread, prompt, response_type, allowed_tools):
|
|
25
|
-
"""
|
|
26
|
-
Runs the assistant with the given parameters.
|
|
27
|
-
"""
|
|
28
|
-
send_initial_message(client, thread, prompt)
|
|
29
|
-
allowed_tool_items = get_allowed_tool_items(allowed_tools)
|
|
30
|
-
response_format = get_response_format(response_type)
|
|
31
|
-
|
|
32
|
-
run = client.beta.threads.runs.create_and_poll(
|
|
33
|
-
thread_id=thread.id,
|
|
34
|
-
assistant_id=assistant.id,
|
|
35
|
-
response_format=response_format,
|
|
36
|
-
tools=allowed_tool_items,
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
max_iterations = 5
|
|
40
|
-
iteration_count = 0
|
|
41
|
-
|
|
42
|
-
while run.status == 'requires_action':
|
|
43
|
-
if iteration_count >= max_iterations:
|
|
44
|
-
print("Exceeded maximum number of iterations for requires_action.")
|
|
45
|
-
return "Error: Exceeded maximum number of iterations for requires_action."
|
|
46
|
-
|
|
47
|
-
tool_outputs = await handle_required_action(run)
|
|
48
|
-
if tool_outputs:
|
|
49
|
-
run = submit_tool_outputs(client, thread, run, tool_outputs)
|
|
50
|
-
|
|
51
|
-
iteration_count += 1
|
|
52
|
-
|
|
53
|
-
return handle_run_completion(client, thread, run)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def send_initial_message(client, thread, prompt):
|
|
57
|
-
client.beta.threads.messages.create(
|
|
58
|
-
thread_id=thread.id,
|
|
59
|
-
role="user",
|
|
60
|
-
content=prompt,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def get_allowed_tool_items(allowed_tools):
|
|
65
|
-
allowed_tool_items = [
|
|
66
|
-
tool for tool in GLOBAL_ASSISTANT_TOOLS
|
|
67
|
-
if tool['type'] == 'function' and tool['function']['name'] in allowed_tools
|
|
68
|
-
]
|
|
69
|
-
allowed_tool_items.extend([
|
|
70
|
-
tool for tool in OPENAPI_GLOBAL_ASSISTANT_TOOLS
|
|
71
|
-
if tool['type'] == 'function' and tool['function']['name'] in allowed_tools
|
|
72
|
-
])
|
|
73
|
-
return allowed_tool_items
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def get_response_format(response_type):
|
|
77
|
-
return {
|
|
78
|
-
'type': 'json_schema',
|
|
79
|
-
'json_schema': {
|
|
80
|
-
"name": response_type.__class__.__name__,
|
|
81
|
-
"schema": response_type.model_json_schema()
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
async def handle_required_action(run):
|
|
87
|
-
tool_outputs = []
|
|
88
|
-
current_batch_size = 0
|
|
89
|
-
max_batch_size = 256 * 1024
|
|
90
|
-
|
|
91
|
-
if hasattr(run, 'required_action') and hasattr(run.required_action, 'submit_tool_outputs'):
|
|
92
|
-
for tool in run.required_action.submit_tool_outputs.tool_calls:
|
|
93
|
-
function, openai_function = get_function(tool.function.name)
|
|
94
|
-
if function:
|
|
95
|
-
output_str, output_size = await invoke_function(function, tool, openai_function)
|
|
96
|
-
if current_batch_size + output_size > max_batch_size:
|
|
97
|
-
tool_outputs.append({"tool_call_id": tool.id, "output": ""})
|
|
98
|
-
else:
|
|
99
|
-
tool_outputs.append({"tool_call_id": tool.id, "output": output_str})
|
|
100
|
-
current_batch_size += output_size
|
|
101
|
-
else:
|
|
102
|
-
print(f"Function {tool.function.name} not found.")
|
|
103
|
-
tool_outputs.append({"tool_call_id": tool.id, "output": "No results found"})
|
|
104
|
-
|
|
105
|
-
return tool_outputs
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def get_function(function_name):
|
|
109
|
-
function = GLOBAL_TOOLS_FUNCTIONS.get(function_name)
|
|
110
|
-
openai_function = False
|
|
111
|
-
if not function:
|
|
112
|
-
function = OPENAPI_CALLABALE_FUNCTIONS.get(function_name)
|
|
113
|
-
openai_function = True
|
|
114
|
-
return function, openai_function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
async def invoke_function(function, tool, openai_function):
|
|
118
|
-
try:
|
|
119
|
-
function_args = json.loads(tool.function.arguments)
|
|
120
|
-
print(f"Invoking function {tool.function.name} with args: {function_args}\n")
|
|
121
|
-
if openai_function:
|
|
122
|
-
output = invoke_openai_function(function, function_args, tool.function.name)
|
|
123
|
-
else:
|
|
124
|
-
output = await function(**function_args)
|
|
125
|
-
output_str = json.dumps(output)
|
|
126
|
-
output_size = len(output_str.encode('utf-8'))
|
|
127
|
-
print(f"\nOutput from function {tool.function.name}: {output_str[:64]}\n")
|
|
128
|
-
return output_str, output_size
|
|
129
|
-
except Exception as e:
|
|
130
|
-
print(f"Error invoking function {tool.function.name}: {e}")
|
|
131
|
-
return "No results found", 0
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def invoke_openai_function(function, function_args, function_name):
|
|
135
|
-
|
|
136
|
-
json_body = function_args.get("json", None)
|
|
137
|
-
path_params = function_args.get("path_params", None)
|
|
138
|
-
fn_args = {"path_params": path_params, "data": json_body}
|
|
139
|
-
headers = get_headers(function_name)
|
|
140
|
-
|
|
141
|
-
query_params = function_args.get("params", {})
|
|
142
|
-
params = get_params(function_name)
|
|
143
|
-
query_params.update(params)
|
|
144
|
-
|
|
145
|
-
output_fn = function(
|
|
146
|
-
name=function_name,
|
|
147
|
-
fn_args=fn_args,
|
|
148
|
-
headers=headers,
|
|
149
|
-
params=query_params,
|
|
150
|
-
)
|
|
151
|
-
print(f"\nOutput from function {function_name}: {output_fn.status_code} {output_fn.reason}\n")
|
|
152
|
-
return {
|
|
153
|
-
"status_code": output_fn.status_code,
|
|
154
|
-
"text": output_fn.text,
|
|
155
|
-
"reason": output_fn.reason,
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def submit_tool_outputs(client, thread, run, tool_outputs):
|
|
160
|
-
try:
|
|
161
|
-
return client.beta.threads.runs.submit_tool_outputs_and_poll(
|
|
162
|
-
thread_id=thread.id,
|
|
163
|
-
run_id=run.id,
|
|
164
|
-
tool_outputs=tool_outputs
|
|
165
|
-
)
|
|
166
|
-
except Exception as e:
|
|
167
|
-
print("Failed to submit tool outputs:", e)
|
|
168
|
-
return run
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def handle_run_completion(client, thread, run):
|
|
172
|
-
if run.status == 'completed':
|
|
173
|
-
messages = client.beta.threads.messages.list(thread_id=thread.id)
|
|
174
|
-
return messages.data[0].content[0].text.value
|
|
175
|
-
else:
|
|
176
|
-
print("Failed to run assistant:", run.status)
|
|
177
|
-
return run.status
|
|
178
|
-
|
|
179
|
-
async def extract_and_structure_data(client, assistant, thread, prompt, user_provider_data, response_type, allowed_tools):
|
|
180
|
-
formatted_prompt = prompt.format(input=user_provider_data)
|
|
181
|
-
output = await run_assistant(client, assistant, thread, formatted_prompt, response_type, allowed_tools)
|
|
182
|
-
return output
|
|
183
|
-
|
|
184
|
-
# Function to get structured output from OpenAI API
|
|
185
|
-
def get_structured_output(message: str, response_type):
|
|
186
|
-
try:
|
|
187
|
-
client = OpenAI()
|
|
188
|
-
completion = client.beta.chat.completions.parse(
|
|
189
|
-
model="gpt-4o-2024-08-06",
|
|
190
|
-
messages=[
|
|
191
|
-
{"role": "system", "content": "Extract structured content from input. Output is in JSON Format."},
|
|
192
|
-
{"role": "user", "content": message},
|
|
193
|
-
],
|
|
194
|
-
response_format=response_type,
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
response = completion.choices[0].message
|
|
198
|
-
if response.parsed:
|
|
199
|
-
return response.parsed, 'SUCCESS'
|
|
200
|
-
elif response.refusal:
|
|
201
|
-
logging.warning("ERROR: Refusal response: %s", response.refusal)
|
|
202
|
-
return response.refusal, 'FAIL'
|
|
203
|
-
|
|
204
|
-
except LengthFinishReasonError as e:
|
|
205
|
-
logging.error(f"Too many tokens: {e}")
|
|
206
|
-
raise HTTPException(status_code=502, detail="The request exceeded the maximum token limit.")
|
|
207
|
-
except OpenAIError as e:
|
|
208
|
-
logging.error(f"OpenAI API error: {e}")
|
|
209
|
-
raise HTTPException(status_code=502, detail="Error communicating with the OpenAI API.")
|
|
210
|
-
except Exception as e:
|
|
211
|
-
logging.error(f"Unexpected error: {e}")
|
|
212
|
-
raise HTTPException(status_code=500, detail="An unexpected error occurred while processing your request.")
|
|
213
|
-
|
|
214
|
-
class RowItem(BaseModel):
|
|
215
|
-
column_value: str
|
|
216
|
-
|
|
217
|
-
class ResponseList(BaseModel):
|
|
218
|
-
rows: List[RowItem]
|
|
219
|
-
|
|
220
|
-
def lookup_response_type(name: str):
|
|
221
|
-
for model in GLOBAL_DATA_MODELS:
|
|
222
|
-
if model.__name__ == name:
|
|
223
|
-
return model
|
|
224
|
-
return None
|
|
225
|
-
|
|
226
|
-
# Function to process a batch request
|
|
227
|
-
async def process_agent_request(row_batch: List[Dict], steps: Dict, custom_instructions: str) -> List[Dict]:
|
|
228
|
-
"""
|
|
229
|
-
Process agent request using the OpenAI client.
|
|
230
|
-
"""
|
|
231
|
-
try:
|
|
232
|
-
client = OpenAI()
|
|
233
|
-
assistant = client.beta.assistants.create(
|
|
234
|
-
name="AI Assistant",
|
|
235
|
-
instructions=f"Hi, You are an AI Assistant. Help the user with their tasks.\n\n{custom_instructions}\n\n",
|
|
236
|
-
tools=[],
|
|
237
|
-
model="gpt-4o-2024-08-06"
|
|
238
|
-
)
|
|
239
|
-
thread = client.beta.threads.create()
|
|
240
|
-
|
|
241
|
-
parsed_outputs = []
|
|
242
|
-
for row in row_batch:
|
|
243
|
-
try:
|
|
244
|
-
input_data = json.dumps(row)
|
|
245
|
-
output = {}
|
|
246
|
-
for step in steps['steps']:
|
|
247
|
-
type = step.get("response_type", None)
|
|
248
|
-
if not type:
|
|
249
|
-
type = "ResponseList"
|
|
250
|
-
response_type = ResponseList
|
|
251
|
-
else:
|
|
252
|
-
response_type = lookup_response_type(step.get("response_type", None))
|
|
253
|
-
if not response_type:
|
|
254
|
-
response_type = ResponseList
|
|
255
|
-
allowed_tools = step.get("allowed_tools", [])
|
|
256
|
-
output = await extract_and_structure_data(client, assistant, thread, step["prompt"], input_data, response_type, allowed_tools)
|
|
257
|
-
output_obj = json.loads(output)
|
|
258
|
-
if 'ID' in row:
|
|
259
|
-
output_obj['INPUT_ID'] = row['ID']
|
|
260
|
-
input_data = output
|
|
261
|
-
parsed_outputs.append(output_obj)
|
|
262
|
-
except Exception as e:
|
|
263
|
-
print(f"Error processing lead {row}: {e}")
|
|
264
|
-
return parsed_outputs
|
|
265
|
-
except Exception as e:
|
|
266
|
-
print(f"An error occurred: {e}")
|
|
267
|
-
return "Error Processing Leads"
|
|
268
|
-
finally:
|
|
269
|
-
try:
|
|
270
|
-
client.beta.assistants.delete(assistant.id)
|
|
271
|
-
except Exception as e:
|
|
272
|
-
print(f"Error deleting assistant: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|