sunholo 0.140.7__py3-none-any.whl → 0.140.8__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.
@@ -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 (add these after your existing globals)
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
- # Fast prep
290
- prep = self.prep_vac(request, vector_name)
291
-
292
- # Check for prep errors
293
- if isinstance(prep, tuple) and len(prep) == 2:
294
- error_response, status_code = prep
295
- return jsonify(error_response), status_code
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
- result_queue.put(f"Streaming Error: {str(e)} {traceback.format_exc()}")
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
- yield f"Streaming Error: {str(e)} {traceback.format_exc()}"
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.debug(f"Streaming response created in {time.time() - request_start:.3f}s")
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,50 @@ 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
- try:
749
- if request.content_type.startswith('application/json'):
750
- data = request.get_json()
751
- elif request.content_type.startswith('multipart/form-data'):
752
- data = request.form.to_dict()
753
- # Handle file upload in background if present
754
- if 'file' in request.files:
755
- file = request.files['file']
756
- if file.filename != '':
757
- log.info(f"Found file: {file.filename} - uploading in background")
758
- # Start file upload in background, don't block
759
- upload_future = _thread_pool.submit(self._handle_file_upload_background, file, vector_name)
760
- data["_upload_future"] = upload_future
761
- else:
762
- return {"error": "Unsupported content type"}, 400
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
- # Extract essential data first
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
- log.error(f"Config error: {e}")
789
- return {"error": f"Unable to find vac_config for {vector_name} - {str(e)}"}, 500
790
-
791
- # Process chat history with caching (should be fast)
792
- paired_messages = extract_chat_history_with_cache(chat_history)
781
+ raise ValueError(f"Unable to find vac_config for {vector_name} - {str(e)}")
793
782
 
794
- # Start tracing in background (don't block)
783
+ # Initialize trace variables
795
784
  trace = None
796
785
  span = None
797
786
  if self.add_langfuse_eval:
787
+ trace_id = data.get('trace_id')
788
+ # Create trace in background - don't block
798
789
  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
790
+
791
+ # Extract data (keep original logic)
792
+ user_input = data.pop('user_input').strip()
793
+ stream_wait_time = data.pop('stream_wait_time', 7)
794
+ stream_timeout = data.pop('stream_timeout', 120)
795
+ chat_history = data.pop('chat_history', None)
796
+ eval_percent = data.pop('eval_percent', 0.01)
797
+ vector_name_param = data.pop('vector_name', vector_name)
798
+ data.pop('trace_id', None) # to ensure not in kwargs
799
+
800
+ # Process chat history with caching
801
+ paired_messages = extract_chat_history_with_cache(chat_history)
800
802
 
801
803
  # Wait for file upload if it was started (with timeout)
802
804
  if "_upload_future" in data:
@@ -823,7 +825,7 @@ if __name__ == "__main__":
823
825
  # Try to get trace result if available (don't block long)
824
826
  if self.add_langfuse_eval:
825
827
  try:
826
- trace = trace_future.result(timeout=0.5) # 500ms max wait
828
+ trace = trace_future.result(timeout=0.1) # Very short timeout
827
829
  if trace:
828
830
  this_vac_config = vac_config.configs_by_kind.get("vacConfig")
829
831
  metadata_config = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.140.7
3
+ Version: 0.140.8
4
4
  Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
5
5
  Author-email: Holosun ApS <multivac@sunholo.com>
6
6
  License: Apache License, Version 2.0
@@ -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=obdnMF6x1m5YJ4GcrHDpmsOy6kjw7FyZZMcswkargQk,37354
17
+ sunholo/agents/flask/vac_routes.py,sha256=YOW64HaRYa0MfMnzwbx2s9IrU6lz-CeqpcfmIo_L3ho,37664
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.7.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
172
- sunholo-0.140.7.dist-info/METADATA,sha256=OdauM1Q7Yq7QdH01pOWb5ULjgVebsZMOuIpglmSZaoQ,10067
173
- sunholo-0.140.7.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
174
- sunholo-0.140.7.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
175
- sunholo-0.140.7.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
176
- sunholo-0.140.7.dist-info/RECORD,,
171
+ sunholo-0.140.8.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
172
+ sunholo-0.140.8.dist-info/METADATA,sha256=30zPLVCgeU87lsGIdFxyaAOFvYDuC-EOayXWNgophxI,10067
173
+ sunholo-0.140.8.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
174
+ sunholo-0.140.8.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
175
+ sunholo-0.140.8.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
176
+ sunholo-0.140.8.dist-info/RECORD,,