sunholo 0.55.15__py3-none-any.whl → 0.56.1__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.
@@ -23,13 +23,6 @@ from ...streaming import start_streaming_chat
23
23
  from ...archive import archive_qa
24
24
  from ...logging import log
25
25
  from ...utils.config import load_config
26
-
27
- try:
28
- from langfuse import Langfuse
29
- langfuse = Langfuse()
30
- except ImportError as err:
31
- print(f"No langfuse installed for agents.flask.register_qna_routes, install via `pip install sunholo[http]` - {str(err)}")
32
- langfuse = None
33
26
 
34
27
  try:
35
28
  from flask import request, jsonify, Response
@@ -103,9 +96,6 @@ def register_qna_routes(app, stream_interpreter, vac_interpreter):
103
96
  generation.end(output=response)
104
97
  span.end(output=response)
105
98
  trace.update(output=response)
106
-
107
- if langfuse:
108
- langfuse.flush()
109
99
 
110
100
  return response
111
101
 
@@ -153,9 +143,6 @@ def register_qna_routes(app, stream_interpreter, vac_interpreter):
153
143
  if trace:
154
144
  span.end(output=jsonify(bot_output))
155
145
  trace.update(output=jsonify(bot_output))
156
-
157
- if langfuse:
158
- langfuse.flush()
159
146
 
160
147
  return jsonify(bot_output)
161
148
 
File without changes
sunholo/cli/cli.py ADDED
@@ -0,0 +1,12 @@
1
+ # sunholo/cli/cli.py
2
+ import argparse
3
+
4
+ def main():
5
+ parser = argparse.ArgumentParser(description="sunholo CLI tool.")
6
+ parser.add_argument('--echo', help='Echo the string you use here')
7
+ args = parser.parse_args()
8
+ if args.echo:
9
+ print(args.echo)
10
+
11
+ if __name__ == "__main__":
12
+ main()
sunholo/components/llm.py CHANGED
@@ -19,7 +19,7 @@ import os
19
19
  def pick_llm(vector_name):
20
20
  log.debug('Picking llm')
21
21
 
22
- llm_str = load_config_key("llm", vector_name, filename = "config/llm_config.yaml")
22
+ llm_str = load_config_key("llm", vector_name, kind="vacConfig")
23
23
 
24
24
  if llm_str == 'openai':
25
25
  llm_chat = get_llm_chat(vector_name)
@@ -49,7 +49,7 @@ def pick_llm(vector_name):
49
49
 
50
50
  def pick_streaming(vector_name):
51
51
 
52
- llm_str = load_config_key("llm", vector_name, filename = "config/llm_config.yaml")
52
+ llm_str = load_config_key("llm", vector_name, kind="vacConfig")
53
53
 
54
54
  if llm_str == 'openai' or llm_str == 'gemini' or llm_str == 'vertex':
55
55
  return True
@@ -57,7 +57,7 @@ def pick_streaming(vector_name):
57
57
  return False
58
58
 
59
59
 
60
- def llm_str_to_llm(llm_str, model=None, vector_name=None, config_file="config/llm_config.yaml"):
60
+ def llm_str_to_llm(llm_str, model=None, vector_name=None):
61
61
  if llm_str == 'openai':
62
62
  # Setup for OpenAI LLM
63
63
  from langchain_openai import ChatOpenAI
