sunholo 0.100.2__tar.gz → 0.100.3__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.
- {sunholo-0.100.2 → sunholo-0.100.3}/PKG-INFO +2 -2
- {sunholo-0.100.2 → sunholo-0.100.3}/setup.py +1 -1
- sunholo-0.100.3/sunholo/invoke/async_class.py +178 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo.egg-info/PKG-INFO +2 -2
- sunholo-0.100.2/sunholo/invoke/async_class.py +0 -117
- {sunholo-0.100.2 → sunholo-0.100.3}/LICENSE.txt +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/MANIFEST.in +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/README.md +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/setup.cfg +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/chat_history.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/dispatch_to_qa.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/fastapi/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/fastapi/base.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/fastapi/qna_routes.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/flask/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/flask/base.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/flask/qna_routes.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/flask/vac_routes.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/langserve.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/pubsub.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/route.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/special_commands.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/agents/swagger.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/archive/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/archive/archive.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/auth/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/auth/gcloud.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/auth/refresh.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/auth/run.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/azure/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/azure/auth.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/azure/blobs.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/azure/event_grid.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/bots/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/bots/discord.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/bots/github_webhook.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/bots/webapp.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/azure.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/doc_handling.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/encode_metadata.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/images.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/loaders.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/message_data.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/pdfs.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/process_chunker_data.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/publish.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/pubsub.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/chunker/splitter.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/chat_vac.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/cli.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/cli_init.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/configs.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/deploy.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/embedder.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/merge_texts.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/run_proxy.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/sun_rich.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/swagger.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/cli/vertex.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/components/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/components/llm.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/components/retriever.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/components/vectorstore.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/custom_logging.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/alloydb.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/alloydb_client.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/database.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/lancedb.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/sql/sb/create_function.sql +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/sql/sb/create_function_time.sql +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/sql/sb/create_table.sql +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/sql/sb/return_sources.sql +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/sql/sb/setup.sql +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/static_dbs.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/database/uuid.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/discovery_engine/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/discovery_engine/chunker_handler.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/discovery_engine/create_new.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/embedder/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/embedder/embed_chunk.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/excel/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/excel/plugin.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/gcs/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/gcs/add_file.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/gcs/download_folder.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/gcs/download_url.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/gcs/metadata.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/genai/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/genai/init.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/genai/process_funcs_cls.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/genai/safety.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/invoke/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/invoke/direct_vac_func.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/invoke/invoke_vac_utils.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/langfuse/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/langfuse/callback.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/langfuse/evals.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/langfuse/prompts.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/llamaindex/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/llamaindex/get_files.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/llamaindex/import_files.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/llamaindex/llamaindex_class.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/llamaindex/user_history.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/lookup/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/lookup/model_lookup.yaml +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/patches/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/patches/langchain/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/patches/langchain/lancedb.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/patches/langchain/vertexai.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/pubsub/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/pubsub/process_pubsub.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/pubsub/pubsub_manager.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/qna/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/qna/parsers.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/qna/retry.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/streaming/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/streaming/content_buffer.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/streaming/langserve.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/streaming/stream_lookup.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/streaming/streaming.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/summarise/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/summarise/summarise.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/terraform/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/terraform/tfvars_editor.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/tools/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/tools/web_browser.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/api_key.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/big_context.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/config.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/config_class.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/config_schema.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/gcp.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/gcp_project.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/parsers.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/timedelta.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/user_ids.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/utils/version.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/vertex/__init__.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/vertex/extensions_call.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/vertex/extensions_class.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/vertex/genai_functions.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/vertex/init.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/vertex/memory_tools.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/vertex/safety.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo/vertex/type_dict_to_json.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo.egg-info/SOURCES.txt +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo.egg-info/dependency_links.txt +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo.egg-info/entry_points.txt +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo.egg-info/requires.txt +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/sunholo.egg-info/top_level.txt +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/tests/test_chat_history.py +0 -0
- {sunholo-0.100.2 → sunholo-0.100.3}/tests/test_config.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.100.
|
|
3
|
+
Version: 0.100.3
|
|
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.3.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Callable, Any, AsyncGenerator, Dict
|
|
3
|
+
import time
|
|
4
|
+
import traceback
|
|
5
|
+
from ..custom_logging import setup_logging
|
|
6
|
+
from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponential, stop_after_attempt
|
|
7
|
+
|
|
8
|
+
log = setup_logging("sunholo_AsyncTaskRunner")
|
|
9
|
+
|
|
10
|
+
class AsyncTaskRunner:
|
|
11
|
+
def __init__(self, retry_enabled=False, retry_kwargs=None):
|
|
12
|
+
self.tasks = []
|
|
13
|
+
self.retry_enabled = retry_enabled
|
|
14
|
+
self.retry_kwargs = retry_kwargs or {}
|
|
15
|
+
|
|
16
|
+
def add_task(self, func: Callable[..., Any], *args: Any):
|
|
17
|
+
"""
|
|
18
|
+
Adds a task to the list of tasks to be executed.
|
|
19
|
+
"""
|
|
20
|
+
log.info(f"Adding task: {func.__name__} with args: {args}")
|
|
21
|
+
self.tasks.append((func.__name__, func, args))
|
|
22
|
+
|
|
23
|
+
async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
24
|
+
"""
|
|
25
|
+
Runs all tasks concurrently and yields results as they complete,
|
|
26
|
+
while periodically sending heartbeat messages.
|
|
27
|
+
"""
|
|
28
|
+
log.info("Running tasks asynchronously and yielding results as they complete")
|
|
29
|
+
|
|
30
|
+
# Create a queue for inter-coroutine communication
|
|
31
|
+
queue = asyncio.Queue()
|
|
32
|
+
|
|
33
|
+
# List to keep track of all running tasks and their heartbeats
|
|
34
|
+
task_infos = []
|
|
35
|
+
|
|
36
|
+
# Start all tasks and their corresponding heartbeats
|
|
37
|
+
for name, func, args in self.tasks:
|
|
38
|
+
# Create an event to signal task completion to the heartbeat
|
|
39
|
+
completion_event = asyncio.Event()
|
|
40
|
+
|
|
41
|
+
# Start the main task with retries
|
|
42
|
+
task_coro = self._run_with_retries(name, func, *args, queue=queue, completion_event=completion_event)
|
|
43
|
+
task = asyncio.create_task(task_coro)
|
|
44
|
+
|
|
45
|
+
# Start the heartbeat coroutine
|
|
46
|
+
heartbeat_coro = self._send_heartbeat(name, completion_event, queue)
|
|
47
|
+
heartbeat_task = asyncio.create_task(heartbeat_coro)
|
|
48
|
+
|
|
49
|
+
# Store task information for management
|
|
50
|
+
task_infos.append({
|
|
51
|
+
'name': name,
|
|
52
|
+
'task': task,
|
|
53
|
+
'heartbeat_task': heartbeat_task,
|
|
54
|
+
'completion_event': completion_event
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
log.info(f"Started task '{name}' and its heartbeat")
|
|
58
|
+
|
|
59
|
+
log.info(f"Started async run with {len(self.tasks)} tasks and heartbeats")
|
|
60
|
+
|
|
61
|
+
# Create a monitor task to detect when all tasks and heartbeats are done
|
|
62
|
+
monitor = asyncio.create_task(self._monitor_tasks(task_infos, queue))
|
|
63
|
+
|
|
64
|
+
# Continuously yield messages from the queue until sentinel is received
|
|
65
|
+
while True:
|
|
66
|
+
message = await queue.get()
|
|
67
|
+
if message is None:
|
|
68
|
+
log.info("Received sentinel. Exiting message loop.")
|
|
69
|
+
break # Sentinel received, all tasks and heartbeats are done
|
|
70
|
+
log.info(f"Received message from queue: {message}")
|
|
71
|
+
yield message
|
|
72
|
+
|
|
73
|
+
# Wait for the monitor to finish
|
|
74
|
+
await monitor
|
|
75
|
+
|
|
76
|
+
log.info("All tasks and heartbeats have completed")
|
|
77
|
+
|
|
78
|
+
async def _monitor_tasks(self, task_infos, queue):
|
|
79
|
+
"""
|
|
80
|
+
Monitors the tasks and heartbeats, and sends a sentinel to the queue when done.
|
|
81
|
+
"""
|
|
82
|
+
# Wait for all main tasks to complete
|
|
83
|
+
main_tasks = [info['task'] for info in task_infos]
|
|
84
|
+
log.info("Monitor: Waiting for all main tasks to complete")
|
|
85
|
+
await asyncio.gather(*main_tasks, return_exceptions=True)
|
|
86
|
+
log.info("Monitor: All main tasks have completed")
|
|
87
|
+
|
|
88
|
+
# Cancel all heartbeat tasks
|
|
89
|
+
for info in task_infos:
|
|
90
|
+
info['heartbeat_task'].cancel()
|
|
91
|
+
try:
|
|
92
|
+
await info['heartbeat_task']
|
|
93
|
+
except asyncio.CancelledError:
|
|
94
|
+
pass
|
|
95
|
+
log.info(f"Monitor: Heartbeat for task '{info['name']}' has been canceled")
|
|
96
|
+
|
|
97
|
+
# Send a sentinel to indicate completion
|
|
98
|
+
await queue.put(None)
|
|
99
|
+
log.info("Monitor: Sent sentinel to queue")
|
|
100
|
+
|
|
101
|
+
async def _run_with_retries(self, name: str, func: Callable[..., Any], *args: Any, queue: asyncio.Queue, completion_event: asyncio.Event) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Executes a task with optional retries and sends completion or error messages to the queue.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
if self.retry_enabled:
|
|
107
|
+
retry_kwargs = {
|
|
108
|
+
'wait': wait_random_exponential(multiplier=1, max=60),
|
|
109
|
+
'stop': stop_after_attempt(5),
|
|
110
|
+
'retry': retry_if_exception_type(Exception),
|
|
111
|
+
**self.retry_kwargs
|
|
112
|
+
}
|
|
113
|
+
async for attempt in AsyncRetrying(**retry_kwargs):
|
|
114
|
+
with attempt:
|
|
115
|
+
log.info(f"Starting task '{name}' with retry")
|
|
116
|
+
result = await self._execute_task(func, *args)
|
|
117
|
+
await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
|
|
118
|
+
log.info(f"Sent 'task_complete' message for task '{name}'")
|
|
119
|
+
return
|
|
120
|
+
else:
|
|
121
|
+
log.info(f"Starting task '{name}' with no retry")
|
|
122
|
+
result = await self._execute_task(func, *args)
|
|
123
|
+
await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
|
|
124
|
+
log.info(f"Sent 'task_complete' message for task '{name}'")
|
|
125
|
+
except Exception as e:
|
|
126
|
+
log.error(f"Error in task '{name}': {e}\n{traceback.format_exc()}")
|
|
127
|
+
await queue.put({'type': 'task_error', 'func_name': name, 'error': f'{e}\n{traceback.format_exc()}'})
|
|
128
|
+
log.info(f"Sent 'task_error' message for task '{name}'")
|
|
129
|
+
finally:
|
|
130
|
+
log.info(f"Task '{name}' completed.")
|
|
131
|
+
# Set the completion event after sending the message
|
|
132
|
+
completion_event.set()
|
|
133
|
+
|
|
134
|
+
async def _execute_task(self, func: Callable[..., Any], *args: Any) -> Any:
|
|
135
|
+
"""
|
|
136
|
+
Executes the given task function and returns its result.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
func (Callable): The callable to execute.
|
|
140
|
+
*args: Arguments to pass to the callable.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Any: The result of the task.
|
|
144
|
+
"""
|
|
145
|
+
if asyncio.iscoroutinefunction(func):
|
|
146
|
+
return await func(*args)
|
|
147
|
+
else:
|
|
148
|
+
return await asyncio.to_thread(func, *args)
|
|
149
|
+
|
|
150
|
+
async def _send_heartbeat(self, func_name: str, completion_event: asyncio.Event, queue: asyncio.Queue, interval: int = 2):
|
|
151
|
+
"""
|
|
152
|
+
Sends periodic heartbeat updates to indicate the task is still in progress.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
func_name (str): The name of the task function.
|
|
156
|
+
completion_event (asyncio.Event): Event to signal when the task is completed.
|
|
157
|
+
queue (asyncio.Queue): The queue to send heartbeat messages to.
|
|
158
|
+
interval (int): How frequently to send heartbeat messages (in seconds).
|
|
159
|
+
"""
|
|
160
|
+
start_time = time.time()
|
|
161
|
+
log.info(f"Starting heartbeat for task '{func_name}' with interval {interval} seconds")
|
|
162
|
+
try:
|
|
163
|
+
while not completion_event.is_set():
|
|
164
|
+
await asyncio.sleep(interval)
|
|
165
|
+
elapsed_time = int(time.time() - start_time)
|
|
166
|
+
heartbeat_message = {
|
|
167
|
+
'type': 'heartbeat',
|
|
168
|
+
'name': func_name,
|
|
169
|
+
'interval': interval,
|
|
170
|
+
'elapsed_time': elapsed_time
|
|
171
|
+
}
|
|
172
|
+
log.info(f"Sending heartbeat for task '{func_name}', running for {elapsed_time} seconds")
|
|
173
|
+
await queue.put(heartbeat_message)
|
|
174
|
+
except asyncio.CancelledError:
|
|
175
|
+
log.info(f"Heartbeat for task '{func_name}' has been canceled")
|
|
176
|
+
finally:
|
|
177
|
+
log.info(f"Heartbeat for task '{func_name}' stopped")
|
|
178
|
+
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.100.
|
|
3
|
+
Version: 0.100.3
|
|
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.3.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -1,117 +0,0 @@
|
|
|
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
|
-
# 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()}'}
|
|
45
|
-
|
|
46
|
-
async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any, callback=None) -> Any:
|
|
47
|
-
"""Wraps the task function to process its output and handle retries, while managing heartbeat updates."""
|
|
48
|
-
async def run_func():
|
|
49
|
-
if asyncio.iscoroutinefunction(func):
|
|
50
|
-
# If the function is async, await it
|
|
51
|
-
return await func(*args)
|
|
52
|
-
else:
|
|
53
|
-
# If the function is sync, run it in a thread to prevent blocking
|
|
54
|
-
return await asyncio.to_thread(func, *args)
|
|
55
|
-
|
|
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))
|
|
60
|
-
|
|
61
|
-
try:
|
|
62
|
-
if self.retry_enabled:
|
|
63
|
-
retry_kwargs = {
|
|
64
|
-
'wait': wait_random_exponential(multiplier=1, max=60),
|
|
65
|
-
'stop': stop_after_attempt(5),
|
|
66
|
-
'retry': retry_if_exception_type(Exception),
|
|
67
|
-
**self.retry_kwargs
|
|
68
|
-
}
|
|
69
|
-
async for attempt in AsyncRetrying(**retry_kwargs):
|
|
70
|
-
with attempt:
|
|
71
|
-
return await run_func()
|
|
72
|
-
else:
|
|
73
|
-
try:
|
|
74
|
-
return await run_func()
|
|
75
|
-
except Exception as e:
|
|
76
|
-
log.error(f"Error in task {name}: {e}\n{traceback.format_exc()}")
|
|
77
|
-
raise
|
|
78
|
-
finally:
|
|
79
|
-
# Stop the heartbeat task
|
|
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)
|
|
95
|
-
|
|
96
|
-
async def _send_heartbeat(self, callback, func_name, interval=2):
|
|
97
|
-
"""
|
|
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
|
-
"""
|
|
105
|
-
# Send the initial spinner HTML
|
|
106
|
-
spinner_html = (
|
|
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>'
|
|
112
|
-
)
|
|
113
|
-
await callback.async_on_llm_new_token(token=spinner_html)
|
|
114
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|