sunholo 0.95.1__tar.gz → 0.95.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. {sunholo-0.95.1 → sunholo-0.95.3}/PKG-INFO +2 -2
  2. {sunholo-0.95.1 → sunholo-0.95.3}/setup.py +1 -1
  3. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/flask/vac_routes.py +55 -58
  4. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/route.py +2 -2
  5. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/genai/process_funcs_cls.py +6 -6
  6. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/invoke/async_class.py +8 -9
  7. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/streaming/content_buffer.py +81 -1
  8. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/streaming/streaming.py +10 -0
  9. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo.egg-info/PKG-INFO +2 -2
  10. {sunholo-0.95.1 → sunholo-0.95.3}/LICENSE.txt +0 -0
  11. {sunholo-0.95.1 → sunholo-0.95.3}/MANIFEST.in +0 -0
  12. {sunholo-0.95.1 → sunholo-0.95.3}/README.md +0 -0
  13. {sunholo-0.95.1 → sunholo-0.95.3}/setup.cfg +0 -0
  14. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/__init__.py +0 -0
  15. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/__init__.py +0 -0
  16. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/chat_history.py +0 -0
  17. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/dispatch_to_qa.py +0 -0
  18. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/fastapi/__init__.py +0 -0
  19. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/fastapi/base.py +0 -0
  20. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/fastapi/qna_routes.py +0 -0
  21. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/flask/__init__.py +0 -0
  22. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/flask/base.py +0 -0
  23. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/flask/qna_routes.py +0 -0
  24. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/langserve.py +0 -0
  25. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/pubsub.py +0 -0
  26. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/special_commands.py +0 -0
  27. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/agents/swagger.py +0 -0
  28. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/archive/__init__.py +0 -0
  29. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/archive/archive.py +0 -0
  30. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/auth/__init__.py +0 -0
  31. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/auth/gcloud.py +0 -0
  32. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/auth/refresh.py +0 -0
  33. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/auth/run.py +0 -0
  34. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/azure/__init__.py +0 -0
  35. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/azure/auth.py +0 -0
  36. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/azure/blobs.py +0 -0
  37. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/azure/event_grid.py +0 -0
  38. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/bots/__init__.py +0 -0
  39. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/bots/discord.py +0 -0
  40. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/bots/github_webhook.py +0 -0
  41. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/bots/webapp.py +0 -0
  42. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/__init__.py +0 -0
  43. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/azure.py +0 -0
  44. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/doc_handling.py +0 -0
  45. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/encode_metadata.py +0 -0
  46. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/images.py +0 -0
  47. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/loaders.py +0 -0
  48. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/message_data.py +0 -0
  49. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/pdfs.py +0 -0
  50. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/process_chunker_data.py +0 -0
  51. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/publish.py +0 -0
  52. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/pubsub.py +0 -0
  53. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/chunker/splitter.py +0 -0
  54. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/__init__.py +0 -0
  55. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/chat_vac.py +0 -0
  56. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/cli.py +0 -0
  57. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/cli_init.py +0 -0
  58. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/configs.py +0 -0
  59. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/deploy.py +0 -0
  60. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/embedder.py +0 -0
  61. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/merge_texts.py +0 -0
  62. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/run_proxy.py +0 -0
  63. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/sun_rich.py +0 -0
  64. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/swagger.py +0 -0
  65. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/cli/vertex.py +0 -0
  66. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/components/__init__.py +0 -0
  67. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/components/llm.py +0 -0
  68. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/components/retriever.py +0 -0
  69. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/components/vectorstore.py +0 -0
  70. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/custom_logging.py +0 -0
  71. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/__init__.py +0 -0
  72. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/alloydb.py +0 -0
  73. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/alloydb_client.py +0 -0
  74. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/database.py +0 -0
  75. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/lancedb.py +0 -0
  76. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/sql/sb/create_function.sql +0 -0
  77. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  78. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/sql/sb/create_table.sql +0 -0
  79. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  80. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/sql/sb/return_sources.sql +0 -0
  81. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/sql/sb/setup.sql +0 -0
  82. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/static_dbs.py +0 -0
  83. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/database/uuid.py +0 -0
  84. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/discovery_engine/__init__.py +0 -0
  85. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/discovery_engine/chunker_handler.py +0 -0
  86. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/discovery_engine/create_new.py +0 -0
  87. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/discovery_engine/discovery_engine_client.py +0 -0
  88. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
  89. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/embedder/__init__.py +0 -0
  90. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/embedder/embed_chunk.py +0 -0
  91. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/excel/__init__.py +0 -0
  92. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/excel/plugin.py +0 -0
  93. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/gcs/__init__.py +0 -0
  94. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/gcs/add_file.py +0 -0
  95. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/gcs/download_folder.py +0 -0
  96. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/gcs/download_url.py +0 -0
  97. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/gcs/metadata.py +0 -0
  98. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/genai/__init__.py +0 -0
  99. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/genai/init.py +0 -0
  100. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/genai/safety.py +0 -0
  101. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/invoke/__init__.py +0 -0
  102. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/invoke/direct_vac_func.py +0 -0
  103. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/invoke/invoke_vac_utils.py +0 -0
  104. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/langfuse/__init__.py +0 -0
  105. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/langfuse/callback.py +0 -0
  106. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/langfuse/evals.py +0 -0
  107. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/langfuse/prompts.py +0 -0
  108. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/llamaindex/__init__.py +0 -0
  109. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/llamaindex/get_files.py +0 -0
  110. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/llamaindex/import_files.py +0 -0
  111. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/llamaindex/llamaindex_class.py +0 -0
  112. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/llamaindex/user_history.py +0 -0
  113. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/lookup/__init__.py +0 -0
  114. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/lookup/model_lookup.yaml +0 -0
  115. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/patches/__init__.py +0 -0
  116. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/patches/langchain/__init__.py +0 -0
  117. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/patches/langchain/lancedb.py +0 -0
  118. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/patches/langchain/vertexai.py +0 -0
  119. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/pubsub/__init__.py +0 -0
  120. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/pubsub/process_pubsub.py +0 -0
  121. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/pubsub/pubsub_manager.py +0 -0
  122. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/qna/__init__.py +0 -0
  123. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/qna/parsers.py +0 -0
  124. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/qna/retry.py +0 -0
  125. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/streaming/__init__.py +0 -0
  126. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/streaming/langserve.py +0 -0
  127. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/streaming/stream_lookup.py +0 -0
  128. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/summarise/__init__.py +0 -0
  129. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/summarise/summarise.py +0 -0
  130. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/terraform/__init__.py +0 -0
  131. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/terraform/tfvars_editor.py +0 -0
  132. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/tools/__init__.py +0 -0
  133. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/tools/web_browser.py +0 -0
  134. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/__init__.py +0 -0
  135. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/api_key.py +0 -0
  136. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/big_context.py +0 -0
  137. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/config.py +0 -0
  138. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/config_class.py +0 -0
  139. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/config_schema.py +0 -0
  140. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/gcp.py +0 -0
  141. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/gcp_project.py +0 -0
  142. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/parsers.py +0 -0
  143. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/timedelta.py +0 -0
  144. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/user_ids.py +0 -0
  145. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/utils/version.py +0 -0
  146. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/vertex/__init__.py +0 -0
  147. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/vertex/extensions_call.py +0 -0
  148. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/vertex/extensions_class.py +0 -0
  149. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/vertex/genai_functions.py +0 -0
  150. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/vertex/init.py +0 -0
  151. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/vertex/memory_tools.py +0 -0
  152. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/vertex/safety.py +0 -0
  153. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo/vertex/type_dict_to_json.py +0 -0
  154. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo.egg-info/SOURCES.txt +0 -0
  155. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo.egg-info/dependency_links.txt +0 -0
  156. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo.egg-info/entry_points.txt +0 -0
  157. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo.egg-info/requires.txt +0 -0
  158. {sunholo-0.95.1 → sunholo-0.95.3}/sunholo.egg-info/top_level.txt +0 -0
  159. {sunholo-0.95.1 → sunholo-0.95.3}/tests/test_chat_history.py +0 -0
  160. {sunholo-0.95.1 → sunholo-0.95.3}/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.3
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.95.1.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.95.3.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.3'
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
  """
@@ -65,14 +65,14 @@ def route_endpoint(vector_name=None, method = 'post', override_endpoint=None, co
65
65
 
66
66
  agents_config = config.agentConfig(agent_type)
67
67
 
68
- log.info(f"agents_config: {agents_config}")
68
+ log.debug(f"agents_config: {agents_config}")
69
69
  if method not in agents_config:
70
70
  raise ValueError(f"Invalid method '{method}' for agent configuration.")
71
71
 
72
72
  # 'post' or 'get'
73
73
  endpoints_config = agents_config[method]
74
74
 
75
- log.info(f"endpoints_config: {endpoints_config}")
75
+ log.debug(f"endpoints_config: {endpoints_config}")
76
76
  # Replace placeholders in the config
77
77
  endpoints = {}
78
78
  for key, value in endpoints_config.items():
@@ -351,7 +351,7 @@ class GenAIFunctionProcessor:
351
351
  log.error(f"Error initializing model: {str(err)}")
352
352
  return None
353
353
 
354
- def run_agent_loop(self, chat, content, callback, guardrail_max=10):
354
+ def run_agent_loop(self, chat, content, callback, guardrail_max=10, loop_return=3):
355
355
  """
