sunholo 0.63.0__py3-none-any.whl → 0.64.4__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.
@@ -16,6 +16,7 @@
16
16
  import json
17
17
  import traceback
18
18
  import datetime
19
+ import uuid
19
20
 
20
21
  from ...agents import extract_chat_history, handle_special_commands
21
22
  from ...qna.parsers import parse_output
@@ -23,7 +24,11 @@ from ...streaming import start_streaming_chat
23
24
  from ...archive import archive_qa
24
25
  from ...logging import log
25
26
  from ...utils.config import load_config
26
-
27
+ from ...utils.version import sunholo_version
28
+ import os
29
+ from ...gcs.add_file import add_file_to_gcs
30
+
31
+
27
32
  try:
28
33
  from flask import request, jsonify, Response
29
34
  except ImportError:
@@ -34,6 +39,7 @@ try:
34
39
  except ImportError:
35
40
  pass
36
41
 
42
+
37
43
  def register_qna_routes(app, stream_interpreter, vac_interpreter):
38
44
 
39
45
  @app.route("/")
@@ -155,9 +161,122 @@ def register_qna_routes(app, stream_interpreter, vac_interpreter):
155
161
  span.end(output=jsonify(bot_output))
156
162
  trace.update(output=jsonify(bot_output))
157
163
 
164
+ # {'answer': 'output'}
158
165
  return jsonify(bot_output)
159
166
 
160
- # Any other QNA related routes can be added here
167
+ @app.route('/openai/v1/chat/completions', methods=['POST'])
168
+ @app.route('/openai/v1/chat/completions/<vector_name>', methods=['POST'])
169
+ def openai_compatible_endpoint(vector_name=None):
170
+ data = request.get_json()
171
+ log.info(f'openai_compatible_endpoint got data: {data}')
172
+
173
+ vector_name = vector_name or data.pop('model', None)
174
+ messages = data.pop('messages', None)
175
+ chat_history = data.pop('chat_history', None)
176
+ stream = data.pop('stream', False)
177
+
178
+ log.info(f'openai_compatible_endpoint got data: {data} for vector: {vector_name}')
179
+
180
+ if not messages:
181
+ return jsonify({"error": "No messages provided"}), 400
182
+
183
+ user_message = next((msg['content'] for msg in messages if msg['role'] == 'user'), None)
184
+ if not user_message:
185
+ return jsonify({"error": "No user message provided"}), 400
186
+
187
+ all_input = {
188
+ "user_input": user_message,
189
+ "chat_history": chat_history,
190
+ "kwargs": data
191
+ }
192
+
193
+ observed_stream_interpreter = observe()(stream_interpreter)
194
+
195
+
196
+ response_id = str(uuid.uuid4())
197
+
198
+ def generate_response_content():
199
+ for chunk in start_streaming_chat(question=user_message,
200
+ vector_name=vector_name,
201
+ qna_func=observed_stream_interpreter,
202
+ chat_history=all_input["chat_history"],
203
+ wait_time=all_input.get("stream_wait_time", 1),
204
+ timeout=all_input.get("stream_timeout", 60),
205
+ **all_input["kwargs"]
206
+ ):
207
+ if isinstance(chunk, dict) and 'answer' in chunk:
208
+ openai_chunk = {
209
+ "id": response_id,
210
+ "object": "chat.completion.chunk",
211
+ "created": str(datetime.time()),
212
+ "model": vector_name,
213
+ "system_fingerprint": sunholo_version(),
214
+ "choices": [{
215
+ "index": 0,
216
+ "delta": {"content": chunk['answer']},
217
+ "logprobs": None,
218
+ "finish_reason": None
219
+ }]
220
+ }
221
+ yield json.dumps(openai_chunk) + "\n"
222
+ else:
223
+ log.info(f"Unknown chunk: {chunk}")
224
+
225
+ final_chunk = {
226
+ "id": response_id,
227
+ "object": "chat.completion.chunk",
228
+ "created": str(datetime.time()),
229
+ "model": vector_name,
230
+ "system_fingerprint": sunholo_version(),
231
+ "choices": [{
232
+ "index": 0,
233
+ "delta": {},
234
+ "logprobs": None,
235
+ "finish_reason": "stop"
236
+ }]
237
+ }
238
+ yield json.dumps(final_chunk) + "\n"
239
+
240
+ if stream:
241
+ return Response(generate_response_content(), content_type='text/plain; charset=utf-8')
242
+
243
+ try:
244
+ bot_output = observed_stream_interpreter(
245
+ question=user_message,
246
+ vector_name=vector_name,
247
+ chat_history=all_input["chat_history"],
248
+ **all_input["kwargs"]
249
+ )
250
+ bot_output = parse_output(bot_output)
251
+
252
+ openai_response = {
253
+ "id": response_id,
254
+ "object": "chat.completion",
255
+ "created": str(datetime.time()),
256
+ "model": vector_name,
257
+ "system_fingerprint": sunholo_version(),
258
+ "choices": [{
259
+ "index": 0,
260
+ "message": {
261
+ "role": "assistant",
262
+ "content": bot_output.get('answer', ''),
263
+ },
264
+ "logprobs": None,
265
+ "finish_reason": "stop"
266
+ }],
267
+ "usage": {
268
+ "prompt_tokens": len(user_message.split()),
269
+ "completion_tokens": len(bot_output.get('answer', '').split()),
270
+ "total_tokens": len(user_message.split()) + len(bot_output.get('answer', '').split())
271
+ }
272
+ }
273
+
274
+ log.info(f"OpenAI response: {openai_response}")
275
+ return jsonify(openai_response)
276
+
277
+ except Exception as err:
278
+ return jsonify({'error': f'QNA_ERROR: An error occurred: {str(err)} traceback: {traceback.format_exc()}'}), 500
279
+
161
280
 
