sunholo 0.99.13__tar.gz → 0.100.1__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.99.13 → sunholo-0.100.1}/PKG-INFO +2 -2
- {sunholo-0.99.13 → sunholo-0.100.1}/setup.py +1 -1
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/invoke/async_class.py +63 -54
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/streaming/content_buffer.py +122 -47
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/streaming/streaming.py +13 -21
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo.egg-info/PKG-INFO +2 -2
- {sunholo-0.99.13 → sunholo-0.100.1}/LICENSE.txt +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/MANIFEST.in +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/README.md +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/setup.cfg +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/chat_history.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/dispatch_to_qa.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/fastapi/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/fastapi/base.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/fastapi/qna_routes.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/flask/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/flask/base.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/flask/qna_routes.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/flask/vac_routes.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/langserve.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/pubsub.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/route.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/special_commands.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/agents/swagger.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/archive/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/archive/archive.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/auth/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/auth/gcloud.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/auth/refresh.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/auth/run.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/azure/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/azure/auth.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/azure/blobs.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/azure/event_grid.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/bots/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/bots/discord.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/bots/github_webhook.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/bots/webapp.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/azure.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/doc_handling.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/encode_metadata.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/images.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/loaders.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/message_data.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/pdfs.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/process_chunker_data.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/publish.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/pubsub.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/chunker/splitter.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/chat_vac.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/cli.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/cli_init.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/configs.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/deploy.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/embedder.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/merge_texts.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/run_proxy.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/sun_rich.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/swagger.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/cli/vertex.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/components/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/components/llm.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/components/retriever.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/components/vectorstore.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/custom_logging.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/alloydb.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/alloydb_client.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/database.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/lancedb.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/sql/sb/create_function.sql +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/sql/sb/create_function_time.sql +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/sql/sb/create_table.sql +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/sql/sb/return_sources.sql +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/sql/sb/setup.sql +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/static_dbs.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/database/uuid.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/discovery_engine/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/discovery_engine/chunker_handler.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/discovery_engine/create_new.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/embedder/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/embedder/embed_chunk.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/excel/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/excel/plugin.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/gcs/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/gcs/add_file.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/gcs/download_folder.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/gcs/download_url.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/gcs/metadata.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/genai/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/genai/init.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/genai/process_funcs_cls.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/genai/safety.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/invoke/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/invoke/direct_vac_func.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/invoke/invoke_vac_utils.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/langfuse/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/langfuse/callback.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/langfuse/evals.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/langfuse/prompts.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/llamaindex/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/llamaindex/get_files.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/llamaindex/import_files.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/llamaindex/llamaindex_class.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/llamaindex/user_history.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/lookup/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/lookup/model_lookup.yaml +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/patches/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/patches/langchain/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/patches/langchain/lancedb.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/patches/langchain/vertexai.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/pubsub/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/pubsub/process_pubsub.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/pubsub/pubsub_manager.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/qna/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/qna/parsers.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/qna/retry.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/streaming/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/streaming/langserve.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/streaming/stream_lookup.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/summarise/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/summarise/summarise.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/terraform/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/terraform/tfvars_editor.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/tools/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/tools/web_browser.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/api_key.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/big_context.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/config.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/config_class.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/config_schema.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/gcp.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/gcp_project.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/parsers.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/timedelta.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/user_ids.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/utils/version.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/vertex/__init__.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/vertex/extensions_call.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/vertex/extensions_class.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/vertex/genai_functions.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/vertex/init.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/vertex/memory_tools.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/vertex/safety.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo/vertex/type_dict_to_json.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo.egg-info/SOURCES.txt +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo.egg-info/dependency_links.txt +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo.egg-info/entry_points.txt +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo.egg-info/requires.txt +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/sunholo.egg-info/top_level.txt +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/tests/test_chat_history.py +0 -0
- {sunholo-0.99.13 → sunholo-0.100.1}/tests/test_config.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.100.1
|
|
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.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.100.1.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -15,47 +15,62 @@ 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
|
|
18
|
+
async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
19
19
|
"""
|
|
20
|
-
Runs all tasks concurrently and yields results as they
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
callback: The callback object that will receive heartbeat messages.
|
|
20
|
+
Runs all tasks concurrently and yields results and heartbeats as they are produced.
|
|
24
21
|
"""
|
|
25
|
-
log.info("Running tasks asynchronously and yielding results as they
|
|
22
|
+
log.info("Running tasks asynchronously and yielding results and heartbeats as they occur")
|
|
23
|
+
queue = asyncio.Queue()
|
|
26
24
|
tasks = {}
|
|
25
|
+
completed_tasks = set()
|
|
26
|
+
|
|
27
27
|
for name, func, args in self.tasks:
|
|
28
|
-
|
|
29
|
-
coro = self._task_wrapper(name, func, args, callback)
|
|
28
|
+
coro = self._task_wrapper(name, func, args, queue)
|
|
30
29
|
task = asyncio.create_task(coro)
|
|
31
30
|
tasks[task] = name
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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})
|
|
44
55
|
|
|
45
|
-
|
|
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:
|
|
46
65
|
"""Wraps the task function to process its output and handle retries, while managing heartbeat updates."""
|
|
47
66
|
async def run_func():
|
|
48
67
|
if asyncio.iscoroutinefunction(func):
|
|
49
|
-
# If the function is async, await it
|
|
50
68
|
return await func(*args)
|
|
51
69
|
else:
|
|
52
|
-
# If the function is sync, run it in a thread to prevent blocking
|
|
53
70
|
return await asyncio.to_thread(func, *args)
|
|
54
71
|
|
|
55
|
-
# Start the heartbeat task
|
|
56
|
-
heartbeat_task =
|
|
57
|
-
if callback:
|
|
58
|
-
heartbeat_task = asyncio.create_task(self._send_heartbeat(callback, name))
|
|
72
|
+
# Start the heartbeat task
|
|
73
|
+
heartbeat_task = asyncio.create_task(self._send_heartbeat(queue, name))
|
|
59
74
|
|
|
60
75
|
try:
|
|
61
76
|
if self.retry_enabled:
|
|
@@ -76,29 +91,16 @@ class AsyncTaskRunner:
|
|
|
76
91
|
raise
|
|
77
92
|
finally:
|
|
78
93
|
# Stop the heartbeat task
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
pass
|
|
86
|
-
|
|
87
|
-
# Send a message indicating task completion to update the spinner's state
|
|
88
|
-
if callback:
|
|
89
|
-
completion_html = (
|
|
90
|
-
f'<div style="display: none;" data-complete-id="{name}-spinner"></div>'
|
|
91
|
-
)
|
|
92
|
-
await callback.async_on_llm_new_token(token=completion_html)
|
|
94
|
+
heartbeat_task.cancel()
|
|
95
|
+
# Wait for the heartbeat task to finish
|
|
96
|
+
try:
|
|
97
|
+
await heartbeat_task
|
|
98
|
+
except asyncio.CancelledError:
|
|
99
|
+
pass
|
|
93
100
|
|
|
94
|
-
async def _send_heartbeat(self,
|
|
101
|
+
async def _send_heartbeat(self, queue: asyncio.Queue, func_name: str, interval=2):
|
|
95
102
|
"""
|
|
96
103
|
Sends a periodic heartbeat to keep the task alive and update the spinner with elapsed time.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
callback: The callback to notify that the task is still working.
|
|
100
|
-
func_name: The name of the task function.
|
|
101
|
-
interval: How frequently to send heartbeat messages (in seconds).
|
|
102
104
|
"""
|
|
103
105
|
# Send the initial spinner HTML
|
|
104
106
|
spinner_html = (
|
|
@@ -107,23 +109,30 @@ class AsyncTaskRunner:
|
|
|
107
109
|
f' <span class="elapsed-time">Task {func_name} is still running... 0s elapsed</span>'
|
|
108
110
|
f'</div>'
|
|
109
111
|
)
|
|
110
|
-
|
|
112
|
+
log.info(f"Heartbeat started for task {func_name}")
|
|
113
|
+
|
|
114
|
+
await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': spinner_html})
|
|
111
115
|
|
|
112
116
|
# Keep track of elapsed time
|
|
113
117
|
elapsed_time = 0
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
try:
|
|
119
|
+
try:
|
|
120
|
+
while True:
|
|
118
121
|
await asyncio.sleep(interval) # Sleep for the interval
|
|
119
122
|
elapsed_time += interval # Increment elapsed time
|
|
120
|
-
|
|
123
|
+
log.info(f"Sending heartbeat for {func_name}: {elapsed_time}s elapsed")
|
|
121
124
|
# Update spinner with the elapsed time
|
|
122
125
|
update_html = (
|
|
123
126
|
f'<div style="display: none;" data-update-id="{func_name}-spinner">'
|
|
124
127
|
f'<span class="elapsed-time">Task {func_name} is still running... {elapsed_time}s elapsed</span>'
|
|
125
128
|
f'</div>'
|
|
126
129
|
)
|
|
127
|
-
await
|
|
128
|
-
|
|
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})
|
|
@@ -16,6 +16,7 @@ from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
|
|
|
16
16
|
from langchain.schema import LLMResult
|
|
17
17
|
|
|
18
18
|
import threading
|
|
19
|
+
import asyncio
|
|
19
20
|
import re
|
|
20
21
|
from ..custom_logging import log
|
|
21
22
|
|
|
@@ -39,6 +40,8 @@ class ContentBuffer:
|
|
|
39
40
|
that the buffer has been created.
|
|
40
41
|
"""
|
|
41
42
|
self.content = ""
|
|
43
|
+
self.lock = asyncio.Lock() # Use an async lock to ensure atomic writes
|
|
44
|
+
self.content_available = asyncio.Event()
|
|
42
45
|
log.debug("Content buffer initialized")
|
|
43
46
|
|
|
44
47
|
def write(self, text: str):
|
|
@@ -61,7 +64,9 @@ class ContentBuffer:
|
|
|
61
64
|
|
|
62
65
|
Adds the given text to the existing content of the buffer.
|
|
63
66
|
"""
|
|
64
|
-
self.
|
|
67
|
+
async with self.lock:
|
|
68
|
+
self.content += text
|
|
69
|
+
self.content_available.set()
|
|
65
70
|
|
|
66
71
|
def read(self) -> str:
|
|
67
72
|
"""
|
|
@@ -81,7 +86,9 @@ class ContentBuffer:
|
|
|
81
86
|
Returns:
|
|
82
87
|
str: The content of the buffer.
|
|
83
88
|
"""
|
|
84
|
-
|
|
89
|
+
async with self.lock:
|
|
90
|
+
content = self.content
|
|
91
|
+
return content
|
|
85
92
|
|
|
86
93
|
def clear(self):
|
|
87
94
|
"""
|
|
@@ -97,7 +104,10 @@ class ContentBuffer:
|
|
|
97
104
|
|
|
98
105
|
Empties the buffer content, resetting it to an empty string.
|
|
99
106
|
"""
|
|
100
|
-
self.
|
|
107
|
+
async with self.lock:
|
|
108
|
+
self.content = ""
|
|
109
|
+
log.debug("Content buffer cleared")
|
|
110
|
+
self.content_available.clear()
|
|
101
111
|
|
|
102
112
|
|
|
103
113
|
class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
|
|
@@ -137,11 +147,20 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
|
|
|
137
147
|
self.tokens = tokens
|
|
138
148
|
self.buffer = ""
|
|
139
149
|
self.stream_finished = threading.Event()
|
|
150
|
+
|
|
140
151
|
self.in_code_block = False
|
|
141
152
|
self.in_question_block = False
|
|
142
153
|
self.question_buffer = ""
|
|
143
154
|
log.info("Starting to stream LLM")
|
|
144
155
|
|
|
156
|
+
def _is_heartbeat_token(self, token: str) -> bool:
|
|
157
|
+
"""Detects if the token is a heartbeat message."""
|
|
158
|
+
return token.startswith('[[HEARTBEAT]]') and token.endswith('[[/HEARTBEAT]]')
|
|
159
|
+
|
|
160
|
+
def _strip_heartbeat_markers(self, token: str) -> str:
|
|
161
|
+
"""Removes the [[HEARTBEAT]] markers from the token."""
|
|
162
|
+
return token[len('[[HEARTBEAT]]'):-len('[[/HEARTBEAT]]')]
|
|
163
|
+
|
|
145
164
|
def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
|
146
165
|
"""
|
|
147
166
|
Processes a new token from the LLM output.
|
|
@@ -154,33 +173,22 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
|
|
|
154
173
|
The buffer content is written to the content buffer when appropriate tokens or
|
|
155
174
|
patterns are detected.
|
|
156
175
|
"""
|
|
157
|
-
log.debug(f"on_llm_new_token: {token}")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
async def async_on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
|
168
|
-
"""
|
|
169
|
-
Asynchronously processes a new token from the LLM output.
|
|
170
|
-
|
|
171
|
-
Args:
|
|
172
|
-
token (str): The new token generated by the LLM.
|
|
173
|
-
**kwargs: Additional keyword arguments.
|
|
174
|
-
"""
|
|
175
|
-
log.debug(f"async_on_llm_new_token: {token}")
|
|
176
|
-
|
|
177
|
-
self.buffer += token
|
|
176
|
+
#log.debug(f"on_llm_new_token: {token}")
|
|
177
|
+
|
|
178
|
+
# Check if the token is a heartbeat message
|
|
179
|
+
if self._is_heartbeat_token(token):
|
|
180
|
+
# Strip the [[HEARTBEAT]] markers and write immediately
|
|
181
|
+
heartbeat_content = self._strip_heartbeat_markers(token)
|
|
182
|
+
#log.info(f"Heartbeat token detected, writing immediately: {heartbeat_content}")
|
|
183
|
+
self.content_buffer.write(heartbeat_content)
|
|
184
|
+
else:
|
|
185
|
+
self.buffer += token
|
|
178
186
|
|
|
179
|
-
|
|
180
|
-
|
|
187
|
+
if '```' in token:
|
|
188
|
+
self.in_code_block = not self.in_code_block
|
|
181
189
|
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
if not self.in_code_block:
|
|
191
|
+
self._process_buffer()
|
|
184
192
|
|
|
185
193
|
def _process_buffer(self):
|
|
186
194
|
"""
|
|
@@ -200,24 +208,6 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
|
|
|
200
208
|
self.content_buffer.write(self.buffer)
|
|
201
209
|
self.buffer = ""
|
|
202
210
|
|
|
203
|
-
async def _async_process_buffer(self):
|
|
204
|
-
"""
|
|
205
|
-
Asynchronously processes the buffer content and writes to the content buffer.
|
|
206
|
-
|
|
207
|
-
If the buffer ends with a numbered list pattern or specified tokens, the buffer is flushed
|
|
208
|
-
to the content buffer. Otherwise, the buffer is left intact for further accumulation.
|
|
209
|
-
"""
|
|
210
|
-
matches = list(re.finditer(r'\n(\d+\.\s)', self.buffer))
|
|
211
|
-
if matches:
|
|
212
|
-
last_match = matches[-1]
|
|
213
|
-
start_of_last_match = last_match.start() + 1
|
|
214
|
-
await self.content_buffer.async_write(self.buffer[:start_of_last_match])
|
|
215
|
-
self.buffer = self.buffer[start_of_last_match:]
|
|
216
|
-
else:
|
|
217
|
-
if any(self.buffer.endswith(t) for t in self.tokens):
|
|
218
|
-
await self.content_buffer.async_write(self.buffer)
|
|
219
|
-
self.buffer = ""
|
|
220
|
-
|
|
221
211
|
def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
|
|
222
212
|
"""
|
|
223
213
|
Handles the end of LLM streaming.
|
|
@@ -237,6 +227,90 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
|
|
|
237
227
|
self.stream_finished.set()
|
|
238
228
|
log.info("Streaming LLM response ended successfully")
|
|
239
229
|
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class BufferStreamingStdOutCallbackHandlerAsync(StreamingStdOutCallbackHandler):
|
|
233
|
+
"""
|
|
234
|
+
An async callback handler for streaming LLM output to a content buffer.
|
|
235
|
+
|
|
236
|
+
This class handles the streaming of output from a large language model (LLM),
|
|
237
|
+
processes tokens from the model output, and writes them to a ContentBuffer.
|
|
238
|
+
It supports handling different types of tokens and keeps track of code blocks
|
|
239
|
+
and questions.
|
|
240
|
+
|
|
241
|
+
Attributes:
|
|
242
|
+
content_buffer (ContentBuffer): The buffer to which content is streamed.
|
|
243
|
+
tokens (str): Tokens that indicate the end of a statement, for buffer flushing.
|
|
244
|
+
buffer (str): Temporary storage for accumulating streamed tokens.
|
|
245
|
+
stream_finished (asyncio.Event): Signals when the streaming is finished.
|
|
246
|
+
in_code_block (bool): Indicates whether the current context is a code block.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def __init__(self, content_buffer: ContentBuffer, tokens: str = ".?!\n", *args, **kwargs):
|
|
250
|
+
"""
|
|
251
|
+
Initializes a new BufferStreamingStdOutCallbackHandler instance.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
content_buffer (ContentBuffer): The buffer to which content will be written.
|
|
255
|
+
tokens (str): Tokens that indicate the end of a statement (default: ".?!\n").
|
|
256
|
+
*args: Additional positional arguments.
|
|
257
|
+
**kwargs: Additional keyword arguments.
|
|
258
|
+
|
|
259
|
+
Sets up the callback handler with the given content buffer and tokens.
|
|
260
|
+
Initializes tracking variables for code blocks, buffer content, and the finished signal.
|
|
261
|
+
"""
|
|
262
|
+
super().__init__(*args, **kwargs)
|
|
263
|
+
self.content_buffer = content_buffer
|
|
264
|
+
self.tokens = tokens
|
|
265
|
+
self.buffer = ""
|
|
266
|
+
self.stream_finished = asyncio.Event() # Use asyncio.Event for async compatibility
|
|
267
|
+
self.in_code_block = False
|
|
268
|
+
log.info("Starting to stream LLM")
|
|
269
|
+
|
|
270
|
+
async def async_on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
|
271
|
+
#log.debug(f"async_on_llm_new_token: {token}")
|
|
272
|
+
|
|
273
|
+
# Check if the token is a heartbeat message
|
|
274
|
+
if self._is_heartbeat_token(token):
|
|
275
|
+
# Strip the [[HEARTBEAT]] markers and write immediately
|
|
276
|
+
heartbeat_content = self._strip_heartbeat_markers(token)
|
|
277
|
+
#log.info(f"Heartbeat token detected, writing immediately: {heartbeat_content}")
|
|
278
|
+
await self.content_buffer.async_write(heartbeat_content)
|
|
279
|
+
else:
|
|
280
|
+
self.buffer += token
|
|
281
|
+
|
|
282
|
+
if '```' in token:
|
|
283
|
+
self.in_code_block = not self.in_code_block
|
|
284
|
+
|
|
285
|
+
if not self.in_code_block:
|
|
286
|
+
await self._async_process_buffer()
|
|
287
|
+
|
|
288
|
+
def _is_heartbeat_token(self, token: str) -> bool:
|
|
289
|
+
"""Detects if the token is a heartbeat message."""
|
|
290
|
+
return token.startswith('[[HEARTBEAT]]') and token.endswith('[[/HEARTBEAT]]')
|
|
291
|
+
|
|
292
|
+
def _strip_heartbeat_markers(self, token: str) -> str:
|
|
293
|
+
"""Removes the [[HEARTBEAT]] markers from the token."""
|
|
294
|
+
return token[len('[[HEARTBEAT]]'):-len('[[/HEARTBEAT]]')]
|
|
295
|
+
|
|
296
|
+
async def _async_process_buffer(self):
|
|
297
|
+
"""
|
|
298
|
+
Asynchronously processes the buffer content and writes to the content buffer.
|
|
299
|
+
|
|
300
|
+
If the buffer ends with a numbered list pattern or specified tokens, the buffer is flushed
|
|
301
|
+
to the content buffer. Otherwise, the buffer is left intact for further accumulation.
|
|
302
|
+
"""
|
|
303
|
+
matches = list(re.finditer(r'\n(\d+\.\s)', self.buffer))
|
|
304
|
+
if matches:
|
|
305
|
+
last_match = matches[-1]
|
|
306
|
+
start_of_last_match = last_match.start() + 1
|
|
307
|
+
await self.content_buffer.async_write(self.buffer[:start_of_last_match])
|
|
308
|
+
self.buffer = self.buffer[start_of_last_match:]
|
|
309
|
+
else:
|
|
310
|
+
if any(self.buffer.endswith(t) for t in self.tokens):
|
|
311
|
+
await self.content_buffer.async_write(self.buffer)
|
|
312
|
+
self.buffer = ""
|
|
313
|
+
|
|
240
314
|
async def async_on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
|
|
241
315
|
"""
|
|
242
316
|
Asynchronously handles the end of LLM streaming.
|
|
@@ -251,4 +325,5 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
|
|
|
251
325
|
log.info("Flushing remaining LLM response buffer")
|
|
252
326
|
|
|
253
327
|
self.stream_finished.set()
|
|
254
|
-
log.info("Streaming LLM response ended successfully")
|
|
328
|
+
log.info("Streaming LLM response ended successfully")
|
|
329
|
+
|
|
@@ -17,7 +17,7 @@ import json
|
|
|
17
17
|
import time
|
|
18
18
|
import asyncio
|
|
19
19
|
|
|
20
|
-
from .content_buffer import ContentBuffer, BufferStreamingStdOutCallbackHandler
|
|
20
|
+
from .content_buffer import ContentBuffer, BufferStreamingStdOutCallbackHandler, BufferStreamingStdOutCallbackHandlerAsync
|
|
21
21
|
|
|
22
22
|
from ..qna.parsers import parse_output
|
|
23
23
|
|
|
@@ -159,7 +159,7 @@ async def start_streaming_chat_async(question, vector_name, qna_func_async, chat
|
|
|
159
159
|
yield "No **kwargs in qna_func_async - please add it"
|
|
160
160
|
|
|
161
161
|
content_buffer = ContentBuffer()
|
|
162
|
-
chat_callback_handler =
|
|
162
|
+
chat_callback_handler = BufferStreamingStdOutCallbackHandlerAsync(content_buffer=content_buffer, tokens=".!?\n")
|
|
163
163
|
|
|
164
164
|
result_queue = Queue()
|
|
165
165
|
exception_queue = Queue()
|
|
@@ -179,32 +179,26 @@ async def start_streaming_chat_async(question, vector_name, qna_func_async, chat
|
|
|
179
179
|
await asyncio.sleep(0)
|
|
180
180
|
|
|
181
181
|
# Read and yield any initial content from the content buffer
|
|
182
|
-
content_to_send = content_buffer.
|
|
182
|
+
content_to_send = await content_buffer.async_read()
|
|
183
183
|
if content_to_send:
|
|
184
184
|
log.info(f"Initial content: {content_to_send}")
|
|
185
185
|
yield content_to_send
|
|
186
|
-
content_buffer.
|
|
187
|
-
|
|
188
|
-
start = time.time()
|
|
186
|
+
await content_buffer.async_clear()
|
|
189
187
|
|
|
190
188
|
while not chat_callback_handler.stream_finished.is_set() and not stop_event.is_set():
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
raise exception
|
|
189
|
+
try:
|
|
190
|
+
await asyncio.wait_for(content_buffer.content_available.wait(), timeout=timeout)
|
|
191
|
+
except asyncio.TimeoutError:
|
|
192
|
+
log.warning(f"Content production has timed out after {timeout} seconds")
|
|
193
|
+
break
|
|
197
194
|
|
|
198
|
-
content_to_send = content_buffer.
|
|
195
|
+
content_to_send = await content_buffer.async_read()
|
|
199
196
|
if content_to_send:
|
|
200
|
-
log.info(f"
|
|
197
|
+
log.info(f"==Async\n{content_to_send}")
|
|
201
198
|
yield content_to_send
|
|
202
|
-
content_buffer.
|
|
203
|
-
start = time.time()
|
|
199
|
+
await content_buffer.async_clear()
|
|
204
200
|
else:
|
|
205
|
-
|
|
206
|
-
log.warning(f"Content production has timed out after {timeout} seconds")
|
|
207
|
-
break
|
|
201
|
+
log.debug("Content available event set but no content found.")
|
|
208
202
|
|
|
209
203
|
stop_event.set()
|
|
210
204
|
await chat_task # Ensure the async task is awaited
|
|
@@ -217,8 +211,6 @@ async def start_streaming_chat_async(question, vector_name, qna_func_async, chat
|
|
|
217
211
|
yield json.dumps(parsed_final_result)
|
|
218
212
|
|
|
219
213
|
|
|
220
|
-
|
|
221
|
-
|
|
222
214
|
def generate_proxy_stream(stream_to_f, user_input, vector_name, chat_history, generate_f_output, **kwargs):
|
|
223
215
|
"""
|
|
224
216
|
Generator function for proxying and processing streaming output from an underlying model.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.100.1
|
|
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.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.100.1.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|