sunholo 0.99.9__py3-none-any.whl → 0.99.11__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/genai/process_funcs_cls.py +21 -2
- sunholo/invoke/async_class.py +75 -23
- {sunholo-0.99.9.dist-info → sunholo-0.99.11.dist-info}/METADATA +2 -2
- {sunholo-0.99.9.dist-info → sunholo-0.99.11.dist-info}/RECORD +8 -8
- {sunholo-0.99.9.dist-info → sunholo-0.99.11.dist-info}/LICENSE.txt +0 -0
- {sunholo-0.99.9.dist-info → sunholo-0.99.11.dist-info}/WHEEL +0 -0
- {sunholo-0.99.9.dist-info → sunholo-0.99.11.dist-info}/entry_points.txt +0 -0
- {sunholo-0.99.9.dist-info → sunholo-0.99.11.dist-info}/top_level.txt +0 -0
|
@@ -12,6 +12,8 @@ from collections import deque
|
|
|
12
12
|
try:
|
|
13
13
|
import google.generativeai as genai
|
|
14
14
|
import proto
|
|
15
|
+
from google.generativeai.types import RequestOptions
|
|
16
|
+
from google.api_core import retry
|
|
15
17
|
except ImportError:
|
|
16
18
|
genai = None
|
|
17
19
|
|
|
@@ -234,6 +236,10 @@ class GenAIFunctionProcessor:
|
|
|
234
236
|
|
|
235
237
|
# Loop through each part in the response to handle multiple function calls
|
|
236
238
|
#TODO: async
|
|
239
|
+
if not full_response.candidates or len(full_response.candidates) == 0:
|
|
240
|
+
log.error("No candidates found in the response. The response might have failed.")
|
|
241
|
+
return "No candidates available in the response. Please check your query or try again."
|
|
242
|
+
|
|
237
243
|
for part in full_response.candidates[0].content.parts:
|
|
238
244
|
if fn := part.function_call:
|
|
239
245
|
# Extract parameters for the function call
|
|
@@ -402,7 +408,14 @@ class GenAIFunctionProcessor:
|
|
|
402
408
|
|
|
403
409
|
try:
|
|
404
410
|
token_queue.append("\n= Calling Agent =\n")
|
|
405
|
-
response = chat.send_message(content, stream=True
|
|
411
|
+
response = chat.send_message(content, stream=True, request_options=RequestOptions(
|
|
412
|
+
retry=retry.Retry(
|
|
413
|
+
initial=10,
|
|
414
|
+
multiplier=2,
|
|
415
|
+
maximum=60,
|
|
416
|
+
timeout=300
|
|
417
|
+
)
|
|
418
|
+
))
|
|
406
419
|
|
|
407
420
|
except Exception as e:
|
|
408
421
|
msg = f"Error sending {content} to model: {str(e)} - {traceback.format_exc()}"
|
|
@@ -440,7 +453,13 @@ class GenAIFunctionProcessor:
|
|
|
440
453
|
except ValueError as err:
|
|
441
454
|
token_queue.append(f"{str(err)} for {chunk=}")
|
|
442
455
|
|
|
443
|
-
|
|
456
|
+
try:
|
|
457
|
+
executed_responses = self.process_funcs(response)
|
|
458
|
+
except Exception as err:
|
|
459
|
+
log.error(f"Error in executions: {str(err)}")
|
|
460
|
+
executed_responses = []
|
|
461
|
+
token_queue.append(f"{str(err)} for {response=}")
|
|
462
|
+
|
|
444
463
|
log.info(f"[{guardrail}] {executed_responses=}")
|
|
445
464
|
|
|
446
465
|
if executed_responses:
|
sunholo/invoke/async_class.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from ..custom_logging import log
|
|
3
3
|
import traceback
|
|
4
|
-
from typing import Callable,
|
|
4
|
+
from typing import Callable, Any, AsyncGenerator, Dict
|
|
5
5
|
from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponential, stop_after_attempt
|
|
6
6
|
|
|
7
7
|
class AsyncTaskRunner:
|
|
@@ -15,12 +15,18 @@ class AsyncTaskRunner:
|
|
|
15
15
|
log.info(f"Adding task: {func.__name__} with args: {args}")
|
|
16
16
|
self.tasks.append((func.__name__, func, args))
|
|
17
17
|
|
|
18
|
-
async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
19
|
-
"""
|
|
18
|
+
async def run_async_as_completed(self, callback=None) -> AsyncGenerator[Dict[str, Any], None]:
|
|
19
|
+
"""
|
|
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.
|
|
24
|
+
"""
|
|
20
25
|
log.info("Running tasks asynchronously and yielding results as they complete")
|
|
21
26
|
tasks = {}
|
|
22
27
|
for name, func, args in self.tasks:
|
|
23
|
-
|
|
28
|
+
# Pass the callback down to _task_wrapper
|
|
29
|
+
coro = self._task_wrapper(name, func, args, callback)
|
|
24
30
|
task = asyncio.create_task(coro)
|
|
25
31
|
tasks[task] = name
|
|
26
32
|
|
|
@@ -35,9 +41,9 @@ class AsyncTaskRunner:
|
|
|
35
41
|
except Exception as e:
|
|
36
42
|
log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
|
|
37
43
|
yield {name: e}
|
|
38
|
-
|
|
39
|
-
async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any) -> Any:
|
|
40
|
-
"""Wraps the task function to process its output and handle retries."""
|
|
44
|
+
|
|
45
|
+
async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any, callback=None) -> Any:
|
|
46
|
+
"""Wraps the task function to process its output and handle retries, while managing heartbeat updates."""
|
|
41
47
|
async def run_func():
|
|
42
48
|
if asyncio.iscoroutinefunction(func):
|
|
43
49
|
# If the function is async, await it
|
|
@@ -45,20 +51,66 @@ class AsyncTaskRunner:
|
|
|
45
51
|
else:
|
|
46
52
|
# If the function is sync, run it in a thread to prevent blocking
|
|
47
53
|
return await asyncio.to_thread(func, *args)
|
|
48
|
-
|
|
49
|
-
if
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
|
|
55
|
+
# Start the heartbeat task if a callback is provided
|
|
56
|
+
heartbeat_task = None
|
|
57
|
+
if callback:
|
|
58
|
+
heartbeat_task = asyncio.create_task(self._send_heartbeat(callback, name))
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
if self.retry_enabled:
|
|
62
|
+
retry_kwargs = {
|
|
63
|
+
'wait': wait_random_exponential(multiplier=1, max=60),
|
|
64
|
+
'stop': stop_after_attempt(5),
|
|
65
|
+
'retry': retry_if_exception_type(Exception),
|
|
66
|
+
**self.retry_kwargs
|
|
67
|
+
}
|
|
68
|
+
async for attempt in AsyncRetrying(**retry_kwargs):
|
|
69
|
+
with attempt:
|
|
70
|
+
return await run_func()
|
|
71
|
+
else:
|
|
72
|
+
try:
|
|
58
73
|
return await run_func()
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
except Exception as e:
|
|
75
|
+
log.error(f"Error in task {name}: {e}\n{traceback.format_exc()}")
|
|
76
|
+
raise
|
|
77
|
+
finally:
|
|
78
|
+
# Stop the heartbeat task
|
|
79
|
+
if heartbeat_task:
|
|
80
|
+
heartbeat_task.cancel()
|
|
81
|
+
try:
|
|
82
|
+
await heartbeat_task # Ensure the heartbeat task is properly canceled
|
|
83
|
+
except asyncio.CancelledError:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
# Send a message indicating task completion and remove spinner
|
|
87
|
+
if callback:
|
|
88
|
+
completion_html = (
|
|
89
|
+
f"<script>"
|
|
90
|
+
f"document.getElementById('{name}-spinner').innerHTML = '✔️ Task {name} completed!';"
|
|
91
|
+
f"</script>"
|
|
92
|
+
)
|
|
93
|
+
await callback.async_on_llm_new_token(token=completion_html)
|
|
94
|
+
|
|
95
|
+
async def _send_heartbeat(self, callback, func_name, interval=2):
|
|
96
|
+
"""
|
|
97
|
+
Sends periodic heartbeat updates to indicate the task is still in progress.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
callback: The callback to notify that the task is still working.
|
|
101
|
+
func_name: The name of the task function.
|
|
102
|
+
interval: How frequently to send heartbeat messages (in seconds).
|
|
103
|
+
"""
|
|
104
|
+
# Send the initial spinner HTML
|
|
105
|
+
spinner_html = (
|
|
106
|
+
f'<div id="{func_name}-spinner" style="display:inline-block; margin: 5px;">'
|
|
107
|
+
f'<div class="spinner" style="width:16px; height:16px; border: 2px solid #f3f3f3; '
|
|
108
|
+
f'border-radius: 50%; border-top: 2px solid #3498db; animation: spin 1s linear infinite;"></div>'
|
|
109
|
+
f'<style>@keyframes spin {{0% {{ transform: rotate(0deg); }} 100% {{ transform: rotate(360deg); }}}}</style>'
|
|
110
|
+
f' <span>Task {func_name} is in progress...</span></div>'
|
|
111
|
+
)
|
|
112
|
+
await callback.async_on_llm_new_token(token=spinner_html)
|
|
113
|
+
|
|
114
|
+
# Keep sending heartbeats until task completes
|
|
115
|
+
while True:
|
|
116
|
+
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.99.
|
|
3
|
+
Version: 0.99.11
|
|
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.99.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.99.11.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -86,10 +86,10 @@ sunholo/gcs/download_url.py,sha256=q1NiJSvEhdNrmU5ZJ-sBCMC_J5CxzrajY8LRgdPOV_M,6
|
|
|
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=MF3wm-N-zoYvme4I8ffXM9I7cog8OFyBnLu1e3A6eVc,26695
|
|
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=O-Jv0MHkACGA5uBG0EuyYnPpY4I5xMKgoNZZ4Q0dECY,5297
|
|
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.99.
|
|
148
|
-
sunholo-0.99.
|
|
149
|
-
sunholo-0.99.
|
|
150
|
-
sunholo-0.99.
|
|
151
|
-
sunholo-0.99.
|
|
152
|
-
sunholo-0.99.
|
|
147
|
+
sunholo-0.99.11.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
|
148
|
+
sunholo-0.99.11.dist-info/METADATA,sha256=EcWW2HDcFkxMivwaUo69Qg5sFI3ySqM8ZxmusIz2U90,8312
|
|
149
|
+
sunholo-0.99.11.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
150
|
+
sunholo-0.99.11.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
|
151
|
+
sunholo-0.99.11.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
|
152
|
+
sunholo-0.99.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|