sunholo 0.99.12__py3-none-any.whl → 0.100.0__py3-none-any.whl

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/gcs/add_file.py CHANGED
@@ -40,6 +40,9 @@ def handle_base64_image(base64_data: str, vector_name: str, extension: str):
40
40
  """
41
41
 
42
42
  model = ConfigManager(vector_name).vacConfig("llm")
43
+ if "," not in base64_data:
44
+ raise ValueError(f"Invalid Base64 image data: Missing metadata header {base64_data}")
45
+
43
46
  if model.startswith("openai"): # pass it to gpt directly
44
47
  return base64_data, base64_data.split(",", 1)
45
48
 
@@ -9,40 +9,52 @@ class AsyncTaskRunner:
9
9
  self.tasks = []
10
10
  self.retry_enabled = retry_enabled
11
11
  self.retry_kwargs = retry_kwargs or {}
12
-
12
+
13
13
  def add_task(self, func: Callable[..., Any], *args: Any):
14
14
  """Adds a task to the list of tasks to be executed."""
15
15
  log.info(f"Adding task: {func.__name__} with args: {args}")
16
16
  self.tasks.append((func.__name__, func, args))
17
-
18
- async def run_async_as_completed(self, callback=None) -> AsyncGenerator[Dict[str, Any], None]:
19
- """
20
- Runs all tasks concurrently and yields results as they complete, while periodically sending heartbeat messages.
21
17
 
22
- Args:
23
- callback: The callback object that will receive heartbeat messages.
18
+ async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
24
19
  """
25
- log.info("Running tasks asynchronously and yielding results as they complete")
20
+ Runs all tasks concurrently and yields results and heartbeats as they are produced.
21
+ """
22
+ log.info("Running tasks asynchronously and yielding results and heartbeats as they occur")
23
+ queue = asyncio.Queue()
24
+
26
25
  tasks = {}
27
26
  for name, func, args in self.tasks:
28
- # Pass the callback down to _task_wrapper
29
- coro = self._task_wrapper(name, func, args, callback)
27
+ coro = self._task_wrapper(name, func, args, queue)
30
28
  task = asyncio.create_task(coro)
31
29
  tasks[task] = name
32
-
33
- log.info(f"Start async run with {len(self.tasks)} runners")
34
- while tasks:
35
- done, _ = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
36
- for task in done:
37
- name = tasks.pop(task)
38
- try:
39
- result = await task
40
- yield {name: result}
41
- except Exception as e:
42
- log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
43
- yield {name: e}
44
30
 
45
- async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any, callback=None) -> Any:
31
+ while tasks or not queue.empty():
32
+ if not queue.empty():
33
+ message = await queue.get()
34
+ log.info(f"Found queue message: {message}")
35
+ yield message
36
+ else:
37
+ # Wait for either a message in the queue or a task to complete
38
+ done, _ = await asyncio.wait(
39
+ list(tasks.keys()),
40
+ timeout=0.1,
41
+ return_when=asyncio.FIRST_COMPLETED
42
+ )
43
+ for task in done:
44
+ name = tasks.pop(task)
45
+ try:
46
+ result = await task
47
+ await queue.put({'type': 'task_complete', 'func_name': name, 'result': result})
48
+ except Exception as e:
49
+ log.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
50
+ await queue.put({'type': 'task_error', 'func_name': name, 'error': e})
51
+
52
+ # After all tasks have completed, process any remaining messages in the queue
53
+ while not queue.empty():
54
+ message = await queue.get()
55
+ yield message
56
+
57
+ async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any, queue: asyncio.Queue) -> Any:
46
58
  """Wraps the task function to process its output and handle retries, while managing heartbeat updates."""
47
59
  async def run_func():
48
60
  if asyncio.iscoroutinefunction(func):
@@ -52,10 +64,8 @@ class AsyncTaskRunner:
52
64
  # If the function is sync, run it in a thread to prevent blocking
