sunholo 0.100.1__py3-none-any.whl → 0.100.2__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.
- sunholo/auth/refresh.py +2 -2
- sunholo/gcs/add_file.py +0 -2
- sunholo/gcs/download_url.py +8 -1
- sunholo/genai/process_funcs_cls.py +1 -2
- sunholo/invoke/async_class.py +61 -82
- {sunholo-0.100.1.dist-info → sunholo-0.100.2.dist-info}/METADATA +2 -2
- {sunholo-0.100.1.dist-info → sunholo-0.100.2.dist-info}/RECORD +11 -11
- {sunholo-0.100.1.dist-info → sunholo-0.100.2.dist-info}/LICENSE.txt +0 -0
- {sunholo-0.100.1.dist-info → sunholo-0.100.2.dist-info}/WHEEL +0 -0
- {sunholo-0.100.1.dist-info → sunholo-0.100.2.dist-info}/entry_points.txt +0 -0
- {sunholo-0.100.1.dist-info → sunholo-0.100.2.dist-info}/top_level.txt +0 -0
sunholo/auth/refresh.py
CHANGED
|
@@ -14,7 +14,7 @@ def get_default_email():
|
|
|
14
14
|
|
|
15
15
|
if gcs_credentials is None:
|
|
16
16
|
log.error("Could not refresh the credentials properly.")
|
|
17
|
-
return None
|
|
17
|
+
return None, None
|
|
18
18
|
|
|
19
19
|
service_account_email = getattr(gcs_credentials, 'service_account_email', None)
|
|
20
20
|
# If you use a service account credential, you can use the embedded email
|
|
@@ -23,7 +23,7 @@ def get_default_email():
|
|
|
23
23
|
if not service_account_email:
|
|
24
24
|
log.error("Could not create the credentials for signed requests - no credentials.service_account_email or GCS_MAIL_USER with roles/iam.serviceAccountTokenCreator")
|
|
25
25
|
|
|
26
|
-
return None
|
|
26
|
+
return None, None
|
|
27
27
|
|
|
28
28
|
log.info(f"Found default email: {service_account_email=} for {project_id=}")
|
|
29
29
|
return service_account_email, gcs_credentials
|
sunholo/gcs/add_file.py
CHANGED
sunholo/gcs/download_url.py
CHANGED
|
@@ -106,7 +106,14 @@ def sign_gcs_url(bucket_name:str, object_name:str, expiry_secs:int = 86400) -> O
|
|
|
106
106
|
Returns:
|
|
107
107
|
str: The signed URL or None if not avialable
|
|
108
108
|
"""
|
|
109
|
-
|
|
109
|
+
result = get_default_email()
|
|
110
|
+
|
|
111
|
+
# Check if the result is None
|
|
112
|
+
if result is None or any(item is None for item in result):
|
|
113
|
+
log.error("Failed to retrieve the service account email and credentials.")
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
service_account_email, gcs_credentials = result
|
|
110
117
|
|
|
111
118
|
expires = datetime.now() + timedelta(seconds=expiry_secs)
|
|
112
119
|
|
|
@@ -262,8 +262,7 @@ class GenAIFunctionProcessor:
|
|
|
262
262
|
# Execute the function with the provided parameters
|
|
263
263
|
result = fn_exec(**params_obj)
|
|
264
264
|
log.info(f"Got result from {function_name}: {result} of type: {type(result)}")
|
|
265
|
-
|
|
266
|
-
log.warning(f"Tool functions should return strings: {function_name} returned type: {type(result)}")
|
|
265
|
+
#TODO: return images
|
|
267
266
|
else:
|
|
268
267
|
fn_result = type(fn).to_dict(fn)
|
|
269
268
|
result = fn_result.get("result")
|
sunholo/invoke/async_class.py
CHANGED
|
@@ -9,68 +9,54 @@ class AsyncTaskRunner:
|
|
|
9
9
|
self.tasks = []
|
|
10
10
|
self.retry_enabled = retry_enabled
|
|
11
11
|
self.retry_kwargs = retry_kwargs or {}
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
def add_task(self, func: Callable[..., Any], *args: Any):
|
|
14
14
|
"""Adds a task to the list of tasks to be executed."""
|
|
15
15
|
log.info(f"Adding task: {func.__name__} with args: {args}")
|
|
16
16
|
self.tasks.append((func.__name__, func, args))
|
|
17
|
-
|
|
18
|
-
async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
17
|
+
|
|
18
|
+
async def run_async_as_completed(self, callback=None) -> AsyncGenerator[Dict[str, Any], None]:
|
|
19
19
|
"""
|
|
20
|
-
Runs all tasks concurrently and yields results
|
|
20
|
+
Runs all tasks concurrently and yields results as they complete, while periodically sending heartbeat messages.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
callback: The callback object that will receive heartbeat messages.
|
|
21
24
|
"""
|
|
22
|
-
log.info("Running tasks asynchronously and yielding results
|
|
23
|
-
queue = asyncio.Queue()
|
|
25
|
+
log.info("Running tasks asynchronously and yielding results as they complete")
|
|
24
26
|
tasks = {}
|
|
25
|
-
completed_tasks = set()
|
|
26
|
-
|
|
27
27
|
for name, func, args in self.tasks:
|
|
28
|
-
|
|
28
|
+
# Pass the callback down to _task_wrapper
|
|
29
|
+
coro = self._task_wrapper(name, func, args, callback)
|
|
29
30
|
task = asyncio.create_task(coro)
|
|
30
31
|
tasks[task] = name
|
|
32
|
+
|
|
33
|
+
log.info(f"Start async run with {len(self.tasks)} runners")
|
|
34
|
+
while tasks:
|
|
35
|
+
done, _ = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
|
|
36
|
+
for task in done:
|
|
37
|
+
name = tasks.pop(task)
|
|
38
|
+
try:
|
|
39
|
+
# func_name = message['func_name']; result = message['result']
|
|
40
|
+
result = await task
|
|
41
|
+
yield {'type':'task_complete', 'func_name': name, 'result': result}
|
|
42
|
+
except Exception as e:
|
|
43
|
+
log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
|
|
44
|
+
yield {'type':'task_error', 'func_name': name, 'error': f'{e}\n{traceback.format_exc()}'}
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
if not queue.empty():
|
|
34
|
-
message = await queue.get()
|
|
35
|
-
log.info(f"Found queue message: {message}")
|
|
36
|
-
# Ignore heartbeats from completed tasks
|
|
37
|
-
if message['type'] == 'heartbeat' and message['func_name'] in completed_tasks:
|
|
38
|
-
continue
|
|
39
|
-
yield message
|
|
40
|
-
else:
|
|
41
|
-
done, _ = await asyncio.wait(
|
|
42
|
-
list(tasks.keys()),
|
|
43
|
-
timeout=0.1,
|
|
44
|
-
return_when=asyncio.FIRST_COMPLETED
|
|
45
|
-
)
|
|
46
|
-
for task in done:
|
|
47
|
-
name = tasks.pop(task)
|
|
48
|
-
completed_tasks.add(name)
|
|
49
|
-
try:
|
|
50
|
-
result = await task
|
|
51
|
-
await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
|
|
52
|
-
except Exception as e:
|
|
53
|
-
log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
|
|
54
|
-
await queue.put({'type': 'task_error', 'func_name': name, 'error': e})
|
|
55
|
-
|
|
56
|
-
# Process any remaining messages in the queue
|
|
57
|
-
while not queue.empty():
|
|
58
|
-
message = await queue.get()
|
|
59
|
-
log.info(f"Found queue message: {message}")
|
|
60
|
-
if message['type'] == 'heartbeat' and message['func_name'] in completed_tasks:
|
|
61
|
-
continue
|
|
62
|
-
yield message
|
|
63
|
-
|
|
64
|
-
async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any, queue: asyncio.Queue) -> Any:
|
|
46
|
+
async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any, callback=None) -> Any:
|
|
65
47
|
"""Wraps the task function to process its output and handle retries, while managing heartbeat updates."""
|
|
66
48
|
async def run_func():
|
|
67
49
|
if asyncio.iscoroutinefunction(func):
|
|
50
|
+
# If the function is async, await it
|
|
68
51
|
return await func(*args)
|
|
69
52
|
else:
|
|
53
|
+
# If the function is sync, run it in a thread to prevent blocking
|
|
70
54
|
return await asyncio.to_thread(func, *args)
|
|
71
55
|
|
|
72
|
-
# Start the heartbeat task
|
|
73
|
-
heartbeat_task =
|
|
56
|
+
# Start the heartbeat task if a callback is provided
|
|
57
|
+
heartbeat_task = None
|
|
58
|
+
if callback:
|
|
59
|
+
heartbeat_task = asyncio.create_task(self._send_heartbeat(callback, name))
|
|
74
60
|
|
|
75
61
|
try:
|
|
76
62
|
if self.retry_enabled:
|
|
@@ -91,48 +77,41 @@ class AsyncTaskRunner:
|
|
|
91
77
|
raise
|
|
92
78
|
finally:
|
|
93
79
|
# Stop the heartbeat task
|
|
94
|
-
heartbeat_task
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
80
|
+
if heartbeat_task:
|
|
81
|
+
heartbeat_task.cancel()
|
|
82
|
+
try:
|
|
83
|
+
await heartbeat_task # Ensure the heartbeat task is properly canceled
|
|
84
|
+
except asyncio.CancelledError:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# Send a message indicating task completion and remove spinner
|
|
88
|
+
if callback:
|
|
89
|
+
completion_html = (
|
|
90
|
+
f"<script>"
|
|
91
|
+
f"document.getElementById('{name}-spinner').innerHTML = '✔️ Task {name} completed!';"
|
|
92
|
+
f"</script>"
|
|
93
|
+
)
|
|
94
|
+
await callback.async_on_llm_new_token(token=completion_html)
|
|
100
95
|
|
|
101
|
-
async def _send_heartbeat(self,
|
|
96
|
+
async def _send_heartbeat(self, callback, func_name, interval=2):
|
|
102
97
|
"""
|
|
103
|
-
Sends
|
|
98
|
+
Sends periodic heartbeat updates to indicate the task is still in progress.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
callback: The callback to notify that the task is still working.
|
|
102
|
+
func_name: The name of the task function.
|
|
103
|
+
interval: How frequently to send heartbeat messages (in seconds).
|
|
104
104
|
"""
|
|
105
105
|
# Send the initial spinner HTML
|
|
106
106
|
spinner_html = (
|
|
107
|
-
f'<div id="{func_name}-spinner"
|
|
108
|
-
f'
|
|
109
|
-
f'
|
|
110
|
-
f'</
|
|
107
|
+
f'<div id="{func_name}-spinner" style="display:inline-block; margin: 5px;">'
|
|
108
|
+
f'<div class="spinner" style="width:16px; height:16px; border: 2px solid #f3f3f3; '
|
|
109
|
+
f'border-radius: 50%; border-top: 2px solid #3498db; animation: spin 1s linear infinite;"></div>'
|
|
110
|
+
f'<style>@keyframes spin {{0% {{ transform: rotate(0deg); }} 100% {{ transform: rotate(360deg); }}}}</style>'
|
|
111
|
+
f' <span>Task {func_name} is in progress...</span></div>'
|
|
111
112
|
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': spinner_html})
|
|
113
|
+
await callback.async_on_llm_new_token(token=spinner_html)
|
|
115
114
|
|
|
116
|
-
# Keep
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
try:
|
|
120
|
-
while True:
|
|
121
|
-
await asyncio.sleep(interval) # Sleep for the interval
|
|
122
|
-
elapsed_time += interval # Increment elapsed time
|
|
123
|
-
log.info(f"Sending heartbeat for {func_name}: {elapsed_time}s elapsed")
|
|
124
|
-
# Update spinner with the elapsed time
|
|
125
|
-
update_html = (
|
|
126
|
-
f'<div style="display: none;" data-update-id="{func_name}-spinner">'
|
|
127
|
-
f'<span class="elapsed-time">Task {func_name} is still running... {elapsed_time}s elapsed</span>'
|
|
128
|
-
f'</div>'
|
|
129
|
-
)
|
|
130
|
-
await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': update_html})
|
|
131
|
-
except asyncio.CancelledError:
|
|
132
|
-
log.info(f"Heartbeat task for {func_name} has been cancelled.")
|
|
133
|
-
finally:
|
|
134
|
-
# Send a message indicating task completion to update the spinner's state
|
|
135
|
-
completion_html = (
|
|
136
|
-
f'<div style="display: none;" data-complete-id="{func_name}-spinner"></div>'
|
|
137
|
-
)
|
|
138
|
-
await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': completion_html})
|
|
115
|
+
# Keep sending heartbeats until task completes
|
|
116
|
+
while True:
|
|
117
|
+
await asyncio.sleep(interval) # Sleep for the interval but do not send multiple messages
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.100.
|
|
3
|
+
Version: 0.100.2
|
|
4
4
|
Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
|
|
5
5
|
Home-page: https://github.com/sunholo-data/sunholo-py
|
|
6
|
-
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.100.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.100.2.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -19,7 +19,7 @@ sunholo/archive/__init__.py,sha256=qNHWm5rGPVOlxZBZCpA1wTYPbalizRT7f8X4rs2t290,3
|
|
|
19
19
|
sunholo/archive/archive.py,sha256=PxVfDtO2_2ZEEbnhXSCbXLdeoHoQVImo4y3Jr2XkCFY,1204
|
|
20
20
|
sunholo/auth/__init__.py,sha256=TeP-OY0XGxYV_8AQcVGoh35bvyWhNUcMRfhuD5l44Sk,91
|
|
21
21
|
sunholo/auth/gcloud.py,sha256=PdbwkuTdRi4RKBmgG9uwsReegqC4VG15_tw5uzmA7Fs,298
|
|
22
|
-
sunholo/auth/refresh.py,sha256=
|
|
22
|
+
sunholo/auth/refresh.py,sha256=WSlKa8TQ70GlZ2e0u83nYknhUsgTeiyyTVi-EFOa8Uc,2029
|
|
23
23
|
sunholo/auth/run.py,sha256=pMSp2lzL6e6ZqlltVUH92bkeUt341yMue027qrE0jQU,2821
|
|
24
24
|
sunholo/azure/__init__.py,sha256=S1WQ5jndzNgzhSBh9UpX_yw7hRVm3hCzkAWNxUdK4dA,48
|
|
25
25
|
sunholo/azure/auth.py,sha256=Y3fDqFLYwbsIyi5hS5L-3hYnwrLWVL96yPng5Sj5c2c,2236
|
|
@@ -80,16 +80,16 @@ sunholo/embedder/embed_chunk.py,sha256=_FdO4-9frUJlDPqXv2Ai49ejUrrMTMGti3D7bfJGf
|
|
|
80
80
|
sunholo/excel/__init__.py,sha256=AqTMN9K4qJYi4maEgoORc5oxDVGO_eqmwzDaVP37JgY,56
|
|
81
81
|
sunholo/excel/plugin.py,sha256=rl3FoECZ6Ts8KKExPrbPwr3u3CegZfsevmcjgUXAlhE,4033
|
|
82
82
|
sunholo/gcs/__init__.py,sha256=SZvbsMFDko40sIRHTHppA37IijvJTae54vrhooEF5-4,90
|
|
83
|
-
sunholo/gcs/add_file.py,sha256=
|
|
83
|
+
sunholo/gcs/add_file.py,sha256=NxUaWTP-h9l5fbjnrEYpipo5rjS3uPkjMWeDhpK113g,8369
|
|
84
84
|
sunholo/gcs/download_folder.py,sha256=ijJTnS595JqZhBH8iHFErQilMbkuKgL-bnTCMLGuvlA,1614
|
|
85
|
-
sunholo/gcs/download_url.py,sha256=
|
|
85
|
+
sunholo/gcs/download_url.py,sha256=oPC8phesRcpXB4zB_p0nWPKPBMs7JBhhVJ7tnVYhzD8,6356
|
|
86
86
|
sunholo/gcs/metadata.py,sha256=oQLcXi4brsZ74aegWyC1JZmhlaEV270HS5_UWtAYYWE,898
|
|
87
87
|
sunholo/genai/__init__.py,sha256=dBl6IA3-Fx6-Vx81r0XqxHlUq6WeW1iDX188dpChu8s,115
|
|
88
88
|
sunholo/genai/init.py,sha256=yG8E67TduFCTQPELo83OJuWfjwTnGZsyACospahyEaY,687
|
|
89
|
-
sunholo/genai/process_funcs_cls.py,sha256=
|
|
89
|
+
sunholo/genai/process_funcs_cls.py,sha256=DPe70E71pofInLMFBFdudFZr0ZCFHN1LFCQjpxtG8xU,26552
|
|
90
90
|
sunholo/genai/safety.py,sha256=mkFDO_BeEgiKjQd9o2I4UxB6XI7a9U-oOFjZ8LGRUC4,1238
|
|
91
91
|
sunholo/invoke/__init__.py,sha256=o1RhwBGOtVK0MIdD55fAIMCkJsxTksi8GD5uoqVKI-8,184
|
|
92
|
-
sunholo/invoke/async_class.py,sha256=
|
|
92
|
+
sunholo/invoke/async_class.py,sha256=LNe57uxwGwLaN44MCOVOOSMNX8FZ5buqCxevYTHUTPg,5501
|
|
93
93
|
sunholo/invoke/direct_vac_func.py,sha256=GXSCMkC6vOWGUtQjxy-ZpTrMvJa3CgcW-y9mDpJwWC8,9533
|
|
94
94
|
sunholo/invoke/invoke_vac_utils.py,sha256=sJc1edHTHMzMGXjji1N67c3iUaP7BmAL5nj82Qof63M,2053
|
|
95
95
|
sunholo/langfuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -144,9 +144,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
|
|
|
144
144
|
sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
|
|
145
145
|
sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
|
|
146
146
|
sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
|
|
147
|
-
sunholo-0.100.
|
|
148
|
-
sunholo-0.100.
|
|
149
|
-
sunholo-0.100.
|
|
150
|
-
sunholo-0.100.
|
|
151
|
-
sunholo-0.100.
|
|
152
|
-
sunholo-0.100.
|
|
147
|
+
sunholo-0.100.2.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
|
148
|
+
sunholo-0.100.2.dist-info/METADATA,sha256=hkHwKSigiDOe9t6fcubbDM0Osr542-T3R8guKamW2YY,8312
|
|
149
|
+
sunholo-0.100.2.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
150
|
+
sunholo-0.100.2.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
|
151
|
+
sunholo-0.100.2.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
|
152
|
+
sunholo-0.100.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|