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.
@@ -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
- start_time = time.time()
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} - uploading in background")
753
- # Start file upload in background, don't block
754
- upload_future = _thread_pool.submit(self._handle_file_upload_background, file, vector_name)
755
- data["_upload_future"] = upload_future
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
- # KEEP ORIGINAL STYLE - return the error response directly
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 keys: {list(data.keys())}")
681
+ log.info(f"vac/{vector_name} got data: {data}")
761
682
 
762
- # Get config from cache first (before processing other data)
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 = self._get_cached_config(vector_name)
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
- # Extract data (keep original logic)
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
- vector_name_param = data.pop('vector_name', vector_name)
775
- data.pop('trace_id', None) # to ensure not in kwargs
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
- # Wait for file upload if it was started (with timeout)
781
- if "_upload_future" in data:
782
- try:
783
- upload_result = data["_upload_future"].result(timeout=3.0) # 3 sec max wait
784
- data.update(upload_result)
785
- log.info(f"File upload completed: {upload_result.get('image_uri', 'no uri')}")
786
- except Exception as e:
787
- log.warning(f"File upload failed or timed out: {e}")
788
- finally:
789
- data.pop("_upload_future", None)
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
- # BUILD all_input BEFORE trace creation (this was moved inside try/catch by mistake)
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
- "trace": trace,
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
-
@@ -6,7 +6,38 @@ from collections import defaultdict
6
6
  from .timedelta import format_timedelta
7
7
 
8
8
  class ConfigManager:
9
- def __init__(self, vector_name: str, validate:bool=True):
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
+ [![PyPi Version](https://img.shields.io/pypi/v/sunholo.svg)](https://pypi.python.org/pypi/sunholo/)
190
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
191
+ [![Python Version](https://img.shields.io/pypi/pyversions/sunholo.svg)](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=QfM1nChWLAXhZ4_YYAsiXMss2CeQgxVI0GPD2fNLkmE,34541
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=uSRiJLj8t5UgWNxaq8W4KPnzxb4SkUJ1avXecDHuP-E,9768
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.10.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
172
- sunholo-0.140.10.dist-info/METADATA,sha256=EfU0N2go-DI1A6WaL5p_K9TtXwf8PkZCdUWZOoM7j4c,10068
173
- sunholo-0.140.10.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
174
- sunholo-0.140.10.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
175
- sunholo-0.140.10.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
176
- sunholo-0.140.10.dist-info/RECORD,,
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
- [![PyPi Version](https://img.shields.io/pypi/v/sunholo.svg)](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
-