53
65
  return await asyncio.to_thread(func, *args)
54
66
 
55
- # Start the heartbeat task if a callback is provided
56
- heartbeat_task = None
57
- if callback:
58
- heartbeat_task = asyncio.create_task(self._send_heartbeat(callback, name))
67
+ # Start the heartbeat task
68
+ heartbeat_task = asyncio.create_task(self._send_heartbeat(queue, name))
59
69
 
60
70
  try:
61
71
  if self.retry_enabled:
@@ -76,38 +86,49 @@ class AsyncTaskRunner:
76
86
  raise
77
87
  finally:
78
88
  # Stop the heartbeat task
79
- if heartbeat_task:
80
- heartbeat_task.cancel()
81
- try:
82
- await heartbeat_task # Ensure the heartbeat task is properly canceled
83
- except asyncio.CancelledError:
84
- pass
89
+ heartbeat_task.cancel()
90
+ # Let the heartbeat_task finish in the background without awaiting it
91
+ try:
92
+ await asyncio.shield(heartbeat_task) # Ensure cancellation is handled cleanly
93
+ except asyncio.CancelledError:
94
+ pass
85
95
 
86
96
  # Send a message indicating task completion to update the spinner's state
87
- if callback:
88
- completion_html = (
89
- f'<div style="display: none;" data-complete-id="{name}-spinner"></div>'
90
- )
91
- await callback.async_on_llm_new_token(token=completion_html)
97
+ completion_html = (
98
+ f'<div style="display: none;" data-complete-id="{name}-spinner"></div>'
99
+ )
100
+ await queue.put({'type': 'heartbeat', 'func_name': name, 'token': completion_html})
92
101
 
93
- async def _send_heartbeat(self, callback, func_name, interval=2):
102
+ async def _send_heartbeat(self, queue: asyncio.Queue, func_name: str, interval=2):
94
103
  """
95
- Sends a single spinner at the start and keeps the task alive.
96
-
97
- Args:
98
- callback: The callback to notify that the task is still working.
99
- func_name: The name of the task function.
100
- interval: How frequently to send heartbeat messages (in seconds).
104
+ Sends a periodic heartbeat to keep the task alive and update the spinner with elapsed time.
101
105
  """
102
106
  # Send the initial spinner HTML
103
107
  spinner_html = (
104
108
  f'<div id="{func_name}-spinner" class="spinner-container">'
105
109
  f' <div class="spinner"></div>'
106
- f' <span class="completed">✔️ Task {func_name} completed!</span>'
110
+ f' <span class="elapsed-time">Task {func_name} is still running... 0s elapsed</span>'
107
111
  f'</div>'
108
112
  )
109
- await callback.async_on_llm_new_token(token=spinner_html)
113
+ log.info(f"Heartbeat started for task {func_name}")
114
+
115
+ await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': spinner_html})
116
+
117
+ # Keep track of elapsed time
118
+ elapsed_time = 0
110
119
 
111
120
  # Keep sending heartbeats until task completes
112
121
  while True:
113
- await asyncio.sleep(interval) # Sleep for the interval but do not send multiple messages
122
+ try:
123
+ await asyncio.sleep(interval) # Sleep for the interval
124
+ elapsed_time += interval # Increment elapsed time
125
+ log.info(f"Sending heartbeat for {func_name}: {elapsed_time}s elapsed")
126
+ # Update spinner with the elapsed time
127
+ update_html = (
128
+ f'<div style="display: none;" data-update-id="{func_name}-spinner">'
129
+ f'<span class="elapsed-time">Task {func_name} is still running... {elapsed_time}s elapsed</span>'
130
+ f'</div>'
131
+ )
132
+ await queue.put({'type': 'heartbeat', 'func_name': func_name, 'token': update_html})
133
+ except asyncio.CancelledError:
134
+ break # Exit the loop if the heartbeat task is canceled
@@ -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.content += text
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
- return self.content
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.content = ""
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.
@@ -155,32 +174,21 @@ class BufferStreamingStdOutCallbackHandler(StreamingStdOutCallbackHandler):
155
174
  patterns are detected.
