sunholo 0.99.10__py3-none-any.whl → 0.99.12__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 +11 -1
- sunholo/invoke/async_class.py +72 -23
- {sunholo-0.99.10.dist-info → sunholo-0.99.12.dist-info}/METADATA +2 -2
- {sunholo-0.99.10.dist-info → sunholo-0.99.12.dist-info}/RECORD +8 -8
- {sunholo-0.99.10.dist-info → sunholo-0.99.12.dist-info}/LICENSE.txt +0 -0
- {sunholo-0.99.10.dist-info → sunholo-0.99.12.dist-info}/WHEEL +0 -0
- {sunholo-0.99.10.dist-info → sunholo-0.99.12.dist-info}/entry_points.txt +0 -0
- {sunholo-0.99.10.dist-info → sunholo-0.99.12.dist-info}/top_level.txt +0 -0
|
@@ -236,6 +236,10 @@ class GenAIFunctionProcessor:
|
|
|
236
236
|
|
|
237
237
|
# Loop through each part in the response to handle multiple function calls
|
|
238
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
|
+
|
|
239
243
|
for part in full_response.candidates[0].content.parts:
|
|
240
244
|
if fn := part.function_call:
|
|
241
245
|
# Extract parameters for the function call
|
|
@@ -449,7 +453,13 @@ class GenAIFunctionProcessor:
|
|
|
449
453
|
except ValueError as err:
|
|
450
454
|
token_queue.append(f"{str(err)} for {chunk=}")
|
|
451
455
|
|
|
452
|
-
|
|
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
|
+
|
|
453
463
|
log.info(f"[{guardrail}] {executed_responses=}")
|
|
454
464
|
|
|
455
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,63 @@ 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 to update the spinner's state
|
|
87
|
+
if callback:
|
|
88
|
+
completion_html = (
|
|
89
|
+
f'<div style="display: none;" data-complete-id="{name}-spinner"></div>'
|
|
90
|
+
)
|
|
91
|
+
await callback.async_on_llm_new_token(token=completion_html)
|
|
92
|
+
|
|
93
|
+
async def _send_heartbeat(self, callback, func_name, interval=2):
|
|
94
|
+
"""
|
|
95
|
+
Sends a single spinner at the start and keeps the task alive.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
callback: The callback to notify that the task is still working.
|
|
99
|
+
func_name: The name of the task function.
|
|
100
|
+
interval: How frequently to send heartbeat messages (in seconds).
|
|
101
|
+
"""
|
|
102
|
+
# Send the initial spinner HTML
|
|
103
|
+
spinner_html = (
|
|
104
|
+
f'<div id="{func_name}-spinner" class="spinner-container">'
|
|
105
|
+
f' <div class="spinner"></div>'
|
|
106
|
+
f' <span class="completed">✔️ Task {func_name} completed!</span>'
|
|
107
|
+
f'</div>'
|
|
108
|
+
)
|
|
109
|
+
await callback.async_on_llm_new_token(token=spinner_html)
|
|
110
|
+
|
|
111
|
+
# Keep sending heartbeats until task completes
|
|
112
|
+
while True:
|
|
113
|
+
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.12
|
|
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.12.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=N45IX9732TijQ1LcgOiBiPTiZg1srKnNU46EY0NbDzo,4946
|
|
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.12.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
|
148
|
+
sunholo-0.99.12.dist-info/METADATA,sha256=DBqgk8OurecnulKJbbQ6r2NNv-e9X-YI4lR9rpKKQYA,8312
|
|
149
|
+
sunholo-0.99.12.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
150
|
+
sunholo-0.99.12.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
|
151
|
+
sunholo-0.99.12.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
|
152
|
+
sunholo-0.99.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|