sunholo 0.95.1__tar.gz → 0.95.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.
Files changed (160) hide show
  1. {sunholo-0.95.1 → sunholo-0.95.2}/PKG-INFO +2 -2
  2. {sunholo-0.95.1 → sunholo-0.95.2}/setup.py +1 -1
  3. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/flask/vac_routes.py +55 -58
  4. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/invoke/async_class.py +8 -9
  5. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/streaming/content_buffer.py +81 -1
  6. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo.egg-info/PKG-INFO +2 -2
  7. {sunholo-0.95.1 → sunholo-0.95.2}/LICENSE.txt +0 -0
  8. {sunholo-0.95.1 → sunholo-0.95.2}/MANIFEST.in +0 -0
  9. {sunholo-0.95.1 → sunholo-0.95.2}/README.md +0 -0
  10. {sunholo-0.95.1 → sunholo-0.95.2}/setup.cfg +0 -0
  11. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/__init__.py +0 -0
  12. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/__init__.py +0 -0
  13. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/chat_history.py +0 -0
  14. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/dispatch_to_qa.py +0 -0
  15. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/fastapi/__init__.py +0 -0
  16. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/fastapi/base.py +0 -0
  17. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/fastapi/qna_routes.py +0 -0
  18. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/flask/__init__.py +0 -0
  19. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/flask/base.py +0 -0
  20. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/flask/qna_routes.py +0 -0
  21. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/langserve.py +0 -0
  22. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/pubsub.py +0 -0
  23. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/route.py +0 -0
  24. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/special_commands.py +0 -0
  25. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/agents/swagger.py +0 -0
  26. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/archive/__init__.py +0 -0
  27. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/archive/archive.py +0 -0
  28. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/auth/__init__.py +0 -0
  29. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/auth/gcloud.py +0 -0
  30. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/auth/refresh.py +0 -0
  31. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/auth/run.py +0 -0
  32. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/azure/__init__.py +0 -0
  33. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/azure/auth.py +0 -0
  34. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/azure/blobs.py +0 -0
  35. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/azure/event_grid.py +0 -0
  36. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/bots/__init__.py +0 -0
  37. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/bots/discord.py +0 -0
  38. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/bots/github_webhook.py +0 -0
  39. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/bots/webapp.py +0 -0
  40. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/__init__.py +0 -0
  41. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/azure.py +0 -0
  42. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/doc_handling.py +0 -0
  43. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/encode_metadata.py +0 -0
  44. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/images.py +0 -0
  45. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/loaders.py +0 -0
  46. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/message_data.py +0 -0
  47. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/pdfs.py +0 -0
  48. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/process_chunker_data.py +0 -0
  49. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/publish.py +0 -0
  50. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/pubsub.py +0 -0
  51. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/chunker/splitter.py +0 -0
  52. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/__init__.py +0 -0
  53. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/chat_vac.py +0 -0
  54. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/cli.py +0 -0
  55. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/cli_init.py +0 -0
  56. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/configs.py +0 -0
  57. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/deploy.py +0 -0
  58. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/embedder.py +0 -0
  59. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/merge_texts.py +0 -0
  60. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/run_proxy.py +0 -0
  61. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/sun_rich.py +0 -0
  62. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/swagger.py +0 -0
  63. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/cli/vertex.py +0 -0
  64. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/components/__init__.py +0 -0
  65. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/components/llm.py +0 -0
  66. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/components/retriever.py +0 -0
  67. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/components/vectorstore.py +0 -0
  68. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/custom_logging.py +0 -0
  69. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/__init__.py +0 -0
  70. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/alloydb.py +0 -0
  71. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/alloydb_client.py +0 -0
  72. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/database.py +0 -0
  73. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/lancedb.py +0 -0
  74. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/sql/sb/create_function.sql +0 -0
  75. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  76. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/sql/sb/create_table.sql +0 -0
  77. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  78. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/sql/sb/return_sources.sql +0 -0
  79. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/sql/sb/setup.sql +0 -0
  80. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/static_dbs.py +0 -0
  81. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/database/uuid.py +0 -0
  82. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/discovery_engine/__init__.py +0 -0
  83. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/discovery_engine/chunker_handler.py +0 -0
  84. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/discovery_engine/create_new.py +0 -0
  85. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
  86. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
  87. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/embedder/__init__.py +0 -0
  88. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/embedder/embed_chunk.py +0 -0
  89. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/excel/__init__.py +0 -0
  90. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/excel/plugin.py +0 -0
  91. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/gcs/__init__.py +0 -0
  92. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/gcs/add_file.py +0 -0
  93. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/gcs/download_folder.py +0 -0
  94. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/gcs/download_url.py +0 -0
  95. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/gcs/metadata.py +0 -0
  96. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/genai/__init__.py +0 -0
  97. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/genai/init.py +0 -0
  98. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/genai/process_funcs_cls.py +0 -0
  99. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/genai/safety.py +0 -0
  100. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/invoke/__init__.py +0 -0
  101. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/invoke/direct_vac_func.py +0 -0
  102. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/invoke/invoke_vac_utils.py +0 -0
  103. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/langfuse/__init__.py +0 -0
  104. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/langfuse/callback.py +0 -0
  105. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/langfuse/evals.py +0 -0
  106. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/langfuse/prompts.py +0 -0
  107. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/llamaindex/__init__.py +0 -0
  108. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/llamaindex/get_files.py +0 -0
  109. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/llamaindex/import_files.py +0 -0
  110. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/llamaindex/llamaindex_class.py +0 -0
  111. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/llamaindex/user_history.py +0 -0
  112. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/lookup/__init__.py +0 -0
  113. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/lookup/model_lookup.yaml +0 -0
  114. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/patches/__init__.py +0 -0
  115. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/patches/langchain/__init__.py +0 -0
  116. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/patches/langchain/lancedb.py +0 -0
  117. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/patches/langchain/vertexai.py +0 -0
  118. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/pubsub/__init__.py +0 -0
  119. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/pubsub/process_pubsub.py +0 -0
  120. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/pubsub/pubsub_manager.py +0 -0
  121. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/qna/__init__.py +0 -0
  122. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/qna/parsers.py +0 -0
  123. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/qna/retry.py +0 -0
  124. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/streaming/__init__.py +0 -0
  125. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/streaming/langserve.py +0 -0
  126. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/streaming/stream_lookup.py +0 -0
  127. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/streaming/streaming.py +0 -0
  128. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/summarise/__init__.py +0 -0
  129. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/summarise/summarise.py +0 -0
  130. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/terraform/__init__.py +0 -0
  131. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/terraform/tfvars_editor.py +0 -0
  132. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/tools/__init__.py +0 -0
  133. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/tools/web_browser.py +0 -0
  134. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/__init__.py +0 -0
  135. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/api_key.py +0 -0
  136. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/big_context.py +0 -0
  137. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/config.py +0 -0
  138. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/config_class.py +0 -0
  139. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/config_schema.py +0 -0
  140. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/gcp.py +0 -0
  141. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/gcp_project.py +0 -0
  142. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/parsers.py +0 -0
  143. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/timedelta.py +0 -0
  144. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/user_ids.py +0 -0
  145. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/utils/version.py +0 -0
  146. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/vertex/__init__.py +0 -0
  147. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/vertex/extensions_call.py +0 -0
  148. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/vertex/extensions_class.py +0 -0
  149. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/vertex/genai_functions.py +0 -0
  150. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/vertex/init.py +0 -0
  151. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/vertex/memory_tools.py +0 -0
  152. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/vertex/safety.py +0 -0
  153. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo/vertex/type_dict_to_json.py +0 -0
  154. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo.egg-info/SOURCES.txt +0 -0
  155. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo.egg-info/dependency_links.txt +0 -0
  156. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo.egg-info/entry_points.txt +0 -0
  157. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo.egg-info/requires.txt +0 -0
  158. {sunholo-0.95.1 → sunholo-0.95.2}/sunholo.egg-info/top_level.txt +0 -0
  159. {sunholo-0.95.1 → sunholo-0.95.2}/tests/test_chat_history.py +0 -0
  160. {sunholo-0.95.1 → sunholo-0.95.2}/tests/test_config.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.95.1
