sunholo 0.62.18__tar.gz → 0.64.3__tar.gz

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.
Files changed (121) hide show
  1. {sunholo-0.62.18/sunholo.egg-info → sunholo-0.64.3}/PKG-INFO +10 -42
  2. sunholo-0.64.3/README.md +37 -0
  3. {sunholo-0.62.18 → sunholo-0.64.3}/setup.py +2 -1
  4. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/flask/qna_routes.py +148 -9
  5. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/auth/run.py +19 -22
  6. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/chat_vac.py +62 -36
  7. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/langfuse/callback.py +3 -4
  8. sunholo-0.64.3/sunholo/utils/version.py +3 -0
  9. {sunholo-0.62.18 → sunholo-0.64.3/sunholo.egg-info}/PKG-INFO +10 -42
  10. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo.egg-info/SOURCES.txt +2 -0
  11. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo.egg-info/requires.txt +1 -0
  12. sunholo-0.64.3/tests/test_chunker.py +23 -0
  13. {sunholo-0.62.18 → sunholo-0.64.3}/tests/test_dispatch_to_qa.py +7 -1
  14. sunholo-0.62.18/README.md +0 -70
  15. {sunholo-0.62.18 → sunholo-0.64.3}/LICENSE.txt +0 -0
  16. {sunholo-0.62.18 → sunholo-0.64.3}/MANIFEST.in +0 -0
  17. {sunholo-0.62.18 → sunholo-0.64.3}/setup.cfg +0 -0
  18. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/__init__.py +0 -0
  19. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/__init__.py +0 -0
  20. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/chat_history.py +0 -0
  21. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/dispatch_to_qa.py +0 -0
  22. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/fastapi/__init__.py +0 -0
  23. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/fastapi/base.py +0 -0
  24. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/fastapi/qna_routes.py +0 -0
  25. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/flask/__init__.py +0 -0
  26. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/flask/base.py +0 -0
  27. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/langserve.py +0 -0
  28. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/pubsub.py +0 -0
  29. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/route.py +0 -0
  30. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/agents/special_commands.py +0 -0
  31. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/archive/__init__.py +0 -0
  32. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/archive/archive.py +0 -0
  33. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/auth/__init__.py +0 -0
  34. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/bots/__init__.py +0 -0
  35. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/bots/discord.py +0 -0
  36. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/bots/github_webhook.py +0 -0
  37. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/bots/webapp.py +0 -0
  38. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/__init__.py +0 -0
  39. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/data_to_embed_pubsub.py +0 -0
  40. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/doc_handling.py +0 -0
  41. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/images.py +0 -0
  42. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/loaders.py +0 -0
  43. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/message_data.py +0 -0
  44. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/pdfs.py +0 -0
  45. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/publish.py +0 -0
  46. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/chunker/splitter.py +0 -0
  47. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/__init__.py +0 -0
  48. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/cli.py +0 -0
  49. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/cli_init.py +0 -0
  50. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/configs.py +0 -0
  51. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/deploy.py +0 -0
  52. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/embedder.py +0 -0
  53. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/merge_texts.py +0 -0
  54. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/run_proxy.py +0 -0
  55. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/cli/sun_rich.py +0 -0
  56. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/components/__init__.py +0 -0
  57. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/components/llm.py +0 -0
  58. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/components/retriever.py +0 -0
  59. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/components/vectorstore.py +0 -0
  60. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/__init__.py +0 -0
  61. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/alloydb.py +0 -0
  62. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/database.py +0 -0
  63. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/lancedb.py +0 -0
  64. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/sql/sb/create_function.sql +0 -0
  65. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/sql/sb/create_function_time.sql +0 -0
  66. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/sql/sb/create_table.sql +0 -0
  67. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
  68. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/sql/sb/return_sources.sql +0 -0
  69. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/sql/sb/setup.sql +0 -0
  70. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/static_dbs.py +0 -0
  71. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/database/uuid.py +0 -0
  72. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/embedder/__init__.py +0 -0
  73. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/embedder/embed_chunk.py +0 -0
  74. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/gcs/__init__.py +0 -0
  75. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/gcs/add_file.py +0 -0
  76. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/gcs/download_url.py +0 -0
  77. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/gcs/metadata.py +0 -0
  78. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/langfuse/__init__.py +0 -0
  79. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/langfuse/prompts.py +0 -0
  80. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/llamaindex/__init__.py +0 -0
  81. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/llamaindex/generate.py +0 -0
  82. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/llamaindex/get_files.py +0 -0
  83. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/llamaindex/import_files.py +0 -0
  84. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/logging.py +0 -0
  85. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/lookup/__init__.py +0 -0
  86. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/lookup/model_lookup.yaml +0 -0
  87. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/patches/__init__.py +0 -0
  88. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/patches/langchain/__init__.py +0 -0
  89. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/patches/langchain/lancedb.py +0 -0
  90. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/patches/langchain/vertexai.py +0 -0
  91. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/pubsub/__init__.py +0 -0
  92. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/pubsub/process_pubsub.py +0 -0
  93. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/pubsub/pubsub_manager.py +0 -0
  94. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/qna/__init__.py +0 -0
  95. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/qna/parsers.py +0 -0
  96. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/qna/retry.py +0 -0
  97. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/streaming/__init__.py +0 -0
  98. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/streaming/content_buffer.py +0 -0
  99. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/streaming/langserve.py +0 -0
  100. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/streaming/stream_lookup.py +0 -0
  101. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/streaming/streaming.py +0 -0
  102. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/summarise/__init__.py +0 -0
  103. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/summarise/summarise.py +0 -0
  104. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/__init__.py +0 -0
  105. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/big_context.py +0 -0
  106. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/config.py +0 -0
  107. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/config_schema.py +0 -0
  108. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/gcp.py +0 -0
  109. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/gcp_project.py +0 -0
  110. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/parsers.py +0 -0
  111. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/timedelta.py +0 -0
  112. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/utils/user_ids.py +0 -0
  113. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/vertex/__init__.py +0 -0
  114. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/vertex/init.py +0 -0
  115. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/vertex/memory_tools.py +0 -0
  116. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo/vertex/safety.py +0 -0
  117. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo.egg-info/dependency_links.txt +0 -0
  118. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo.egg-info/entry_points.txt +0 -0
  119. {sunholo-0.62.18 → sunholo-0.64.3}/sunholo.egg-info/top_level.txt +0 -0
  120. {sunholo-0.62.18 → sunholo-0.64.3}/tests/test_chat_history.py +0 -0
  121. {sunholo-0.62.18 → sunholo-0.64.3}/tests/test_config.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.62.18