162
281
  def create_langfuse_trace(request, vector_name):
163
282
  try:
@@ -172,10 +291,8 @@ def create_langfuse_trace(request, vector_name):
172
291
  session_id = request.headers.get("X-Session-ID")
173
292
  message_source = request.headers.get("X-Message-Source")
174
293
 
175
- # can't import tags yet via CallbackHandler
176
- from importlib.metadata import version
177
- package_version = version('sunholo')
178
- tags = [f"sunholo-v{package_version}"]
294
+ package_version = sunholo_version()
295
+ tags = [package_version]
179
296
  if message_source:
180
297
  tags.append(message_source)
181
298
 
@@ -191,8 +308,28 @@ def prep_vac(request, vector_name):
191
308
  #trace = create_langfuse_trace(request, vector_name)
192
309
  trace = None
193
310
  span = None
194
- data = request.get_json()
311
+
312
+ if request.content_type.startswith('application/json'):
313
+ data = request.get_json()
314
+ elif request.content_type.startswith('multipart/form-data'):
315
+ data = request.form.to_dict()
316
+ if 'file' in request.files:
317
+ file = request.files['file']
318
+ if file.filename != '':
319
+ log.info(f"Found file: {file.filename} to upload to GCS")
320
+ try:
321
+ image_uri, mime_type = handle_file_upload(file, vector_name)
322
+ data["image_uri"] = image_uri
323
+ data["mime"] = mime_type
324
+ except Exception as e:
325
+ return jsonify({'error': str(e), 'traceback': traceback.format_exc()}), 500
326
+ else:
327
+ return jsonify({"error": "No file selected"}), 400
328
+ else:
329
+ return jsonify({"error": "Unsupported content type"}), 400
330
+
195
331
  log.info(f"vac/{vector_name} got data: {data}")
332
+
196
333
  config, _ = load_config("config/llm_config.yaml")
197
334
  vac_configs = config.get("vac")
198
335
  if vac_configs:
@@ -213,7 +350,7 @@ def prep_vac(request, vector_name):
213
350
  'vector_name': vector_name,
214
351
  'chat_history': paired_messages,
215
352
  'stream_wait_time': stream_wait_time,
216
- 'stream_timeout':stream_timeout,
353
+ 'stream_timeout': stream_timeout,
217
354
  'kwargs': data}
218
355
 
219
356
  if trace:
@@ -233,4 +370,14 @@ def prep_vac(request, vector_name):
233
370
  "command_response": command_response,
234
371
  "all_input": all_input,
235
372
  "vac_config": vac_config
236
- }
373
+ }
374
+
375
+
376
+ def handle_file_upload(file, vector_name):
377
+ try:
378
+ file.save(file.filename)
379
+ image_uri = add_file_to_gcs(file.filename, vector_name)
380
+ os.remove(file.filename) # Clean up the saved file
381
+ return image_uri, file.mimetype
382
+ except Exception as e:
383
+ raise Exception(f'File upload failed: {str(e)}')
@@ -7,6 +7,8 @@ try:
7
7
  except ImportError:
8
8
  CallbackHandler = None
9
9
 
10
+ from ..utils.version import sunholo_version
11
+
10
12
  def create_langfuse_callback(**kwargs):
11
13
 
12
14
  if not CallbackHandler:
@@ -40,10 +42,7 @@ def add_langfuse_tracing(
40
42
  session_id = request.headers.get("X-Session-ID")
41
43
  message_source = request.headers.get("X-Message-Source")
42
44
 
43
- # can't import tags yet via CallbackHandler
44
- from importlib.metadata import version
45
- package_version = version('sunholo')
46
- tags = [f"sunholo-v{package_version}"]
45
+ tags = [sunholo_version()]
47
46
  if message_source:
48
47
  tags.append(message_source)
49
48
 
@@ -0,0 +1,3 @@
1
+ def sunholo_version():
2
+ from importlib.metadata import version
3
+ return f"sunholo-{version('sunholo')}"
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.63.0
3
+ Version: 0.64.4
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.63.0.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.64.4.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
21
  Requires-Dist: google-auth
22
+ Requires-Dist: tenacity ==8.3.0
22
23
  Requires-Dist: langchain
23
24
  Requires-Dist: langchain-experimental
24
25
  Requires-Dist: langchain-community
@@ -12,7 +12,7 @@ sunholo/agents/fastapi/base.py,sha256=clk76cHbUAvU0OYJrRfCWX_5f0ACbhDsIzYBhI3wyo
12
12
  sunholo/agents/fastapi/qna_routes.py,sha256=DgK4Btu5XriOC1JaRQ4G_nWEjJfnQ0J5pyLanF6eF1g,3857
13
13
  sunholo/agents/flask/__init__.py,sha256=uqfHNw2Ru3EJ4dJEcbp86h_lkquBQPMxZbjhV_xe3rs,72
14
14
  sunholo/agents/flask/base.py,sha256=FgSaCODyoTtlstJtsqlLPScdgRUtv9_plxftdzHdVFo,809
15
- sunholo/agents/flask/qna_routes.py,sha256=Twx6bitUAQ8Urtuwu9PtdoxYv-3aBDbF4cOUl9kdas8,8652
15
+ sunholo/agents/flask/qna_routes.py,sha256=yskVD7bP_MAvWipZcdsdNyvh1WVlYr9VqbokfV09pYM,14513
16
16
  sunholo/archive/__init__.py,sha256=qNHWm5rGPVOlxZBZCpA1wTYPbalizRT7f8X4rs2t290,31
17
17
  sunholo/archive/archive.py,sha256=C-UhG5x-XtZ8VheQp92IYJqgD0V3NFQjniqlit94t18,1197
18
18
  sunholo/auth/__init__.py,sha256=4owDjSaWYkbTlPK47UHTOC0gCWbZsqn4ZIEw5NWZTlg,28
@@ -63,7 +63,7 @@ sunholo/gcs/add_file.py,sha256=QBzPnl5HVZ2Z-rWmBua7gJHBnLAizgXkObjC3dTbccs,4958
63
63
  sunholo/gcs/download_url.py,sha256=8XSEf8byfubqs5CMQeF_tn9wxqwUTq3n9mo5mLNIUTA,4801
64
64
  sunholo/gcs/metadata.py,sha256=C9sMPsHsq1ETetdQCqB3EBs3Kws8b8QHS9L7ei_v5aw,891
65
65
  sunholo/langfuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
- sunholo/langfuse/callback.py,sha256=G9xcZHpLvyzolU57ycItLaooMCtRuM37QJSWjiwQEd0,1776
66
+ sunholo/langfuse/callback.py,sha256=CTaos8sYcrga949BG6lIZ4I62DiiQSHxwz5re9XjDWQ,1677
67
67
  sunholo/langfuse/prompts.py,sha256=HO4Zy9usn5tKooBPCKksuw4Lff3c03Ny5wqn4ce_xZM,1217
68
68
  sunholo/llamaindex/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
69
  sunholo/llamaindex/generate.py,sha256=l1Picr-hVwkmAUD7XmTCa63qY9ERliFHQXwyX3BqB2Q,686
@@ -97,13 +97,14 @@ sunholo/utils/gcp_project.py,sha256=0ozs6tzI4qEvEeXb8MxLnCdEVoWKxlM6OH05htj7_tc,
97
97
  sunholo/utils/parsers.py,sha256=OrHmASqIbI45atVOhiGodgLvnfrzkvVzyHnSvAXD89I,3841
98
98
  sunholo/utils/timedelta.py,sha256=BbLabEx7_rbErj_YbNM0MBcaFN76DC4PTe4zD2ucezg,493
99
99
  sunholo/utils/user_ids.py,sha256=SQd5_H7FE7vcTZp9AQuQDWBXd4FEEd7TeVMQe1H4Ny8,292
100
+ sunholo/utils/version.py,sha256=jjU_4anXBikJxPg0Wur0X-B7-ec1tC7jToykAnAG9Dg,108
100
101
  sunholo/vertex/__init__.py,sha256=JvHcGFuv6R_nAhY2AdoqqhMpJ5ugeWPZ_svGhWrObBk,136
101
102
  sunholo/vertex/init.py,sha256=JDMUaBRdednzbKF-5p33qqLit2LMsvgvWW-NRz0AqO0,1801
102
103
  sunholo/vertex/memory_tools.py,sha256=8F1iTWnqEK9mX4W5RzCVKIjydIcNp6OFxjn_dtQ3GXo,5379
103
104
  sunholo/vertex/safety.py,sha256=3meAX0HyGZYrH7rXPUAHxtI_3w_zoy_RX7Shtkoa660,1275
104
- sunholo-0.63.0.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
105
- sunholo-0.63.0.dist-info/METADATA,sha256=0jHRQDDREVfY0MVPf6CZe6wZ_kiSePMGUg5li128Yf8,5939
106
- sunholo-0.63.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
107
- sunholo-0.63.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
108
- sunholo-0.63.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
109
- sunholo-0.63.0.dist-info/RECORD,,
105
+ sunholo-0.64.4.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
106
+ sunholo-0.64.4.dist-info/METADATA,sha256=-vzVQ820NsKlAjLeentAjVDxSWvjKIH5y4Up5Wi1vtU,5971
107
+ sunholo-0.64.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
108
+ sunholo-0.64.4.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
109
+ sunholo-0.64.4.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
110
+ sunholo-0.64.4.dist-info/RECORD,,