156
175
  """
157
176
  log.debug(f"on_llm_new_token: {token}")
158
-
159
- self.buffer += token
160
-
161
- if '```' in token:
162
- self.in_code_block = not self.in_code_block
163
177
 
164
- if not self.in_code_block:
165
- self._process_buffer()
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
+ # 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
- if '```' in token:
180
- self.in_code_block = not self.in_code_block
187
+ if '```' in token:
188
+ self.in_code_block = not self.in_code_block
181
189
 
182
- if not self.in_code_block:
183
- await self._async_process_buffer()
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 = BufferStreamingStdOutCallbackHandler(content_buffer=content_buffer, tokens=".!?\n")
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.read()
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.clear()
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
- await asyncio.sleep(wait_time) # Use asyncio.sleep for async compatibility
192
- log.info(f"async heartbeat - {round(time.time() - start, 2)} seconds")
193
-
194
- while not exception_queue.empty():
195
- exception = exception_queue.get_nowait()
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.read()
195
+ content_to_send = await content_buffer.async_read()
199
196
  if content_to_send:
200
- log.info(f"==\n{content_to_send}")
197
+ log.info(f"Content to send: {content_to_send}")
201
198
  yield content_to_send
202
- content_buffer.clear()
203
- start = time.time()
199
+ await content_buffer.async_clear()
204
200
  else:
205
- if time.time() - start > timeout:
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.99.12
3
+ Version: 0.100.0
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.99.12.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.100.0.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -80,7 +80,7 @@ sunholo/embedder/embed_chunk.py,sha256=_FdO4-9frUJlDPqXv2Ai49ejUrrMTMGti3D7bfJGf
80
80
  sunholo/excel/__init__.py,sha256=AqTMN9K4qJYi4maEgoORc5oxDVGO_eqmwzDaVP37JgY,56
81
81
  sunholo/excel/plugin.py,sha256=rl3FoECZ6Ts8KKExPrbPwr3u3CegZfsevmcjgUXAlhE,4033
82
82
  sunholo/gcs/__init__.py,sha256=SZvbsMFDko40sIRHTHppA37IijvJTae54vrhooEF5-4,90
83
- sunholo/gcs/add_file.py,sha256=oSUCrTS_bdKJ-WcK8SAUCGEkS2noVbBhUBLwJRNxtpQ,8245
83
+ sunholo/gcs/add_file.py,sha256=XuntzdFz9b0flMArgUe-4DSaPI7zdBw8hlsG1rsRQmc,8371
84
84
  sunholo/gcs/download_folder.py,sha256=ijJTnS595JqZhBH8iHFErQilMbkuKgL-bnTCMLGuvlA,1614
85
85
  sunholo/gcs/download_url.py,sha256=q1NiJSvEhdNrmU5ZJ-sBCMC_J5CxzrajY8LRgdPOV_M,6130
86
86
  sunholo/gcs/metadata.py,sha256=oQLcXi4brsZ74aegWyC1JZmhlaEV270HS5_UWtAYYWE,898
@@ -89,7 +89,7 @@ sunholo/genai/init.py,sha256=yG8E67TduFCTQPELo83OJuWfjwTnGZsyACospahyEaY,687
89
89
  sunholo/genai/process_funcs_cls.py,sha256=MF3wm-N-zoYvme4I8ffXM9I7cog8OFyBnLu1e3A6eVc,26695
90
90
  sunholo/genai/safety.py,sha256=mkFDO_BeEgiKjQd9o2I4UxB6XI7a9U-oOFjZ8LGRUC4,1238
91
91
  sunholo/invoke/__init__.py,sha256=o1RhwBGOtVK0MIdD55fAIMCkJsxTksi8GD5uoqVKI-8,184
92
- sunholo/invoke/async_class.py,sha256=N45IX9732TijQ1LcgOiBiPTiZg1srKnNU46EY0NbDzo,4946
92
+ sunholo/invoke/async_class.py,sha256=6AQ3d1-8F0lG9y3pXli1IsJW7eDqGLxoIFxYnmjbmn0,6067
93
93
  sunholo/invoke/direct_vac_func.py,sha256=GXSCMkC6vOWGUtQjxy-ZpTrMvJa3CgcW-y9mDpJwWC8,9533
94
94
  sunholo/invoke/invoke_vac_utils.py,sha256=sJc1edHTHMzMGXjji1N67c3iUaP7BmAL5nj82Qof63M,2053
95
95
  sunholo/langfuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -114,10 +114,10 @@ sunholo/qna/__init__.py,sha256=F8q1uR_HreoSX0IfmKY1qoSwIgXhO2Q8kuDSxh9_-EE,28
114
114
  sunholo/qna/parsers.py,sha256=YpOaK5S_LxJ6FbliSYDc3AVOJ62RVduayoNnzi_p8CM,2494
115
115
  sunholo/qna/retry.py,sha256=yMw7RTkw-RXCzfENPJOt8c32mXlpvOR589EGkvK-6yI,2028
116
116
  sunholo/streaming/__init__.py,sha256=MpbydI2UYo_adttPQFkxNM33b-QRyNEbrKJx0C2AGPc,241
117
- sunholo/streaming/content_buffer.py,sha256=HLj-EJcCxxFwPcEZ4Xu3Ns0tDT6awkTe5QY9S7bMnjg,9162
117
+ sunholo/streaming/content_buffer.py,sha256=0oNltM8jBU_3-728--DGdXfS278C2s1xwdcbWthGFks,12810
118
118
  sunholo/streaming/langserve.py,sha256=hi7q8WY8DPKrALl9m_dOMxWOdE-iEuk7YW05SVDFIX8,6514
119
119
  sunholo/streaming/stream_lookup.py,sha256=hYg1DbdSE_QNJ8ZB-ynXJlWgvFjrGvwoUsGJu_E0pRQ,360
120
- sunholo/streaming/streaming.py,sha256=Z_M6rn6XZUMfQggiQ79dw5HD7xaodvq0UGS34C5dHbQ,16549
120
+ sunholo/streaming/streaming.py,sha256=FUdcotNbLQg2VV_mHnHloLJNL2gDQYAYsaNiHjzaT2I,16467
121
121
  sunholo/summarise/__init__.py,sha256=MZk3dblUMODcPb1crq4v-Z508NrFIpkSWNf9FIO8BcU,38
122
122
  sunholo/summarise/summarise.py,sha256=95A-6PXFGanjona8DvZPnnIHLbzZ2ip5hO0wOAJQhfw,3791
123
123
  sunholo/terraform/__init__.py,sha256=yixxEltc3n9UpZaVi05GlgS-YRq_DVGjUc37I9ajeP4,76
@@ -144,9 +144,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
144
144
  sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
145
145
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
146
146
  sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
147
- sunholo-0.99.12.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
148
- sunholo-0.99.12.dist-info/METADATA,sha256=DBqgk8OurecnulKJbbQ6r2NNv-e9X-YI4lR9rpKKQYA,8312
149
- sunholo-0.99.12.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
150
- sunholo-0.99.12.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
151
- sunholo-0.99.12.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
152
- sunholo-0.99.12.dist-info/RECORD,,
147
+ sunholo-0.100.0.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
148
+ sunholo-0.100.0.dist-info/METADATA,sha256=3E-fkTmyUzOkUNGCshJmLpnirzeSiKHaRk8oYec-xQQ,8312
149
+ sunholo-0.100.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
150
+ sunholo-0.100.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
151
+ sunholo-0.100.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
152
+ sunholo-0.100.0.dist-info/RECORD,,