sunholo 0.94.0__py3-none-any.whl → 0.95.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/agents/flask/vac_routes.py +95 -31
- sunholo/invoke/__init__.py +2 -1
- sunholo/invoke/async_class.py +62 -133
- sunholo/qna/parsers.py +1 -1
- sunholo/streaming/streaming.py +14 -15
- {sunholo-0.94.0.dist-info → sunholo-0.95.0.dist-info}/METADATA +4 -2
- {sunholo-0.94.0.dist-info → sunholo-0.95.0.dist-info}/RECORD +11 -11
- {sunholo-0.94.0.dist-info → sunholo-0.95.0.dist-info}/WHEEL +1 -1
- {sunholo-0.94.0.dist-info → sunholo-0.95.0.dist-info}/LICENSE.txt +0 -0
- {sunholo-0.94.0.dist-info → sunholo-0.95.0.dist-info}/entry_points.txt +0 -0
- {sunholo-0.94.0.dist-info → sunholo-0.95.0.dist-info}/top_level.txt +0 -0
|
@@ -4,10 +4,12 @@ import datetime
|
|
|
4
4
|
import uuid
|
|
5
5
|
import random
|
|
6
6
|
from functools import partial
|
|
7
|
+
import inspect
|
|
8
|
+
import asyncio
|
|
7
9
|
|
|
8
10
|
from ...agents import extract_chat_history, handle_special_commands
|
|
9
11
|
from ...qna.parsers import parse_output
|
|
10
|
-
from ...streaming import start_streaming_chat
|
|
12
|
+
from ...streaming import start_streaming_chat, start_streaming_chat_async
|
|
11
13
|
from ...archive import archive_qa
|
|
12
14
|
from ...custom_logging import log
|
|
13
15
|
from ...utils import ConfigManager
|
|
@@ -183,10 +185,16 @@ if __name__ == "__main__":
|
|
|
183
185
|
|
|
184
186
|
log.info(f"OpenAI response: {openai_response}")
|
|
185
187
|
return jsonify(openai_response)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
|
|
188
189
|
def handle_stream_vac(self, vector_name):
|
|
189
190
|
observed_stream_interpreter = self.stream_interpreter
|
|
191
|
+
|
|
192
|
+
# Determine if the stream interpreter is async or sync
|
|
193
|
+
is_async = inspect.iscoroutinefunction(self.stream_interpreter)
|
|
194
|
+
|
|
195
|
+
if is_async:
|
|
196
|
+
log.info(f"Stream interpreter is async: {observed_stream_interpreter}")
|
|
197
|
+
|
|
190
198
|
prep = self.prep_vac(request, vector_name)
|
|
191
199
|
log.info(f"Processing prep: {prep}")
|
|
192
200
|
trace = prep["trace"]
|
|
@@ -203,41 +211,88 @@ if __name__ == "__main__":
|
|
|
203
211
|
generation = span.generation(
|
|
204
212
|
name="start_streaming_chat",
|
|
205
213
|
metadata=vac_config.configs_by_kind,
|
|
206
|
-
input
|
|
214
|
+
input=all_input,
|
|
207
215
|
completion_start_time=str(int(datetime.datetime.now().timestamp())),
|
|
208
216
|
model=vac_config.vacConfig("model") or vac_config.vacConfig("llm")
|
|
209
217
|
)
|
|
210
218
|
|
|
211
219
|
def generate_response_content():
|
|
220
|
+
try:
|
|
221
|
+
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()
|
|
212
267
|
|
|
213
|
-
for chunk in start_streaming_chat(question=all_input["user_input"],
|
|
214
|
-
vector_name=vector_name,
|
|
215
|
-
qna_func=observed_stream_interpreter,
|
|
216
|
-
chat_history=all_input["chat_history"],
|
|
217
|
-
wait_time=all_input["stream_wait_time"],
|
|
218
|
-
timeout=all_input["stream_timeout"],
|
|
219
|
-
trace_id=trace.id if trace else None,
|
|
220
|
-
#kwargs
|
|
221
|
-
**all_input["kwargs"]
|
|
222
|
-
):
|
|
223
|
-
if isinstance(chunk, dict) and 'answer' in chunk:
|
|
224
|
-
# When we encounter the dictionary, we yield it as a JSON string
|
|
225
|
-
# and stop the generator.
|
|
226
|
-
if trace:
|
|
227
|
-
chunk["trace_id"] = trace.id
|
|
228
|
-
chunk["trace_url"] = trace.get_trace_url()
|
|
229
|
-
archive_qa(chunk, vector_name)
|
|
230
|
-
if trace:
|
|
231
|
-
generation.end(output=json.dumps(chunk))
|
|
232
|
-
span.end(output=json.dumps(chunk))
|
|
233
|
-
trace.update(output=json.dumps(chunk))
|
|
234
|
-
|
|
235
|
-
return json.dumps(chunk)
|
|
236
|
-
|
|
237
268
|
else:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
269
|
+
log.info("sync streaming response")
|
|
270
|
+
for chunk in start_streaming_chat(
|
|
271
|
+
question=all_input["user_input"],
|
|
272
|
+
vector_name=vector_name,
|
|
273
|
+
qna_func=observed_stream_interpreter,
|
|
274
|
+
chat_history=all_input["chat_history"],
|
|
275
|
+
wait_time=all_input["stream_wait_time"],
|
|
276
|
+
timeout=all_input["stream_timeout"],
|
|
277
|
+
trace_id=trace.id if trace else None,
|
|
278
|
+
**all_input["kwargs"]
|
|
279
|
+
):
|
|
280
|
+
if isinstance(chunk, dict) and 'answer' in chunk:
|
|
281
|
+
if trace:
|
|
282
|
+
chunk["trace_id"] = trace.id
|
|
283
|
+
chunk["trace_url"] = trace.get_trace_url()
|
|
284
|
+
archive_qa(chunk, vector_name)
|
|
285
|
+
if trace:
|
|
286
|
+
generation.end(output=json.dumps(chunk))
|
|
287
|
+
span.end(output=json.dumps(chunk))
|
|
288
|
+
trace.update(output=json.dumps(chunk))
|
|
289
|
+
return json.dumps(chunk)
|
|
290
|
+
else:
|
|
291
|
+
yield chunk
|
|
292
|
+
|
|
293
|
+
except Exception as e:
|
|
294
|
+
yield f"Streaming Error: {str(e)} {traceback.format_exc()}"
|
|
295
|
+
|
|
241
296
|
# Here, the generator function will handle streaming the content to the client.
|
|
242
297
|
response = Response(generate_response_content(), content_type='text/plain; charset=utf-8')
|
|
243
298
|
response.headers['Transfer-Encoding'] = 'chunked'
|
|
@@ -250,6 +305,14 @@ if __name__ == "__main__":
|
|
|
250
305
|
self.langfuse_eval_response(trace_id=trace.id, eval_percent=all_input.get('eval_percent'))
|
|
251
306
|
|
|
252
307
|
return response
|
|
308
|
+
|
|
309
|
+
@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
|
|
253
316
|
|
|
254
317
|
def langfuse_eval_response(self, trace_id, eval_percent=0.01):
|
|
255
318
|
"""
|
|
@@ -282,6 +345,7 @@ if __name__ == "__main__":
|
|
|
282
345
|
log.info(f"Did not do Langfuse eval due to random sampling not passed: {eval_percent=}")
|
|
283
346
|
|
|
284
347
|
def handle_process_vac(self, vector_name):
|
|
348
|
+
#TODO: handle async
|
|
285
349
|
observed_vac_interpreter = self.vac_interpreter
|
|
286
350
|
prep = self.prep_vac(request, vector_name)
|
|
287
351
|
log.debug(f"Processing prep: {prep}")
|
sunholo/invoke/__init__.py
CHANGED
sunholo/invoke/async_class.py
CHANGED
|
@@ -1,142 +1,72 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
|
|
3
|
+
import traceback
|
|
4
|
+
from typing import Callable, List, Any, AsyncGenerator, Dict
|
|
5
|
+
from tenacity import AsyncRetrying, retry_if_exception_type, wait_random_exponential, stop_after_attempt
|
|
4
6
|
|
|
5
|
-
# Setup basic logging configuration
|
|
6
|
-
logging.basicConfig(level=logging.INFO)
|
|
7
7
|
logger = logging.getLogger(__name__)
|
|
8
8
|
|
|
9
9
|
class AsyncTaskRunner:
|
|
10
|
-
|
|
11
|
-
Example async functions for testing
|
|
12
|
-
```python
|
|
13
|
-
async def api_call_1(url, params):
|
|
14
|
-
await asyncio.sleep(1)
|
|
15
|
-
if "fail" in params:
|
|
16
|
-
raise ValueError(f"Error in api_call_1 with params: {params}")
|
|
17
|
-
return f"api_call_1 response from {url} with params {params}"
|
|
18
|
-
|
|
19
|
-
async def api_call_2(url, params):
|
|
20
|
-
await asyncio.sleep(2)
|
|
21
|
-
if "fail" in params:
|
|
22
|
-
raise ValueError(f"Error in api_call_2 with params: {params}")
|
|
23
|
-
return f"api_call_2 response from {url} with params {params}"
|
|
24
|
-
|
|
25
|
-
# Example usage in an existing async function
|
|
26
|
-
async def example_usage():
|
|
27
|
-
runner = AsyncTaskRunner()
|
|
28
|
-
|
|
29
|
-
runner.add_task(api_call_1, "http://example.com", {"key": "value"})
|
|
30
|
-
runner.add_task(api_call_2, "http://example.org", {"key": "fail"})
|
|
31
|
-
|
|
32
|
-
# Run all tasks within the existing event loop
|
|
33
|
-
results = await runner.run_async()
|
|
34
|
-
for result in results:
|
|
35
|
-
print(result)
|
|
36
|
-
|
|
37
|
-
# Example of calling run_sync() in a synchronous context
|
|
38
|
-
if __name__ == "__main__":
|
|
39
|
-
runner = AsyncTaskRunner()
|
|
40
|
-
|
|
41
|
-
runner.add_task(api_call_1, "http://example.com", {"key": "value"})
|
|
42
|
-
runner.add_task(api_call_2, "http://example.org", {"key": "fail"})
|
|
43
|
-
|
|
44
|
-
# Running in a synchronous context
|
|
45
|
-
results = runner.run_sync()
|
|
46
|
-
for result in results:
|
|
47
|
-
print(result)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Example streaming fast and slow
|
|
51
|
-
|
|
52
|
-
```python
|
|
53
|
-
import asyncio
|
|
54
|
-
from typing import AsyncGenerator
|
|
55
|
-
|
|
56
|
-
# Example streaming function that simulates yielding chunks of data
|
|
57
|
-
async def stream_chunks(url: str, params: dict) -> AsyncGenerator[str, None]:
|
|
58
|
-
# Simulate streaming with a series of chunks
|
|
59
|
-
for i in range(5):
|
|
60
|
-
await asyncio.sleep(1) # Simulate delay between chunks
|
|
61
|
-
yield f"Chunk {i+1} from {url} with params {params}"
|
|
62
|
-
|
|
63
|
-
# Example slow API call function
|
|
64
|
-
async def slow_api_call(url: str, params: dict) -> str:
|
|
65
|
-
await asyncio.sleep(5) # Simulate a slow API response
|
|
66
|
-
return f"Slow API response from {url} with params {params}"
|
|
67
|
-
|
|
68
|
-
# Function to manage streaming and slow API call
|
|
69
|
-
async def process_api_calls(stream_url: str, stream_params: dict, slow_url: str, slow_params: dict) -> AsyncGenerator[str, None]:
|
|
70
|
-
# Create the AsyncTaskRunner instance
|
|
71
|
-
runner = AsyncTaskRunner()
|
|
72
|
-
|
|
73
|
-
# Add the slow API call as a task
|
|
74
|
-
runner.add_task(slow_api_call, slow_url, slow_params)
|
|
75
|
-
|
|
76
|
-
# Run the slow API call concurrently with the streaming
|
|
77
|
-
slow_api_result_task = asyncio.create_task(runner.run_async())
|
|
78
|
-
|
|
79
|
-
# Process the streaming chunks and yield them
|
|
80
|
-
async for chunk in stream_chunks(stream_url, stream_params):
|
|
81
|
-
yield chunk
|
|
82
|
-
|
|
83
|
-
# Wait for the slow API call to complete and get the result
|
|
84
|
-
slow_api_results = await slow_api_result_task
|
|
85
|
-
|
|
86
|
-
# Yield the slow API response after streaming is finished
|
|
87
|
-
for result in slow_api_results:
|
|
88
|
-
yield result
|
|
89
|
-
|
|
90
|
-
# Example usage in an existing async function
|
|
91
|
-
async def example_usage():
|
|
92
|
-
# Define the URLs and parameters for the calls
|
|
93
|
-
stream_url = "http://streaming.example.com"
|
|
94
|
-
stream_params = {"key": "stream_value"}
|
|
95
|
-
slow_url = "http://slowapi.example.com"
|
|
96
|
-
slow_params = {"key": "slow_value"}
|
|
97
|
-
|
|
98
|
-
# Process the API calls and stream the results
|
|
99
|
-
async for output in process_api_calls(stream_url, stream_params, slow_url, slow_params):
|
|
100
|
-
print(output)
|
|
101
|
-
|
|
102
|
-
# Running the example usage
|
|
103
|
-
if __name__ == "__main__":
|
|
104
|
-
asyncio.run(example_usage())
|
|
105
|
-
```
|
|
106
|
-
"""
|
|
107
|
-
def __init__(self):
|
|
10
|
+
def __init__(self, retry_enabled=False, retry_kwargs=None):
|
|
108
11
|
self.tasks = []
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
12
|
+
self.retry_enabled = retry_enabled
|
|
13
|
+
self.retry_kwargs = retry_kwargs or {}
|
|
14
|
+
|
|
15
|
+
def add_task(self, func: Callable[..., Any], *args: Any):
|
|
16
|
+
"""Adds a task to the list of tasks to be executed."""
|
|
112
17
|
logger.info(f"Adding task: {func.__name__} with args: {args}")
|
|
113
|
-
self.tasks.append(func
|
|
114
|
-
|
|
115
|
-
def
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
self.tasks
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
18
|
+
self.tasks.append((func.__name__, func, args))
|
|
19
|
+
|
|
20
|
+
async def run_async_as_completed(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
21
|
+
"""Runs all tasks concurrently and yields results as they complete."""
|
|
22
|
+
logger.info("Running tasks asynchronously and yielding results as they complete")
|
|
23
|
+
tasks = {}
|
|
24
|
+
for name, func, args in self.tasks:
|
|
25
|
+
coro = self._task_wrapper(name, func, args)
|
|
26
|
+
task = asyncio.create_task(coro)
|
|
27
|
+
tasks[task] = name
|
|
28
|
+
|
|
29
|
+
while tasks:
|
|
30
|
+
done, _ = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
|
|
31
|
+
for task in done:
|
|
32
|
+
name = tasks.pop(task)
|
|
33
|
+
try:
|
|
34
|
+
result = await task
|
|
35
|
+
yield {name: result}
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.error(f"Task {name} resulted in an error: {e}\n{traceback.format_exc()}")
|
|
38
|
+
yield {name: e}
|
|
39
|
+
|
|
40
|
+
async def _task_wrapper(self, name: str, func: Callable[..., Any], args: Any) -> Any:
|
|
41
|
+
"""Wraps the task function to process its output and handle retries."""
|
|
42
|
+
async def run_func():
|
|
43
|
+
gen = func(*args)
|
|
44
|
+
if hasattr(gen, '__aiter__'):
|
|
45
|
+
# If it's an async generator, consume it completely
|
|
46
|
+
output = ''
|
|
47
|
+
async for chunk in gen:
|
|
48
|
+
output += chunk
|
|
49
|
+
return output
|
|
50
|
+
else:
|
|
51
|
+
# If it's a coroutine, await the result
|
|
52
|
+
return await gen
|
|
53
|
+
|
|
54
|
+
if self.retry_enabled:
|
|
55
|
+
retry_kwargs = {
|
|
56
|
+
'wait': wait_random_exponential(multiplier=1, max=60),
|
|
57
|
+
'stop': stop_after_attempt(5),
|
|
58
|
+
'retry': retry_if_exception_type(Exception),
|
|
59
|
+
**self.retry_kwargs
|
|
60
|
+
}
|
|
61
|
+
async for attempt in AsyncRetrying(**retry_kwargs):
|
|
62
|
+
with attempt:
|
|
63
|
+
return await run_func()
|
|
64
|
+
else:
|
|
65
|
+
try:
|
|
66
|
+
return await run_func()
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.error(f"Error in task {name}: {e}\n{traceback.format_exc()}")
|
|
69
|
+
raise
|
|
140
70
|
|
|
141
71
|
def _log_results(self, results: List[Any]):
|
|
142
72
|
"""Logs the results of the task executions."""
|
|
@@ -144,5 +74,4 @@ class AsyncTaskRunner:
|
|
|
144
74
|
if isinstance(result, Exception):
|
|
145
75
|
logger.error(f"Task resulted in an error: {result}")
|
|
146
76
|
else:
|
|
147
|
-
logger.info(f"Task completed successfully: {result}")
|
|
148
|
-
|
|
77
|
+
logger.info(f"Task completed successfully: {result}")
|
sunholo/qna/parsers.py
CHANGED
|
@@ -54,7 +54,7 @@ def parse_output(bot_output):
|
|
|
54
54
|
|
|
55
55
|
elif isinstance(bot_output, dict):
|
|
56
56
|
if not bot_output.get("answer"):
|
|
57
|
-
raise ValueError(f"VAC output was not a string or a dict with the key 'answer' - got: {bot_output}")
|
|
57
|
+
raise ValueError(f"VAC output was not a string or a dict with the key 'answer' - got: {bot_output} {type(bot_output)}")
|
|
58
58
|
else:
|
|
59
59
|
|
|
60
60
|
return bot_output
|
sunholo/streaming/streaming.py
CHANGED
|
@@ -107,7 +107,7 @@ def start_streaming_chat(question,
|
|
|
107
107
|
# parses out source_documents if not present etc.
|
|
108
108
|
yield parse_output(final_result)
|
|
109
109
|
|
|
110
|
-
async def start_streaming_chat_async(question, vector_name,
|
|
110
|
+
async def start_streaming_chat_async(question, vector_name, qna_func_async, chat_history=[], wait_time=2, timeout=120, **kwargs):
|
|
111
111
|
"""
|
|
112
112
|
Asynchronously initiates a chat session that streams responses back to the caller in real-time.
|
|
113
113
|
This function manages the chat by starting a separate thread to handle the chat interaction,
|
|
@@ -116,7 +116,7 @@ async def start_streaming_chat_async(question, vector_name, qna_func, chat_histo
|
|
|
116
116
|
Parameters:
|
|
117
117
|
- question (str): The initial question to start the chat with.
|
|
118
118
|
- vector_name (str): The vector configuration that determines the behavior of the AI in the chat.
|
|
119
|
-
-
|
|
119
|
+
- qna_func_async (callable): The async function that processes the chat and generates responses.
|
|
120
120
|
- chat_history (list, optional): A list of previous messages in the chat session to maintain context.
|
|
121
121
|
- wait_time (int, optional): The interval in seconds between checking for new chat responses.
|
|
122
122
|
- timeout (int, optional): The maximum time in seconds to wait for a new response before timing out.
|
|
@@ -154,9 +154,9 @@ async def start_streaming_chat_async(question, vector_name, qna_func, chat_histo
|
|
|
154
154
|
The `start_streaming_chat_async` function is used in a coroutine that prints messages as they are generated.
|
|
155
155
|
"""
|
|
156
156
|
|
|
157
|
-
log.info(f"Streaming chat with wait time {wait_time} seconds and timeout {timeout} seconds and kwargs {kwargs}")
|
|
158
|
-
if not check_kwargs_support(
|
|
159
|
-
yield "No **kwargs in
|
|
157
|
+
log.info(f"Async Streaming chat with wait time {wait_time} seconds and timeout {timeout} seconds and kwargs {kwargs}")
|
|
158
|
+
if not check_kwargs_support(qna_func_async):
|
|
159
|
+
yield "No **kwargs in qna_func_async - please add it"
|
|
160
160
|
|
|
161
161
|
content_buffer = ContentBuffer()
|
|
162
162
|
chat_callback_handler = BufferStreamingStdOutCallbackHandler(content_buffer=content_buffer, tokens=".!?\n")
|
|
@@ -165,21 +165,21 @@ async def start_streaming_chat_async(question, vector_name, qna_func, chat_histo
|
|
|
165
165
|
exception_queue = Queue()
|
|
166
166
|
stop_event = Event()
|
|
167
167
|
|
|
168
|
-
def start_chat():
|
|
168
|
+
async def start_chat():
|
|
169
169
|
try:
|
|
170
|
-
final_result =
|
|
170
|
+
final_result = await qna_func_async(question, vector_name, chat_history, callback=chat_callback_handler, **kwargs)
|
|
171
171
|
result_queue.put(final_result)
|
|
172
172
|
except Exception as e:
|
|
173
173
|
exception_queue.put(e)
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
# Run start_chat asynchronously
|
|
176
|
+
chat_task = asyncio.create_task(start_chat())
|
|
177
177
|
|
|
178
178
|
start = time.time()
|
|
179
179
|
|
|
180
180
|
while not chat_callback_handler.stream_finished.is_set() and not stop_event.is_set():
|
|
181
181
|
await asyncio.sleep(wait_time) # Use asyncio.sleep for async compatibility
|
|
182
|
-
log.info(f"heartbeat - {round(time.time() - start, 2)} seconds")
|
|
182
|
+
log.info(f"async heartbeat - {round(time.time() - start, 2)} seconds")
|
|
183
183
|
|
|
184
184
|
while not exception_queue.empty():
|
|
185
185
|
exception = exception_queue.get_nowait()
|
|
@@ -197,15 +197,14 @@ async def start_streaming_chat_async(question, vector_name, qna_func, chat_histo
|
|
|
197
197
|
break
|
|
198
198
|
|
|
199
199
|
stop_event.set()
|
|
200
|
-
|
|
200
|
+
await chat_task # Ensure the async task is awaited
|
|
201
201
|
|
|
202
202
|
# Handle final result
|
|
203
203
|
if not result_queue.empty():
|
|
204
204
|
final_result = result_queue.get()
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
yield f"###JSON_START###{json.dumps(parsed_final_result)}###JSON_END###"
|
|
205
|
+
parsed_final_result = parse_output(final_result)
|
|
206
|
+
if 'answer' in parsed_final_result:
|
|
207
|
+
yield json.dumps(parsed_final_result)
|
|
209
208
|
|
|
210
209
|
|
|
211
210
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.95.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.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.95.0.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -72,6 +72,7 @@ Requires-Dist: rich ; extra == 'all'
|
|
|
72
72
|
Requires-Dist: supabase ; extra == 'all'
|
|
73
73
|
Requires-Dist: tabulate ; extra == 'all'
|
|
74
74
|
Requires-Dist: tantivy ; extra == 'all'
|
|
75
|
+
Requires-Dist: tenacity ; extra == 'all'
|
|
75
76
|
Requires-Dist: tiktoken ; extra == 'all'
|
|
76
77
|
Requires-Dist: unstructured[local-inference] ==0.14.9 ; extra == 'all'
|
|
77
78
|
Requires-Dist: xlwings ; extra == 'all'
|
|
@@ -124,6 +125,7 @@ Requires-Dist: httpx ; extra == 'http'
|
|
|
124
125
|
Requires-Dist: langfuse ; extra == 'http'
|
|
125
126
|
Requires-Dist: python-socketio ; extra == 'http'
|
|
126
127
|
Requires-Dist: requests ; extra == 'http'
|
|
128
|
+
Requires-Dist: tenacity ; extra == 'http'
|
|
127
129
|
Provides-Extra: iac
|
|
128
130
|
Requires-Dist: python-hcl2 ; extra == 'iac'
|
|
129
131
|
Provides-Extra: openai
|
|
@@ -14,7 +14,7 @@ sunholo/agents/fastapi/qna_routes.py,sha256=lKHkXPmwltu9EH3RMwmD153-J6pE7kWQ4BhB
|
|
|
14
14
|
sunholo/agents/flask/__init__.py,sha256=poJDKMr2qj8qMb99JqCvCPSiEt1tj2tLQ3hKW3f2aVw,107
|
|
15
15
|
sunholo/agents/flask/base.py,sha256=FgSaCODyoTtlstJtsqlLPScdgRUtv9_plxftdzHdVFo,809
|
|
16
16
|
sunholo/agents/flask/qna_routes.py,sha256=uwUD1yrzOPH27m2AXpiQrPk_2VfJOQOM6dAynOWQtoQ,22532
|
|
17
|
-
sunholo/agents/flask/vac_routes.py,sha256=
|
|
17
|
+
sunholo/agents/flask/vac_routes.py,sha256=6Z8Eqt1SgfcmUf5V-7O3vYSXnGCBWQKWBUPj-NwX1RU,27269
|
|
18
18
|
sunholo/archive/__init__.py,sha256=qNHWm5rGPVOlxZBZCpA1wTYPbalizRT7f8X4rs2t290,31
|
|
19
19
|
sunholo/archive/archive.py,sha256=PxVfDtO2_2ZEEbnhXSCbXLdeoHoQVImo4y3Jr2XkCFY,1204
|
|
20
20
|
sunholo/auth/__init__.py,sha256=TeP-OY0XGxYV_8AQcVGoh35bvyWhNUcMRfhuD5l44Sk,91
|
|
@@ -88,8 +88,8 @@ sunholo/genai/__init__.py,sha256=dBl6IA3-Fx6-Vx81r0XqxHlUq6WeW1iDX188dpChu8s,115
|
|
|
88
88
|
sunholo/genai/init.py,sha256=yG8E67TduFCTQPELo83OJuWfjwTnGZsyACospahyEaY,687
|
|
89
89
|
sunholo/genai/process_funcs_cls.py,sha256=1yDVh7Gi5qX0FMzV__8k3vn3BJ7AreX9CvPxM_QPFkU,24235
|
|
90
90
|
sunholo/genai/safety.py,sha256=mkFDO_BeEgiKjQd9o2I4UxB6XI7a9U-oOFjZ8LGRUC4,1238
|
|
91
|
-
sunholo/invoke/__init__.py,sha256=
|
|
92
|
-
sunholo/invoke/async_class.py,sha256=
|
|
91
|
+
sunholo/invoke/__init__.py,sha256=VOpsfhNf98az3at7bMaY1Fiw4UIZAS6zMmSqWd6_ksI,141
|
|
92
|
+
sunholo/invoke/async_class.py,sha256=3QR_hFxyEEkksbXmXziDuC__dg4XWNghuw2ZUAPpQ3Q,3195
|
|
93
93
|
sunholo/invoke/direct_vac_func.py,sha256=fuTJlH5PsqWhN_yVMaWisHCTZU1JEUz8I8yVbWsNUFE,4268
|
|
94
94
|
sunholo/invoke/invoke_vac_utils.py,sha256=sJc1edHTHMzMGXjji1N67c3iUaP7BmAL5nj82Qof63M,2053
|
|
95
95
|
sunholo/langfuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -111,13 +111,13 @@ sunholo/pubsub/__init__.py,sha256=DfTEk4zmCfqn6gFxRrqDO0pOrvXTDqH-medpgYO4PGw,11
|
|
|
111
111
|
sunholo/pubsub/process_pubsub.py,sha256=rN2N4WM6PZkMKDrdT8pnEfTvsXACRyJFqIHJQCbuxLs,3088
|
|
112
112
|
sunholo/pubsub/pubsub_manager.py,sha256=19w_N0LiG-wgVWvgJ13b8BUeN8ZzgSPXAhPmL1HRRSI,6966
|
|
113
113
|
sunholo/qna/__init__.py,sha256=F8q1uR_HreoSX0IfmKY1qoSwIgXhO2Q8kuDSxh9_-EE,28
|
|
114
|
-
sunholo/qna/parsers.py,sha256=
|
|
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
117
|
sunholo/streaming/content_buffer.py,sha256=A9rVhlmLZxaFPvGYjVqCs5GfIsA1EMl_tyk-0VCDvo4,6462
|
|
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=
|
|
120
|
+
sunholo/streaming/streaming.py,sha256=tMCAdB_piQXiiQLPkkYJwDiDxfg6UOBeto0d9C8m_Jk,16211
|
|
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=q_phxgGX2TG2j2MXNULF2xGzQnQPENwjPN9nZ_A9Gh0,7526
|
|
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.
|
|
148
|
-
sunholo-0.
|
|
149
|
-
sunholo-0.
|
|
150
|
-
sunholo-0.
|
|
151
|
-
sunholo-0.
|
|
152
|
-
sunholo-0.
|
|
147
|
+
sunholo-0.95.0.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
|
148
|
+
sunholo-0.95.0.dist-info/METADATA,sha256=Ot1yshHTv-QqO_hNg1O7TElzvyDaopq3Ohij0_cyQJg,7889
|
|
149
|
+
sunholo-0.95.0.dist-info/WHEEL,sha256=5Mi1sN9lKoFv_gxcPtisEVrJZihrm_beibeg5R6xb4I,91
|
|
150
|
+
sunholo-0.95.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
|
151
|
+
sunholo-0.95.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
|
152
|
+
sunholo-0.95.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|