3
+ Version: 0.95.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.95.1.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.95.2.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- version = '0.95.1'
3
+ version = '0.95.2'
4
4
 
5
5
  setup(
6
6
  name='sunholo',
@@ -185,11 +185,9 @@ if __name__ == "__main__":
185
185
 
186
186
  log.info(f"OpenAI response: {openai_response}")
187
187
  return jsonify(openai_response)
188
-
188
+
189
189
  def handle_stream_vac(self, vector_name):
190
190
  observed_stream_interpreter = self.stream_interpreter
191
-
192
- # Determine if the stream interpreter is async or sync
193
191
  is_async = inspect.iscoroutinefunction(self.stream_interpreter)
194
192
 
195
193
  if is_async:
@@ -219,52 +217,53 @@ if __name__ == "__main__":
219
217
  def generate_response_content():
220
218
  try:
221
219
  if is_async:
222
- # Run the async version
223
- async def process_async():
224
- async_gen = start_streaming_chat_async(
225
- question=all_input["user_input"],
226
- vector_name=vector_name,
227
- qna_func_async=observed_stream_interpreter,
228
- chat_history=all_input["chat_history"],
229
- wait_time=all_input["stream_wait_time"],
230
- timeout=all_input["stream_timeout"],
231
- trace_id=trace.id if trace else None,
232
- **all_input["kwargs"]
233
- )
234
- log.info(f"{async_gen=}")
235
- # Yield intermediate results as they become available
236
- async for chunk in async_gen:
237
- if isinstance(chunk, dict) and 'answer' in chunk:
238
- if trace:
239
- chunk["trace_id"] = trace.id
240
- chunk["trace_url"] = trace.get_trace_url()
241
- generation.end(output=json.dumps(chunk))
242
- span.end(output=json.dumps(chunk))
243
- trace.update(output=json.dumps(chunk))
244
-
245
- archive_qa(chunk, vector_name)
246
-
247
- yield json.dumps(chunk)
248
- else:
249
- yield chunk
250
-
251
- try:
252
- loop = asyncio.get_running_loop()
253
- # Use the event loop to consume the async generator and yield results
254
- return [chunk for chunk in loop.run_until_complete(self._async_generator_to_list(process_async()))]
255
- except RuntimeError:
256
- # No running event loop, create a new one
257
- log.info("Creating new event loop")
258
- loop = asyncio.new_event_loop()
259
- asyncio.set_event_loop(loop)
260
- try:
261
- log.info(f"Run until complete loop for {process_async}")
262
- chunks = loop.run_until_complete(self._async_generator_to_list(process_async()))
263
- for chunk in chunks:
264
- yield chunk
265
- finally:
266
- loop.close()
267
-
220
+ from queue import Queue, Empty
221
+ result_queue = Queue()
222
+ import threading
223
+
224
+ def run_async():
225
+ async def process_async():
226
+ try:
227
+ async_gen = start_streaming_chat_async(
228
+ question=all_input["user_input"],
229
+ vector_name=vector_name,
230
+ qna_func_async=observed_stream_interpreter,
231
+ chat_history=all_input["chat_history"],
232
+ wait_time=all_input["stream_wait_time"],
233
+ timeout=all_input["stream_timeout"],
234
+ trace_id=trace.id if trace else None,
235
+ **all_input["kwargs"]
236
+ )
237
+ log.info(f"{async_gen=}")
238
+ async for chunk in async_gen:
239
+ if isinstance(chunk, dict) and 'answer' in chunk:
240
+ if trace:
241
+ chunk["trace_id"] = trace.id
242
+ chunk["trace_url"] = trace.get_trace_url()
243
+ generation.end(output=json.dumps(chunk))
244
+ span.end(output=json.dumps(chunk))
245
+ trace.update(output=json.dumps(chunk))
246
+ archive_qa(chunk, vector_name)
247
+ result_queue.put(json.dumps(chunk))
248
+ else:
249
+ result_queue.put(chunk)
250
+ except Exception as e:
251
+ result_queue.put(f"Streaming Error: {str(e)} {traceback.format_exc()}")
252
+ finally:
253
+ result_queue.put(None) # Sentinel
254
+ asyncio.run(process_async())
255
+
256
+ thread = threading.Thread(target=run_async)
257
+ thread.start()
258
+
259
+ # Read from the queue and yield results
260
+ while True:
261
+ chunk = result_queue.get()
262
+ if chunk is None:
263
+ break
264
+ yield chunk
265
+
266
+ thread.join()
268
267
  else:
269
268
  log.info("sync streaming response")
270
269
  for chunk in start_streaming_chat(
@@ -286,16 +285,16 @@ if __name__ == "__main__":
286
285
  generation.end(output=json.dumps(chunk))
287
286
  span.end(output=json.dumps(chunk))
288
287
  trace.update(output=json.dumps(chunk))
289
- return json.dumps(chunk)
288
+ yield json.dumps(chunk)
290
289
  else:
291
290
  yield chunk
292
291
 
293
292
  except Exception as e:
294
293
  yield f"Streaming Error: {str(e)} {traceback.format_exc()}"
295
-
294
+
296
295
  # Here, the generator function will handle streaming the content to the client.
297
296
  response = Response(generate_response_content(), content_type='text/plain; charset=utf-8')
298
- response.headers['Transfer-Encoding'] = 'chunked'
297
+ response.headers['Transfer-Encoding'] = 'chunked'
299
298
 
300
299
  log.debug(f"streaming response: {response}")
301
300
  if trace:
@@ -307,12 +306,10 @@ if __name__ == "__main__":
307
306
  return response
308
307
 
309
308
  @staticmethod
310
- async def _async_generator_to_list(async_gen):
311
- """Helper function to collect an async generator's values into a list."""
312
- result = []
313
- async for item in async_gen:
314
- result.append(item)
315
- return result
309
+ async def _async_generator_to_stream(async_gen_func):
310
+ """Helper function to stream the async generator's values to the client."""
311
+ async for item in async_gen_func():
312
+ yield item
316
313
 
317
314
  def langfuse_eval_response(self, trace_id, eval_percent=0.01):
318
315
  """
@@ -1,11 +1,9 @@
1
1
  import asyncio
2
- import logging
2
+ from ..custom_logging import log
3
3
  import traceback
4
4
  from typing import Callable, List, Any, AsyncGenerator, Dict
5
5
  from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponential, stop_after_attempt
6
6
 
7
- logger = logging.getLogger(__name__)
8
-
9
7
  class AsyncTaskRunner:
10
8
  def __init__(self, retry_enabled=False, retry_kwargs=None):
11
9
  self.tasks = []
@@ -14,18 +12,19 @@ class AsyncTaskRunner:
14
12
 
15
13
  def add_task(self, func: Callable[..., Any], *args: Any):
16
14
  """Adds a task to the list of tasks to be executed."""
17
- logger.info(f"Adding task: {func.__name__} with args: {args}")
15
+ log.info(f"Adding task: {func.__name__} with args: {args}")
18
16
  self.tasks.append((func.__name__, func, args))
19
17
 
20
18
  async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
21
19
  """Runs all tasks concurrently and yields results as they complete."""
22
- logger.info("Running tasks asynchronously and yielding results as they complete")
20
+ log.info("Running tasks asynchronously and yielding results as they complete")
23
21
  tasks = {}
24
22
  for name, func, args in self.tasks:
25
23
  coro = self._task_wrapper(name, func, args)
26
24
  task = asyncio.create_task(coro)
27
25
  tasks[task] = name
28
26
 
27
+ log.info(f"Start async run with {len(self.tasks)} runners")
29
28
  while tasks:
30
29
  done, _ = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
31
30
  for task in done:
@@ -34,7 +33,7 @@ class AsyncTaskRunner:
34
33
  result = await task
35
34
  yield {name: result}
36
35
  except Exception as e:
37
- logger.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
36
+ log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
38
37
  yield {name: e}
39
38
 
40
39
  async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any) -> Any:
@@ -65,13 +64,13 @@ class AsyncTaskRunner:
65
64
  try:
66
65
  return await run_func()
67
66
  except Exception as e:
68
- logger.error(f"Error in task {name}: {e}\n{traceback.format_exc()}")
67
+ log.error(f"Error in task {name}: {e}\n{traceback.format_exc()}")
69
68
  raise
70
69
 
71
70
  def _log_results(self, results: List[Any]):
72
71
  """Logs the results of the task executions."""
73
72
  for result in results:
74
73
  if isinstance(result, Exception):
75
- logger.error(f"Task resulted in an error: {result}")
74
+ log.error(f"Task resulted in an error: {result}")
76
75
  else:
77
- logger.info(f"Task completed successfully: {result}")
76
+ log.info(f"Task completed successfully: {result}")
@@ -51,6 +51,17 @@ class ContentBuffer:
51
51
  Adds the given text to the existing content of the buffer.
52
52
  """
53
53
  self.content += text
54
+
55
+ async def async_write(self, text: str):
56
+ """
57
+ Asynchronously writes text to the content buffer.
58
+
59
+ Args:
60
+ text (str): The text to be added to the buffer.
61
+
62
+ Adds the given text to the existing content of the buffer.
63
+ """
64
+ self.content += text
54
65
 
55
66
  def read(self) -> str:
56
67
  """
@@ -63,6 +74,15 @@ class ContentBuffer:
63
74
  """
64
75
  return self.content
65
76
 
77
+ async def async_read(self) -> str:
78
+ """
79
+ Asynchronously reads the entire content from the buffer.
80
+
81
+ Returns:
82
+ str: The content of the buffer.
83
+ """
84
+ return self.content
85
+
66
86
  def clear(self):
67
87
  """
68
88
  Clears the content buffer.
@@ -71,7 +91,15 @@ class ContentBuffer:
71
91
  """
72
92
  self.content = ""
73
93
 
74
-
94
+ async def async_clear(self):
95
+ """
96
+ Asynchronously clears the content buffer.
97
+
98
+ Empties the buffer content, resetting it to an empty string.
99
+ """
100
+ self.content = ""
101
+
102
+
75
103
  class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
76
104
  """
77
105
  A callback handler for streaming LLM output to a content buffer.
@@ -136,6 +164,24 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
136
164
  if not self.in_code_block:
137
165
  self._process_buffer()
138
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
178
+
179
+ if '```' in token:
180
+ self.in_code_block = not self.in_code_block
181
+
182
+ if not self.in_code_block:
183
+ await self._async_process_buffer()
184
+
139
185
  def _process_buffer(self):
140
186
  """
141
187
  Processes the buffer content and writes to the content buffer.
@@ -154,6 +200,24 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
154
200
  self.content_buffer.write(self.buffer)
155
201
  self.buffer = ""
156
202
 
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
+
157
221
  def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
158
222
  """
159
223
  Handles the end of LLM streaming.
@@ -172,3 +236,19 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
172
236
 
173
237
  self.stream_finished.set()
174
238
  log.info("Streaming LLM response ended successfully")
239
+
240
+ async def async_on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
241
+ """
242
+ Asynchronously handles the end of LLM streaming.
243
+
244
+ Args:
245
+ response (LLMResult): The result returned by the LLM.
246
+ **kwargs: Additional keyword arguments.
247
+ """
248
+ if self.buffer:
249
+ await self.content_buffer.async_write(self.buffer)
250
+ self.buffer = ""
251
+ log.info("Flushing remaining LLM response buffer")
252
+
253
+ self.stream_finished.set()
254
+ log.info("Streaming LLM response ended successfully")
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.95.1
3
+ Version: 0.95.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.95.1.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.95.2.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