@@ -79,7 +79,7 @@ def llm_str_to_llm(llm_str, model=None, vector_name=None, config_file="config/ll
79
79
 
80
80
  elif llm_str == 'model_garden':
81
81
  from ..patches.langchain.vertexai import VertexAIModelGarden
82
- model_garden_config = load_config_key("gcp_config", vector_name, filename = config_file)
82
+ model_garden_config = load_config_key("gcp_config", vector_name, kind="vacConfig")
83
83
  if model_garden_config is None:
84
84
  raise ValueError("llm='model_garden' requires a gcp_config entry in config yaml file")
85
85
 
@@ -97,21 +97,21 @@ def llm_str_to_llm(llm_str, model=None, vector_name=None, config_file="config/ll
97
97
  if llm_str is None:
98
98
  raise NotImplementedError(f'No llm implemented for {llm_str}')
99
99
 
100
- def get_llm(vector_name, model=None, config_file="config/llm_config.yaml"):
101
- llm_str = load_config_key("llm", vector_name, filename=config_file)
100
+ def get_llm(vector_name, model=None):
101
+ llm_str = load_config_key("llm", vector_name, kind="vacConfig")
102
102
  #model_lookup_filepath = get_module_filepath("lookup/model_lookup.yaml")
103
103
  #model_lookup, _ = load_config(model_lookup_filepath)
104
104
 
105
105
  if not model:
106
- model = load_config_key("model", vector_name, filename=config_file)
106
+ model = load_config_key("model", vector_name, kind="vacConfig")
107
107
 
108
108
  log.debug(f"Chose LLM: {llm_str}")
109
- return llm_str_to_llm(llm_str, model=model, vector_name=vector_name, config_file=config_file)
109
+ return llm_str_to_llm(llm_str, model=model, vector_name=vector_name, kind="vacConfig")
110
110
 
111
111
  def get_llm_chat(vector_name, model=None, config_file="config/llm_config.yaml"):
112
- llm_str = load_config_key("llm", vector_name, filename=config_file)
112
+ llm_str = load_config_key("llm", vector_name, kind="vacConfig")
113
113
  if not model:
114
- model = load_config_key("model", vector_name, filename=config_file)
114
+ model = load_config_key("model", vector_name, kind="vacConfig")
115
115
 
116
116
  log.debug(f"Chose LLM: {llm_str}")
117
117
  # Configure LLMs based on llm_str
@@ -150,7 +150,7 @@ def get_llm_chat(vector_name, model=None, config_file="config/llm_config.yaml"):
150
150
  return ChatAnthropic(model_name = model, temperature=0)
151
151
  elif llm_str == 'azure':
152
152
  from langchain_openai import AzureChatOpenAI
153
- azure_config = load_config_key("azure", vector_name, filename="config/llm_config.yaml")
153
+ azure_config = load_config_key("azure", vector_name, kind="vacConfig")
154
154
  if not azure_config:
155
155
  raise ValueError("Need to configure azure.config if llm='azure'")
156
156
 
@@ -195,13 +195,13 @@ def get_llm_chat(vector_name, model=None, config_file="config/llm_config.yaml"):
195
195
  def get_embeddings(vector_name):
196
196
 
197
197
  llm_str = None
198
- embed_dict = load_config_key("embedder", vector_name, filename="config/llm_config.yaml")
198
+ embed_dict = load_config_key("embedder", vector_name, kind="vacConfig")
199
199
 
200
200
  if embed_dict:
201
201
  llm_str = embed_dict.get('llm')
202
202
 
203
203
  if llm_str is None:
204
- llm_str = load_config_key("llm", vector_name, filename="config/llm_config.yaml")
204
+ llm_str = load_config_key("llm", vector_name, kind="vacConfig")
205
205
 
206
206
  return pick_embedding(llm_str, vector_name=vector_name)
207
207
 
@@ -227,7 +227,7 @@ def pick_embedding(llm_str: str, vector_name: str=None):
227
227
  elif llm_str == 'azure':
228
228
  from langchain_openai import AzureOpenAIEmbeddings
229
229
 
230
- azure_config = load_config_key("azure", vector_name, filename="config/llm_config.yaml")
230
+ azure_config = load_config_key("azure", vector_name, kind="vacConfig")
231
231
  if not azure_config:
232
232
  raise ValueError("Need to configure azure.config if llm='azure'")
233
233
 
sunholo/logging.py CHANGED
@@ -220,11 +220,16 @@ def setup_logging(logger_name=None, log_level=logging.INFO, project_id=None):
220
220
  if logger_name is None:
221
221
  logger_name = "sunholo"
222
222
 
223
- if Client:
223
+ if Client and os.environ.get('GOOGLE_CLOUD_LOGGING') != "1":
224
+ print("GOOGLE_CLOUD_LOGGING != 1 but authentication with Google Cloud Logging enabled - missing env var setting?")
225
+
226
+ if not Client and os.environ.get('GOOGLE_CLOUD_LOGGING') == "1":
227
+ print("Found GOOGLE_CLOUD_LOGGING=1 but no GCP Client available, install via `pip install sunholo[gcp]` and/or authenticate")
228
+
229
+ if Client and os.environ.get('GOOGLE_CLOUD_LOGGING') == "1":
224
230
  # Instantiate the GoogleCloudLogging class
225
231
  gc_logger = GoogleCloudLogging(project_id, log_level=log_level, logger_name=logger_name)
226
232
  else:
227
- print("Could not find GoogleCloudLogging Client, install via `pip install sunholo[gcp]`")
228
233
  return logger
229
234
 
230
235
  # Setup logging and return the logger instance
sunholo/utils/config.py CHANGED
@@ -16,6 +16,7 @@ import os
16
16
  import json
17
17
  import yaml
18
18
  from datetime import datetime, timedelta
19
+ from collections import defaultdict
19
20
 
20
21
  try:
21
22
  from google.cloud import storage
@@ -95,6 +96,58 @@ def get_module_filepath(filepath: str):
95
96
 
96
97
  # Global cache
97
98
  config_cache = {}
99
+ def load_all_configs():
100
+ """
101
+ Load all configuration files from the specified directory into a dictionary.
102
+ Files are expected to be either YAML or JSON and must contain a 'kind' key at the root.
103
+ Caching is used to avoid reloading files within a 5-minute window.
104
+ """
105
+ from ..logging import log
106
+
107
+ config_folder = os.getenv("_CONFIG_FOLDER", os.getcwd())
108
+ log.debug(f"Loading all configs from folder: {config_folder}")
109
+ current_time = datetime.now()
110
+
111
+ configs_by_kind = defaultdict(dict)
112
+ for filename in os.listdir(config_folder):
113
+ if filename.endswith(('.yaml', '.yml', '.json')):
114
+ config_file = os.path.join(config_folder, filename)
115
+
116
+ # Check cache first
117
+ if filename in config_cache:
118
+ cached_config, cache_time = config_cache[filename]
119
+ if (current_time - cache_time) < timedelta(minutes=5):
120
+ log.debug(f"Returning cached config for {filename}")
121
+ config = cached_config
122
+ else:
123
+ config = reload_config_file(config_file, filename)
124
+ else:
125
+ config = reload_config_file(config_file, filename)
126
+
127
+ kind = config.get('kind')
128
+ if kind:
129
+ configs_by_kind[kind] = config
130
+ else:
131
+ log.warning(f"No 'kind' found in {filename}")
132
+
133
+ return configs_by_kind
134
+
135
+ def reload_config_file(config_file, filename):
136
+ """
137
+ Helper function to load a config file and update the cache.
138
+ """
139
+ from ..logging import log
140
+ with open(config_file, 'r') as file:
141
+ if filename.endswith('.json'):
142
+ config = json.load(file)
143
+ else:
144
+ config = yaml.safe_load(file)
145
+
146
+ config_cache[filename] = (config, datetime.now())
147
+ log.debug(f"Loaded and cached {filename}")
148
+ return config
149
+
150
+
98
151
 
99
152
  def load_config(filename: str=None) -> tuple[dict, str]:
100
153
  """
@@ -156,14 +209,15 @@ def load_config(filename: str=None) -> tuple[dict, str]:
156
209
 
157
210
  return config, filename
158
211
 
159
- def load_config_key(key: str, vector_name: str, filename: str=None):
212
+ def load_config_key(key: str, vector_name: str, filename: str=None, kind: str=None):
160
213
  """
161
214
  Load a specific key from a configuration file.
162
215
 
163
216
  Args:
164
217
  key (str): The key to fetch from the configuration.
165
218
  vector_name (str): The name of the vector in the configuration file.
166
- filename (str, optional): The configuration file name. Defaults to the `_CONFIG_FILE` environment variable.
219
+ filename (str, optional): The configuration file name. Defaults to the `_CONFIG_FILE` environment variable. Deprecated - use 'kind' instead
220
+ kind: (str, optional): Specify the type of configuration to retrieve e.g. 'vacConfig' which will pick from files within `_CONFIG_FOLDER`
167
221
 
168
222
  Returns:
169
223
  str: The value associated with the specified key.
@@ -179,7 +233,13 @@ def load_config_key(key: str, vector_name: str, filename: str=None):
179
233
  assert isinstance(key, str), f"key must be a string got a {type(key)}"
180
234
  assert isinstance(vector_name, str), f"vector_name must be a string, got a {type(vector_name)}"
181
235
 
182
- config, filename = load_config(filename)
236
+ configs_by_kind = load_all_configs()
237
+ if kind and configs_by_kind.get(kind):
238
+ config = configs_by_kind[kind]
239
+ filename = kind
240
+ else:
241
+ config, filename = load_config(filename)
242
+
183
243
  log.info(f"Fetching {key} for {vector_name}")
184
244
  apiVersion = config.get('apiVersion')
185
245
  kind = config.get('kind')
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.55.15
3
+ Version: 0.56.1
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.55.15.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.56.1.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -30,6 +30,7 @@ Requires-Dist: google-cloud-alloydb-connector[pg8000] ; extra == 'all'
30
30
  Requires-Dist: google-cloud-logging ; extra == 'all'
31
31
  Requires-Dist: google-cloud-storage ; extra == 'all'
32
32
  Requires-Dist: google-cloud-pubsub ; extra == 'all'
33
+ Requires-Dist: gunicorn ; extra == 'all'
33
34
  Requires-Dist: httpcore ; extra == 'all'
34
35
  Requires-Dist: httpx ; extra == 'all'
35
36
  Requires-Dist: lancedb ; extra == 'all'
@@ -61,11 +62,12 @@ Requires-Dist: langchain-google-alloydb-pg ; extra == 'gcp'
61
62
  Requires-Dist: google-api-python-client ; extra == 'gcp'
62
63
  Requires-Dist: google-cloud-alloydb-connector[pg8000] ; extra == 'gcp'
63
64
  Provides-Extra: http
64
- Requires-Dist: langfuse ; extra == 'http'
65
65
  Requires-Dist: fastapi ; extra == 'http'
66
66
  Requires-Dist: flask ; extra == 'http'
67
+ Requires-Dist: gunicorn ; extra == 'http'
67
68
  Requires-Dist: httpcore ; extra == 'http'
68
69
  Requires-Dist: httpx ; extra == 'http'
70
+ Requires-Dist: langfuse ; extra == 'http'
69
71
  Requires-Dist: python-socketio ; extra == 'http'
70
72
  Provides-Extra: openai
71
73
  Requires-Dist: langchain-openai ; extra == 'openai'
@@ -1,5 +1,5 @@
1
1
  sunholo/__init__.py,sha256=hEN0kOEikUGBot_YIMra1nnPNmcwmO_kwvPOmLW8-_8,692
2
- sunholo/logging.py,sha256=z0aI0u1Bk_be3c6EDCGf0hn4JZKymYqIySbfCKOqpkQ,10881
2
+ sunholo/logging.py,sha256=wvaT33baueD2VV5oP84duhwKKpH4os_3LtqZVWaGPIw,11226
3
3
  sunholo/agents/__init__.py,sha256=CnlbVohPt-Doth9PyROSlN3P8xMV9j9yS19YE-wCS90,341
4
4
  sunholo/agents/chat_history.py,sha256=PbwYmw1TwzI8H-cwQIGgHZ6UIr2Qb-JWow0RG3ayLM8,5195
5
5
  sunholo/agents/dispatch_to_qa.py,sha256=kWrO-CJel5kJAyyCShShpACUuZpqDOP7DN8vo_7ciao,8056
@@ -13,7 +13,7 @@ sunholo/agents/fastapi/base.py,sha256=clk76cHbUAvU0OYJrRfCWX_5f0ACbhDsIzYBhI3wyo
13
13
  sunholo/agents/fastapi/qna_routes.py,sha256=Xl05L8DjUozGMK7kKPqz7Twr4W56BnLQu_uprsTv_q0,4075
14
14
  sunholo/agents/flask/__init__.py,sha256=uqfHNw2Ru3EJ4dJEcbp86h_lkquBQPMxZbjhV_xe3rs,72
15
15
  sunholo/agents/flask/base.py,sha256=RUGWBYWeV60FatYF5sMRrxD-INU97Vodsi6JaB6i93s,763
16
- sunholo/agents/flask/qna_routes.py,sha256=A_L9w966Pb_MZSh17gcu5HxmzJdiclKaKTZ0ARaSRjg,8785
16
+ sunholo/agents/flask/qna_routes.py,sha256=hjUwW3-HEpE0RE2OxtnFJc_CAuzRexJxFSRXEAnFUVo,8426
17
17
  sunholo/archive/__init__.py,sha256=qNHWm5rGPVOlxZBZCpA1wTYPbalizRT7f8X4rs2t290,31
18
18
  sunholo/archive/archive.py,sha256=C-UhG5x-XtZ8VheQp92IYJqgD0V3NFQjniqlit94t18,1197
19
19
  sunholo/auth/__init__.py,sha256=4owDjSaWYkbTlPK47UHTOC0gCWbZsqn4ZIEw5NWZTlg,28
@@ -30,8 +30,10 @@ sunholo/chunker/message_data.py,sha256=iDP94dySU3Xct-gWGnB4NNRSh2luQmgJeCfQb7ktt
30
30
  sunholo/chunker/pdfs.py,sha256=daCZ1xjn1YvxlifIyxskWNpLJLe-Q9D_Jq12MWx3tZo,2473
31
31
  sunholo/chunker/publish.py,sha256=PoT8q3XJeFCg10WrLkYhuaaXIrGVkvUD3-R9IfoWoH4,2703
32
32
  sunholo/chunker/splitter.py,sha256=CZ33xVWeYdjckd1VTrZnxuLypzzn-yKXQBFZaN7UcjI,6697
33
+ sunholo/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ sunholo/cli/cli.py,sha256=i6L729GLkxcYNGFtujYxrkoILUxMvNiHmv_-Zlo7nAY,304
33
35
  sunholo/components/__init__.py,sha256=RJGNEihwvRIiDScKis04RHJv4yZGI1UpXlOmuCptNZI,208
34
- sunholo/components/llm.py,sha256=CMviwqnEOyCSdrVlBgXFUKF5EPyEK9oqFA7uThPK-O0,10641
36
+ sunholo/components/llm.py,sha256=clZktcwEOwZwHE0Bl8hDRt5aDhOOEgCi1STcW0PBxc8,10430
35
37
  sunholo/components/prompt.py,sha256=eZSghXkIlRzXiSrzgkG7e5ytUYq6R6LV-qjHU8jStig,6353
36
38
  sunholo/components/retriever.py,sha256=TiM-axCeaZ6CZ8rGKGx-io16JKDe8z0pnMccBi1yqHw,3509
37
39
  sunholo/components/vectorstore.py,sha256=J5zzW7Acc7A4W6dGnYTYDxST3p6W4OtckXUUwAEeaqE,4941
@@ -78,11 +80,12 @@ sunholo/streaming/streaming.py,sha256=0Bgl_FYmd2GJuy_4khDx5y_nfPqDp7Yu8ZkQ9POVhG
78
80
  sunholo/summarise/__init__.py,sha256=MZk3dblUMODcPb1crq4v-Z508NrFIpkSWNf9FIO8BcU,38
79
81
  sunholo/summarise/summarise.py,sha256=C3HhjepTjUhUC8FLk4jMQIBvq1BcORniwuTFHjPVhVo,3784
80
82
  sunholo/utils/__init__.py,sha256=MxuxoJ-oOie_skGnB4mOagVYjzvfmX9Gz9N5heI8azM,62
81
- sunholo/utils/config.py,sha256=KYuhwmhYS7qOpt2ZcpAWnlHPJ9rBDX1SgP8gnjkAW6E,7321
83
+ sunholo/utils/config.py,sha256=qkcK1T2AvobmkTp8D-SNXLBxe86TKCuquA_5TkrVfk8,9589
82
84
  sunholo/utils/gcp.py,sha256=B2G1YKjeD7X9dqO86Jrp2vPuFwZ223Xl5Tg09Ndw-oc,5760
83
85
  sunholo/utils/parsers.py,sha256=E-M7s3_rPviT5zCqQHzhb1DwYmz5a1J472ZVazx10M8,3400
84
- sunholo-0.55.15.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
85
- sunholo-0.55.15.dist-info/METADATA,sha256=rCF_1oWdOx8Zvn9HZA3E5JZKZXBSvM05F1tX8Q1jheA,6434
86
- sunholo-0.55.15.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
87
- sunholo-0.55.15.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
88
- sunholo-0.55.15.dist-info/RECORD,,
86
+ sunholo-0.56.1.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
87
+ sunholo-0.56.1.dist-info/METADATA,sha256=2uvcoErVkLNHebBWGCkxsR20evWPXStpQwxOdiIHCt4,6515
88
+ sunholo-0.56.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
89
+ sunholo-0.56.1.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
90
+ sunholo-0.56.1.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
91
+ sunholo-0.56.1.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sunholo = sunholo.cli.cli:main