356
356
  Runs the agent loop, sending messages to the orchestrator, processing responses, and executing functions.
357
357
 
@@ -360,12 +360,13 @@ class GenAIFunctionProcessor:
360
360
  content: The initial content to send to the agent.
361
361
  callback: The callback object for handling intermediate responses.
362
362
  guardrail_max (int): The maximum number of iterations for the loop.
363
+ loop_return (int): The number of last loop iterations to return. Default 3 will return last 3 iterations. If loop_return > guardrail_max then all iterations are returned.
363
364
 
364
365
  Returns:
365
366
  tuple: (big_text, usage_metadata) from the loop execution.
366
367
  """
367
368
  guardrail = 0
368
- big_text = ""
369
+ big_result = []
369
370
  usage_metadata = {
370
371
  "prompt_token_count": 0,
371
372
  "candidates_token_count": 0,
@@ -422,7 +423,6 @@ class GenAIFunctionProcessor:
422
423
  if hasattr(chunk, 'text') and isinstance(chunk.text, str):
423
424
  token = chunk.text
424
425
  token_queue.append(token)
425
- big_text += token
426
426
  this_text += token
427
427
  else:
428
428
  log.info("skipping chunk with no text")
@@ -486,18 +486,17 @@ class GenAIFunctionProcessor:
486
486
  else:
487
487
  token += f"{fn_result}\n--- end ---\n"
488
488
 
489
- big_text += token
490
489
  this_text += token
491
490
  token_queue.append(token)
492
491
  else:
493
492
  token = "\nNo function executions were found\n"
494
493
  token_queue.append(token)
495
- big_text += token
496
494
  this_text += token
497
495
 
498
496
  if this_text:
499
497
  content.append(f"Agent: {this_text}")
500
498
  log.info(f"[{guardrail}] Updated content:\n{this_text}")
499
+ big_result.append(this_text)
501
500
  else:
502
501
  log.warning(f"[{guardrail}] No content created this loop")
503
502
  content.append(f"Agent: No response was found for loop [{guardrail}]")
@@ -523,7 +522,8 @@ class GenAIFunctionProcessor:
523
522
  callback.on_llm_new_token(token=token)
524
523
 
525
524
  usage_metadata["functions_called"] = functions_called
526
- #usage_metadata["function_results"] = function_results
525
+
526
+ big_text = "\n".join(big_result[loop_return:])
527
527
 
528
528
  return big_text, usage_metadata
529
529
 
@@ -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")
@@ -175,6 +175,16 @@ async def start_streaming_chat_async(question, vector_name, qna_func_async, chat
175
175
  # Run start_chat asynchronously
176
176
  chat_task = asyncio.create_task(start_chat())
177
177
 
178
+ # Allow the event loop to process the scheduled tasks
179
+ await asyncio.sleep(0)
180
+
181
+ # Read and yield any initial content from the content buffer
182
+ content_to_send = content_buffer.read()
183
+ if content_to_send:
184
+ log.info(f"Initial content: {content_to_send}")
185
+ yield content_to_send
186
+ content_buffer.clear()
187
+
178
188
  start = time.time()
179
189
 
180
190
  while not chat_callback_handler.stream_finished.is_set() and not stop_event.is_set():
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.95.1
3
+ Version: 0.95.3
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.95.1.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.95.3.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