sunholo 0.140.7__py3-none-any.whl → 0.140.9__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 +64 -124
- {sunholo-0.140.7.dist-info → sunholo-0.140.9.dist-info}/METADATA +1 -1
- {sunholo-0.140.7.dist-info → sunholo-0.140.9.dist-info}/RECORD +7 -7
- {sunholo-0.140.7.dist-info → sunholo-0.140.9.dist-info}/WHEEL +0 -0
- {sunholo-0.140.7.dist-info → sunholo-0.140.9.dist-info}/entry_points.txt +0 -0
- {sunholo-0.140.7.dist-info → sunholo-0.140.9.dist-info}/licenses/LICENSE.txt +0 -0
- {sunholo-0.140.7.dist-info → sunholo-0.140.9.dist-info}/top_level.txt +0 -0
@@ -36,7 +36,7 @@ except ImportError:
|
|
36
36
|
# Cache dictionary to store validated API keys
|
37
37
|
api_key_cache = {}
|
38
38
|
cache_duration = timedelta(minutes=5) # Cache duration
|
39
|
-
# Global caches and thread pool
|
39
|
+
# Global caches and thread pool
|
40
40
|
_config_cache = {}
|
41
41
|
_config_lock = threading.Lock()
|
42
42
|
_thread_pool = ThreadPoolExecutor(max_workers=4)
|
@@ -94,17 +94,24 @@ if __name__ == "__main__":
|
|
94
94
|
except Exception as e:
|
95
95
|
log.warning(f"Failed to pre-load config for {vector_name}: {e}")
|
96
96
|
|
97
|
-
|
98
|
-
@lru_cache(maxsize=100)
|
99
97
|
def _get_cached_config(self, vector_name: str):
|
100
|
-
"""Cached config loader with thread safety"""
|
98
|
+
"""Cached config loader with thread safety - CORRECTED VERSION"""
|
99
|
+
# Check cache first (without lock for read)
|
100
|
+
if vector_name in _config_cache:
|
101
|
+
log.debug(f"Using cached config for {vector_name}")
|
102
|
+
return _config_cache[vector_name]
|
103
|
+
|
104
|
+
# Need to load config
|
101
105
|
with _config_lock:
|
106
|
+
# Double-check inside lock (another thread might have loaded it)
|
102
107
|
if vector_name in _config_cache:
|
103
108
|
return _config_cache[vector_name]
|
104
109
|
|
105
110
|
try:
|
111
|
+
log.info(f"Loading fresh config for {vector_name}")
|
106
112
|
config = ConfigManager(vector_name)
|
107
113
|
_config_cache[vector_name] = config
|
114
|
+
log.info(f"Cached config for {vector_name}")
|
108
115
|
return config
|
109
116
|
except Exception as e:
|
110
117
|
log.error(f"Error loading config for {vector_name}: {e}")
|
@@ -286,13 +293,13 @@ if __name__ == "__main__":
|
|
286
293
|
if is_async:
|
287
294
|
log.info(f"Stream interpreter is async: {observed_stream_interpreter}")
|
288
295
|
|
289
|
-
#
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
error_response
|
295
|
-
return jsonify(error_response),
|
296
|
+
# Call prep_vac and handle errors properly
|
297
|
+
try:
|
298
|
+
prep = self.prep_vac(request, vector_name)
|
299
|
+
except Exception as e:
|
300
|
+
log.error(f"prep_vac failed: {e}")
|
301
|
+
error_response = {'error': f'Prep error: {str(e)}'}
|
302
|
+
return jsonify(error_response), 500
|
296
303
|
|
297
304
|
log.info(f"Processing prep completed in {time.time() - request_start:.3f}s")
|
298
305
|
|
@@ -343,7 +350,9 @@ if __name__ == "__main__":
|
|
343
350
|
else:
|
344
351
|
result_queue.put(chunk)
|
345
352
|
except Exception as e:
|
346
|
-
|
353
|
+
error_msg = f"Streaming Error: {str(e)} {traceback.format_exc()}"
|
354
|
+
log.error(error_msg)
|
355
|
+
result_queue.put(error_msg)
|
347
356
|
finally:
|
348
357
|
result_queue.put(None) # Sentinel
|
349
358
|
|
@@ -385,16 +394,18 @@ if __name__ == "__main__":
|
|
385
394
|
yield chunk
|
386
395
|
|
387
396
|
except Exception as e:
|
388
|
-
|
397
|
+
error_msg = f"Streaming Error: {str(e)} {traceback.format_exc()}"
|
398
|
+
log.error(error_msg)
|
399
|
+
yield error_msg
|
389
400
|
|
390
401
|
# Create streaming response
|
391
402
|
response = Response(generate_response_content(), content_type='text/plain; charset=utf-8')
|
392
403
|
response.headers['Transfer-Encoding'] = 'chunked'
|
393
404
|
|
394
|
-
log.
|
405
|
+
log.info(f"Streaming response created in {time.time() - request_start:.3f}s")
|
395
406
|
|
407
|
+
# Do final trace operations in background (don't block the response)
|
396
408
|
if trace:
|
397
|
-
# Do final trace operations in background
|
398
409
|
_thread_pool.submit(self._finalize_trace_background, trace, span, response, all_input)
|
399
410
|
|
400
411
|
return response
|
@@ -744,59 +755,42 @@ if __name__ == "__main__":
|
|
744
755
|
def prep_vac(self, request, vector_name):
|
745
756
|
start_time = time.time()
|
746
757
|
|
747
|
-
# Fast request parsing
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
except Exception as e:
|
764
|
-
return {"error": f"Request parsing error: {str(e)}"}, 400
|
758
|
+
# Fast request parsing - KEEP ORIGINAL ERROR HANDLING STYLE
|
759
|
+
if request.content_type.startswith('application/json'):
|
760
|
+
data = request.get_json()
|
761
|
+
elif request.content_type.startswith('multipart/form-data'):
|
762
|
+
data = request.form.to_dict()
|
763
|
+
# Handle file upload in background if present
|
764
|
+
if 'file' in request.files:
|
765
|
+
file = request.files['file']
|
766
|
+
if file.filename != '':
|
767
|
+
log.info(f"Found file: {file.filename} - uploading in background")
|
768
|
+
# Start file upload in background, don't block
|
769
|
+
upload_future = _thread_pool.submit(self._handle_file_upload_background, file, vector_name)
|
770
|
+
data["_upload_future"] = upload_future
|
771
|
+
else:
|
772
|
+
# KEEP ORIGINAL STYLE - return the error response directly
|
773
|
+
raise ValueError("Unsupported content type")
|
765
774
|
|
766
775
|
log.info(f"vac/{vector_name} got data keys: {list(data.keys())}")
|
767
776
|
|
768
|
-
#
|
769
|
-
try:
|
770
|
-
user_input = data.pop('user_input').strip()
|
771
|
-
stream_wait_time = data.pop('stream_wait_time', 7)
|
772
|
-
stream_timeout = data.pop('stream_timeout', 120)
|
773
|
-
chat_history = data.pop('chat_history', None)
|
774
|
-
eval_percent = data.pop('eval_percent', 0.01)
|
775
|
-
vector_name_param = data.pop('vector_name', vector_name)
|
776
|
-
trace_id = data.pop('trace_id', None)
|
777
|
-
|
778
|
-
if not user_input:
|
779
|
-
return {"error": "No user input provided"}, 400
|
780
|
-
|
781
|
-
except Exception as e:
|
782
|
-
return {"error": f"Required field missing: {str(e)}"}, 400
|
783
|
-
|
784
|
-
# Get config from cache (should be very fast)
|
777
|
+
# Get config from cache first (before processing other data)
|
785
778
|
try:
|
786
779
|
vac_config = self._get_cached_config(vector_name)
|
787
780
|
except Exception as e:
|
788
|
-
|
789
|
-
return {"error": f"Unable to find vac_config for {vector_name} - {str(e)}"}, 500
|
781
|
+
raise ValueError(f"Unable to find vac_config for {vector_name} - {str(e)}")
|
790
782
|
|
791
|
-
#
|
792
|
-
|
783
|
+
# Extract data (keep original logic)
|
784
|
+
user_input = data.pop('user_input').strip()
|
785
|
+
stream_wait_time = data.pop('stream_wait_time', 7)
|
786
|
+
stream_timeout = data.pop('stream_timeout', 120)
|
787
|
+
chat_history = data.pop('chat_history', None)
|
788
|
+
eval_percent = data.pop('eval_percent', 0.01)
|
789
|
+
vector_name_param = data.pop('vector_name', vector_name)
|
790
|
+
data.pop('trace_id', None) # to ensure not in kwargs
|
793
791
|
|
794
|
-
#
|
795
|
-
|
796
|
-
span = None
|
797
|
-
if self.add_langfuse_eval:
|
798
|
-
trace_future = _thread_pool.submit(self._create_langfuse_trace_background, request, vector_name, trace_id)
|
799
|
-
# We'll get the trace result later if needed
|
792
|
+
# Process chat history with caching
|
793
|
+
paired_messages = extract_chat_history_with_cache(chat_history)
|
800
794
|
|
801
795
|
# Wait for file upload if it was started (with timeout)
|
802
796
|
if "_upload_future" in data:
|
@@ -809,7 +803,7 @@ if __name__ == "__main__":
|
|
809
803
|
finally:
|
810
804
|
data.pop("_upload_future", None)
|
811
805
|
|
812
|
-
#
|
806
|
+
# BUILD all_input BEFORE trace creation (this was moved inside try/catch by mistake)
|
813
807
|
all_input = {
|
814
808
|
'user_input': user_input,
|
815
809
|
'vector_name': vector_name_param,
|
@@ -820,10 +814,17 @@ if __name__ == "__main__":
|
|
820
814
|
'kwargs': data
|
821
815
|
}
|
822
816
|
|
823
|
-
#
|
817
|
+
# Initialize trace variables
|
818
|
+
trace = None
|
819
|
+
span = None
|
824
820
|
if self.add_langfuse_eval:
|
821
|
+
trace_id = data.get('trace_id')
|
822
|
+
# Create trace in background - don't block
|
823
|
+
trace_future = _thread_pool.submit(self._create_langfuse_trace_background, request, vector_name, trace_id)
|
824
|
+
|
825
|
+
# Try to get trace result if available (don't block long)
|
825
826
|
try:
|
826
|
-
trace = trace_future.result(timeout=0.
|
827
|
+
trace = trace_future.result(timeout=0.1) # Very short timeout
|
827
828
|
if trace:
|
828
829
|
this_vac_config = vac_config.configs_by_kind.get("vacConfig")
|
829
830
|
metadata_config = None
|
@@ -851,67 +852,6 @@ if __name__ == "__main__":
|
|
851
852
|
"vac_config": vac_config
|
852
853
|
}
|
853
854
|
|
854
|
-
async def prep_vac_async(self, request, vector_name):
|
855
|
-
"""Async version of prep_vac."""
|
856
|
-
# Parse request data
|
857
|
-
if request.content_type.startswith('application/json'):
|
858
|
-
data = request.get_json()
|
859
|
-
elif request.content_type.startswith('multipart/form-data'):
|
860
|
-
data = request.form.to_dict()
|
861
|
-
if 'file' in request.files:
|
862
|
-
file = request.files['file']
|
863
|
-
if file.filename != '':
|
864
|
-
log.info(f"Found file: {file.filename} to upload to GCS")
|
865
|
-
try:
|
866
|
-
# Make file upload async if possible
|
867
|
-
image_uri, mime_type = await self.handle_file_upload_async(file, vector_name)
|
868
|
-
data["image_uri"] = image_uri
|
869
|
-
data["mime"] = mime_type
|
870
|
-
except Exception as e:
|
871
|
-
log.error(traceback.format_exc())
|
872
|
-
return jsonify({'error': str(e), 'traceback': traceback.format_exc()}), 500
|
873
|
-
else:
|
874
|
-
log.error("No file selected")
|
875
|
-
return jsonify({"error": "No file selected"}), 400
|
876
|
-
else:
|
877
|
-
return jsonify({"error": "Unsupported content type"}), 400
|
878
|
-
|
879
|
-
log.info(f"vac/{vector_name} got data: {data}")
|
880
|
-
|
881
|
-
# Run these operations concurrently
|
882
|
-
tasks = []
|
883
|
-
|
884
|
-
# Extract other data while configs load
|
885
|
-
user_input = data.pop('user_input').strip()
|
886
|
-
stream_wait_time = data.pop('stream_wait_time', 7)
|
887
|
-
stream_timeout = data.pop('stream_timeout', 120)
|
888
|
-
chat_history = data.pop('chat_history', None)
|
889
|
-
vector_name_param = data.pop('vector_name', vector_name)
|
890
|
-
data.pop('trace_id', None) # to ensure not in kwargs
|
891
|
-
|
892
|
-
# Task 3: Process chat history
|
893
|
-
chat_history_task = asyncio.create_task(extract_chat_history_async_cached(chat_history))
|
894
|
-
tasks.append(chat_history_task)
|
895
|
-
|
896
|
-
# Await all tasks concurrently
|
897
|
-
results = await asyncio.gather(*tasks, return_exceptions=True)
|
898
|
-
|
899
|
-
paired_messages = results[0] if not isinstance(results[0], Exception) else []
|
900
|
-
|
901
|
-
# Only create span after we have trace
|
902
|
-
all_input = {
|
903
|
-
'user_input': user_input,
|
904
|
-
'vector_name': vector_name_param,
|
905
|
-
'chat_history': paired_messages,
|
906
|
-
'stream_wait_time': stream_wait_time,
|
907
|
-
'stream_timeout': stream_timeout,
|
908
|
-
'kwargs': data
|
909
|
-
}
|
910
|
-
|
911
|
-
return {
|
912
|
-
"all_input": all_input
|
913
|
-
}
|
914
|
-
|
915
855
|
def handle_file_upload(self, file, vector_name):
|
916
856
|
try:
|
917
857
|
file.save(file.filename)
|
@@ -14,7 +14,7 @@ sunholo/agents/fastapi/base.py,sha256=W-cyF8ZDUH40rc-c-Apw3-_8IIi2e4Y9qRtnoVnsc1
|
|
14
14
|
sunholo/agents/fastapi/qna_routes.py,sha256=lKHkXPmwltu9EH3RMwmD153-J6pE7kWQ4BhBlV3to-s,3864
|
15
15
|
sunholo/agents/flask/__init__.py,sha256=dEoByI3gDNUOjpX1uVKP7uPjhfFHJubbiaAv3xLopnk,63
|
16
16
|
sunholo/agents/flask/base.py,sha256=vnpxFEOnCmt9humqj-jYPLfJcdwzsop9NorgkJ-tSaU,1756
|
17
|
-
sunholo/agents/flask/vac_routes.py,sha256=
|
17
|
+
sunholo/agents/flask/vac_routes.py,sha256=eafqIudPKAtsOC73bnIXCpreL8AhMz_LQ212HuXqGhc,35101
|
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
|
@@ -168,9 +168,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
|
|
168
168
|
sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
|
169
169
|
sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
|
170
170
|
sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
|
171
|
-
sunholo-0.140.
|
172
|
-
sunholo-0.140.
|
173
|
-
sunholo-0.140.
|
174
|
-
sunholo-0.140.
|
175
|
-
sunholo-0.140.
|
176
|
-
sunholo-0.140.
|
171
|
+
sunholo-0.140.9.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
172
|
+
sunholo-0.140.9.dist-info/METADATA,sha256=PrTJywV40Kp5vGdiwPaLEzu2pujsY0DuoJSKuhis0MA,10067
|
173
|
+
sunholo-0.140.9.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
174
|
+
sunholo-0.140.9.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
175
|
+
sunholo-0.140.9.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
176
|
+
sunholo-0.140.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|