sunholo 0.64.5__tar.gz → 0.64.8__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.
- {sunholo-0.64.5 → sunholo-0.64.8}/PKG-INFO +2 -2
- {sunholo-0.64.5 → sunholo-0.64.8}/setup.py +1 -1
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/__init__.py +1 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/flask/qna_routes.py +143 -2
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/route.py +10 -5
- sunholo-0.64.8/sunholo/agents/swagger.py +208 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/auth/run.py +2 -2
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/chat_vac.py +2 -1
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/cli.py +3 -1
- sunholo-0.64.8/sunholo/cli/swagger.py +38 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/alloydb.py +4 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/gcs/add_file.py +18 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/streaming/stream_lookup.py +2 -1
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo.egg-info/PKG-INFO +2 -2
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo.egg-info/SOURCES.txt +4 -1
- sunholo-0.64.8/tests/test_swagger.py +15 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/LICENSE.txt +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/MANIFEST.in +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/README.md +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/setup.cfg +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/chat_history.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/dispatch_to_qa.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/fastapi/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/fastapi/base.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/fastapi/qna_routes.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/flask/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/flask/base.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/langserve.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/pubsub.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/agents/special_commands.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/archive/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/archive/archive.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/auth/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/bots/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/bots/discord.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/bots/github_webhook.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/bots/webapp.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/data_to_embed_pubsub.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/doc_handling.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/images.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/loaders.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/message_data.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/pdfs.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/publish.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/chunker/splitter.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/cli_init.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/configs.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/deploy.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/embedder.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/merge_texts.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/run_proxy.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/cli/sun_rich.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/components/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/components/llm.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/components/retriever.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/components/vectorstore.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/database.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/lancedb.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/sql/sb/create_function.sql +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/sql/sb/create_function_time.sql +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/sql/sb/create_table.sql +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/sql/sb/delete_source_row.sql +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/sql/sb/return_sources.sql +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/sql/sb/setup.sql +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/static_dbs.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/database/uuid.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/embedder/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/embedder/embed_chunk.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/gcs/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/gcs/download_url.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/gcs/metadata.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/langfuse/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/langfuse/callback.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/langfuse/prompts.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/llamaindex/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/llamaindex/generate.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/llamaindex/get_files.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/llamaindex/import_files.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/logging.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/lookup/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/lookup/model_lookup.yaml +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/patches/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/patches/langchain/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/patches/langchain/lancedb.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/patches/langchain/vertexai.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/pubsub/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/pubsub/process_pubsub.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/pubsub/pubsub_manager.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/qna/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/qna/parsers.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/qna/retry.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/streaming/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/streaming/content_buffer.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/streaming/langserve.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/streaming/streaming.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/summarise/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/summarise/summarise.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/big_context.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/config.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/config_schema.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/gcp.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/gcp_project.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/parsers.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/timedelta.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/user_ids.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/utils/version.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/vertex/__init__.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/vertex/init.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/vertex/memory_tools.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo/vertex/safety.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo.egg-info/dependency_links.txt +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo.egg-info/entry_points.txt +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo.egg-info/requires.txt +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/sunholo.egg-info/top_level.txt +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/tests/test_chat_history.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/tests/test_chunker.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/tests/test_config.py +0 -0
- {sunholo-0.64.5 → sunholo-0.64.8}/tests/test_dispatch_to_qa.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.64.
|
|
3
|
+
Version: 0.64.8
|
|
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.64.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.64.8.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -4,3 +4,4 @@ from .pubsub import process_pubsub
|
|
|
4
4
|
from .special_commands import handle_special_commands, app_to_store, handle_files
|
|
5
5
|
from .flask import register_qna_routes, create_app
|
|
6
6
|
from .fastapi import register_qna_fastapi_routes, create_fastapi_app
|
|
7
|
+
from .swagger import config_to_swagger
|
|
@@ -26,7 +26,7 @@ from ...logging import log
|
|
|
26
26
|
from ...utils.config import load_config
|
|
27
27
|
from ...utils.version import sunholo_version
|
|
28
28
|
import os
|
|
29
|
-
from ...gcs.add_file import add_file_to_gcs
|
|
29
|
+
from ...gcs.add_file import add_file_to_gcs, handle_base64_image
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
try:
|
|
@@ -41,7 +41,32 @@ except ImportError:
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def register_qna_routes(app, stream_interpreter, vac_interpreter):
|
|
44
|
+
"""
|
|
45
|
+
Register Q&A routes for a Flask application.
|
|
44
46
|
|
|
47
|
+
This function sets up multiple routes for handling Q&A operations,
|
|
48
|
+
including streaming responses and processing static responses.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
app (Flask): The Flask application instance.
|
|
52
|
+
stream_interpreter (function): Function to handle streaming Q&A responses.
|
|
53
|
+
vac_interpreter (function): Function to handle static Q&A responses.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
None
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
from flask import Flask
|
|
60
|
+
app = Flask(__name__)
|
|
61
|
+
|
|
62
|
+
def dummy_stream_interpreter(...):
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
def dummy_vac_interpreter(...):
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
register_qna_routes(app, dummy_stream_interpreter, dummy_vac_interpreter)
|
|
69
|
+
"""
|
|
45
70
|
@app.route("/")
|
|
46
71
|
def home():
|
|
47
72
|
return jsonify("OK")
|
|
@@ -52,6 +77,21 @@ def register_qna_routes(app, stream_interpreter, vac_interpreter):
|
|
|
52
77
|
|
|
53
78
|
@app.route('/vac/streaming/<vector_name>', methods=['POST'])
|
|
54
79
|
def stream_qa(vector_name):
|
|
80
|
+
"""
|
|
81
|
+
Handle streaming Q&A responses.
|
|
82
|
+
|
|
83
|
+
This function sets up a route to handle streaming Q&A responses based on
|
|
84
|
+
the provided vector name.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
vector_name (str): The name of the vector for the request.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Response: A Flask response object streaming the Q&A response content.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
response = stream_qa("example_vector")
|
|
94
|
+
"""
|
|
55
95
|
observed_stream_interpreter = observe()(stream_interpreter)
|
|
56
96
|
prep = prep_vac(request, vector_name)
|
|
57
97
|
log.debug(f"Processing prep: {prep}")
|
|
@@ -117,6 +157,21 @@ def register_qna_routes(app, stream_interpreter, vac_interpreter):
|
|
|
117
157
|
|
|
118
158
|
@app.route('/vac/<vector_name>', methods=['POST'])
|
|
119
159
|
def process_qna(vector_name):
|
|
160
|
+
"""
|
|
161
|
+
Handle static Q&A responses.
|
|
162
|
+
|
|
163
|
+
This function sets up a route to handle static Q&A responses based on
|
|
164
|
+
the provided vector name.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
vector_name (str): The name of the vector for the request.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Response: A Flask response object with the Q&A response content.
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
response = process_qna("example_vector")
|
|
174
|
+
"""
|
|
120
175
|
observed_vac_interpreter = observe()(vac_interpreter)
|
|
121
176
|
prep = prep_vac(request, vector_name)
|
|
122
177
|
log.debug(f"Processing prep: {prep}")
|
|
@@ -167,6 +222,21 @@ def register_qna_routes(app, stream_interpreter, vac_interpreter):
|
|
|
167
222
|
@app.route('/openai/v1/chat/completions', methods=['POST'])
|
|
168
223
|
@app.route('/openai/v1/chat/completions/<vector_name>', methods=['POST'])
|
|
169
224
|
def openai_compatible_endpoint(vector_name=None):
|
|
225
|
+
"""
|
|
226
|
+
Handle OpenAI-compatible chat completions.
|
|
227
|
+
|
|
228
|
+
This function sets up routes to handle OpenAI-compatible chat completion requests,
|
|
229
|
+
both with and without a specified vector name.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
vector_name (str, optional): The name of the vector for the request. Defaults to None.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Response: A Flask response object with the chat completion content.
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
response = openai_compatible_endpoint("example_vector")
|
|
239
|
+
"""
|
|
170
240
|
data = request.get_json()
|
|
171
241
|
log.info(f'openai_compatible_endpoint got data: {data} for vector: {vector_name}')
|
|
172
242
|
|
|
@@ -178,9 +248,29 @@ def register_qna_routes(app, stream_interpreter, vac_interpreter):
|
|
|
178
248
|
if not messages:
|
|
179
249
|
return jsonify({"error": "No messages provided"}), 400
|
|
180
250
|
|
|
181
|
-
user_message =
|
|
251
|
+
user_message = None
|
|
252
|
+
image_uri = None
|
|
253
|
+
mime_type = None
|
|
254
|
+
|
|
255
|
+
for msg in reversed(messages):
|
|
256
|
+
if msg['role'] == 'user':
|
|
257
|
+
if isinstance(msg['content'], list):
|
|
258
|
+
for content_item in msg['content']:
|
|
259
|
+
if content_item['type'] == 'text':
|
|
260
|
+
user_message = content_item['text']
|
|
261
|
+
elif content_item['type'] == 'image_url':
|
|
262
|
+
base64_data = content_item['image_url']['url']
|
|
263
|
+
image_uri, mime_type = handle_base64_image(base64_data, vector_name)
|
|
264
|
+
else:
|
|
265
|
+
user_message = msg['content']
|
|
266
|
+
break
|
|
267
|
+
|
|
182
268
|
if not user_message:
|
|
183
269
|
return jsonify({"error": "No user message provided"}), 400
|
|
270
|
+
|
|
271
|
+
if image_uri:
|
|
272
|
+
data["image_uri"] = image_uri
|
|
273
|
+
data["mime"] = mime_type
|
|
184
274
|
|
|
185
275
|
all_input = {
|
|
186
276
|
"user_input": user_message,
|
|
@@ -281,6 +371,22 @@ def register_qna_routes(app, stream_interpreter, vac_interpreter):
|
|
|
281
371
|
|
|
282
372
|
|
|
283
373
|
def create_langfuse_trace(request, vector_name):
|
|
374
|
+
"""
|
|
375
|
+
Create a Langfuse trace for tracking requests.
|
|
376
|
+
|
|
377
|
+
This function initializes a Langfuse trace object based on the request headers
|
|
378
|
+
and vector name.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
request (Request): The Flask request object.
|
|
382
|
+
vector_name (str): The name of the vector for the request.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Langfuse.Trace: The Langfuse trace object.
|
|
386
|
+
|
|
387
|
+
Example:
|
|
388
|
+
trace = create_langfuse_trace(request, "example_vector")
|
|
389
|
+
"""
|
|
284
390
|
try:
|
|
285
391
|
from langfuse import Langfuse
|
|
286
392
|
langfuse = Langfuse()
|
|
@@ -307,6 +413,22 @@ def create_langfuse_trace(request, vector_name):
|
|
|
307
413
|
)
|
|
308
414
|
|
|
309
415
|
def prep_vac(request, vector_name):
|
|
416
|
+
"""
|
|
417
|
+
Prepare the input data for a VAC request.
|
|
418
|
+
|
|
419
|
+
This function processes the incoming request data, extracts relevant
|
|
420
|
+
information, and prepares the data for VAC processing.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
request (Request): The Flask request object.
|
|
424
|
+
vector_name (str): The name of the vector for the request.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
dict: A dictionary containing prepared input data and metadata.
|
|
428
|
+
|
|
429
|
+
Example:
|
|
430
|
+
prep_data = prep_vac(request, "example_vector")
|
|
431
|
+
"""
|
|
310
432
|
#trace = create_langfuse_trace(request, vector_name)
|
|
311
433
|
trace = None
|
|
312
434
|
span = None
|
|
@@ -376,6 +498,25 @@ def prep_vac(request, vector_name):
|
|
|
376
498
|
|
|
377
499
|
|
|
378
500
|
def handle_file_upload(file, vector_name):
|
|
501
|
+
"""
|
|
502
|
+
Handle file upload and store the file in Google Cloud Storage.
|
|
503
|
+
|
|
504
|
+
This function saves the uploaded file locally, uploads it to Google Cloud Storage,
|
|
505
|
+
and then removes the local copy.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
file (FileStorage): The uploaded file.
|
|
509
|
+
vector_name (str): The name of the vector for the request.
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
tuple: A tuple containing the URI of the uploaded file and its MIME type.
|
|
513
|
+
|
|
514
|
+
Raises:
|
|
515
|
+
Exception: If the file upload fails.
|
|
516
|
+
|
|
517
|
+
Example:
|
|
518
|
+
uri, mime_type = handle_file_upload(file, "example_vector")
|
|
519
|
+
"""
|
|
379
520
|
try:
|
|
380
521
|
file.save(file.filename)
|
|
381
522
|
image_uri = add_file_to_gcs(file.filename, vector_name)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
from ..logging import log
|
|
15
15
|
from ..utils import load_config_key, load_config
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def route_vac(vector_name):
|
|
18
18
|
|
|
19
19
|
agent_url = load_config_key('agent_url', vector_name=vector_name, kind="vacConfig")
|
|
20
20
|
if agent_url:
|
|
@@ -35,17 +35,22 @@ def route_qna(vector_name):
|
|
|
35
35
|
log.info(f'agent_url: {agent_url}')
|
|
36
36
|
return agent_url
|
|
37
37
|
|
|
38
|
-
def route_endpoint(vector_name, override_endpoint=None):
|
|
38
|
+
def route_endpoint(vector_name, method = 'post', override_endpoint=None):
|
|
39
39
|
|
|
40
40
|
agent_type = load_config_key('agent_type', vector_name, kind="vacConfig")
|
|
41
41
|
if not agent_type:
|
|
42
42
|
agent_type = load_config_key('agent', vector_name, kind="vacConfig")
|
|
43
43
|
|
|
44
|
-
stem =
|
|
44
|
+
stem = route_vac(vector_name) if not override_endpoint else override_endpoint
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
agents_config = load_config_key(agent_type, vector_name, kind="agentConfig")
|
|
47
47
|
|
|
48
|
-
log.info(f"endpoints_config: {
|
|
48
|
+
log.info(f"endpoints_config: {agents_config}")
|
|
49
|
+
if method not in agents_config:
|
|
50
|
+
raise ValueError(f"Invalid method '{method}' for agent configuration.")
|
|
51
|
+
|
|
52
|
+
# 'post' or 'get'
|
|
53
|
+
endpoints_config = agents_config[method]
|
|
49
54
|
|
|
50
55
|
# Replace placeholders in the config
|
|
51
56
|
endpoints = {}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
import copy
|
|
3
|
+
|
|
4
|
+
from ..utils.config import load_all_configs
|
|
5
|
+
from .route import route_vac
|
|
6
|
+
from ..logging import log
|
|
7
|
+
|
|
8
|
+
def config_to_swagger():
|
|
9
|
+
"""
|
|
10
|
+
Load configuration files and generate a Swagger specification.
|
|
11
|
+
|
|
12
|
+
This function loads the 'vacConfig' and 'agentConfig' configuration files,
|
|
13
|
+
validates their presence, and then generates a Swagger specification
|
|
14
|
+
based on these configurations.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
str: The generated Swagger specification in YAML format.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError: If 'vacConfig' or 'agentConfig' is not loaded.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
```python
|
|
24
|
+
swagger_yaml = config_to_swagger()
|
|
25
|
+
print(swagger_yaml)
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
configs = load_all_configs()
|
|
29
|
+
|
|
30
|
+
vac_config = configs.get('vacConfig')
|
|
31
|
+
agent_config = configs.get('agentConfig')
|
|
32
|
+
|
|
33
|
+
if not vac_config:
|
|
34
|
+
raise ValueError("Need valid 'vacConfig' loaded")
|
|
35
|
+
|
|
36
|
+
if not agent_config:
|
|
37
|
+
raise ValueError("Need valid 'agentConfig' loaded")
|
|
38
|
+
|
|
39
|
+
swag = generate_swagger(vac_config, agent_config)
|
|
40
|
+
|
|
41
|
+
return swag
|
|
42
|
+
|
|
43
|
+
def generate_swagger(vac_config, agent_config):
|
|
44
|
+
"""
|
|
45
|
+
Generate a Swagger specification based on the provided configurations.
|
|
46
|
+
|
|
47
|
+
This function creates a Swagger specification using the provided 'vacConfig'
|
|
48
|
+
and 'agentConfig'. It dynamically builds paths and responses based on the
|
|
49
|
+
configurations.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
vac_config (dict): The VAC configuration.
|
|
53
|
+
agent_config (dict): The agent configuration.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
str: The generated Swagger specification in YAML format.
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
```python
|
|
60
|
+
vac_config = {
|
|
61
|
+
'vac': {
|
|
62
|
+
'service1': {
|
|
63
|
+
'llm': 'vertex',
|
|
64
|
+
'model': 'gemini-1.5-flash-001',
|
|
65
|
+
'agent': 'langserve'
|
|
66
|
+
},
|
|
67
|
+
'service2': {
|
|
68
|
+
'llm': 'openai',
|
|
69
|
+
'agent': 'crewai',
|
|
70
|
+
'secrets': ['OPENAI_API_KEY']
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
agent_config = {
|
|
76
|
+
'agents': {
|
|
77
|
+
'default': {
|
|
78
|
+
'stream': "{stem}/vac/streaming/{vector_name}",
|
|
79
|
+
'invoke': "{stem}/vac/{vector_name}",
|
|
80
|
+
'post': {
|
|
81
|
+
'stream': "{stem}/vac/streaming/{vector_name}",
|
|
82
|
+
'invoke': "{stem}/vac/{vector_name}",
|
|
83
|
+
'openai': "{stem}/openai/v1/chat/completions",
|
|
84
|
+
'openai-vac': "{stem}/openai/v1/chat/completions/{vector_name}"
|
|
85
|
+
},
|
|
86
|
+
'get': {
|
|
87
|
+
'home': "{stem}/",
|
|
88
|
+
'health': "{stem}/health"
|
|
89
|
+
},
|
|
90
|
+
'response': {
|
|
91
|
+
'invoke': {
|
|
92
|
+
'200': {
|
|
93
|
+
'description': 'Successful invocation response',
|
|
94
|
+
'schema': {
|
|
95
|
+
'type': 'object',
|
|
96
|
+
'properties': {
|
|
97
|
+
'answer': {'type': 'string'},
|
|
98
|
+
'source_documents': {
|
|
99
|
+
'type': 'array',
|
|
100
|
+
'items': {
|
|
101
|
+
'type': 'object',
|
|
102
|
+
'properties': {
|
|
103
|
+
'page_content': {'type': 'string'},
|
|
104
|
+
'metadata': {'type': 'string'}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
swagger_yaml = generate_swagger(vac_config, agent_config)
|
|
118
|
+
print(swagger_yaml)
|
|
119
|
+
```
|
|
120
|
+
"""
|
|
121
|
+
swagger_template = {
|
|
122
|
+
'swagger': '2.0',
|
|
123
|
+
'info': {
|
|
124
|
+
'title': 'Multivac - Cloud Endpoints + Cloud Run',
|
|
125
|
+
'description': 'Multivac - Cloud Endpoints with a Cloud Run backend',
|
|
126
|
+
'version': '0.1.0'
|
|
127
|
+
},
|
|
128
|
+
'host': '${_ENDPOINTS_HOST}',
|
|
129
|
+
'schemes': ['https'],
|
|
130
|
+
'produces': ['application/json'],
|
|
131
|
+
'paths': {}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
vac_services = vac_config['vac']
|
|
135
|
+
|
|
136
|
+
for vector_name, config in vac_services.items():
|
|
137
|
+
agent_type = config['agent']
|
|
138
|
+
agent_config_paths = agent_config['agents'].get(agent_type, {})
|
|
139
|
+
log.info(f'Configuring swagger for agent_type: {agent_type} for vector_name: {vector_name}')
|
|
140
|
+
try:
|
|
141
|
+
stem = route_vac(vector_name)
|
|
142
|
+
except ValueError:
|
|
143
|
+
stem = f"${{{agent_type.upper()}_BACKEND_URL}}"
|
|
144
|
+
log.warning(f"Failed to find URL stem for {vector_name}/{agent_type} - using {stem} instead")
|
|
145
|
+
|
|
146
|
+
for method, endpoints in agent_config_paths.items():
|
|
147
|
+
if method not in ['get', 'post']:
|
|
148
|
+
log.warning(f"Skipping {endpoints}")
|
|
149
|
+
continue
|
|
150
|
+
for endpoint_key, endpoint_template in endpoints.items():
|
|
151
|
+
endpoint_path = endpoint_template.replace("{stem}", f"/{agent_type}").replace("{vector_name}", vector_name)
|
|
152
|
+
if endpoint_path not in swagger_template['paths']:
|
|
153
|
+
swagger_template['paths'][endpoint_path] = {}
|
|
154
|
+
swagger_template['paths'][endpoint_path][method] = {
|
|
155
|
+
'summary': f"{method.capitalize()} {vector_name}",
|
|
156
|
+
'operationId': f"{method}_{agent_type}_{endpoint_key}",
|
|
157
|
+
'x-google-backend': {
|
|
158
|
+
'address': endpoint_template.replace("{stem}", stem).replace("{vector_name}", vector_name),
|
|
159
|
+
'protocol': 'h2'
|
|
160
|
+
},
|
|
161
|
+
'responses': copy.deepcopy(agent_config_paths.get('response', {}).get(endpoint_key, {
|
|
162
|
+
'200': {
|
|
163
|
+
'description': 'Default - A successful response',
|
|
164
|
+
'schema': {
|
|
165
|
+
'type': 'string'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}))
|
|
169
|
+
}
|
|
170
|
+
# Handle default agent configuration for agent types without specific entries
|
|
171
|
+
default_agent_config = agent_config['agents'].get('default', {})
|
|
172
|
+
|
|
173
|
+
for vector_name, config in vac_services.items():
|
|
174
|
+
agent_type = config['agent']
|
|
175
|
+
if agent_type in agent_config['agents']:
|
|
176
|
+
continue
|
|
177
|
+
log.info(f'Applying default configuration for agent_type: {agent_type} for vector_name: {vector_name}')
|
|
178
|
+
try:
|
|
179
|
+
stem = route_vac(vector_name)
|
|
180
|
+
except ValueError:
|
|
181
|
+
stem = f"${{{agent_type.upper()}_BACKEND_URL}}"
|
|
182
|
+
log.warning(f"Failed to find URL stem for {vector_name}/{agent_type} - using {stem} instead")
|
|
183
|
+
|
|
184
|
+
for method, endpoints in default_agent_config.items():
|
|
185
|
+
if method not in ['get', 'post']:
|
|
186
|
+
continue
|
|
187
|
+
for endpoint_key, endpoint_template in endpoints.items():
|
|
188
|
+
endpoint_path = endpoint_template.replace("{stem}", f"/{agent_type}").replace("{vector_name}", vector_name)
|
|
189
|
+
if endpoint_path not in swagger_template['paths']:
|
|
190
|
+
swagger_template['paths'][endpoint_path] = {}
|
|
191
|
+
swagger_template['paths'][endpoint_path][method] = {
|
|
192
|
+
'summary': f"{method.capitalize()} {agent_type}",
|
|
193
|
+
'operationId': f"{method}_{agent_type}_{endpoint_key}",
|
|
194
|
+
'x-google-backend': {
|
|
195
|
+
'address': endpoint_template.replace("{stem}", stem).replace("{vector_name}", vector_name),
|
|
196
|
+
'protocol': 'h2'
|
|
197
|
+
},
|
|
198
|
+
'responses': copy.deepcopy(default_agent_config.get('response', {}).get(endpoint_key, {
|
|
199
|
+
'200': {
|
|
200
|
+
'description': 'Default - A successful response',
|
|
201
|
+
'schema': {
|
|
202
|
+
'type': 'string'
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}))
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return yaml.dump(swagger_template, default_flow_style=False)
|
|
@@ -5,14 +5,14 @@ from typing import Dict, Optional
|
|
|
5
5
|
from ..utils.config import load_config_key, load_config
|
|
6
6
|
from ..utils.gcp import is_running_on_cloudrun
|
|
7
7
|
from ..logging import log
|
|
8
|
-
from ..agents.route import
|
|
8
|
+
from ..agents.route import route_vac
|
|
9
9
|
|
|
10
10
|
def get_run_url(vector_name=None):
|
|
11
11
|
|
|
12
12
|
if not vector_name:
|
|
13
13
|
raise ValueError('Vector name was not specified')
|
|
14
14
|
|
|
15
|
-
cloud_urls =
|
|
15
|
+
cloud_urls = route_vac(vector_name)
|
|
16
16
|
|
|
17
17
|
cloud_urls, _ = load_config('config/cloud_run_urls.json')
|
|
18
18
|
agent = load_config_key("agent", vector_name=vector_name, kind="vacConfig")
|
|
@@ -272,8 +272,9 @@ def vac_command(args):
|
|
|
272
272
|
display_name = load_config_key("display_name", vector_name=args.vac_name, kind="vacConfig")
|
|
273
273
|
description = load_config_key("description", vector_name=args.vac_name, kind="vacConfig")
|
|
274
274
|
endpoints_config = load_config_key(agent_name, "dummy_value", kind="agentConfig")
|
|
275
|
+
post_endpoints = endpoints_config['post']
|
|
275
276
|
|
|
276
|
-
display_endpoints = ' '.join(f"{key}: {value}" for key, value in
|
|
277
|
+
display_endpoints = ' '.join(f"{key}: {value}" for key, value in post_endpoints.items())
|
|
277
278
|
display_endpoints = display_endpoints.replace("{stem}", service_url).replace("{vector_name}", args.vac_name)
|
|
278
279
|
|
|
279
280
|
if agent_name == "langserve":
|
|
@@ -8,6 +8,7 @@ from .merge_texts import setup_merge_text_subparser
|
|
|
8
8
|
from .run_proxy import setup_proxy_subparser
|
|
9
9
|
from .chat_vac import setup_vac_subparser
|
|
10
10
|
from .embedder import setup_embedder_subparser
|
|
11
|
+
from .swagger import setup_swagger_subparser
|
|
11
12
|
|
|
12
13
|
from ..utils.config import load_config_key
|
|
13
14
|
|
|
@@ -17,7 +18,6 @@ from .sun_rich import console
|
|
|
17
18
|
import sys
|
|
18
19
|
from rich.panel import Panel
|
|
19
20
|
|
|
20
|
-
|
|
21
21
|
def load_default_gcp_config():
|
|
22
22
|
try:
|
|
23
23
|
gcp_config = load_config_key('gcp_config', 'global', kind="vacConfig")
|
|
@@ -82,6 +82,8 @@ def main(args=None):
|
|
|
82
82
|
setup_vac_subparser(subparsers)
|
|
83
83
|
# embed command
|
|
84
84
|
setup_embedder_subparser(subparsers)
|
|
85
|
+
# swagger generation
|
|
86
|
+
setup_swagger_subparser(subparsers)
|
|
85
87
|
|
|
86
88
|
#TODO: add database setup commands: alloydb and supabase
|
|
87
89
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from ..agents.swagger import generate_swagger
|
|
2
|
+
from ..utils.config import load_all_configs
|
|
3
|
+
from .sun_rich import console
|
|
4
|
+
|
|
5
|
+
def cli_swagger(args):
|
|
6
|
+
|
|
7
|
+
configs = load_all_configs()
|
|
8
|
+
|
|
9
|
+
vac_config = args.vac_config_path or configs.get('vacConfig')
|
|
10
|
+
agent_config = args.agent_config_path or configs.get('agentConfig')
|
|
11
|
+
if not agent_config:
|
|
12
|
+
raise ValueError('Need an agentConfig path')
|
|
13
|
+
|
|
14
|
+
if not vac_config:
|
|
15
|
+
raise ValueError('Need a vacConfig path')
|
|
16
|
+
|
|
17
|
+
swag = generate_swagger(vac_config, agent_config)
|
|
18
|
+
|
|
19
|
+
console.print(swag)
|
|
20
|
+
|
|
21
|
+
return swag
|
|
22
|
+
|
|
23
|
+
def setup_swagger_subparser(subparsers):
|
|
24
|
+
"""
|
|
25
|
+
Sets up an argparse subparser for the 'swagger' command.
|
|
26
|
+
|
|
27
|
+
By default will use the 'vacConfig' configuration within the folder specified by '_CONFIG_FOLDER'
|
|
28
|
+
|
|
29
|
+
Example command:
|
|
30
|
+
```bash
|
|
31
|
+
sunholo swagger --config .
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
deploy_parser = subparsers.add_parser('swagger', help='Create a swagger specification based off a "vacConfig" configuration')
|
|
35
|
+
deploy_parser.add_argument('--vac_config_path', help='Path to the vacConfig file. Set _CONFIG_FOLDER env var and place file in there to change default config location.')
|
|
36
|
+
deploy_parser.add_argument('--agent_config_path', help='Path to agentConfig file. Set _CONFIG_FOLDER env var and place file in there to change default config location.')
|
|
37
|
+
deploy_parser.set_defaults(func=cli_swagger)
|
|
38
|
+
|
|
@@ -234,6 +234,10 @@ def create_alloydb_table(vector_name, engine, type = "vectorstore", alloydb_conf
|
|
|
234
234
|
log.info(f"AlloyDB Table '{table_name}' exists in cache, skipping creation.")
|
|
235
235
|
|
|
236
236
|
return table_name
|
|
237
|
+
|
|
238
|
+
alloydb_table_cache[table_name] = True
|
|
239
|
+
return table_name
|
|
240
|
+
|
|
237
241
|
log.info(f"# Creating AlloyDB table {table_name}")
|
|
238
242
|
try:
|
|
239
243
|
engine.init_vectorstore_table(
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import datetime
|
|
15
15
|
import os
|
|
16
|
+
import base64
|
|
17
|
+
import uuid
|
|
16
18
|
|
|
17
19
|
try:
|
|
18
20
|
from google.cloud import storage
|
|
@@ -22,6 +24,22 @@ except ImportError:
|
|
|
22
24
|
from ..logging import log
|
|
23
25
|
from ..utils.config import load_config_key
|
|
24
26
|
|
|
27
|
+
|
|
28
|
+
def handle_base64_image(base64_data, vector_name):
|
|
29
|
+
try:
|
|
30
|
+
header, encoded = base64_data.split(",", 1)
|
|
31
|
+
data = base64.b64decode(encoded)
|
|
32
|
+
|
|
33
|
+
filename = f"{uuid.uuid4()}.jpg"
|
|
34
|
+
with open(filename, "wb") as f:
|
|
35
|
+
f.write(data)
|
|
36
|
+
|
|
37
|
+
image_uri = add_file_to_gcs(filename, vector_name)
|
|
38
|
+
os.remove(filename) # Clean up the saved file
|
|
39
|
+
return image_uri, "image/jpeg"
|
|
40
|
+
except Exception as e:
|
|
41
|
+
raise Exception(f'Base64 image upload failed: {str(e)}')
|
|
42
|
+
|
|
25
43
|
def add_file_to_gcs(filename: str, vector_name:str, bucket_name: str=None, metadata:dict=None, bucket_filepath:str=None):
|
|
26
44
|
|
|
27
45
|
if not storage:
|
|
@@ -5,8 +5,9 @@ def can_agent_stream(agent_name: str):
|
|
|
5
5
|
|
|
6
6
|
log.debug(f"agent_type: {agent_name} checking streaming...")
|
|
7
7
|
endpoints_config = load_config_key(agent_name, "dummy_value", kind="agentConfig")
|
|
8
|
+
post_endpoints = endpoints_config['post']
|
|
8
9
|
|
|
9
|
-
return 'stream' in
|
|
10
|
+
return 'stream' in post_endpoints
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sunholo
|
|
3
|
-
Version: 0.64.
|
|
3
|
+
Version: 0.64.8
|
|
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.64.
|
|
6
|
+
Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.64.8.tar.gz
|
|
7
7
|
Author: Holosun ApS
|
|
8
8
|
Author-email: multivac@sunholo.com
|
|
9
9
|
License: Apache License, Version 2.0
|
|
@@ -18,6 +18,7 @@ sunholo/agents/langserve.py
|
|
|
18
18
|
sunholo/agents/pubsub.py
|
|
19
19
|
sunholo/agents/route.py
|
|
20
20
|
sunholo/agents/special_commands.py
|
|
21
|
+
sunholo/agents/swagger.py
|
|
21
22
|
sunholo/agents/fastapi/__init__.py
|
|
22
23
|
sunholo/agents/fastapi/base.py
|
|
23
24
|
sunholo/agents/fastapi/qna_routes.py
|
|
@@ -51,6 +52,7 @@ sunholo/cli/embedder.py
|
|
|
51
52
|
sunholo/cli/merge_texts.py
|
|
52
53
|
sunholo/cli/run_proxy.py
|
|
53
54
|
sunholo/cli/sun_rich.py
|
|
55
|
+
sunholo/cli/swagger.py
|
|
54
56
|
sunholo/components/__init__.py
|
|
55
57
|
sunholo/components/llm.py
|
|
56
58
|
sunholo/components/retriever.py
|
|
@@ -116,4 +118,5 @@ sunholo/vertex/safety.py
|
|
|
116
118
|
tests/test_chat_history.py
|
|
117
119
|
tests/test_chunker.py
|
|
118
120
|
tests/test_config.py
|
|
119
|
-
tests/test_dispatch_to_qa.py
|
|
121
|
+
tests/test_dispatch_to_qa.py
|
|
122
|
+
tests/test_swagger.py
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from sunholo.agents import config_to_swagger
|
|
2
|
+
import yaml
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# Test cases for config_to_swagger and generate_swagger functions
|
|
6
|
+
def test_config_to_swagger():
|
|
7
|
+
swagger_yaml = config_to_swagger()
|
|
8
|
+
swagger_dict = yaml.safe_load(swagger_yaml)
|
|
9
|
+
|
|
10
|
+
assert 'swagger' in swagger_dict
|
|
11
|
+
assert swagger_dict['swagger'] == '2.0'
|
|
12
|
+
assert 'info' in swagger_dict
|
|
13
|
+
assert swagger_dict['info']['title'] == 'Multivac - Cloud Endpoints + Cloud Run'
|
|
14
|
+
assert 'paths' in swagger_dict
|
|
15
|
+
#TODO: more tests for specific agents
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|