3
+ Version: 0.64.3
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.62.18.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.64.3.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
@@ -111,54 +112,21 @@ This is the Sunholo Python project, a comprehensive toolkit for working with lan
111
112
 
112
113
  Please refer to the website for full documentation at https://dev.sunholo.com/
113
114
 
114
- # sunholo-py
115
-
116
- (draft release https://pypi.org/project/sunholo/ )
117
- ## Table of Contents
118
- - [Agents](#agents)
119
- - [Archive](#archive)
120
- - [Bots](#bots)
121
- - [Chunker](#chunker)
122
- - [Components](#components)
123
- - [Database](#database)
124
- - [Embedder](#embedder)
125
- - [PubSub](#pubsub)
126
- - [QnA](#qna)
127
- - [Streaming](#streaming)
128
- - [Summarise](#summarise)
129
- - [Utils](#utils)
115
+ ## Demos
130
116
 
117
+ Using https://github.com/charmbracelet/vhs
131
118
 
132
119
  ```sh
133
- pip install sunholo
120
+ vhs record > cassette.tape
134
121
  ```
135
122
 
136
- A python library to enable LLMOps within cloud environments
137
-
138
- `sunholo` provides utilities to help manage LLM operations on Google Cloud Platform at first, but it is hoped that making it open source will help it support other clouds in the future. A lot of the functionality is not Google Cloud Platform specific, so still may be helpful.
139
-
140
- It is derived from the Edmonbrain project, the original blog post you can read here: https://code.markedmondson.me/running-llms-on-gcp/ and owes a lot to Langchain ( https://github.com/langchain-ai/langchain )
141
-
142
- The package includes:
123
+ Then make gif:
143
124
 
144
- * `agents/` - functions for working with agents, including easy flask apps, parsing chat history and dispatching requests to different agent endpoints
145
- * `archive/` - functions to record all Q&A activity to BigQuery via PubSub
146
- * `bots/` - functions for special cases regarding frontend bots such as GChat, Web Apps, Discord and Slack
147
- * `chunker/` - functions to slice up documents for sending into vector stores
148
- * `components/` - functions to help configure which LLM, prompt, vectorstore or document retriever you will use based on a yaml config file
149
- * `database/` - database setup functions and SQL to run on those sources such as Supabase
150
- * `embedder/` - functions to send chunks into embedding vector stores
151
- * `pubsub/` - use of PubSub for a message queue between components
152
- * `qna/` - utilities for running agents such as retry strats and parsing of output/input
153
- * `streaming/` - creation of streaming responses from LLM bots
154
- * `summarise/` - creation of summaries of large documents
155
- * `utils/` - reading configuration files, Google Cloud Platform metadata
156
-
157
- ## Configuration
125
+ ```sh
126
+ vhs docs/tapes/config-list.tape
127
+ ```
158
128
 
159
- The library uses the config specifications that some examples are given in the `config/` folder.
160
129
 
161
- When using the functions, make sure to have the `config/` folder in the root of where your application is running (usually `$HOME/config`)
162
130
 
163
131
  ```
164
132
  Copyright [2024] [Holosun ApS]
@@ -0,0 +1,37 @@
1
+ ## Introduction
2
+ 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.
3
+
4
+ Please refer to the website for full documentation at https://dev.sunholo.com/
5
+
6
+ ## Demos
7
+
8
+ Using https://github.com/charmbracelet/vhs
9
+
10
+ ```sh
11
+ vhs record > cassette.tape
12
+ ```
13
+
14
+ Then make gif:
15
+
16
+ ```sh
17
+ vhs docs/tapes/config-list.tape
18
+ ```
19
+
20
+
21
+
22
+ ```
23
+ Copyright [2024] [Holosun ApS]
24
+
25
+ Licensed under the Apache License, Version 2.0 (the "License");
26
+ you may not use this file except in compliance with the License.
27
+ You may obtain a copy of the License at
28
+
29
+ http://www.apache.org/licenses/LICENSE-2.0
30
+
31
+ Unless required by applicable law or agreed to in writing, software
32
+ distributed under the License is distributed on an "AS IS" BASIS,
33
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34
+ See the License for the specific language governing permissions and
35
+ limitations under the License.
36
+ ```
37
+
@@ -1,7 +1,7 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
3
  # Define your base version
4
- version = '0.62.18'
4
+ version = '0.64.3'
5
5
 
6
6
  setup(
7
7
  name='sunholo',
@@ -29,6 +29,7 @@ setup(
29
29
  install_requires=[
30
30
  # Base dependencies
31
31
  "google-auth", # to check if on gcp
32
+ "tenacity==8.3.0",
32
33
  "langchain",
33
34
  "langchain_experimental",
34
35
  "langchain-community",
@@ -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,114 @@ 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
+ def openai_compatible_endpoint():
169
+ data = request.get_json()
170
+ model = data.pop('model', None)
171
+ messages = data.pop('messages', None)
172
+ chat_history = data.pop('chat_history', None)
173
+ stream = data.pop('stream', False)
174
+
175
+ if not messages:
176
+ return jsonify({"error": "No messages provided"}), 400
177
+
178
+ user_message = next((msg['content'] for msg in messages if msg['role'] == 'user'), None)
179
+ if not user_message:
180
+ return jsonify({"error": "No user message provided"}), 400
181
+
182
+ all_input = {
183
+ "user_input": user_message,
184
+ "chat_history": chat_history,
185
+ "kwargs": data
186
+ }
187
+
188
+ observed_stream_interpreter = observe()(stream_interpreter)
189
+
190
+
191
+ response_id = str(uuid.uuid4())
192
+
193
+ def generate_response_content():
194
+ for chunk in start_streaming_chat(question=user_message,
195
+ vector_name=model,
196
+ qna_func=observed_stream_interpreter,
197
+ chat_history=all_input["chat_history"],
198
+ wait_time=all_input.get("stream_wait_time", 1),
199
+ timeout=all_input.get("stream_timeout", 60),
200
+ **all_input["kwargs"]
201
+ ):
202
+ if isinstance(chunk, dict) and 'content' in chunk:
203
+ openai_chunk = {
204
+ "id": response_id,
205
+ "object": "chat.completion.chunk",
206
+ "created": int(datetime.time()),
207
+ "model": model,
208
+ "system_fingerprint": sunholo_version(),
209
+ "choices": [{
210
+ "index": 0,
211
+ "delta": {"content": chunk['content']},
212
+ "logprobs": None,
213
+ "finish_reason": None
214
+ }]
215
+ }
216
+ yield json.dumps(openai_chunk) + "\n"
217
+
218
+ final_chunk = {
219
+ "id": response_id,
220
+ "object": "chat.completion.chunk",
221
+ "created": int(datetime.time()),
222
+ "model": model,
223
+ "system_fingerprint": sunholo_version(),
224
+ "choices": [{
225
+ "index": 0,
226
+ "delta": {},
227
+ "logprobs": None,
228
+ "finish_reason": "stop"
229
+ }]
230
+ }
231
+ yield json.dumps(final_chunk) + "\n"
232
+
233
+ if stream:
234
+ return Response(generate_response_content(), content_type='text/plain; charset=utf-8')
235
+
236
+ try:
237
+ bot_output = observed_stream_interpreter(
238
+ question=user_message,
239
+ vector_name=model,
240
+ chat_history=all_input["chat_history"],
241
+ **all_input["kwargs"]
242
+ )
243
+ bot_output = parse_output(bot_output)
244
+
245
+ openai_response = {
246
+ "id": response_id,
247
+ "object": "chat.completion",
248
+ "created": int(datetime.time()),
249
+ "model": model,
250
+ "system_fingerprint": sunholo_version(),
251
+ "choices": [{
252
+ "index": 0,
253
+ "message": {
254
+ "role": "assistant",
255
+ "content": bot_output.get('answer', ''),
256
+ },
257
+ "logprobs": None,
258
+ "finish_reason": "stop"
259
+ }],
260
+ "usage": {
261
+ "prompt_tokens": len(user_message.split()),
262
+ "completion_tokens": len(bot_output.get('answer', '').split()),
263
+ "total_tokens": len(user_message.split()) + len(bot_output.get('answer', '').split())
264
+ }
265
+ }
266
+
267
+ return jsonify(openai_response)
268
+
269
+ except Exception as err:
270
+ return jsonify({'error': f'QNA_ERROR: An error occurred: {str(err)} traceback: {traceback.format_exc()}'}), 500
271
+
161
272
 
162
273
  def create_langfuse_trace(request, vector_name):
163
274
  try:
@@ -172,10 +283,8 @@ def create_langfuse_trace(request, vector_name):
172
283
  session_id = request.headers.get("X-Session-ID")
173
284
  message_source = request.headers.get("X-Message-Source")
174
285
 
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}"]
286
+ package_version = sunholo_version()
287
+ tags = [package_version]
179
288
  if message_source:
180
289
  tags.append(message_source)
181
290
 
@@ -191,8 +300,28 @@ def prep_vac(request, vector_name):
191
300
  #trace = create_langfuse_trace(request, vector_name)
192
301
  trace = None
193
302
  span = None
194
- data = request.get_json()
303
+
304
+ if request.content_type.startswith('application/json'):
305
+ data = request.get_json()
306
+ elif request.content_type.startswith('multipart/form-data'):
307
+ data = request.form.to_dict()
308
+ if 'file' in request.files:
309
+ file = request.files['file']
310
+ if file.filename != '':
311
+ log.info(f"Found file: {file.filename} to upload to GCS")
312
+ try:
313
+ image_uri, mime_type = handle_file_upload(file, vector_name)
314
+ data["image_uri"] = image_uri
315
+ data["mime"] = mime_type
316
+ except Exception as e:
317
+ return jsonify({'error': str(e), 'traceback': traceback.format_exc()}), 500
318
+ else:
319
+ return jsonify({"error": "No file selected"}), 400
320
+ else:
321
+ return jsonify({"error": "Unsupported content type"}), 400
322
+
195
323
  log.info(f"vac/{vector_name} got data: {data}")
324
+
196
325
  config, _ = load_config("config/llm_config.yaml")
197
326
  vac_configs = config.get("vac")
198
327
  if vac_configs:
@@ -213,7 +342,7 @@ def prep_vac(request, vector_name):
213
342
  'vector_name': vector_name,
214
343
  'chat_history': paired_messages,
215
344
  'stream_wait_time': stream_wait_time,
216
- 'stream_timeout':stream_timeout,
345
+ 'stream_timeout': stream_timeout,
217
346
  'kwargs': data}
218
347
 
219
348
  if trace:
@@ -233,4 +362,14 @@ def prep_vac(request, vector_name):
233
362
  "command_response": command_response,
234
363
  "all_input": all_input,
235
364
  "vac_config": vac_config
236
- }
365
+ }
366
+
367
+
368
+ def handle_file_upload(file, vector_name):
369
+ try:
370
+ file.save(file.filename)
371
+ image_uri = add_file_to_gcs(file.filename, vector_name)
372
+ os.remove(file.filename) # Clean up the saved file
373
+ return image_uri, file.mimetype
374
+ except Exception as e:
375
+ raise Exception(f'File upload failed: {str(e)}')
@@ -53,26 +53,23 @@ def get_header(vector_name) -> Optional[dict]:
53
53
  else:
54
54
  run_url = "http://127.0.0.1:8080"
55
55
 
56
- if "http://" in run_url:
57
- return None
58
- else:
59
- # Append ID Token to make authenticated requests to Cloud Run services
60
- frame = inspect.currentframe()
61
- caller_frame = frame.f_back if frame is not None else None # One level up in the stack
56
+ # Append ID Token to make authenticated requests to Cloud Run services
57
+ frame = inspect.currentframe()
58
+ caller_frame = frame.f_back if frame is not None else None # One level up in the stack
59
+ deets = {
60
+ 'message': 'Authenticating for run_url',
61
+ 'run_url': run_url
62
+ }
63
+ if caller_frame:
62
64
  deets = {
63
- 'message': 'Authenticating for run_url',
64
- 'run_url': run_url
65
- }
66
- if caller_frame:
67
- deets = {
68
- 'message': 'Authenticating for run_url',
69
- 'file': caller_frame.f_code.co_filename,
70
- 'line': str(caller_frame.f_lineno),
71
- 'function': caller_frame.f_code.co_name,
72
- 'run_url': run_url
73
- }
74
- log.info(f"Authenticating for run_url {run_url} from {caller_frame.f_code.co_name}")
75
- id_token = get_id_token(run_url)
76
- headers = {"Authorization": f"Bearer {id_token}"}
77
- #log.info(f"id_token {id_token}")
78
- return headers
65
+ 'message': 'Authenticating for run_url',
66
+ 'file': caller_frame.f_code.co_filename,
67
+ 'line': str(caller_frame.f_lineno),
68
+ 'function': caller_frame.f_code.co_name,
69
+ 'run_url': run_url
70
+ }
71
+ log.info(f"Authenticating for run_url {run_url} from {caller_frame.f_code.co_name}")
72
+ id_token = get_id_token(run_url)
73
+ headers = {"Authorization": f"Bearer {id_token}"}
74
+ #log.info(f"id_token {id_token}")
75
+ return headers
@@ -149,46 +149,72 @@ def headless_mode(service_url, service_name, user_input, chat_history=None, stre
149
149
  user_id = generate_user_id()
150
150
  session_id = str(uuid.uuid4())
151
151
 
152
- def stream_response():
153
- generate = generate_proxy_stream(
154
- send_to_qa,
155
- user_input,
156
- vector_name=service_name,
157
- chat_history=chat_history,
158
- generate_f_output=lambda x: x, # Replace with actual processing function
159
- stream_wait_time=0.5,
160
- stream_timeout=120,
161
- message_author=user_id,
162
- #TODO: populate these
163
- image_url=None,
164
- source_filters=None,
165
- search_kwargs=None,
166
- private_docs=None,
167
- whole_document=False,
168
- source_filters_and_or=False,
169
- # system kwargs
170
- configurable={
171
- "vector_name": service_name,
172
- },
173
- user_id=user_id,
174
- session_id=session_id,
175
- message_source="cli",
176
- override_endpoint=service_url
177
- )
178
- for part in generate():
179
- yield part
152
+ if not stream:
153
+ vac_response = send_to_qa(user_input,
154
+ vector_name=service_name,
155
+ chat_history=chat_history,
156
+ message_author=user_id,
157
+ #TODO: populate these
158
+ image_url=None,
159
+ source_filters=None,
160
+ search_kwargs=None,
161
+ private_docs=None,
162
+ whole_document=False,
163
+ source_filters_and_or=False,
164
+ # system kwargs
165
+ configurable={
166
+ "vector_name": service_name,
167
+ },
168
+ user_id=user_id,
169
+ session_id=session_id,
170
+ message_source="cli",
171
+ override_endpoint=service_url)
172
+
173
+ # ensures {'answer': answer}
174
+ answer = parse_output(vac_response)
175
+
176
+ console.print(answer.get('answer'))
177
+ else:
178
+ def stream_response():
179
+ generate = generate_proxy_stream(
180
+ send_to_qa,
181
+ user_input,
182
+ vector_name=service_name,
183
+ chat_history=chat_history,
184
+ generate_f_output=lambda x: x, # Replace with actual processing function
185
+ stream_wait_time=0.5,
186
+ stream_timeout=120,
187
+ message_author=user_id,
188
+ #TODO: populate these
189
+ image_url=None,
190
+ source_filters=None,
191
+ search_kwargs=None,
192
+ private_docs=None,
193
+ whole_document=False,
194
+ source_filters_and_or=False,
195
+ # system kwargs
196
+ configurable={
197
+ "vector_name": service_name,
198
+ },
199
+ user_id=user_id,
200
+ session_id=session_id,
201
+ message_source="cli",
202
+ override_endpoint=service_url
203
+ )
204
+ for part in generate():
205
+ yield part
180
206
 
181
- vac_response = ""
207
+ answer = ""
182
208
 
183
- for token in stream_response():
184
- if isinstance(token, bytes):
185
- token = token.decode('utf-8')
186
- print(token, end='', flush=True)
187
- vac_response += token
209
+ for token in stream_response():
210
+ if isinstance(token, bytes):
211
+ token = token.decode('utf-8')
212
+ print(token, end='', flush=True)
213
+ answer += token
188
214
 
189
- if vac_response:
215
+ if answer:
190
216
  chat_history.append({"name": "Human", "content": user_input})
191
- chat_history.append({"name": "AI", "content": vac_response})
217
+ chat_history.append({"name": "AI", "content": answer})
192
218
  print() # For new line after streaming ends
193
219
 
194
220
  return chat_history
@@ -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.62.18
3
+ Version: 0.64.3
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.62.18.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.64.3.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
@@ -111,54 +112,21 @@ This is the Sunholo Python project, a comprehensive toolkit for working with lan
111
112
 
112
113
  Please refer to the website for full documentation at https://dev.sunholo.com/
113
114
 
114
- # sunholo-py
115
-
116
- (draft release https://pypi.org/project/sunholo/ )
117
- ## Table of Contents
118
- - [Agents](#agents)
119
- - [Archive](#archive)
120
- - [Bots](#bots)
121
- - [Chunker](#chunker)
122
- - [Components](#components)
123
- - [Database](#database)
124
- - [Embedder](#embedder)
125
- - [PubSub](#pubsub)
126
- - [QnA](#qna)
127
- - [Streaming](#streaming)
128
- - [Summarise](#summarise)
129
- - [Utils](#utils)
115
+ ## Demos
130
116
 
117
+ Using https://github.com/charmbracelet/vhs
131
118
 
132
119
  ```sh
133
- pip install sunholo
120
+ vhs record > cassette.tape
134
121
  ```
135
122
 
136
- A python library to enable LLMOps within cloud environments
137
-
138
- `sunholo` provides utilities to help manage LLM operations on Google Cloud Platform at first, but it is hoped that making it open source will help it support other clouds in the future. A lot of the functionality is not Google Cloud Platform specific, so still may be helpful.
139
-
140
- It is derived from the Edmonbrain project, the original blog post you can read here: https://code.markedmondson.me/running-llms-on-gcp/ and owes a lot to Langchain ( https://github.com/langchain-ai/langchain )
141
-
142
- The package includes:
123
+ Then make gif:
143
124
 
144
- * `agents/` - functions for working with agents, including easy flask apps, parsing chat history and dispatching requests to different agent endpoints
145
- * `archive/` - functions to record all Q&A activity to BigQuery via PubSub
146
- * `bots/` - functions for special cases regarding frontend bots such as GChat, Web Apps, Discord and Slack
147
- * `chunker/` - functions to slice up documents for sending into vector stores
148
- * `components/` - functions to help configure which LLM, prompt, vectorstore or document retriever you will use based on a yaml config file
149
- * `database/` - database setup functions and SQL to run on those sources such as Supabase
150
- * `embedder/` - functions to send chunks into embedding vector stores
151
- * `pubsub/` - use of PubSub for a message queue between components
152
- * `qna/` - utilities for running agents such as retry strats and parsing of output/input
153
- * `streaming/` - creation of streaming responses from LLM bots
154
- * `summarise/` - creation of summaries of large documents
155
- * `utils/` - reading configuration files, Google Cloud Platform metadata
156
-
157
- ## Configuration
125
+ ```sh
126
+ vhs docs/tapes/config-list.tape
127
+ ```
158
128
 
159
- The library uses the config specifications that some examples are given in the `config/` folder.
160
129
 
161
- When using the functions, make sure to have the `config/` folder in the root of where your application is running (usually `$HOME/config`)
162
130
 
163
131
  ```
164
132
  Copyright [2024] [Holosun ApS]
@@ -108,10 +108,12 @@ sunholo/utils/gcp_project.py
108
108
  sunholo/utils/parsers.py
109
109
  sunholo/utils/timedelta.py
110
110
  sunholo/utils/user_ids.py
111
+ sunholo/utils/version.py
111
112
  sunholo/vertex/__init__.py
112
113
  sunholo/vertex/init.py
113
114
  sunholo/vertex/memory_tools.py
114
115
  sunholo/vertex/safety.py
115
116
  tests/test_chat_history.py
117
+ tests/test_chunker.py
116
118
  tests/test_config.py
117
119
  tests/test_dispatch_to_qa.py
@@ -1,4 +1,5 @@
1
1
  google-auth
2
+ tenacity==8.3.0
2
3
  langchain
3
4
  langchain_experimental
4
5
  langchain-community
@@ -0,0 +1,23 @@
1
+ import pytest
2
+ from unittest.mock import patch, MagicMock
3
+ from sunholo.chunker.data_to_embed_pubsub import data_to_embed_pubsub
4
+
5
+ # Mock external calls within the function
6
+ @patch('sunholo.chunker.data_to_embed_pubsub.process_pubsub_message', return_value=({}, {}, 'test_vector'))
7
+ @patch('sunholo.chunker.data_to_embed_pubsub.process_chunker_data', return_value='processed_data')
8
+ def test_data_to_embed_pubsub(mock_process_chunker_data, mock_process_pubsub_message):
9
+ # Test the function with various inputs including edge cases
10
+ assert data_to_embed_pubsub({}) == 'processed_data'
11
+ assert data_to_embed_pubsub({'key': 'value'}) == 'processed_data'
12
+ mock_process_pubsub_message.assert_called()
13
+ mock_process_chunker_data.assert_called()
14
+
15
+ # Ensure tests are self-contained and do not require external dependencies
16
+ mock_process_pubsub_message = MagicMock(return_value=({}, {}, 'test_vector'))
17
+ mock_process_chunker_data = MagicMock(return_value='processed_data')
18
+ assert data_to_embed_pubsub({'key': 'value'}) == 'processed_data'
19
+
20
+ # Validate the function's output against expected results
21
+ expected_output = 'processed_data'
22
+ actual_output = data_to_embed_pubsub({'key': 'value'})
23
+ assert actual_output == expected_output, f"Expected {expected_output}, got {actual_output}"
@@ -8,13 +8,19 @@ from sunholo.agents.dispatch_to_qa import prep_request_payload
8
8
  # Additional test cases to cover all logic paths
9
9
  ("", [], "empty_input_vector", False, "http://example.com/invoke", {"user_input": "", "chat_history": [], "vector_name": "empty_input_vector"}),
10
10
  ("Valid input", ["Chat history present"], "history_vector", False, "http://example.com/invoke", {"user_input": "Valid input", "chat_history": ["Chat history present"], "vector_name": "history_vector"}),
11
- ("Stream request", [], "stream_vector", True, "http://example.com/stream", {"user_input": "Stream request", "chat_history": [], "vector_name": "stream_vector"})
11
+ ("Stream request", [], "stream_vector", True, "http://example.com/stream", {"user_input": "Stream request", "chat_history": [], "vector_name": "stream_vector"}),
12
+ # New test cases for prep_request_payload function
13
+ ("User input with override endpoint", [], "override_vector", False, "http://override.com/invoke", {"user_input": "User input with override endpoint", "chat_history": [], "vector_name": "override_vector"}),
14
+ ("Stream with chat history and override endpoint", ["Chat history for override"], "override_stream_vector", True, "http://override.com/stream", {"user_input": "Stream with chat history and override endpoint", "chat_history": ["Chat history for override"], "vector_name": "override_stream_vector"})
12
15
  ])
13
16
  @patch('sunholo.agents.dispatch_to_qa.load_config_key')
14
17
  @patch('sunholo.agents.dispatch_to_qa.route_endpoint')
15
18
  def test_prep_request_payload(mock_route_endpoint, mock_load_config_key, user_input, chat_history, vector_name, stream, expected_endpoint, expected_payload):
16
19
  mock_route_endpoint.return_value = {"invoke": "http://example.com/invoke", "stream": "http://example.com/stream"}
17
20
  mock_load_config_key.side_effect = lambda key, vector_name=None, kind=None: "my_vector" if key == "vector_name" else None
21
+ # Mocking for override endpoint scenario
22
+ if vector_name.startswith("override"):
23
+ mock_route_endpoint.return_value = {"invoke": "http://override.com/invoke", "stream": "http://override.com/stream"}
18
24
 
19
25
  endpoint, payload = prep_request_payload(user_input, chat_history, vector_name, stream)
20
26
 
sunholo-0.62.18/README.md DELETED
@@ -1,70 +0,0 @@
1
- ## Introduction
2
- 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.
3
-
4
- Please refer to the website for full documentation at https://dev.sunholo.com/
5
-
6
- # sunholo-py
7
-
8
- (draft release https://pypi.org/project/sunholo/ )
9
- ## Table of Contents
10
- - [Agents](#agents)
11
- - [Archive](#archive)
12
- - [Bots](#bots)
13
- - [Chunker](#chunker)
14
- - [Components](#components)
15
- - [Database](#database)
16
- - [Embedder](#embedder)
17
- - [PubSub](#pubsub)
18
- - [QnA](#qna)
19
- - [Streaming](#streaming)
20
- - [Summarise](#summarise)
21
- - [Utils](#utils)
22
-
23
-
24
- ```sh
25
- pip install sunholo
26
- ```
27
-
28
- A python library to enable LLMOps within cloud environments
29
-
30
- `sunholo` provides utilities to help manage LLM operations on Google Cloud Platform at first, but it is hoped that making it open source will help it support other clouds in the future. A lot of the functionality is not Google Cloud Platform specific, so still may be helpful.
31
-
32
- It is derived from the Edmonbrain project, the original blog post you can read here: https://code.markedmondson.me/running-llms-on-gcp/ and owes a lot to Langchain ( https://github.com/langchain-ai/langchain )
33
-
34
- The package includes:
35
-
36
- * `agents/` - functions for working with agents, including easy flask apps, parsing chat history and dispatching requests to different agent endpoints
37
- * `archive/` - functions to record all Q&A activity to BigQuery via PubSub
38
- * `bots/` - functions for special cases regarding frontend bots such as GChat, Web Apps, Discord and Slack
39
- * `chunker/` - functions to slice up documents for sending into vector stores
40
- * `components/` - functions to help configure which LLM, prompt, vectorstore or document retriever you will use based on a yaml config file
41
- * `database/` - database setup functions and SQL to run on those sources such as Supabase
42
- * `embedder/` - functions to send chunks into embedding vector stores
43
- * `pubsub/` - use of PubSub for a message queue between components
44
- * `qna/` - utilities for running agents such as retry strats and parsing of output/input
45
- * `streaming/` - creation of streaming responses from LLM bots
46
- * `summarise/` - creation of summaries of large documents
47
- * `utils/` - reading configuration files, Google Cloud Platform metadata
48
-
49
- ## Configuration
50
-
51
- The library uses the config specifications that some examples are given in the `config/` folder.
52
-
53
- When using the functions, make sure to have the `config/` folder in the root of where your application is running (usually `$HOME/config`)
54
-
55
- ```
56
- Copyright [2024] [Holosun ApS]
57
-
58
- Licensed under the Apache License, Version 2.0 (the "License");
59
- you may not use this file except in compliance with the License.
60
- You may obtain a copy of the License at
61
-
62
- http://www.apache.org/licenses/LICENSE-2.0
63
-
64
- Unless required by applicable law or agreed to in writing, software
65
- distributed under the License is distributed on an "AS IS" BASIS,
66
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
67
- See the License for the specific language governing permissions and
68
- limitations under the License.
69
- ```
70
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes