sunholo 0.99.9__tar.gz → 0.99.11__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.
Files changed (161) hide show
  1. {sunholo-0.99.9 → sunholo-0.99.11}/PKG-INFO +2 -2
  2. {sunholo-0.99.9 → sunholo-0.99.11}/setup.py +1 -1
  3. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/genai/process_funcs_cls.py +21 -2
  4. sunholo-0.99.11/sunholo/invoke/async_class.py +116 -0
  5. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo.egg-info/PKG-INFO +2 -2
  6. sunholo-0.99.9/sunholo/invoke/async_class.py +0 -64
  7. {sunholo-0.99.9 → sunholo-0.99.11}/LICENSE.txt +0 -0
  8. {sunholo-0.99.9 → sunholo-0.99.11}/MANIFEST.in +0 -0
  9. {sunholo-0.99.9 → sunholo-0.99.11}/README.md +0 -0
  10. {sunholo-0.99.9 → sunholo-0.99.11}/setup.cfg +0 -0
  11. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/__init__.py +0 -0
  12. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/__init__.py +0 -0
  13. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/chat_history.py +0 -0
  14. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/dispatch_to_qa.py +0 -0
  15. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/fastapi/__init__.py +0 -0
  16. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/fastapi/base.py +0 -0
  17. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/fastapi/qna_routes.py +0 -0
  18. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/flask/__init__.py +0 -0
  19. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/flask/base.py +0 -0
  20. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/flask/qna_routes.py +0 -0
  21. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/flask/vac_routes.py +0 -0
  22. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/langserve.py +0 -0
  23. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/pubsub.py +0 -0
  24. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/route.py +0 -0
  25. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/special_commands.py +0 -0
  26. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/agents/swagger.py +0 -0
  27. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/archive/__init__.py +0 -0
  28. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/archive/archive.py +0 -0
  29. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/auth/__init__.py +0 -0
  30. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/auth/gcloud.py +0 -0
  31. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/auth/refresh.py +0 -0
  32. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/auth/run.py +0 -0
  33. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/azure/__init__.py +0 -0
  34. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/azure/auth.py +0 -0
  35. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/azure/blobs.py +0 -0
  36. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/azure/event_grid.py +0 -0
  37. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/bots/__init__.py +0 -0
  38. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/bots/discord.py +0 -0
  39. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/bots/github_webhook.py +0 -0
  40. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/bots/webapp.py +0 -0
  41. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/__init__.py +0 -0
  42. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/azure.py +0 -0
  43. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/doc_handling.py +0 -0
  44. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/encode_metadata.py +0 -0
  45. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/images.py +0 -0
  46. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/loaders.py +0 -0
  47. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/message_data.py +0 -0
  48. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/pdfs.py +0 -0
  49. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/process_chunker_data.py +0 -0
  50. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/publish.py +0 -0
  51. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/pubsub.py +0 -0
  52. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/chunker/splitter.py +0 -0
  53. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/__init__.py +0 -0
  54. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/chat_vac.py +0 -0
  55. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/cli.py +0 -0
  56. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/cli_init.py +0 -0
  57. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/configs.py +0 -0
  58. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/deploy.py +0 -0
  59. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/embedder.py +0 -0
  60. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/merge_texts.py +0 -0
  61. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/run_proxy.py +0 -0
  62. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/sun_rich.py +0 -0
  63. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/swagger.py +0 -0
  64. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/cli/vertex.py +0 -0
  65. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/components/__init__.py +0 -0
  66. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/components/llm.py +0 -0
  67. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/components/retriever.py +0 -0
  68. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/components/vectorstore.py +0 -0
  69. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/custom_logging.py +0 -0
  70. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/__init__.py +0 -0
  71. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/alloydb.py +0 -0
  72. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/alloydb_client.py +0 -0
  73. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/database.py +0 -0
  74. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/lancedb.py +0 -0
  75. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/sql/sb/create_function.sql +0 -0
  76. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  77. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/sql/sb/create_table.sql +0 -0
  78. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  79. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/sql/sb/return_sources.sql +0 -0
  80. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/sql/sb/setup.sql +0 -0
  81. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/static_dbs.py +0 -0
  82. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/database/uuid.py +0 -0
  83. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/discovery_engine/__init__.py +0 -0
  84. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/discovery_engine/chunker_handler.py +0 -0
  85. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/discovery_engine/create_new.py +0 -0
  86. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
  87. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
  88. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/embedder/__init__.py +0 -0
  89. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/embedder/embed_chunk.py +0 -0
  90. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/excel/__init__.py +0 -0
  91. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/excel/plugin.py +0 -0
  92. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/gcs/__init__.py +0 -0
  93. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/gcs/add_file.py +0 -0
  94. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/gcs/download_folder.py +0 -0
  95. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/gcs/download_url.py +0 -0
  96. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/gcs/metadata.py +0 -0
  97. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/genai/__init__.py +0 -0
  98. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/genai/init.py +0 -0
  99. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/genai/safety.py +0 -0
  100. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/invoke/__init__.py +0 -0
  101. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/invoke/direct_vac_func.py +0 -0
  102. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/invoke/invoke_vac_utils.py +0 -0
  103. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/langfuse/__init__.py +0 -0
  104. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/langfuse/callback.py +0 -0
  105. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/langfuse/evals.py +0 -0
  106. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/langfuse/prompts.py +0 -0
  107. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/llamaindex/__init__.py +0 -0
  108. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/llamaindex/get_files.py +0 -0
  109. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/llamaindex/import_files.py +0 -0
  110. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/llamaindex/llamaindex_class.py +0 -0
  111. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/llamaindex/user_history.py +0 -0
  112. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/lookup/__init__.py +0 -0
  113. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/lookup/model_lookup.yaml +0 -0
  114. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/patches/__init__.py +0 -0
  115. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/patches/langchain/__init__.py +0 -0
  116. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/patches/langchain/lancedb.py +0 -0
  117. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/patches/langchain/vertexai.py +0 -0
  118. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/pubsub/__init__.py +0 -0
  119. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/pubsub/process_pubsub.py +0 -0
  120. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/pubsub/pubsub_manager.py +0 -0
  121. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/qna/__init__.py +0 -0
  122. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/qna/parsers.py +0 -0
  123. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/qna/retry.py +0 -0
  124. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/streaming/__init__.py +0 -0
  125. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/streaming/content_buffer.py +0 -0
  126. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/streaming/langserve.py +0 -0
  127. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/streaming/stream_lookup.py +0 -0
  128. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/streaming/streaming.py +0 -0
  129. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/summarise/__init__.py +0 -0
  130. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/summarise/summarise.py +0 -0
  131. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/terraform/__init__.py +0 -0
  132. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/terraform/tfvars_editor.py +0 -0
  133. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/tools/__init__.py +0 -0
  134. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/tools/web_browser.py +0 -0
  135. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/__init__.py +0 -0
  136. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/api_key.py +0 -0
  137. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/big_context.py +0 -0
  138. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/config.py +0 -0
  139. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/config_class.py +0 -0
  140. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/config_schema.py +0 -0
  141. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/gcp.py +0 -0
  142. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/gcp_project.py +0 -0
  143. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/parsers.py +0 -0
  144. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/timedelta.py +0 -0
  145. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/user_ids.py +0 -0
  146. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/utils/version.py +0 -0
  147. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/vertex/__init__.py +0 -0
  148. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/vertex/extensions_call.py +0 -0
  149. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/vertex/extensions_class.py +0 -0
  150. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/vertex/genai_functions.py +0 -0
  151. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/vertex/init.py +0 -0
  152. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/vertex/memory_tools.py +0 -0
  153. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/vertex/safety.py +0 -0
  154. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo/vertex/type_dict_to_json.py +0 -0
  155. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo.egg-info/SOURCES.txt +0 -0
  156. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo.egg-info/dependency_links.txt +0 -0
  157. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo.egg-info/entry_points.txt +0 -0
  158. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo.egg-info/requires.txt +0 -0
  159. {sunholo-0.99.9 → sunholo-0.99.11}/sunholo.egg-info/top_level.txt +0 -0
  160. {sunholo-0.99.9 → sunholo-0.99.11}/tests/test_chat_history.py +0 -0
  161. {sunholo-0.99.9 → sunholo-0.99.11}/tests/test_config.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.99.9
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.9.tar.gz
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
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- version = '0.99.9'
3
+ version = '0.99.11'
4
4
 
5
5
  setup(
6
6
  name='sunholo',
@@ -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
- executed_responses = self.process_funcs(response)
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:
@@ -0,0 +1,116 @@
1
+ import asyncio
2
+ from ..custom_logging import log
3
+ import traceback
4
+ from typing import Callable, Any, AsyncGenerator, Dict
5
+ from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponential, stop_after_attempt
6
+
7
+ class AsyncTaskRunner:
8
+ def __init__(self, retry_enabled=False, retry_kwargs=None):
9
+ self.tasks = []
10
+ self.retry_enabled = retry_enabled
11
+ self.retry_kwargs = retry_kwargs or {}
12
+
13
+ def add_task(self, func: Callable[..., Any], *args: Any):
14
+ """Adds a task to the list of tasks to be executed."""
15
+ log.info(f"Adding task: {func.__name__} with args: {args}")
16
+ self.tasks.append((func.__name__, func, args))
17
+
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
+ """
25
+ log.info("Running tasks asynchronously and yielding results as they complete")
26
+ tasks = {}
27
+ for name, func, args in self.tasks:
28
+ # Pass the callback down to _task_wrapper
29
+ coro = self._task_wrapper(name, func, args, callback)
30
+ task = asyncio.create_task(coro)
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
+ result = await task
40
+ yield {name: result}
41
+ except Exception as e:
42
+ log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
43
+ yield {name: e}
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."""
47
+ async def run_func():
48
+ if asyncio.iscoroutinefunction(func):
49
+ # If the function is async, await it
50
+ return await func(*args)
51
+ else:
52
+ # If the function is sync, run it in a thread to prevent blocking
53
+ return await asyncio.to_thread(func, *args)
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:
73
+ return await run_func()
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.9
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.9.tar.gz
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
@@ -1,64 +0,0 @@
1
- import asyncio
2
- from ..custom_logging import log
3
- import traceback
4
- from typing import Callable, List, Any, AsyncGenerator, Dict
5
- from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponential, stop_after_attempt
6
-
7
- class AsyncTaskRunner:
8
- def __init__(self, retry_enabled=False, retry_kwargs=None):
9
- self.tasks = []
10
- self.retry_enabled = retry_enabled
11
- self.retry_kwargs = retry_kwargs or {}
12
-
13
- def add_task(self, func: Callable[..., Any], *args: Any):
14
- """Adds a task to the list of tasks to be executed."""
15
- log.info(f"Adding task: {func.__name__} with args: {args}")
16
- self.tasks.append((func.__name__, func, args))
17
-
18
- async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
19
- """Runs all tasks concurrently and yields results as they complete."""
20
- log.info("Running tasks asynchronously and yielding results as they complete")
21
- tasks = {}
22
- for name, func, args in self.tasks:
23
- coro = self._task_wrapper(name, func, args)
24
- task = asyncio.create_task(coro)
25
- tasks[task] = name
26
-
27
- log.info(f"Start async run with {len(self.tasks)} runners")
28
- while tasks:
29
- done, _ = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
30
- for task in done:
31
- name = tasks.pop(task)
32
- try:
33
- result = await task
34
- yield {name: result}
35
- except Exception as e:
36
- log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
37
- 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."""
41
- async def run_func():
42
- if asyncio.iscoroutinefunction(func):
43
- # If the function is async, await it
44
- return await func(*args)
45
- else:
46
- # If the function is sync, run it in a thread to prevent blocking
47
- return await asyncio.to_thread(func, *args)
48
-
49
- if self.retry_enabled:
50
- retry_kwargs = {
51
- 'wait': wait_random_exponential(multiplier=1, max=60),
52
- 'stop': stop_after_attempt(5),
53
- 'retry': retry_if_exception_type(Exception),
54
- **self.retry_kwargs
55
- }
56
- async for attempt in AsyncRetrying(**retry_kwargs):
57
- with attempt:
58
- return await run_func()
59
- else:
60
- try:
61
- return await run_func()
62
- except Exception as e:
63
- log.error(f"Error in task {name}: {e}\n{traceback.format_exc()}")
64
- raise
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