sunholo 0.140.10__py3-none-any.whl → 0.140.12__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 +105 -149
- sunholo/utils/config_class.py +94 -2
- sunholo-0.140.12.dist-info/METADATA +522 -0
- {sunholo-0.140.10.dist-info → sunholo-0.140.12.dist-info}/RECORD +8 -8
- sunholo-0.140.10.dist-info/METADATA +0 -249
- {sunholo-0.140.10.dist-info → sunholo-0.140.12.dist-info}/WHEEL +0 -0
- {sunholo-0.140.10.dist-info → sunholo-0.140.12.dist-info}/entry_points.txt +0 -0
- {sunholo-0.140.10.dist-info → sunholo-0.140.12.dist-info}/licenses/LICENSE.txt +0 -0
- {sunholo-0.140.10.dist-info → sunholo-0.140.12.dist-info}/top_level.txt +0 -0
@@ -6,10 +6,6 @@ import random
|
|
6
6
|
from functools import partial
|
7
7
|
import inspect
|
8
8
|
import asyncio
|
9
|
-
import time
|
10
|
-
import threading
|
11
|
-
from functools import lru_cache
|
12
|
-
from concurrent.futures import ThreadPoolExecutor
|
13
9
|
|
14
10
|
from ..chat_history import extract_chat_history_with_cache, extract_chat_history_async_cached
|
15
11
|
from ...qna.parsers import parse_output
|
@@ -36,11 +32,6 @@ except ImportError:
|
|
36
32
|
# Cache dictionary to store validated API keys
|
37
33
|
api_key_cache = {}
|
38
34
|
cache_duration = timedelta(minutes=5) # Cache duration
|
39
|
-
# Global caches and thread pool
|
40
|
-
_config_cache = {}
|
41
|
-
_config_lock = threading.Lock()
|
42
|
-
_thread_pool = ThreadPoolExecutor(max_workers=4)
|
43
|
-
|
44
35
|
|
45
36
|
class VACRoutes:
|
46
37
|
"""
|
@@ -78,44 +69,8 @@ if __name__ == "__main__":
|
|
78
69
|
self.additional_routes = additional_routes if additional_routes is not None else []
|
79
70
|
self.async_stream = async_stream
|
80
71
|
self.add_langfuse_eval = add_langfuse_eval
|
81
|
-
|
82
|
-
# Pre-warm common configs
|
83
|
-
self._preload_common_configs()
|
84
|
-
|
85
72
|
self.register_routes()
|
86
|
-
|
87
|
-
def _preload_common_configs(self):
|
88
|
-
"""Pre-load commonly used configurations to cache"""
|
89
|
-
common_vector_names = ["aitana3"] # Add your common vector names
|
90
|
-
for vector_name in common_vector_names:
|
91
|
-
try:
|
92
|
-
self._get_cached_config(vector_name)
|
93
|
-
log.info(f"Pre-loaded config for {vector_name}")
|
94
|
-
except Exception as e:
|
95
|
-
log.warning(f"Failed to pre-load config for {vector_name}: {e}")
|
96
|
-
|
97
|
-
def _get_cached_config(self, vector_name: str):
|
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
73
|
|
104
|
-
# Need to load config
|
105
|
-
with _config_lock:
|
106
|
-
# Double-check inside lock (another thread might have loaded it)
|
107
|
-
if vector_name in _config_cache:
|
108
|
-
return _config_cache[vector_name]
|
109
|
-
|
110
|
-
try:
|
111
|
-
log.info(f"Loading fresh config for {vector_name}")
|
112
|
-
config = ConfigManager(vector_name)
|
113
|
-
_config_cache[vector_name] = config
|
114
|
-
log.info(f"Cached config for {vector_name}")
|
115
|
-
return config
|
116
|
-
except Exception as e:
|
117
|
-
log.error(f"Error loading config for {vector_name}: {e}")
|
118
|
-
raise
|
119
74
|
|
120
75
|
def vac_interpreter_default(self, question: str, vector_name: str, chat_history=[], **kwargs):
|
121
76
|
# Create a callback that does nothing for streaming if you don't want intermediate outputs
|
@@ -273,18 +228,7 @@ if __name__ == "__main__":
|
|
273
228
|
|
274
229
|
log.info(f"OpenAI response: {openai_response}")
|
275
230
|
return jsonify(openai_response)
|
276
|
-
|
277
|
-
def _finalize_trace_background(self, trace, span, response, all_input):
|
278
|
-
"""Finalize trace operations in background"""
|
279
|
-
try:
|
280
|
-
if span:
|
281
|
-
span.end(output=str(response))
|
282
|
-
if trace:
|
283
|
-
trace.update(output=str(response))
|
284
|
-
self.langfuse_eval_response(trace_id=trace.id, eval_percent=all_input.get('eval_percent'))
|
285
|
-
except Exception as e:
|
286
|
-
log.warning(f"Background trace finalization failed: {e}")
|
287
|
-
|
231
|
+
|
288
232
|
def handle_stream_vac(self, vector_name):
|
289
233
|
observed_stream_interpreter = self.stream_interpreter
|
290
234
|
is_async = inspect.iscoroutinefunction(self.stream_interpreter)
|
@@ -710,131 +654,144 @@ if __name__ == "__main__":
|
|
710
654
|
tags = tags,
|
711
655
|
release = package_version
|
712
656
|
)
|
713
|
-
|
714
|
-
def _create_langfuse_trace_background(self, request, vector_name, trace_id):
|
715
|
-
"""Create Langfuse trace in background"""
|
716
|
-
try:
|
717
|
-
return self.create_langfuse_trace(request, vector_name, trace_id)
|
718
|
-
except Exception as e:
|
719
|
-
log.warning(f"Background trace creation failed: {e}")
|
720
|
-
return None
|
721
657
|
|
722
|
-
def _handle_file_upload_background(self, file, vector_name):
|
723
|
-
"""Handle file upload in background thread"""
|
724
|
-
try:
|
725
|
-
# Save with timestamp to avoid conflicts
|
726
|
-
temp_filename = f"temp_{int(time.time() * 1000)}_{file.filename}"
|
727
|
-
file.save(temp_filename)
|
728
|
-
|
729
|
-
# Upload to GCS
|
730
|
-
image_uri = add_file_to_gcs(temp_filename, vector_name)
|
731
|
-
|
732
|
-
# Clean up
|
733
|
-
os.remove(temp_filename)
|
734
|
-
|
735
|
-
return {"image_uri": image_uri, "mime": file.mimetype}
|
736
|
-
except Exception as e:
|
737
|
-
log.error(f"Background file upload failed: {e}")
|
738
|
-
return {}
|
739
|
-
|
740
658
|
def prep_vac(self, request, vector_name):
|
741
|
-
|
742
|
-
|
743
|
-
# Fast request parsing - KEEP ORIGINAL ERROR HANDLING STYLE
|
659
|
+
|
744
660
|
if request.content_type.startswith('application/json'):
|
745
661
|
data = request.get_json()
|
746
662
|
elif request.content_type.startswith('multipart/form-data'):
|
747
663
|
data = request.form.to_dict()
|
748
|
-
# Handle file upload in background if present
|
749
664
|
if 'file' in request.files:
|
750
665
|
file = request.files['file']
|
751
666
|
if file.filename != '':
|
752
|
-
log.info(f"Found file: {file.filename}
|
753
|
-
|
754
|
-
|
755
|
-
|
667
|
+
log.info(f"Found file: {file.filename} to upload to GCS")
|
668
|
+
try:
|
669
|
+
image_uri, mime_type = self.handle_file_upload(file, vector_name)
|
670
|
+
data["image_uri"] = image_uri
|
671
|
+
data["mime"] = mime_type
|
672
|
+
except Exception as e:
|
673
|
+
log.error(traceback.format_exc())
|
674
|
+
return jsonify({'error': str(e), 'traceback': traceback.format_exc()}), 500
|
675
|
+
else:
|
676
|
+
log.error("No file selected")
|
677
|
+
return jsonify({"error": "No file selected"}), 400
|
756
678
|
else:
|
757
|
-
|
758
|
-
raise ValueError("Unsupported content type")
|
679
|
+
return jsonify({"error": "Unsupported content type"}), 400
|
759
680
|
|
760
|
-
log.info(f"vac/{vector_name} got data
|
681
|
+
log.info(f"vac/{vector_name} got data: {data}")
|
761
682
|
|
762
|
-
|
683
|
+
trace = None
|
684
|
+
span = None
|
685
|
+
if self.add_langfuse_eval:
|
686
|
+
trace_id = data.get('trace_id')
|
687
|
+
trace = self.create_langfuse_trace(request, vector_name, trace_id)
|
688
|
+
log.info(f"Using existing langfuse trace: {trace_id}")
|
689
|
+
|
690
|
+
#config, _ = load_config("config/llm_config.yaml")
|
763
691
|
try:
|
764
|
-
vac_config =
|
692
|
+
vac_config = ConfigManager(vector_name)
|
765
693
|
except Exception as e:
|
766
694
|
raise ValueError(f"Unable to find vac_config for {vector_name} - {str(e)}")
|
767
695
|
|
768
|
-
|
696
|
+
if trace:
|
697
|
+
this_vac_config = vac_config.configs_by_kind.get("vacConfig")
|
698
|
+
metadata_config=None
|
699
|
+
if this_vac_config:
|
700
|
+
metadata_config = this_vac_config.get(vector_name)
|
701
|
+
|
702
|
+
trace.update(input=data, metadata=metadata_config)
|
703
|
+
|
769
704
|
user_input = data.pop('user_input').strip()
|
770
705
|
stream_wait_time = data.pop('stream_wait_time', 7)
|
771
706
|
stream_timeout = data.pop('stream_timeout', 120)
|
772
707
|
chat_history = data.pop('chat_history', None)
|
773
708
|
eval_percent = data.pop('eval_percent', 0.01)
|
774
|
-
|
775
|
-
data.pop('trace_id', None)
|
709
|
+
vector_name = data.pop('vector_name', vector_name)
|
710
|
+
data.pop('trace_id', None) # to ensure not in kwargs
|
776
711
|
|
777
|
-
# Process chat history with caching
|
778
712
|
paired_messages = extract_chat_history_with_cache(chat_history)
|
779
713
|
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
714
|
+
all_input = {'user_input': user_input,
|
715
|
+
'vector_name': vector_name,
|
716
|
+
'chat_history': paired_messages,
|
717
|
+
'stream_wait_time': stream_wait_time,
|
718
|
+
'stream_timeout': stream_timeout,
|
719
|
+
'eval_percent': eval_percent,
|
720
|
+
'kwargs': data}
|
721
|
+
|
722
|
+
if trace:
|
723
|
+
span = trace.span(
|
724
|
+
name="VAC",
|
725
|
+
metadata=vac_config.configs_by_kind,
|
726
|
+
input = all_input
|
727
|
+
)
|
728
|
+
|
729
|
+
return {
|
730
|
+
"trace": trace,
|
731
|
+
"span": span,
|
732
|
+
"all_input": all_input,
|
733
|
+
"vac_config": vac_config
|
734
|
+
}
|
790
735
|
|
791
|
-
|
736
|
+
async def prep_vac_async(self, request, vector_name):
|
737
|
+
"""Async version of prep_vac."""
|
738
|
+
# Parse request data
|
739
|
+
if request.content_type.startswith('application/json'):
|
740
|
+
data = request.get_json()
|
741
|
+
elif request.content_type.startswith('multipart/form-data'):
|
742
|
+
data = request.form.to_dict()
|
743
|
+
if 'file' in request.files:
|
744
|
+
file = request.files['file']
|
745
|
+
if file.filename != '':
|
746
|
+
log.info(f"Found file: {file.filename} to upload to GCS")
|
747
|
+
try:
|
748
|
+
# Make file upload async if possible
|
749
|
+
image_uri, mime_type = await self.handle_file_upload_async(file, vector_name)
|
750
|
+
data["image_uri"] = image_uri
|
751
|
+
data["mime"] = mime_type
|
752
|
+
except Exception as e:
|
753
|
+
log.error(traceback.format_exc())
|
754
|
+
return jsonify({'error': str(e), 'traceback': traceback.format_exc()}), 500
|
755
|
+
else:
|
756
|
+
log.error("No file selected")
|
757
|
+
return jsonify({"error": "No file selected"}), 400
|
758
|
+
else:
|
759
|
+
return jsonify({"error": "Unsupported content type"}), 400
|
760
|
+
|
761
|
+
log.info(f"vac/{vector_name} got data: {data}")
|
762
|
+
|
763
|
+
# Run these operations concurrently
|
764
|
+
tasks = []
|
765
|
+
|
766
|
+
# Extract other data while configs load
|
767
|
+
user_input = data.pop('user_input').strip()
|
768
|
+
stream_wait_time = data.pop('stream_wait_time', 7)
|
769
|
+
stream_timeout = data.pop('stream_timeout', 120)
|
770
|
+
chat_history = data.pop('chat_history', None)
|
771
|
+
vector_name_param = data.pop('vector_name', vector_name)
|
772
|
+
data.pop('trace_id', None) # to ensure not in kwargs
|
773
|
+
|
774
|
+
# Task 3: Process chat history
|
775
|
+
chat_history_task = asyncio.create_task(extract_chat_history_async_cached(chat_history))
|
776
|
+
tasks.append(chat_history_task)
|
777
|
+
|
778
|
+
# Await all tasks concurrently
|
779
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
780
|
+
|
781
|
+
paired_messages = results[0] if not isinstance(results[0], Exception) else []
|
782
|
+
|
783
|
+
# Only create span after we have trace
|
792
784
|
all_input = {
|
793
785
|
'user_input': user_input,
|
794
786
|
'vector_name': vector_name_param,
|
795
787
|
'chat_history': paired_messages,
|
796
788
|
'stream_wait_time': stream_wait_time,
|
797
789
|
'stream_timeout': stream_timeout,
|
798
|
-
'eval_percent': eval_percent,
|
799
790
|
'kwargs': data
|
800
791
|
}
|
801
|
-
|
802
|
-
# Initialize trace variables
|
803
|
-
trace = None
|
804
|
-
span = None
|
805
|
-
if self.add_langfuse_eval:
|
806
|
-
trace_id = data.get('trace_id')
|
807
|
-
# Create trace in background - don't block
|
808
|
-
trace_future = _thread_pool.submit(self._create_langfuse_trace_background, request, vector_name, trace_id)
|
809
|
-
|
810
|
-
# Try to get trace result if available (don't block long)
|
811
|
-
try:
|
812
|
-
trace = trace_future.result(timeout=0.1) # Very short timeout
|
813
|
-
if trace:
|
814
|
-
this_vac_config = vac_config.configs_by_kind.get("vacConfig")
|
815
|
-
metadata_config = None
|
816
|
-
if this_vac_config:
|
817
|
-
metadata_config = this_vac_config.get(vector_name)
|
818
|
-
trace.update(input=data, metadata=metadata_config)
|
819
|
-
|
820
|
-
span = trace.span(
|
821
|
-
name="VAC",
|
822
|
-
metadata=vac_config.configs_by_kind,
|
823
|
-
input=all_input
|
824
|
-
)
|
825
|
-
except Exception as e:
|
826
|
-
log.warning(f"Langfuse trace creation timed out or failed: {e}")
|
827
|
-
trace = None
|
828
|
-
span = None
|
829
|
-
|
830
|
-
prep_time = time.time() - start_time
|
831
|
-
log.info(f"prep_vac completed in {prep_time:.3f}s")
|
832
|
-
|
792
|
+
|
833
793
|
return {
|
834
|
-
"
|
835
|
-
"span": span,
|
836
|
-
"all_input": all_input,
|
837
|
-
"vac_config": vac_config
|
794
|
+
"all_input": all_input
|
838
795
|
}
|
839
796
|
|
840
797
|
def handle_file_upload(self, file, vector_name):
|
@@ -846,4 +803,3 @@ if __name__ == "__main__":
|
|
846
803
|
except Exception as e:
|
847
804
|
raise Exception(f'File upload failed: {str(e)}')
|
848
805
|
|
849
|
-
|
sunholo/utils/config_class.py
CHANGED
@@ -6,7 +6,38 @@ from collections import defaultdict
|
|
6
6
|
from .timedelta import format_timedelta
|
7
7
|
|
8
8
|
class ConfigManager:
|
9
|
-
|
9
|
+
# Class-level cache for instances
|
10
|
+
_instances = {}
|
11
|
+
_instance_cache_time = {}
|
12
|
+
_instance_cache_duration = timedelta(minutes=30) # Cache instances for 30 minutes
|
13
|
+
|
14
|
+
def __new__(cls, vector_name: str, validate: bool = True):
|
15
|
+
"""
|
16
|
+
Override __new__ to implement instance caching.
|
17
|
+
Returns existing instance if available and not expired.
|
18
|
+
"""
|
19
|
+
current_time = datetime.now()
|
20
|
+
cache_key = (vector_name, validate)
|
21
|
+
|
22
|
+
# Check if we have a cached instance
|
23
|
+
if cache_key in cls._instances:
|
24
|
+
cached_time = cls._instance_cache_time.get(cache_key)
|
25
|
+
if cached_time and (current_time - cached_time) < cls._instance_cache_duration:
|
26
|
+
# Return cached instance
|
27
|
+
return cls._instances[cache_key]
|
28
|
+
else:
|
29
|
+
# Cache expired, remove from cache
|
30
|
+
del cls._instances[cache_key]
|
31
|
+
if cache_key in cls._instance_cache_time:
|
32
|
+
del cls._instance_cache_time[cache_key]
|
33
|
+
|
34
|
+
# Create new instance
|
35
|
+
instance = super().__new__(cls)
|
36
|
+
cls._instances[cache_key] = instance
|
37
|
+
cls._instance_cache_time[cache_key] = current_time
|
38
|
+
return instance
|
39
|
+
|
40
|
+
def __init__(self, vector_name: str, validate: bool = True):
|
10
41
|
"""
|
11
42
|
Initialize the ConfigManager with a vector name.
|
12
43
|
Requires a local config/ folder holding your configuration files or the env var VAC_CONFIG_FOLDER to be set.
|
@@ -24,6 +55,10 @@ class ConfigManager:
|
|
24
55
|
agent = config.vacConfig("agent")
|
25
56
|
```
|
26
57
|
"""
|
58
|
+
# Prevent re-initialization of cached instances
|
59
|
+
if hasattr(self, '_initialized'):
|
60
|
+
return
|
61
|
+
|
27
62
|
if os.getenv("VAC_CONFIG_FOLDER") is None:
|
28
63
|
print("WARNING: No VAC_CONFIG_FOLDER environment variable was specified")
|
29
64
|
local_config_folder = os.path.join(os.getcwd(), "config")
|
@@ -39,11 +74,68 @@ class ConfigManager:
|
|
39
74
|
self.local_config_folder = local_config_folder
|
40
75
|
self.configs_by_kind = self.load_all_configs()
|
41
76
|
self.validate = validate
|
77
|
+
self._initialized = True
|
42
78
|
|
43
79
|
test_agent = self.vacConfig("agent")
|
44
80
|
if not test_agent and self.vector_name != "global" and self.validate:
|
45
81
|
print(f"WARNING: No vacConfig.agent found for {self.vector_name} - are you in right folder? {local_config_folder=} {self.config_folder=}")
|
46
82
|
|
83
|
+
@classmethod
|
84
|
+
def get_instance(cls, vector_name: str, validate: bool = True):
|
85
|
+
"""
|
86
|
+
Alternative class method to get an instance. This is more explicit than using __new__.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
vector_name (str): The name of the vector in the configuration files.
|
90
|
+
validate (bool): Whether to validate the configurations
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
ConfigManager: The ConfigManager instance (cached or new)
|
94
|
+
"""
|
95
|
+
return cls(vector_name, validate)
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def clear_instance_cache(cls, vector_name: str = None, validate: bool = None):
|
99
|
+
"""
|
100
|
+
Clear the instance cache. Useful for forcing fresh instances.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
vector_name (str, optional): If provided, only clear cache for this vector_name
|
104
|
+
validate (bool, optional): If provided along with vector_name, clear specific cache entry
|
105
|
+
"""
|
106
|
+
if vector_name is not None:
|
107
|
+
if validate is not None:
|
108
|
+
# Clear specific cache entry
|
109
|
+
cache_key = (vector_name, validate)
|
110
|
+
if cache_key in cls._instances:
|
111
|
+
del cls._instances[cache_key]
|
112
|
+
if cache_key in cls._instance_cache_time:
|
113
|
+
del cls._instance_cache_time[cache_key]
|
114
|
+
else:
|
115
|
+
# Clear all entries for this vector_name
|
116
|
+
keys_to_remove = [key for key in cls._instances.keys() if key[0] == vector_name]
|
117
|
+
for key in keys_to_remove:
|
118
|
+
del cls._instances[key]
|
119
|
+
if key in cls._instance_cache_time:
|
120
|
+
del cls._instance_cache_time[key]
|
121
|
+
else:
|
122
|
+
# Clear all cache
|
123
|
+
cls._instances.clear()
|
124
|
+
cls._instance_cache_time.clear()
|
125
|
+
|
126
|
+
@classmethod
|
127
|
+
def get_cached_instances(cls):
|
128
|
+
"""
|
129
|
+
Get information about currently cached instances.
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
dict: Dictionary with cache keys and their creation times
|
133
|
+
"""
|
134
|
+
return {
|
135
|
+
key: cls._instance_cache_time.get(key)
|
136
|
+
for key in cls._instances.keys()
|
137
|
+
}
|
138
|
+
|
47
139
|
def load_all_configs(self):
|
48
140
|
"""
|
49
141
|
Load all configuration files from the specified directories into a dictionary.
|
@@ -251,4 +343,4 @@ class ConfigManager:
|
|
251
343
|
if key in agents:
|
252
344
|
return agents[key]
|
253
345
|
else:
|
254
|
-
return None
|
346
|
+
return None
|
@@ -0,0 +1,522 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: sunholo
|
3
|
+
Version: 0.140.12
|
4
|
+
Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
|
5
|
+
Author-email: Holosun ApS <multivac@sunholo.com>
|
6
|
+
License: Apache License, Version 2.0
|
7
|
+
Project-URL: Homepage, https://github.com/sunholo-data/sunholo-py
|
8
|
+
Project-URL: Download, https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz
|
9
|
+
Keywords: llms,devops,google_cloud_platform
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
11
|
+
Classifier: Intended Audience :: Developers
|
12
|
+
Classifier: Topic :: Software Development :: Build Tools
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
18
|
+
Requires-Python: >=3.10
|
19
|
+
Description-Content-Type: text/markdown
|
20
|
+
License-File: LICENSE.txt
|
21
|
+
Requires-Dist: aiohttp
|
22
|
+
Requires-Dist: google-auth
|
23
|
+
Requires-Dist: ollama>=0.4.7
|
24
|
+
Requires-Dist: pillow>=11.0.0
|
25
|
+
Requires-Dist: pydantic
|
26
|
+
Requires-Dist: requests
|
27
|
+
Requires-Dist: ruamel.yaml
|
28
|
+
Requires-Dist: tenacity
|
29
|
+
Provides-Extra: test
|
30
|
+
Requires-Dist: pytest; extra == "test"
|
31
|
+
Requires-Dist: pytest-cov; extra == "test"
|
32
|
+
Provides-Extra: all
|
33
|
+
Requires-Dist: aiofiles; extra == "all"
|
34
|
+
Requires-Dist: aiohttp; extra == "all"
|
35
|
+
Requires-Dist: anthropic[vertex]; extra == "all"
|
36
|
+
Requires-Dist: asyncpg; extra == "all"
|
37
|
+
Requires-Dist: azure-identity; extra == "all"
|
38
|
+
Requires-Dist: azure-storage-blob; extra == "all"
|
39
|
+
Requires-Dist: fastapi; extra == "all"
|
40
|
+
Requires-Dist: flask; extra == "all"
|
41
|
+
Requires-Dist: google-auth; extra == "all"
|
42
|
+
Requires-Dist: google-auth-httplib2; extra == "all"
|
43
|
+
Requires-Dist: google-auth-oauthlib; extra == "all"
|
44
|
+
Requires-Dist: google-cloud-aiplatform>=1.58.0; extra == "all"
|
45
|
+
Requires-Dist: google-api-python-client; extra == "all"
|
46
|
+
Requires-Dist: google-cloud-alloydb-connector[pg8000]; extra == "all"
|
47
|
+
Requires-Dist: google-cloud-bigquery; extra == "all"
|
48
|
+
Requires-Dist: google-cloud-build; extra == "all"
|
49
|
+
Requires-Dist: google-cloud-service-control; extra == "all"
|
50
|
+
Requires-Dist: google-cloud-logging; extra == "all"
|
51
|
+
Requires-Dist: google-cloud-storage; extra == "all"
|
52
|
+
Requires-Dist: google-cloud-pubsub; extra == "all"
|
53
|
+
Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "all"
|
54
|
+
Requires-Dist: google-cloud-texttospeech; extra == "all"
|
55
|
+
Requires-Dist: google-generativeai>=0.7.1; extra == "all"
|
56
|
+
Requires-Dist: google-genai>=0.2.2; extra == "all"
|
57
|
+
Requires-Dist: gunicorn; extra == "all"
|
58
|
+
Requires-Dist: httpcore; extra == "all"
|
59
|
+
Requires-Dist: httpx; extra == "all"
|
60
|
+
Requires-Dist: jsonschema; extra == "all"
|
61
|
+
Requires-Dist: lancedb; extra == "all"
|
62
|
+
Requires-Dist: langchain>=0.2.16; extra == "all"
|
63
|
+
Requires-Dist: langchain-experimental>=0.0.61; extra == "all"
|
64
|
+
Requires-Dist: langchain-community>=0.2.11; extra == "all"
|
65
|
+
Requires-Dist: langchain-openai>=0.3.2; extra == "all"
|
66
|
+
Requires-Dist: langchain-google-genai>=2.0.9; extra == "all"
|
67
|
+
Requires-Dist: langchain_google_alloydb_pg; extra == "all"
|
68
|
+
Requires-Dist: langchain-anthropic>=0.1.23; extra == "all"
|
69
|
+
Requires-Dist: langchain-google-vertexai; extra == "all"
|
70
|
+
Requires-Dist: langchain-unstructured; extra == "all"
|
71
|
+
Requires-Dist: langfuse; extra == "all"
|
72
|
+
Requires-Dist: mcp; extra == "all"
|
73
|
+
Requires-Dist: numpy; extra == "all"
|
74
|
+
Requires-Dist: opencv-python; extra == "all"
|
75
|
+
Requires-Dist: pg8000; extra == "all"
|
76
|
+
Requires-Dist: pgvector; extra == "all"
|
77
|
+
Requires-Dist: pillow; extra == "all"
|
78
|
+
Requires-Dist: playwright; extra == "all"
|
79
|
+
Requires-Dist: psutil; extra == "all"
|
80
|
+
Requires-Dist: psycopg2-binary; extra == "all"
|
81
|
+
Requires-Dist: pydantic; extra == "all"
|
82
|
+
Requires-Dist: pypdf; extra == "all"
|
83
|
+
Requires-Dist: python-hcl2; extra == "all"
|
84
|
+
Requires-Dist: python-socketio; extra == "all"
|
85
|
+
Requires-Dist: pytesseract; extra == "all"
|
86
|
+
Requires-Dist: requests; extra == "all"
|
87
|
+
Requires-Dist: rich; extra == "all"
|
88
|
+
Requires-Dist: sounddevice; extra == "all"
|
89
|
+
Requires-Dist: supabase; extra == "all"
|
90
|
+
Requires-Dist: tabulate; extra == "all"
|
91
|
+
Requires-Dist: tantivy; extra == "all"
|
92
|
+
Requires-Dist: tenacity; extra == "all"
|
93
|
+
Requires-Dist: tiktoken; extra == "all"
|
94
|
+
Requires-Dist: unstructured[all-docs,local-inference]; extra == "all"
|
95
|
+
Requires-Dist: xlwings; extra == "all"
|
96
|
+
Provides-Extra: langchain
|
97
|
+
Requires-Dist: langchain; extra == "langchain"
|
98
|
+
Requires-Dist: langchain_experimental; extra == "langchain"
|
99
|
+
Requires-Dist: langchain-community; extra == "langchain"
|
100
|
+
Requires-Dist: langsmith; extra == "langchain"
|
101
|
+
Requires-Dist: langchain-unstructured; extra == "langchain"
|
102
|
+
Provides-Extra: azure
|
103
|
+
Requires-Dist: azure-identity; extra == "azure"
|
104
|
+
Requires-Dist: azure-storage-blob; extra == "azure"
|
105
|
+
Provides-Extra: cli
|
106
|
+
Requires-Dist: jsonschema>=4.21.1; extra == "cli"
|
107
|
+
Requires-Dist: rich; extra == "cli"
|
108
|
+
Provides-Extra: database
|
109
|
+
Requires-Dist: asyncpg; extra == "database"
|
110
|
+
Requires-Dist: supabase; extra == "database"
|
111
|
+
Requires-Dist: sqlalchemy; extra == "database"
|
112
|
+
Requires-Dist: pg8000; extra == "database"
|
113
|
+
Requires-Dist: pgvector; extra == "database"
|
114
|
+
Requires-Dist: psycopg2-binary; extra == "database"
|
115
|
+
Requires-Dist: lancedb; extra == "database"
|
116
|
+
Requires-Dist: tantivy; extra == "database"
|
117
|
+
Provides-Extra: pipeline
|
118
|
+
Requires-Dist: GitPython; extra == "pipeline"
|
119
|
+
Requires-Dist: lark; extra == "pipeline"
|
120
|
+
Requires-Dist: langchain>=0.2.16; extra == "pipeline"
|
121
|
+
Requires-Dist: langchain-unstructured; extra == "pipeline"
|
122
|
+
Requires-Dist: psutil; extra == "pipeline"
|
123
|
+
Requires-Dist: pypdf; extra == "pipeline"
|
124
|
+
Requires-Dist: pytesseract; extra == "pipeline"
|
125
|
+
Requires-Dist: tabulate; extra == "pipeline"
|
126
|
+
Requires-Dist: unstructured[all-docs,local-inference]; extra == "pipeline"
|
127
|
+
Provides-Extra: gcp
|
128
|
+
Requires-Dist: aiofiles; extra == "gcp"
|
129
|
+
Requires-Dist: anthropic[vertex]; extra == "gcp"
|
130
|
+
Requires-Dist: google-api-python-client; extra == "gcp"
|
131
|
+
Requires-Dist: google-auth-httplib2; extra == "gcp"
|
132
|
+
Requires-Dist: google-auth-oauthlib; extra == "gcp"
|
133
|
+
Requires-Dist: google-cloud-alloydb-connector[pg8000]; extra == "gcp"
|
134
|
+
Requires-Dist: google-cloud-aiplatform>=1.58.0; extra == "gcp"
|
135
|
+
Requires-Dist: google-cloud-bigquery; extra == "gcp"
|
136
|
+
Requires-Dist: google-cloud-build; extra == "gcp"
|
137
|
+
Requires-Dist: google-cloud-service-control; extra == "gcp"
|
138
|
+
Requires-Dist: google-cloud-storage; extra == "gcp"
|
139
|
+
Requires-Dist: google-cloud-logging; extra == "gcp"
|
140
|
+
Requires-Dist: google-cloud-pubsub; extra == "gcp"
|
141
|
+
Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "gcp"
|
142
|
+
Requires-Dist: google-cloud-texttospeech; extra == "gcp"
|
143
|
+
Requires-Dist: google-genai>=0.2.2; extra == "gcp"
|
144
|
+
Requires-Dist: google-generativeai>=0.8.3; extra == "gcp"
|
145
|
+
Requires-Dist: langchain; extra == "gcp"
|
146
|
+
Requires-Dist: langchain-google-genai>=2.0.0; extra == "gcp"
|
147
|
+
Requires-Dist: langchain_google_alloydb_pg>=0.2.2; extra == "gcp"
|
148
|
+
Requires-Dist: langchain-google-vertexai; extra == "gcp"
|
149
|
+
Requires-Dist: pillow; extra == "gcp"
|
150
|
+
Provides-Extra: ollama
|
151
|
+
Requires-Dist: pillow; extra == "ollama"
|
152
|
+
Requires-Dist: ollama>=0.4.7; extra == "ollama"
|
153
|
+
Provides-Extra: openai
|
154
|
+
Requires-Dist: langchain-openai>=0.3.2; extra == "openai"
|
155
|
+
Requires-Dist: tiktoken; extra == "openai"
|
156
|
+
Provides-Extra: anthropic
|
157
|
+
Requires-Dist: langchain-anthropic>=0.1.23; extra == "anthropic"
|
158
|
+
Requires-Dist: mcp; extra == "anthropic"
|
159
|
+
Provides-Extra: tools
|
160
|
+
Requires-Dist: openapi-spec-validator; extra == "tools"
|
161
|
+
Requires-Dist: playwright; extra == "tools"
|
162
|
+
Provides-Extra: http
|
163
|
+
Requires-Dist: fastapi; extra == "http"
|
164
|
+
Requires-Dist: flask; extra == "http"
|
165
|
+
Requires-Dist: gunicorn; extra == "http"
|
166
|
+
Requires-Dist: httpcore; extra == "http"
|
167
|
+
Requires-Dist: httpx; extra == "http"
|
168
|
+
Requires-Dist: langchain; extra == "http"
|
169
|
+
Requires-Dist: langfuse; extra == "http"
|
170
|
+
Requires-Dist: python-socketio; extra == "http"
|
171
|
+
Requires-Dist: requests; extra == "http"
|
172
|
+
Requires-Dist: tenacity; extra == "http"
|
173
|
+
Provides-Extra: excel
|
174
|
+
Requires-Dist: xlwings; extra == "excel"
|
175
|
+
Requires-Dist: requests; extra == "excel"
|
176
|
+
Requires-Dist: rich; extra == "excel"
|
177
|
+
Provides-Extra: iac
|
178
|
+
Requires-Dist: python-hcl2; extra == "iac"
|
179
|
+
Provides-Extra: tts
|
180
|
+
Requires-Dist: google-cloud-texttospeech; extra == "tts"
|
181
|
+
Requires-Dist: numpy; extra == "tts"
|
182
|
+
Requires-Dist: sounddevice; extra == "tts"
|
183
|
+
Provides-Extra: video
|
184
|
+
Requires-Dist: opencv-python; extra == "video"
|
185
|
+
Dynamic: license-file
|
186
|
+
|
187
|
+
# Sunholo Python Library
|
188
|
+
|
189
|
+
[](https://pypi.python.org/pypi/sunholo/)
|
190
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
191
|
+
[](https://pypi.python.org/pypi/sunholo/)
|
192
|
+
|
193
|
+
🚀 **AI DevOps framework for building GenAI applications on Google Cloud Platform**
|
194
|
+
|
195
|
+
Sunholo is a comprehensive Python framework that streamlines the development, deployment, and management of Generative AI applications (VACs - Virtual Agent Computers). It provides a configuration-driven approach with deep integration into Google Cloud services while supporting multiple AI providers.
|
196
|
+
|
197
|
+
## 🎯 What is Sunholo?
|
198
|
+
|
199
|
+
Sunholo helps you:
|
200
|
+
- 🤖 Build conversational AI agents with any LLM provider (Vertex AI, OpenAI, Anthropic, Ollama)
|
201
|
+
- ☁️ Deploy to Google Cloud Run with automatic scaling
|
202
|
+
- 🗄️ Use AlloyDB and Discovery Engine for vector storage and search
|
203
|
+
- 🔄 Handle streaming responses and async processing
|
204
|
+
- 📄 Process documents with chunking and embedding pipelines
|
205
|
+
- 🔧 Manage complex configurations with YAML files
|
206
|
+
- 🎨 Create APIs, web apps, and chat bots
|
207
|
+
|
208
|
+
## 🚀 Quick Start
|
209
|
+
|
210
|
+
### Prerequisites
|
211
|
+
|
212
|
+
Install [uv](https://docs.astral.sh/uv/) - a fast, modern Python package manager:
|
213
|
+
|
214
|
+
```bash
|
215
|
+
# macOS/Linux
|
216
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
217
|
+
|
218
|
+
# Windows
|
219
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
220
|
+
```
|
221
|
+
|
222
|
+
### Installation
|
223
|
+
|
224
|
+
```bash
|
225
|
+
# Install with CLI tools (recommended)
|
226
|
+
uv tool install --from "sunholo[cli]" sunholo
|
227
|
+
|
228
|
+
# Install with all features including GCP
|
229
|
+
uv tool install --from "sunholo[cli]" sunholo --with "sunholo[all]"
|
230
|
+
```
|
231
|
+
|
232
|
+
### Your First VAC
|
233
|
+
|
234
|
+
1. **Initialize a new project:**
|
235
|
+
```bash
|
236
|
+
sunholo init my-ai-agent
|
237
|
+
cd my-ai-agent
|
238
|
+
```
|
239
|
+
|
240
|
+
2. **Configure your AI agent:**
|
241
|
+
Edit `config/vac_config.yaml`:
|
242
|
+
```yaml
|
243
|
+
kind: vacConfig
|
244
|
+
apiVersion: v1
|
245
|
+
vac:
|
246
|
+
my-agent:
|
247
|
+
llm: vertex
|
248
|
+
model: gemini-1.5-pro
|
249
|
+
agent: simple
|
250
|
+
description: "My AI agent powered by Google Cloud"
|
251
|
+
```
|
252
|
+
|
253
|
+
3. **Chat with your agent locally:**
|
254
|
+
```bash
|
255
|
+
sunholo vac chat my-agent
|
256
|
+
```
|
257
|
+
|
258
|
+
4. **Deploy to Google Cloud Run:**
|
259
|
+
```bash
|
260
|
+
sunholo deploy my-agent
|
261
|
+
```
|
262
|
+
|
263
|
+
## 📋 Features
|
264
|
+
|
265
|
+
### Core Capabilities
|
266
|
+
|
267
|
+
- **Multi-Model Support**: Integrate Vertex AI, OpenAI, Anthropic, Ollama in one app
|
268
|
+
- **Document Processing**: Chunk, embed, and index documents with Discovery Engine
|
269
|
+
- **Vector Databases**: Native support for AlloyDB, LanceDB, Supabase
|
270
|
+
- **Streaming**: Real-time response streaming for chat applications
|
271
|
+
- **Async Processing**: Pub/Sub integration for background tasks
|
272
|
+
- **Authentication**: Built-in Google Cloud IAM and custom auth
|
273
|
+
|
274
|
+
### Google Cloud Integration
|
275
|
+
|
276
|
+
- **Vertex AI**: Access Gemini, PaLM, and custom models
|
277
|
+
- **AlloyDB**: PostgreSQL-compatible vector database
|
278
|
+
- **Discovery Engine**: Enterprise search and RAG
|
279
|
+
- **Cloud Run**: Serverless deployment
|
280
|
+
- **Cloud Storage**: Document and file management
|
281
|
+
- **Pub/Sub**: Asynchronous message processing
|
282
|
+
- **Cloud Logging**: Centralized logging
|
283
|
+
|
284
|
+
### Framework Support
|
285
|
+
|
286
|
+
- **Web Frameworks**: Flask and FastAPI templates
|
287
|
+
- **AI Frameworks**: LangChain and LlamaIndex integration
|
288
|
+
- **Observability**: Langfuse for tracing and monitoring
|
289
|
+
- **API Standards**: OpenAI-compatible endpoints
|
290
|
+
|
291
|
+
## 🛠 Installation Options
|
292
|
+
|
293
|
+
### Using uv
|
294
|
+
|
295
|
+
```bash
|
296
|
+
# Core CLI features
|
297
|
+
uv tool install --from "sunholo[cli]" sunholo
|
298
|
+
|
299
|
+
# With Google Cloud Platform integration
|
300
|
+
uv tool install --from "sunholo[cli]" sunholo --with "sunholo[gcp]"
|
301
|
+
|
302
|
+
# With specific LLM providers
|
303
|
+
uv tool install --from "sunholo[cli]" sunholo --with "sunholo[openai]"
|
304
|
+
uv tool install --from "sunholo[cli]" sunholo --with "sunholo[anthropic]"
|
305
|
+
|
306
|
+
# With database support
|
307
|
+
uv tool install --from "sunholo[cli]" sunholo --with "sunholo[database]"
|
308
|
+
|
309
|
+
# Everything
|
310
|
+
uv tool install --from "sunholo[cli]" sunholo --with "sunholo[all]"
|
311
|
+
```
|
312
|
+
|
313
|
+
### Managing Installations
|
314
|
+
|
315
|
+
```bash
|
316
|
+
# Upgrade
|
317
|
+
uv tool upgrade sunholo
|
318
|
+
|
319
|
+
# List installed
|
320
|
+
uv tool list
|
321
|
+
|
322
|
+
# Uninstall
|
323
|
+
uv tool uninstall sunholo
|
324
|
+
```
|
325
|
+
|
326
|
+
### Development Setup
|
327
|
+
|
328
|
+
```bash
|
329
|
+
# Clone repository
|
330
|
+
git clone https://github.com/sunholo-data/sunholo-py.git
|
331
|
+
cd sunholo-py
|
332
|
+
|
333
|
+
# Install in development mode
|
334
|
+
uv venv
|
335
|
+
uv pip install -e ".[all]"
|
336
|
+
|
337
|
+
# Run tests
|
338
|
+
pytest tests/
|
339
|
+
```
|
340
|
+
|
341
|
+
## ⚙️ Configuration
|
342
|
+
|
343
|
+
Sunholo uses YAML configuration files:
|
344
|
+
|
345
|
+
```yaml
|
346
|
+
# config/vac_config.yaml
|
347
|
+
kind: vacConfig
|
348
|
+
apiVersion: v1
|
349
|
+
gcp_config:
|
350
|
+
project_id: my-gcp-project
|
351
|
+
location: us-central1
|
352
|
+
vac:
|
353
|
+
my-agent:
|
354
|
+
llm: vertex
|
355
|
+
model: gemini-1.5-pro
|
356
|
+
agent: langchain
|
357
|
+
memory:
|
358
|
+
- alloydb:
|
359
|
+
project_id: my-gcp-project
|
360
|
+
region: us-central1
|
361
|
+
cluster: my-cluster
|
362
|
+
instance: my-instance
|
363
|
+
tools:
|
364
|
+
- search
|
365
|
+
- calculator
|
366
|
+
```
|
367
|
+
|
368
|
+
## 🔧 CLI Commands
|
369
|
+
|
370
|
+
```bash
|
371
|
+
# Project Management
|
372
|
+
sunholo init <project-name> # Create new project
|
373
|
+
sunholo list-configs # List all configurations
|
374
|
+
sunholo list-configs --validate # Validate configs
|
375
|
+
|
376
|
+
# Development
|
377
|
+
sunholo vac chat <vac-name> # Chat with a VAC locally
|
378
|
+
sunholo vac list # List available VACs
|
379
|
+
sunholo proxy start <service> # Start local proxy to cloud service
|
380
|
+
|
381
|
+
# Deployment
|
382
|
+
sunholo deploy <vac-name> # Deploy to Cloud Run
|
383
|
+
sunholo deploy <vac-name> --dev # Deploy to dev environment
|
384
|
+
|
385
|
+
# Document Processing
|
386
|
+
sunholo embed <vac-name> # Embed documents
|
387
|
+
sunholo merge-text <folder> <output> # Merge files for context
|
388
|
+
|
389
|
+
# Cloud Services
|
390
|
+
sunholo discovery-engine create <name> # Create Discovery Engine
|
391
|
+
sunholo proxy list # List running proxies
|
392
|
+
```
|
393
|
+
|
394
|
+
## 📝 Examples
|
395
|
+
|
396
|
+
### Vertex AI Chat with Memory
|
397
|
+
|
398
|
+
```python
|
399
|
+
from sunholo.utils import ConfigManager
|
400
|
+
from sunholo.components import pick_llm
|
401
|
+
from sunholo.agents import memory_client
|
402
|
+
|
403
|
+
config = ConfigManager('my-agent')
|
404
|
+
llm = pick_llm(config=config)
|
405
|
+
memory = memory_client(config=config)
|
406
|
+
|
407
|
+
# Chat with context
|
408
|
+
response = llm.invoke("What is Google Cloud?")
|
409
|
+
memory.add_message("user", "What is Google Cloud?")
|
410
|
+
memory.add_message("assistant", response)
|
411
|
+
```
|
412
|
+
|
413
|
+
### Document Processing with Discovery Engine
|
414
|
+
|
415
|
+
```python
|
416
|
+
from sunholo.discovery_engine import DiscoveryEngineClient
|
417
|
+
from sunholo.chunker import chunk_doc
|
418
|
+
|
419
|
+
# Initialize client
|
420
|
+
client = DiscoveryEngineClient(
|
421
|
+
project_id='my-project',
|
422
|
+
data_store_id='my-datastore'
|
423
|
+
)
|
424
|
+
|
425
|
+
# Process and index document
|
426
|
+
chunks = chunk_doc.chunk_file("document.pdf", chunk_size=1000)
|
427
|
+
client.import_documents(chunks)
|
428
|
+
|
429
|
+
# Search
|
430
|
+
results = client.search("What is the main topic?")
|
431
|
+
```
|
432
|
+
|
433
|
+
### Streaming Flask API
|
434
|
+
|
435
|
+
```python
|
436
|
+
from sunholo.agents import dispatch_to_qa
|
437
|
+
|
438
|
+
@app.route('/vac/streaming/<vac_name>', methods=['POST'])
|
439
|
+
def streaming_endpoint(vac_name):
|
440
|
+
question = request.json.get('user_input')
|
441
|
+
|
442
|
+
def generate():
|
443
|
+
for chunk in dispatch_to_qa(
|
444
|
+
question,
|
445
|
+
vac_name=vac_name,
|
446
|
+
stream=True
|
447
|
+
):
|
448
|
+
yield f"data: {chunk}\n\n"
|
449
|
+
|
450
|
+
return Response(generate(), content_type='text/event-stream')
|
451
|
+
```
|
452
|
+
|
453
|
+
### Deploy from Template
|
454
|
+
|
455
|
+
```bash
|
456
|
+
# Create from template
|
457
|
+
sunholo init my-api --template agent
|
458
|
+
|
459
|
+
# Customize configuration
|
460
|
+
cd my-api
|
461
|
+
vi config/vac_config.yaml
|
462
|
+
|
463
|
+
# Test locally
|
464
|
+
sunholo vac chat my-agent --local
|
465
|
+
|
466
|
+
# Deploy to production
|
467
|
+
sunholo deploy my-agent
|
468
|
+
```
|
469
|
+
|
470
|
+
## 🧪 Testing
|
471
|
+
|
472
|
+
```bash
|
473
|
+
# Run all tests
|
474
|
+
pytest tests/
|
475
|
+
|
476
|
+
# Run specific test file
|
477
|
+
pytest tests/test_config.py
|
478
|
+
|
479
|
+
# Run with coverage
|
480
|
+
pytest --cov=src/sunholo tests/
|
481
|
+
|
482
|
+
# Run async tests
|
483
|
+
pytest tests/test_async_genai2.py
|
484
|
+
```
|
485
|
+
|
486
|
+
## 📚 Documentation
|
487
|
+
|
488
|
+
- 📖 **Full Documentation**: https://dev.sunholo.com/
|
489
|
+
- 🎓 **Tutorials**: https://dev.sunholo.com/docs/howto/
|
490
|
+
- 🤖 **VAC Examples**: https://github.com/sunholo-data/vacs-public
|
491
|
+
- 🎧 **Audio Overview**: [Listen to the NotebookLM podcast](https://drive.google.com/file/d/1GvwRmiYDjPjN2hXQ8plhnVDByu6TmgCQ/view?usp=drive_link)
|
492
|
+
|
493
|
+
## 🤝 Contributing
|
494
|
+
|
495
|
+
We welcome contributions! See our [Contributing Guidelines](CONTRIBUTING.md).
|
496
|
+
|
497
|
+
1. Fork the repository
|
498
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
499
|
+
3. Commit your changes (`git commit -m 'Add AmazingFeature'`)
|
500
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
501
|
+
5. Open a Pull Request
|
502
|
+
|
503
|
+
## 📜 License
|
504
|
+
|
505
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE.txt) file for details.
|
506
|
+
|
507
|
+
```
|
508
|
+
Copyright [2024] [Holosun ApS]
|
509
|
+
|
510
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
511
|
+
you may not use this file except in compliance with the License.
|
512
|
+
You may obtain a copy of the License at
|
513
|
+
|
514
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
515
|
+
```
|
516
|
+
|
517
|
+
## 🙏 Support
|
518
|
+
|
519
|
+
- 📧 Email: multivac@sunholo.com
|
520
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/sunholo-data/sunholo-py/issues)
|
521
|
+
- 💬 Discussions: [GitHub Discussions](https://github.com/sunholo-data/sunholo-py/discussions)
|
522
|
+
- 📖 Documentation: https://dev.sunholo.com/
|
@@ -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=TEM0u2vkZC0BSKJABxQVPm4QiUsEFoPOwJZIOxzi1Sk,32621
|
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
|
@@ -151,7 +151,7 @@ sunholo/utils/__init__.py,sha256=Hv02T5L2zYWvCso5hzzwm8FQogwBq0OgtUbN_7Quzqc,89
|
|
151
151
|
sunholo/utils/api_key.py,sha256=Ct4bIAQZxzPEw14hP586LpVxBAVi_W9Serpy0BK-7KI,244
|
152
152
|
sunholo/utils/big_context.py,sha256=HuP9_r_Nx1jvZHxjMEihgoZAXmnCh80zzsj1fq3mIOg,6021
|
153
153
|
sunholo/utils/config.py,sha256=OeYr4UvhRv94e1OVFab4kuQa5TdbbljQjvhnGlkDewM,9160
|
154
|
-
sunholo/utils/config_class.py,sha256=
|
154
|
+
sunholo/utils/config_class.py,sha256=U0xwyCz68KCJgzyhXd0AmbFnstMBFvZMedb-lLKKa5Q,13363
|
155
155
|
sunholo/utils/config_schema.py,sha256=Wv-ncitzljOhgbDaq9qnFqH5LCuxNv59dTGDWgd1qdk,4189
|
156
156
|
sunholo/utils/gcp.py,sha256=lus1HH8YhFInw6QRKwfvKZq-Lz-2KQg4ips9v1I_3zE,4783
|
157
157
|
sunholo/utils/gcp_project.py,sha256=Fa0IhCX12bZ1ctF_PKN8PNYd7hihEUfb90kilBfUDjg,1411
|
@@ -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.12.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
172
|
+
sunholo-0.140.12.dist-info/METADATA,sha256=UBt83NkXY5EpTwdtDF5LCOe2IsBXlR1TCVx9EoAuAOA,17116
|
173
|
+
sunholo-0.140.12.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
174
|
+
sunholo-0.140.12.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
175
|
+
sunholo-0.140.12.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
176
|
+
sunholo-0.140.12.dist-info/RECORD,,
|
@@ -1,249 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: sunholo
|
3
|
-
Version: 0.140.10
|
4
|
-
Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
|
5
|
-
Author-email: Holosun ApS <multivac@sunholo.com>
|
6
|
-
License: Apache License, Version 2.0
|
7
|
-
Project-URL: Homepage, https://github.com/sunholo-data/sunholo-py
|
8
|
-
Project-URL: Download, https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz
|
9
|
-
Keywords: llms,devops,google_cloud_platform
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
11
|
-
Classifier: Intended Audience :: Developers
|
12
|
-
Classifier: Topic :: Software Development :: Build Tools
|
13
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
15
|
-
Classifier: Programming Language :: Python :: 3.10
|
16
|
-
Classifier: Programming Language :: Python :: 3.11
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
18
|
-
Requires-Python: >=3.10
|
19
|
-
Description-Content-Type: text/markdown
|
20
|
-
License-File: LICENSE.txt
|
21
|
-
Requires-Dist: aiohttp
|
22
|
-
Requires-Dist: google-auth
|
23
|
-
Requires-Dist: ollama>=0.4.7
|
24
|
-
Requires-Dist: pillow>=11.0.0
|
25
|
-
Requires-Dist: pydantic
|
26
|
-
Requires-Dist: requests
|
27
|
-
Requires-Dist: ruamel.yaml
|
28
|
-
Requires-Dist: tenacity
|
29
|
-
Provides-Extra: test
|
30
|
-
Requires-Dist: pytest; extra == "test"
|
31
|
-
Requires-Dist: pytest-cov; extra == "test"
|
32
|
-
Provides-Extra: all
|
33
|
-
Requires-Dist: aiofiles; extra == "all"
|
34
|
-
Requires-Dist: aiohttp; extra == "all"
|
35
|
-
Requires-Dist: anthropic[vertex]; extra == "all"
|
36
|
-
Requires-Dist: asyncpg; extra == "all"
|
37
|
-
Requires-Dist: azure-identity; extra == "all"
|
38
|
-
Requires-Dist: azure-storage-blob; extra == "all"
|
39
|
-
Requires-Dist: fastapi; extra == "all"
|
40
|
-
Requires-Dist: flask; extra == "all"
|
41
|
-
Requires-Dist: google-auth; extra == "all"
|
42
|
-
Requires-Dist: google-auth-httplib2; extra == "all"
|
43
|
-
Requires-Dist: google-auth-oauthlib; extra == "all"
|
44
|
-
Requires-Dist: google-cloud-aiplatform>=1.58.0; extra == "all"
|
45
|
-
Requires-Dist: google-api-python-client; extra == "all"
|
46
|
-
Requires-Dist: google-cloud-alloydb-connector[pg8000]; extra == "all"
|
47
|
-
Requires-Dist: google-cloud-bigquery; extra == "all"
|
48
|
-
Requires-Dist: google-cloud-build; extra == "all"
|
49
|
-
Requires-Dist: google-cloud-service-control; extra == "all"
|
50
|
-
Requires-Dist: google-cloud-logging; extra == "all"
|
51
|
-
Requires-Dist: google-cloud-storage; extra == "all"
|
52
|
-
Requires-Dist: google-cloud-pubsub; extra == "all"
|
53
|
-
Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "all"
|
54
|
-
Requires-Dist: google-cloud-texttospeech; extra == "all"
|
55
|
-
Requires-Dist: google-generativeai>=0.7.1; extra == "all"
|
56
|
-
Requires-Dist: google-genai>=0.2.2; extra == "all"
|
57
|
-
Requires-Dist: gunicorn; extra == "all"
|
58
|
-
Requires-Dist: httpcore; extra == "all"
|
59
|
-
Requires-Dist: httpx; extra == "all"
|
60
|
-
Requires-Dist: jsonschema; extra == "all"
|
61
|
-
Requires-Dist: lancedb; extra == "all"
|
62
|
-
Requires-Dist: langchain>=0.2.16; extra == "all"
|
63
|
-
Requires-Dist: langchain-experimental>=0.0.61; extra == "all"
|
64
|
-
Requires-Dist: langchain-community>=0.2.11; extra == "all"
|
65
|
-
Requires-Dist: langchain-openai>=0.3.2; extra == "all"
|
66
|
-
Requires-Dist: langchain-google-genai>=2.0.9; extra == "all"
|
67
|
-
Requires-Dist: langchain_google_alloydb_pg; extra == "all"
|
68
|
-
Requires-Dist: langchain-anthropic>=0.1.23; extra == "all"
|
69
|
-
Requires-Dist: langchain-google-vertexai; extra == "all"
|
70
|
-
Requires-Dist: langchain-unstructured; extra == "all"
|
71
|
-
Requires-Dist: langfuse; extra == "all"
|
72
|
-
Requires-Dist: mcp; extra == "all"
|
73
|
-
Requires-Dist: numpy; extra == "all"
|
74
|
-
Requires-Dist: opencv-python; extra == "all"
|
75
|
-
Requires-Dist: pg8000; extra == "all"
|
76
|
-
Requires-Dist: pgvector; extra == "all"
|
77
|
-
Requires-Dist: pillow; extra == "all"
|
78
|
-
Requires-Dist: playwright; extra == "all"
|
79
|
-
Requires-Dist: psutil; extra == "all"
|
80
|
-
Requires-Dist: psycopg2-binary; extra == "all"
|
81
|
-
Requires-Dist: pydantic; extra == "all"
|
82
|
-
Requires-Dist: pypdf; extra == "all"
|
83
|
-
Requires-Dist: python-hcl2; extra == "all"
|
84
|
-
Requires-Dist: python-socketio; extra == "all"
|
85
|
-
Requires-Dist: pytesseract; extra == "all"
|
86
|
-
Requires-Dist: requests; extra == "all"
|
87
|
-
Requires-Dist: rich; extra == "all"
|
88
|
-
Requires-Dist: sounddevice; extra == "all"
|
89
|
-
Requires-Dist: supabase; extra == "all"
|
90
|
-
Requires-Dist: tabulate; extra == "all"
|
91
|
-
Requires-Dist: tantivy; extra == "all"
|
92
|
-
Requires-Dist: tenacity; extra == "all"
|
93
|
-
Requires-Dist: tiktoken; extra == "all"
|
94
|
-
Requires-Dist: unstructured[all-docs,local-inference]; extra == "all"
|
95
|
-
Requires-Dist: xlwings; extra == "all"
|
96
|
-
Provides-Extra: langchain
|
97
|
-
Requires-Dist: langchain; extra == "langchain"
|
98
|
-
Requires-Dist: langchain_experimental; extra == "langchain"
|
99
|
-
Requires-Dist: langchain-community; extra == "langchain"
|
100
|
-
Requires-Dist: langsmith; extra == "langchain"
|
101
|
-
Requires-Dist: langchain-unstructured; extra == "langchain"
|
102
|
-
Provides-Extra: azure
|
103
|
-
Requires-Dist: azure-identity; extra == "azure"
|
104
|
-
Requires-Dist: azure-storage-blob; extra == "azure"
|
105
|
-
Provides-Extra: cli
|
106
|
-
Requires-Dist: jsonschema>=4.21.1; extra == "cli"
|
107
|
-
Requires-Dist: rich; extra == "cli"
|
108
|
-
Provides-Extra: database
|
109
|
-
Requires-Dist: asyncpg; extra == "database"
|
110
|
-
Requires-Dist: supabase; extra == "database"
|
111
|
-
Requires-Dist: sqlalchemy; extra == "database"
|
112
|
-
Requires-Dist: pg8000; extra == "database"
|
113
|
-
Requires-Dist: pgvector; extra == "database"
|
114
|
-
Requires-Dist: psycopg2-binary; extra == "database"
|
115
|
-
Requires-Dist: lancedb; extra == "database"
|
116
|
-
Requires-Dist: tantivy; extra == "database"
|
117
|
-
Provides-Extra: pipeline
|
118
|
-
Requires-Dist: GitPython; extra == "pipeline"
|
119
|
-
Requires-Dist: lark; extra == "pipeline"
|
120
|
-
Requires-Dist: langchain>=0.2.16; extra == "pipeline"
|
121
|
-
Requires-Dist: langchain-unstructured; extra == "pipeline"
|
122
|
-
Requires-Dist: psutil; extra == "pipeline"
|
123
|
-
Requires-Dist: pypdf; extra == "pipeline"
|
124
|
-
Requires-Dist: pytesseract; extra == "pipeline"
|
125
|
-
Requires-Dist: tabulate; extra == "pipeline"
|
126
|
-
Requires-Dist: unstructured[all-docs,local-inference]; extra == "pipeline"
|
127
|
-
Provides-Extra: gcp
|
128
|
-
Requires-Dist: aiofiles; extra == "gcp"
|
129
|
-
Requires-Dist: anthropic[vertex]; extra == "gcp"
|
130
|
-
Requires-Dist: google-api-python-client; extra == "gcp"
|
131
|
-
Requires-Dist: google-auth-httplib2; extra == "gcp"
|
132
|
-
Requires-Dist: google-auth-oauthlib; extra == "gcp"
|
133
|
-
Requires-Dist: google-cloud-alloydb-connector[pg8000]; extra == "gcp"
|
134
|
-
Requires-Dist: google-cloud-aiplatform>=1.58.0; extra == "gcp"
|
135
|
-
Requires-Dist: google-cloud-bigquery; extra == "gcp"
|
136
|
-
Requires-Dist: google-cloud-build; extra == "gcp"
|
137
|
-
Requires-Dist: google-cloud-service-control; extra == "gcp"
|
138
|
-
Requires-Dist: google-cloud-storage; extra == "gcp"
|
139
|
-
Requires-Dist: google-cloud-logging; extra == "gcp"
|
140
|
-
Requires-Dist: google-cloud-pubsub; extra == "gcp"
|
141
|
-
Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "gcp"
|
142
|
-
Requires-Dist: google-cloud-texttospeech; extra == "gcp"
|
143
|
-
Requires-Dist: google-genai>=0.2.2; extra == "gcp"
|
144
|
-
Requires-Dist: google-generativeai>=0.8.3; extra == "gcp"
|
145
|
-
Requires-Dist: langchain; extra == "gcp"
|
146
|
-
Requires-Dist: langchain-google-genai>=2.0.0; extra == "gcp"
|
147
|
-
Requires-Dist: langchain_google_alloydb_pg>=0.2.2; extra == "gcp"
|
148
|
-
Requires-Dist: langchain-google-vertexai; extra == "gcp"
|
149
|
-
Requires-Dist: pillow; extra == "gcp"
|
150
|
-
Provides-Extra: ollama
|
151
|
-
Requires-Dist: pillow; extra == "ollama"
|
152
|
-
Requires-Dist: ollama>=0.4.7; extra == "ollama"
|
153
|
-
Provides-Extra: openai
|
154
|
-
Requires-Dist: langchain-openai>=0.3.2; extra == "openai"
|
155
|
-
Requires-Dist: tiktoken; extra == "openai"
|
156
|
-
Provides-Extra: anthropic
|
157
|
-
Requires-Dist: langchain-anthropic>=0.1.23; extra == "anthropic"
|
158
|
-
Requires-Dist: mcp; extra == "anthropic"
|
159
|
-
Provides-Extra: tools
|
160
|
-
Requires-Dist: openapi-spec-validator; extra == "tools"
|
161
|
-
Requires-Dist: playwright; extra == "tools"
|
162
|
-
Provides-Extra: http
|
163
|
-
Requires-Dist: fastapi; extra == "http"
|
164
|
-
Requires-Dist: flask; extra == "http"
|
165
|
-
Requires-Dist: gunicorn; extra == "http"
|
166
|
-
Requires-Dist: httpcore; extra == "http"
|
167
|
-
Requires-Dist: httpx; extra == "http"
|
168
|
-
Requires-Dist: langchain; extra == "http"
|
169
|
-
Requires-Dist: langfuse; extra == "http"
|
170
|
-
Requires-Dist: python-socketio; extra == "http"
|
171
|
-
Requires-Dist: requests; extra == "http"
|
172
|
-
Requires-Dist: tenacity; extra == "http"
|
173
|
-
Provides-Extra: excel
|
174
|
-
Requires-Dist: xlwings; extra == "excel"
|
175
|
-
Requires-Dist: requests; extra == "excel"
|
176
|
-
Requires-Dist: rich; extra == "excel"
|
177
|
-
Provides-Extra: iac
|
178
|
-
Requires-Dist: python-hcl2; extra == "iac"
|
179
|
-
Provides-Extra: tts
|
180
|
-
Requires-Dist: google-cloud-texttospeech; extra == "tts"
|
181
|
-
Requires-Dist: numpy; extra == "tts"
|
182
|
-
Requires-Dist: sounddevice; extra == "tts"
|
183
|
-
Provides-Extra: video
|
184
|
-
Requires-Dist: opencv-python; extra == "video"
|
185
|
-
Dynamic: license-file
|
186
|
-
|
187
|
-
[](https://pypi.python.org/pypi/sunholo/)
|
188
|
-
|
189
|
-
## Introduction
|
190
|
-
This is the Sunholo Python project, a comprehensive toolkit for working with language models and vector stores on Google Cloud Platform. It provides a wide range of functionalities and utilities to facilitate the development and deployment of language model applications.
|
191
|
-
|
192
|
-
Please refer to the website for full documentation at https://dev.sunholo.com/
|
193
|
-
|
194
|
-
## Listen to the audio file:
|
195
|
-
|
196
|
-
A [NotebookLM](https://notebooklm.google/) generated podcast of the codebase that may help give you an overview of what the library is capable of:
|
197
|
-
|
198
|
-
[Listen to the audio file from Google Drive](https://drive.google.com/file/d/1GvwRmiYDjPjN2hXQ8plhnVDByu6TmgCQ/view?usp=drive_link) or on the website at https://dev.sunholo.com/docs/
|
199
|
-
|
200
|
-
> "Ever wish you could build your own AI?..."
|
201
|
-
|
202
|
-
## Tests via pytest
|
203
|
-
|
204
|
-
If loading from GitHub, run tests:
|
205
|
-
|
206
|
-
```bash
|
207
|
-
pip install pytest
|
208
|
-
pip install . --use-feature=in-tree-build
|
209
|
-
pytest tests
|
210
|
-
```
|
211
|
-
|
212
|
-
## Local dev
|
213
|
-
|
214
|
-
```sh
|
215
|
-
uv tool install --from "sunholo[cli]" sunholo --with ".[all]"
|
216
|
-
```
|
217
|
-
|
218
|
-
## Demos
|
219
|
-
|
220
|
-
Using https://github.com/charmbracelet/vhs
|
221
|
-
|
222
|
-
```sh
|
223
|
-
vhs record > cassette.tape
|
224
|
-
```
|
225
|
-
|
226
|
-
Then make gif:
|
227
|
-
|
228
|
-
```sh
|
229
|
-
vhs docs/tapes/config-list.tape
|
230
|
-
```
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
```
|
235
|
-
Copyright [2024] [Holosun ApS]
|
236
|
-
|
237
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
238
|
-
you may not use this file except in compliance with the License.
|
239
|
-
You may obtain a copy of the License at
|
240
|
-
|
241
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
242
|
-
|
243
|
-
Unless required by applicable law or agreed to in writing, software
|
244
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
245
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
246
|
-
See the License for the specific language governing permissions and
|
247
|
-
limitations under the License.
|
248
|
-
```
|
249
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|