sunholo 0.100.1__tar.gz → 0.100.2__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.1 → sunholo-0.100.2}/PKG-INFO +2 -2
- {sunholo-0.100.1 → sunholo-0.100.2}/setup.py +1 -1
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/auth/refresh.py +2 -2
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/gcs/add_file.py +0 -2
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/gcs/download_url.py +8 -1
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/genai/process_funcs_cls.py +1 -2
- sunholo-0.100.2/sunholo/invoke/async_class.py +117 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo.egg-info/PKG-INFO +2 -2
- sunholo-0.100.1/sunholo/invoke/async_class.py +0 -138
- {sunholo-0.100.1 → sunholo-0.100.2}/LICENSE.txt +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/MANIFEST.in +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/README.md +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/setup.cfg +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/chat_history.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/dispatch_to_qa.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/fastapi/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/fastapi/base.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/fastapi/qna_routes.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/flask/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/flask/base.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/flask/qna_routes.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/flask/vac_routes.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/langserve.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/pubsub.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/route.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/special_commands.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/agents/swagger.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/archive/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/archive/archive.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/auth/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/auth/gcloud.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/auth/run.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/azure/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/azure/auth.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/azure/blobs.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/azure/event_grid.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/bots/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/bots/discord.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/bots/github_webhook.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/bots/webapp.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/azure.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/doc_handling.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/encode_metadata.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/images.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/loaders.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/message_data.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/pdfs.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/process_chunker_data.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/publish.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/pubsub.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/chunker/splitter.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/chat_vac.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/cli.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/cli_init.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/configs.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/deploy.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/embedder.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/merge_texts.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/run_proxy.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/sun_rich.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/swagger.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/cli/vertex.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/components/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/components/llm.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/components/retriever.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/components/vectorstore.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/custom_logging.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/alloydb.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/alloydb_client.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/database.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/lancedb.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/sql/sb/create_function.sql +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/sql/sb/create_function_time.sql +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/sql/sb/create_table.sql +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/sql/sb/return_sources.sql +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/sql/sb/setup.sql +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/static_dbs.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/database/uuid.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/discovery_engine/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/discovery_engine/chunker_handler.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/discovery_engine/create_new.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/embedder/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/embedder/embed_chunk.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/excel/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/excel/plugin.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/gcs/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/gcs/download_folder.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/gcs/metadata.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/genai/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/genai/init.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/genai/safety.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/invoke/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/invoke/direct_vac_func.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/invoke/invoke_vac_utils.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/langfuse/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/langfuse/callback.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/langfuse/evals.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/langfuse/prompts.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/llamaindex/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/llamaindex/get_files.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/llamaindex/import_files.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/llamaindex/llamaindex_class.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/llamaindex/user_history.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/lookup/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/lookup/model_lookup.yaml +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/patches/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/patches/langchain/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/patches/langchain/lancedb.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/patches/langchain/vertexai.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/pubsub/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/pubsub/process_pubsub.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/pubsub/pubsub_manager.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/qna/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/qna/parsers.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/qna/retry.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/streaming/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/streaming/content_buffer.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/streaming/langserve.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/streaming/stream_lookup.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/streaming/streaming.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/summarise/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/summarise/summarise.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/terraform/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/terraform/tfvars_editor.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/tools/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/tools/web_browser.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/api_key.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/big_context.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/config.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/config_class.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/config_schema.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/gcp.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/gcp_project.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/parsers.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/timedelta.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/user_ids.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/utils/version.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/vertex/__init__.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/vertex/extensions_call.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/vertex/extensions_class.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/vertex/genai_functions.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/vertex/init.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/vertex/memory_tools.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/vertex/safety.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo/vertex/type_dict_to_json.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo.egg-info/SOURCES.txt +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo.egg-info/dependency_links.txt +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo.egg-info/entry_points.txt +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo.egg-info/requires.txt +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/sunholo.egg-info/top_level.txt +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/tests/test_chat_history.py +0 -0
- {sunholo-0.100.1 → sunholo-0.100.2}/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.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
|
|
@@ -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
|
|
@@ -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")
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
|
@@ -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
|
|
@@ -1,138 +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) -> AsyncGenerator[Dict[str, Any], None]:
|
|
19
|
-
"""
|
|
20
|
-
Runs all tasks concurrently and yields results and heartbeats as they are produced.
|
|
21
|
-
"""
|
|
22
|
-
log.info("Running tasks asynchronously and yielding results and heartbeats as they occur")
|
|
23
|
-
queue = asyncio.Queue()
|
|
24
|
-
tasks = {}
|
|
25
|
-
completed_tasks = set()
|
|
26
|
-
|
|
27
|
-
for name, func, args in self.tasks:
|
|
28
|
-
coro = self._task_wrapper(name, func, args, queue)
|
|
29
|
-
task = asyncio.create_task(coro)
|
|
30
|
-
tasks[task] = name
|
|
31
|
-
|
|
32
|
-
while tasks or not queue.empty():
|
|
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:
|
|
65
|
-
"""Wraps the task function to process its output and handle retries, while managing heartbeat updates."""
|
|
66
|
-
async def run_func():
|
|
67
|
-
if asyncio.iscoroutinefunction(func):
|
|
68
|
-
return await func(*args)
|
|
69
|
-
else:
|
|
70
|
-
return await asyncio.to_thread(func, *args)
|
|
71
|
-
|
|
72
|
-
# Start the heartbeat task
|
|
73
|
-
heartbeat_task = asyncio.create_task(self._send_heartbeat(queue, name))
|
|
74
|
-
|
|
75
|
-
try:
|
|
76
|
-
if self.retry_enabled:
|
|
77
|
-
retry_kwargs = {
|
|
78
|
-
'wait': wait_random_exponential(multiplier=1, max=60),
|
|
79
|
-
'stop': stop_after_attempt(5),
|
|
80
|
-
'retry': retry_if_exception_type(Exception),
|
|
81
|
-
**self.retry_kwargs
|
|
82
|
-
}
|
|
83
|
-
async for attempt in AsyncRetrying(**retry_kwargs):
|
|
84
|
-
with attempt:
|
|
85
|
-
return await run_func()
|
|
86
|
-
else:
|
|
87
|
-
try:
|
|
88
|
-
return await run_func()
|
|
89
|
-
except Exception as e:
|
|
90
|
-
log.error(f"Error in task {name}: {e}\n{traceback.format_exc()}")
|
|
91
|
-
raise
|
|
92
|
-
finally:
|
|
93
|
-
# Stop the heartbeat task
|
|
94
|
-
heartbeat_task.cancel()
|
|
95
|
-
# Wait for the heartbeat task to finish
|
|
96
|
-
try:
|
|
97
|
-
await heartbeat_task
|
|
98
|
-
except asyncio.CancelledError:
|
|
99
|
-
pass
|
|
100
|
-
|
|
101
|
-
async def _send_heartbeat(self, queue: asyncio.Queue, func_name: str, interval=2):
|
|
102
|
-
"""
|
|
103
|
-
Sends a periodic heartbeat to keep the task alive and update the spinner with elapsed time.
|
|
104
|
-
"""
|
|
105
|
-
# Send the initial spinner HTML
|
|
106
|
-
spinner_html = (
|
|
107
|
-
f'<div id="{func_name}-spinner" class="spinner-container">'
|
|
108
|
-
f' <div class="spinner"></div>'
|
|
109
|
-
f' <span class="elapsed-time">Task {func_name} is still running... 0s elapsed</span>'
|
|
110
|
-
f'</div>'
|
|
111
|
-
)
|
|
112
|
-
log.info(f"Heartbeat started for task {func_name}")
|
|
113
|
-
|
|
114
|
-
await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': spinner_html})
|
|
115
|
-
|
|
116
|
-
# Keep track of elapsed time
|
|
117
|
-
elapsed_time = 0
|
|
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})
|
|
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
|