sunholo 0.140.11__py3-none-any.whl → 0.140.13__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.
@@ -441,10 +441,10 @@ def create_message_element(message: dict):
441
441
  ```
442
442
  """
443
443
  if 'text' in message: # This is a Slack or Google Chat message
444
- log.info(f"Found text element - {message['text']}")
444
+ #log.info(f"Found text element - {message['text']}")
445
445
  return message['text']
446
446
  elif 'content' in message: # Discord or OpenAI history message
447
- log.info(f"Found content element - {message['content']}")
447
+ #log.info(f"Found content element - {message['content']}")
448
448
  return message['content']
449
449
  else:
450
450
  raise KeyError(f"Could not extract 'content' or 'text' element from message: {message}, {type(message)}")
@@ -6,7 +6,38 @@ from collections import defaultdict
6
6
  from .timedelta import format_timedelta
7
7
 
8
8
  class ConfigManager:
9
- def __init__(self, vector_name: str, validate:bool=True):
9
+ # Class-level cache for instances
10
+ _instances = {}
11
+ _instance_cache_time = {}
12
+ _instance_cache_duration = timedelta(minutes=30) # Cache instances for 30 minutes
13
+
14
+ def __new__(cls, vector_name: str, validate: bool = True):
15
+ """
16
+ Override __new__ to implement instance caching.
17
+ Returns existing instance if available and not expired.
18
+ """
19
+ current_time = datetime.now()
20
+ cache_key = (vector_name, validate)
21
+
22
+ # Check if we have a cached instance
23
+ if cache_key in cls._instances:
24
+ cached_time = cls._instance_cache_time.get(cache_key)
25
+ if cached_time and (current_time - cached_time) < cls._instance_cache_duration:
26
+ # Return cached instance
27
+ return cls._instances[cache_key]
28
+ else:
29
+ # Cache expired, remove from cache
30
+ del cls._instances[cache_key]
31
+ if cache_key in cls._instance_cache_time:
32
+ del cls._instance_cache_time[cache_key]
33
+
34
+ # Create new instance
35
+ instance = super().__new__(cls)
36
+ cls._instances[cache_key] = instance
37
+ cls._instance_cache_time[cache_key] = current_time
38
+ return instance
39
+
40
+ def __init__(self, vector_name: str, validate: bool = True):
10
41
  """
11
42
  Initialize the ConfigManager with a vector name.
12
43
  Requires a local config/ folder holding your configuration files or the env var VAC_CONFIG_FOLDER to be set.
@@ -24,6 +55,10 @@ class ConfigManager:
24
55
  agent = config.vacConfig("agent")
25
56
  ```
26
57
  """
58
+ # Prevent re-initialization of cached instances
59
+ if hasattr(self, '_initialized'):
60
+ return
61
+
27
62
  if os.getenv("VAC_CONFIG_FOLDER") is None:
28
63
  print("WARNING: No VAC_CONFIG_FOLDER environment variable was specified")
29
64
  local_config_folder = os.path.join(os.getcwd(), "config")
@@ -39,11 +74,68 @@ class ConfigManager:
39
74
  self.local_config_folder = local_config_folder
40
75
  self.configs_by_kind = self.load_all_configs()
41
76
  self.validate = validate
77
+ self._initialized = True
42
78
 
43
79
  test_agent = self.vacConfig("agent")
44
80
  if not test_agent and self.vector_name != "global" and self.validate:
45
81
  print(f"WARNING: No vacConfig.agent found for {self.vector_name} - are you in right folder? {local_config_folder=} {self.config_folder=}")
46
82
 
83
+ @classmethod
84
+ def get_instance(cls, vector_name: str, validate: bool = True):
85
+ """
86
+ Alternative class method to get an instance. This is more explicit than using __new__.
87
+
88
+ Args:
89
+ vector_name (str): The name of the vector in the configuration files.
90
+ validate (bool): Whether to validate the configurations
91
+
92
+ Returns:
93
+ ConfigManager: The ConfigManager instance (cached or new)
94
+ """
95
+ return cls(vector_name, validate)
96
+
97
+ @classmethod
98
+ def clear_instance_cache(cls, vector_name: str = None, validate: bool = None):
99
+ """
100
+ Clear the instance cache. Useful for forcing fresh instances.
101
+
102
+ Args:
103
+ vector_name (str, optional): If provided, only clear cache for this vector_name
104
+ validate (bool, optional): If provided along with vector_name, clear specific cache entry
105
+ """
106
+ if vector_name is not None:
107
+ if validate is not None:
108
+ # Clear specific cache entry
109
+ cache_key = (vector_name, validate)
110
+ if cache_key in cls._instances:
111
+ del cls._instances[cache_key]
112
+ if cache_key in cls._instance_cache_time:
113
+ del cls._instance_cache_time[cache_key]
114
+ else:
115
+ # Clear all entries for this vector_name
116
+ keys_to_remove = [key for key in cls._instances.keys() if key[0] == vector_name]
117
+ for key in keys_to_remove:
118
+ del cls._instances[key]
119
+ if key in cls._instance_cache_time:
120
+ del cls._instance_cache_time[key]
121
+ else:
122
+ # Clear all cache
123
+ cls._instances.clear()
124
+ cls._instance_cache_time.clear()
125
+
126
+ @classmethod
127
+ def get_cached_instances(cls):
128
+ """
129
+ Get information about currently cached instances.
130
+
131
+ Returns:
132
+ dict: Dictionary with cache keys and their creation times
133
+ """
134
+ return {
135
+ key: cls._instance_cache_time.get(key)
136
+ for key in cls._instances.keys()
137
+ }
138
+
47
139
  def load_all_configs(self):
48
140
  """
49
141
  Load all configuration files from the specified directories into a dictionary.
@@ -251,4 +343,4 @@ class ConfigManager:
251
343
  if key in agents:
252
344
  return agents[key]
253
345
  else:
254
- return None
346
+ return None
@@ -0,0 +1,546 @@
1
+ Metadata-Version: 2.4
2
+ Name: sunholo
3
+ Version: 0.140.13
4
+ Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
5
+ Author-email: Holosun ApS <multivac@sunholo.com>
6
+ License: Apache License, Version 2.0
7
+ Project-URL: Homepage, https://github.com/sunholo-data/sunholo-py
8
+ Project-URL: Download, https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz
9
+ Keywords: llms,devops,google_cloud_platform
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Topic :: Software Development :: Build Tools
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE.txt
21
+ Requires-Dist: aiohttp
22
+ Requires-Dist: google-auth
23
+ Requires-Dist: ollama>=0.4.7
24
+ Requires-Dist: pillow>=11.0.0
25
+ Requires-Dist: pydantic
26
+ Requires-Dist: requests
27
+ Requires-Dist: ruamel.yaml
28
+ Requires-Dist: tenacity
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest; extra == "test"
31
+ Requires-Dist: pytest-cov; extra == "test"
32
+ Provides-Extra: all
33
+ Requires-Dist: aiofiles; extra == "all"
34
+ Requires-Dist: aiohttp; extra == "all"
35
+ Requires-Dist: anthropic[vertex]; extra == "all"
36
+ Requires-Dist: asyncpg; extra == "all"
37
+ Requires-Dist: azure-identity; extra == "all"
38
+ Requires-Dist: azure-storage-blob; extra == "all"
39
+ Requires-Dist: fastapi; extra == "all"
40
+ Requires-Dist: flask; extra == "all"
41
+ Requires-Dist: google-auth; extra == "all"
42
+ Requires-Dist: google-auth-httplib2; extra == "all"
43
+ Requires-Dist: google-auth-oauthlib; extra == "all"
44
+ Requires-Dist: google-cloud-aiplatform>=1.58.0; extra == "all"
45
+ Requires-Dist: google-api-python-client; extra == "all"
46
+ Requires-Dist: google-cloud-alloydb-connector[pg8000]; extra == "all"
47
+ Requires-Dist: google-cloud-bigquery; extra == "all"
48
+ Requires-Dist: google-cloud-build; extra == "all"
49
+ Requires-Dist: google-cloud-service-control; extra == "all"
50
+ Requires-Dist: google-cloud-logging; extra == "all"
51
+ Requires-Dist: google-cloud-storage; extra == "all"
52
+ Requires-Dist: google-cloud-pubsub; extra == "all"
53
+ Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "all"
54
+ Requires-Dist: google-cloud-texttospeech; extra == "all"
55
+ Requires-Dist: google-generativeai>=0.7.1; extra == "all"
56
+ Requires-Dist: google-genai>=0.2.2; extra == "all"
57
+ Requires-Dist: gunicorn; extra == "all"
58
+ Requires-Dist: httpcore; extra == "all"
59
+ Requires-Dist: httpx; extra == "all"
60
+ Requires-Dist: jsonschema; extra == "all"
61
+ Requires-Dist: lancedb; extra == "all"
62
+ Requires-Dist: langchain>=0.2.16; extra == "all"
63
+ Requires-Dist: langchain-experimental>=0.0.61; extra == "all"
64
+ Requires-Dist: langchain-community>=0.2.11; extra == "all"
65
+ Requires-Dist: langchain-openai>=0.3.2; extra == "all"
66
+ Requires-Dist: langchain-google-genai>=2.0.9; extra == "all"
67
+ Requires-Dist: langchain_google_alloydb_pg; extra == "all"
68
+ Requires-Dist: langchain-anthropic>=0.1.23; extra == "all"
69
+ Requires-Dist: langchain-google-vertexai; extra == "all"
70
+ Requires-Dist: langchain-unstructured; extra == "all"
71
+ Requires-Dist: langfuse; extra == "all"
72
+ Requires-Dist: mcp; extra == "all"
73
+ Requires-Dist: numpy; extra == "all"
74
+ Requires-Dist: opencv-python; extra == "all"
75
+ Requires-Dist: pg8000; extra == "all"
76
+ Requires-Dist: pgvector; extra == "all"
77
+ Requires-Dist: pillow; extra == "all"
78
+ Requires-Dist: playwright; extra == "all"
79
+ Requires-Dist: psutil; extra == "all"
80
+ Requires-Dist: psycopg2-binary; extra == "all"
81
+ Requires-Dist: pydantic; extra == "all"
82
+ Requires-Dist: pypdf; extra == "all"
83
+ Requires-Dist: python-hcl2; extra == "all"
84
+ Requires-Dist: python-socketio; extra == "all"
85
+ Requires-Dist: pytesseract; extra == "all"
86
+ Requires-Dist: requests; extra == "all"
87
+ Requires-Dist: rich; extra == "all"
88
+ Requires-Dist: sounddevice; extra == "all"
89
+ Requires-Dist: supabase; extra == "all"
90
+ Requires-Dist: tabulate; extra == "all"
91
+ Requires-Dist: tantivy; extra == "all"
92
+ Requires-Dist: tenacity; extra == "all"
93
+ Requires-Dist: tiktoken; extra == "all"
94
+ Requires-Dist: unstructured[all-docs,local-inference]; extra == "all"
95
+ Requires-Dist: xlwings; extra == "all"
96
+ Provides-Extra: langchain
97
+ Requires-Dist: langchain; extra == "langchain"
98
+ Requires-Dist: langchain_experimental; extra == "langchain"
99
+ Requires-Dist: langchain-community; extra == "langchain"
100
+ Requires-Dist: langsmith; extra == "langchain"
101
+ Requires-Dist: langchain-unstructured; extra == "langchain"
102
+ Provides-Extra: azure
103
+ Requires-Dist: azure-identity; extra == "azure"
104
+ Requires-Dist: azure-storage-blob; extra == "azure"
105
+ Provides-Extra: cli
106
+ Requires-Dist: jsonschema>=4.21.1; extra == "cli"
107
+ Requires-Dist: rich; extra == "cli"
108
+ Provides-Extra: database
109
+ Requires-Dist: asyncpg; extra == "database"
110
+ Requires-Dist: supabase; extra == "database"
111
+ Requires-Dist: sqlalchemy; extra == "database"
112
+ Requires-Dist: pg8000; extra == "database"
113
+ Requires-Dist: pgvector; extra == "database"
114
+ Requires-Dist: psycopg2-binary; extra == "database"
115
+ Requires-Dist: lancedb; extra == "database"
116
+ Requires-Dist: tantivy; extra == "database"
117
+ Provides-Extra: pipeline
118
+ Requires-Dist: GitPython; extra == "pipeline"
119
+ Requires-Dist: lark; extra == "pipeline"
120
+ Requires-Dist: langchain>=0.2.16; extra == "pipeline"
121
+ Requires-Dist: langchain-unstructured; extra == "pipeline"
122
+ Requires-Dist: psutil; extra == "pipeline"
123
+ Requires-Dist: pypdf; extra == "pipeline"
124
+ Requires-Dist: pytesseract; extra == "pipeline"
125
+ Requires-Dist: tabulate; extra == "pipeline"
126
+ Requires-Dist: unstructured[all-docs,local-inference]; extra == "pipeline"
127
+ Provides-Extra: gcp
128
+ Requires-Dist: aiofiles; extra == "gcp"
129
+ Requires-Dist: anthropic[vertex]; extra == "gcp"
130
+ Requires-Dist: google-api-python-client; extra == "gcp"
131
+ Requires-Dist: google-auth-httplib2; extra == "gcp"
132
+ Requires-Dist: google-auth-oauthlib; extra == "gcp"
133
+ Requires-Dist: google-cloud-alloydb-connector[pg8000]; extra == "gcp"
134
+ Requires-Dist: google-cloud-aiplatform>=1.58.0; extra == "gcp"
135
+ Requires-Dist: google-cloud-bigquery; extra == "gcp"
136
+ Requires-Dist: google-cloud-build; extra == "gcp"
137
+ Requires-Dist: google-cloud-service-control; extra == "gcp"
138
+ Requires-Dist: google-cloud-storage; extra == "gcp"
139
+ Requires-Dist: google-cloud-logging; extra == "gcp"
140
+ Requires-Dist: google-cloud-pubsub; extra == "gcp"
141
+ Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "gcp"
142
+ Requires-Dist: google-cloud-texttospeech; extra == "gcp"
143
+ Requires-Dist: google-genai>=0.2.2; extra == "gcp"
144
+ Requires-Dist: google-generativeai>=0.8.3; extra == "gcp"
145
+ Requires-Dist: langchain; extra == "gcp"
146
+ Requires-Dist: langchain-google-genai>=2.0.0; extra == "gcp"
147
+ Requires-Dist: langchain_google_alloydb_pg>=0.2.2; extra == "gcp"
148
+ Requires-Dist: langchain-google-vertexai; extra == "gcp"
149
+ Requires-Dist: pillow; extra == "gcp"
150
+ Provides-Extra: ollama
151
+ Requires-Dist: pillow; extra == "ollama"
152
+ Requires-Dist: ollama>=0.4.7; extra == "ollama"
153
+ Provides-Extra: openai
154
+ Requires-Dist: langchain-openai>=0.3.2; extra == "openai"
155
+ Requires-Dist: tiktoken; extra == "openai"
156
+ Provides-Extra: anthropic
157
+ Requires-Dist: langchain-anthropic>=0.1.23; extra == "anthropic"
158
+ Requires-Dist: mcp; extra == "anthropic"
159
+ Provides-Extra: tools
160
+ Requires-Dist: openapi-spec-validator; extra == "tools"
161
+ Requires-Dist: playwright; extra == "tools"
162
+ Provides-Extra: http
163
+ Requires-Dist: fastapi; extra == "http"
164
+ Requires-Dist: flask; extra == "http"
165
+ Requires-Dist: gunicorn; extra == "http"
166
+ Requires-Dist: httpcore; extra == "http"
167
+ Requires-Dist: httpx; extra == "http"
168
+ Requires-Dist: langchain; extra == "http"
169
+ Requires-Dist: langfuse; extra == "http"
170
+ Requires-Dist: python-socketio; extra == "http"
171
+ Requires-Dist: requests; extra == "http"
172
+ Requires-Dist: tenacity; extra == "http"
173
+ Provides-Extra: excel
174
+ Requires-Dist: xlwings; extra == "excel"
175
+ Requires-Dist: requests; extra == "excel"
176
+ Requires-Dist: rich; extra == "excel"
177
+ Provides-Extra: iac
178
+ Requires-Dist: python-hcl2; extra == "iac"
179
+ Provides-Extra: tts
180
+ Requires-Dist: google-cloud-texttospeech; extra == "tts"
181
+ Requires-Dist: numpy; extra == "tts"
182
+ Requires-Dist: sounddevice; extra == "tts"
183
+ Provides-Extra: video
184
+ Requires-Dist: opencv-python; extra == "video"
185
+ Dynamic: license-file
186
+
187
+ # Sunholo Python Library
188
+
189
+ [![PyPi Version](https://img.shields.io/pypi/v/sunholo.svg)](https://pypi.python.org/pypi/sunholo/)
190
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
191
+ [![Python Version](https://img.shields.io/pypi/pyversions/sunholo.svg)](https://pypi.python.org/pypi/sunholo/)
192
+
193
+ 🚀 **AI DevOps framework for building GenAI applications on Google Cloud Platform**
194
+
195
+ Sunholo is a comprehensive Python framework that streamlines the development, deployment, and management of Generative AI applications (VACs - Virtual Agent Computers). It provides a configuration-driven approach with deep integration into Google Cloud services while supporting multiple AI providers.
196
+
197
+ ## 🎯 What is Sunholo?
198
+
199
+ Sunholo helps you:
200
+ - 🤖 Build conversational AI agents with any LLM provider (Vertex AI, OpenAI, Anthropic, Ollama)
201
+ - ☁️ Deploy to Google Cloud Run with automatic scaling
202
+ - 🗄️ Use AlloyDB and Discovery Engine for vector storage and search
203
+ - 🔄 Handle streaming responses and async processing
204
+ - 📄 Process documents with chunking and embedding pipelines
205
+ - 🔧 Manage complex configurations with YAML files
206
+ - 🎨 Create APIs, web apps, and chat bots
207
+
208
+ ## 🚀 Quick Start
209
+
210
+ ### Prerequisites
211
+
212
+ Install [uv](https://docs.astral.sh/uv/) - a fast, modern Python package manager:
213
+
214
+ ```bash
215
+ # macOS/Linux
216
+ curl -LsSf https://astral.sh/uv/install.sh | sh
217
+
218
+ # Windows
219
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
220
+ ```
221
+
222
+ ### Installation
223
+
224
+ ```bash
225
+ # Install with CLI tools (recommended)
226
+ uv tool install --from "sunholo[cli]" sunholo
227
+
228
+ # Install with all features including GCP
229
+ uv tool install --from "sunholo[cli]" sunholo --with "sunholo[all]"
230
+ ```
231
+
232
+ ### Your First VAC
233
+
234
+ 1. **Initialize a new project:**
235
+ ```bash
236
+ sunholo init my-ai-agent
237
+ cd my-ai-agent
238
+ ```
239
+
240
+ 2. **Configure your AI agent:**
241
+ Edit `config/vac_config.yaml`:
242
+ ```yaml
243
+ kind: vacConfig
244
+ apiVersion: v1
245
+ vac:
246
+ my-agent:
247
+ llm: vertex
248
+ model: gemini-1.5-pro
249
+ agent: simple
250
+ description: "My AI agent powered by Google Cloud"
251
+ ```
252
+
253
+ 3. **Chat with your agent locally:**
254
+ ```bash
255
+ sunholo vac chat my-agent
256
+ ```
257
+
258
+ 4. **Deploy to Google Cloud Run:**
259
+ ```bash
260
+ sunholo deploy my-agent
261
+ ```
262
+
263
+ ## 📋 Features
264
+
265
+ ### Core Capabilities
266
+
267
+ - **Multi-Model Support**: Integrate Vertex AI, OpenAI, Anthropic, Ollama in one app
268
+ - **Document Processing**: Chunk, embed, and index documents with Discovery Engine
269
+ - **Vector Databases**: Native support for AlloyDB, LanceDB, Supabase
270
+ - **Streaming**: Real-time response streaming for chat applications
271
+ - **Async Processing**: Pub/Sub integration for background tasks
272
+ - **Authentication**: Built-in Google Cloud IAM and custom auth
273
+
274
+ ### Google Cloud Integration
275
+
276
+ - **Vertex AI**: Access Gemini, PaLM, and custom models
277
+ - **AlloyDB**: PostgreSQL-compatible vector database
278
+ - **Discovery Engine**: Enterprise search and RAG
279
+ - **Cloud Run**: Serverless deployment
280
+ - **Cloud Storage**: Document and file management
281
+ - **Pub/Sub**: Asynchronous message processing
282
+ - **Cloud Logging**: Centralized logging
283
+
284
+ ### Framework Support
285
+
286
+ - **Web Frameworks**: Flask and FastAPI templates
287
+ - **AI Frameworks**: LangChain and LlamaIndex integration
288
+ - **Observability**: Langfuse for tracing and monitoring
289
+ - **API Standards**: OpenAI-compatible endpoints
290
+
291
+ ## 🛠 Installation Options
292
+
293
+ ### Using uv
294
+
295
+ ```bash
296
+ # Core CLI features
297
+ uv tool install --from "sunholo[cli]" sunholo
298
+
299
+ # With Google Cloud Platform integration
300
+ uv tool install --from "sunholo[cli]" sunholo --with "sunholo[gcp]"
301
+
302
+ # With specific LLM providers
303
+ uv tool install --from "sunholo[cli]" sunholo --with "sunholo[openai]"
304
+ uv tool install --from "sunholo[cli]" sunholo --with "sunholo[anthropic]"
305
+
306
+ # With database support
307
+ uv tool install --from "sunholo[cli]" sunholo --with "sunholo[database]"
308
+
309
+ # Everything
310
+ uv tool install --from "sunholo[cli]" sunholo --with "sunholo[all]"
311
+ ```
312
+
313
+ ### Managing Installations
314
+
315
+ ```bash
316
+ # Upgrade
317
+ uv tool upgrade sunholo
318
+
319
+ # List installed
320
+ uv tool list
321
+
322
+ # Uninstall
323
+ uv tool uninstall sunholo
324
+ ```
325
+
326
+ ### Development Setup
327
+
328
+ ```bash
329
+ # Clone repository
330
+ git clone https://github.com/sunholo-data/sunholo-py.git
331
+ cd sunholo-py
332
+
333
+ # Install in development mode
334
+ uv venv
335
+ uv pip install -e ".[all]"
336
+
337
+ # Run tests
338
+ pytest tests/
339
+ ```
340
+
341
+ ## ⚙️ Configuration
342
+
343
+ Sunholo uses YAML configuration files:
344
+
345
+ ```yaml
346
+ # config/vac_config.yaml
347
+ kind: vacConfig
348
+ apiVersion: v1
349
+ gcp_config:
350
+ project_id: my-gcp-project
351
+ location: us-central1
352
+ vac:
353
+ my-agent:
354
+ llm: vertex
355
+ model: gemini-1.5-pro
356
+ agent: langchain
357
+ memory:
358
+ - alloydb:
359
+ project_id: my-gcp-project
360
+ region: us-central1
361
+ cluster: my-cluster
362
+ instance: my-instance
363
+ tools:
364
+ - search
365
+ - calculator
366
+ ```
367
+
368
+ ## 🔧 CLI Commands
369
+
370
+ ```bash
371
+ # Project Management
372
+ sunholo init <project-name> # Create new project
373
+ sunholo list-configs # List all configurations
374
+ sunholo list-configs --validate # Validate configs
375
+
376
+ # Development
377
+ sunholo vac chat <vac-name> # Chat with a VAC locally
378
+ sunholo vac list # List available VACs
379
+ sunholo proxy start <service> # Start local proxy to cloud service
380
+
381
+ # Deployment
382
+ sunholo deploy <vac-name> # Deploy to Cloud Run
383
+ sunholo deploy <vac-name> --dev # Deploy to dev environment
384
+
385
+ # Document Processing
386
+ sunholo embed <vac-name> # Embed documents
387
+ sunholo merge-text <folder> <output> # Merge files for context
388
+
389
+ # Cloud Services
390
+ sunholo discovery-engine create <name> # Create Discovery Engine
391
+ sunholo proxy list # List running proxies
392
+ ```
393
+
394
+ ## 📝 Examples
395
+
396
+ ### Chat with History Extraction
397
+
398
+ ```python
399
+ from sunholo.utils import ConfigManager
400
+ from sunholo.components import pick_llm
401
+ from sunholo.agents import extract_chat_history
402
+
403
+ config = ConfigManager('my-agent')
404
+ llm = pick_llm(config=config)
405
+
406
+ # Extract chat history from messages
407
+ chat_history = [
408
+ {"role": "user", "content": "Hello"},
409
+ {"role": "assistant", "content": "Hi there!"}
410
+ ]
411
+ history_str = extract_chat_history(chat_history)
412
+
413
+ # Use in prompt
414
+ response = llm.invoke(f"Given this history:\n{history_str}\n\nUser: How are you?")
415
+ ```
416
+
417
+ ### Document Processing with Chunker
418
+
419
+ ```python
420
+ from sunholo.chunker import direct_file_to_embed
421
+ from sunholo.utils import ConfigManager
422
+
423
+ config = ConfigManager('my-agent')
424
+
425
+ # Process a file directly
426
+ result = direct_file_to_embed(
427
+ "document.pdf",
428
+ embed_prefix="doc",
429
+ metadata={"source": "user_upload"},
430
+ vectorstore=config.vacConfig("vectorstore")
431
+ )
432
+ ```
433
+
434
+ ### Vertex AI with Memory Tools
435
+
436
+ ```python
437
+ from sunholo.vertex import get_vertex_memories
438
+ from sunholo.utils import ConfigManager
439
+
440
+ config = ConfigManager('my-agent')
441
+
442
+ # Get Vertex AI memory configuration
443
+ memory_config = get_vertex_memories(config)
444
+
445
+ # Use with Vertex AI
446
+ if memory_config:
447
+ print(f"Memory tools configured: {memory_config}")
448
+ ```
449
+
450
+ ### Streaming Response with Flask
451
+
452
+ ```python
453
+ from sunholo.agents import send_to_qa
454
+ from flask import Response, request
455
+
456
+ @app.route('/vac/streaming/<vac_name>', methods=['POST'])
457
+ def streaming_endpoint(vac_name):
458
+ question = request.json.get('user_input')
459
+
460
+ def generate():
461
+ # Stream responses from the QA system
462
+ response = send_to_qa(
463
+ question,
464
+ vac_name=vac_name,
465
+ stream=True
466
+ )
467
+ if hasattr(response, '__iter__'):
468
+ for chunk in response:
469
+ yield f"data: {chunk}\n\n"
470
+ else:
471
+ yield f"data: {response}\n\n"
472
+
473
+ return Response(generate(), content_type='text/event-stream')
474
+ ```
475
+
476
+ ### Discovery Engine Integration
477
+
478
+ ```python
479
+ from sunholo.discovery_engine import DiscoveryEngineClient
480
+
481
+ # Initialize client
482
+ client = DiscoveryEngineClient(
483
+ project_id='my-project',
484
+ data_store_id='my-datastore'
485
+ )
486
+
487
+ # Search documents
488
+ results = client.search("What is Vertex AI?")
489
+ for result in results:
490
+ print(f"Content: {result.chunk.content}")
491
+ print(f"Score: {result.relevance_score}")
492
+ ```
493
+
494
+ ## 🧪 Testing
495
+
496
+ ```bash
497
+ # Run all tests
498
+ pytest tests/
499
+
500
+ # Run specific test file
501
+ pytest tests/test_config.py
502
+
503
+ # Run with coverage
504
+ pytest --cov=src/sunholo tests/
505
+
506
+ # Run async tests
507
+ pytest tests/test_async_genai2.py
508
+ ```
509
+
510
+ ## 📚 Documentation
511
+
512
+ - 📖 **Full Documentation**: https://dev.sunholo.com/
513
+ - 🎓 **Tutorials**: https://dev.sunholo.com/docs/howto/
514
+ - 🤖 **VAC Examples**: https://github.com/sunholo-data/vacs-public
515
+ - 🎧 **Audio Overview**: [Listen to the NotebookLM podcast](https://drive.google.com/file/d/1GvwRmiYDjPjN2hXQ8plhnVDByu6TmgCQ/view?usp=drive_link)
516
+
517
+ ## 🤝 Contributing
518
+
519
+ We welcome contributions! See our [Contributing Guidelines](CONTRIBUTING.md).
520
+
521
+ 1. Fork the repository
522
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
523
+ 3. Commit your changes (`git commit -m 'Add AmazingFeature'`)
524
+ 4. Push to the branch (`git push origin feature/AmazingFeature`)
525
+ 5. Open a Pull Request
526
+
527
+ ## 📜 License
528
+
529
+ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE.txt) file for details.
530
+
531
+ ```
532
+ Copyright [2024] [Holosun ApS]
533
+
534
+ Licensed under the Apache License, Version 2.0 (the "License");
535
+ you may not use this file except in compliance with the License.
536
+ You may obtain a copy of the License at
537
+
538
+ http://www.apache.org/licenses/LICENSE-2.0
539
+ ```
540
+
541
+ ## 🙏 Support
542
+
543
+ - 📧 Email: multivac@sunholo.com
544
+ - 🐛 Issues: [GitHub Issues](https://github.com/sunholo-data/sunholo-py/issues)
545
+ - 💬 Discussions: [GitHub Discussions](https://github.com/sunholo-data/sunholo-py/discussions)
546
+ - 📖 Documentation: https://dev.sunholo.com/
@@ -2,7 +2,7 @@ sunholo/__init__.py,sha256=InRbX4V0-qdNHo9zYH3GEye7ASLR6LX8-SMvPV4Jsaw,1212
2
2
  sunholo/custom_logging.py,sha256=JXZTnXp_DixP3jwYfKw4LYRDS9IuTq7ctCgfZbI2rxA,22023
3
3
  sunholo/langchain_types.py,sha256=uZ4zvgej_f7pLqjtu4YP7qMC_eZD5ym_5x4pyvA1Ih4,1834
4
4
  sunholo/agents/__init__.py,sha256=AauG3l0y4r5Fzx1zJfZ634M4o-0o7B7J5T8k_gPvNqE,370
5
- sunholo/agents/chat_history.py,sha256=e2NmiooaRUxKGr_aoU05rzhHi3VsKjbZZmzeDr2yJJE,17780
5
+ sunholo/agents/chat_history.py,sha256=gRuIUyU-53A72Q17SmSgf6Ok3YO8hKAZhsc64976018,17782
6
6
  sunholo/agents/dispatch_to_qa.py,sha256=NHihwAoCJ5_Lk11e_jZnucVUGQyZHCB-YpkfMHBCpQk,8882
7
7
  sunholo/agents/langserve.py,sha256=C46ph2mnygr6bdHijYWYyfQDI9ylAF0_9Kx2PfcCJpU,4414
8
8
  sunholo/agents/pubsub.py,sha256=TscZN_6am6DfaQkC-Yl18ZIBOoLE-0nDSiil6GpQEh4,1344
@@ -151,7 +151,7 @@ sunholo/utils/__init__.py,sha256=Hv02T5L2zYWvCso5hzzwm8FQogwBq0OgtUbN_7Quzqc,89
151
151
  sunholo/utils/api_key.py,sha256=Ct4bIAQZxzPEw14hP586LpVxBAVi_W9Serpy0BK-7KI,244
152
152
  sunholo/utils/big_context.py,sha256=HuP9_r_Nx1jvZHxjMEihgoZAXmnCh80zzsj1fq3mIOg,6021
153
153
  sunholo/utils/config.py,sha256=OeYr4UvhRv94e1OVFab4kuQa5TdbbljQjvhnGlkDewM,9160
154
- sunholo/utils/config_class.py,sha256=uSRiJLj8t5UgWNxaq8W4KPnzxb4SkUJ1avXecDHuP-E,9768
154
+ sunholo/utils/config_class.py,sha256=U0xwyCz68KCJgzyhXd0AmbFnstMBFvZMedb-lLKKa5Q,13363
155
155
  sunholo/utils/config_schema.py,sha256=Wv-ncitzljOhgbDaq9qnFqH5LCuxNv59dTGDWgd1qdk,4189
156
156
  sunholo/utils/gcp.py,sha256=lus1HH8YhFInw6QRKwfvKZq-Lz-2KQg4ips9v1I_3zE,4783
157
157
  sunholo/utils/gcp_project.py,sha256=Fa0IhCX12bZ1ctF_PKN8PNYd7hihEUfb90kilBfUDjg,1411
@@ -168,9 +168,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
168
168
  sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
169
169
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
170
170
  sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
171
- sunholo-0.140.11.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
172
- sunholo-0.140.11.dist-info/METADATA,sha256=5dpzFz8d_yiaY_tXGWGUsAxCHy2TPeQcHcooeZzbIcE,10068
173
- sunholo-0.140.11.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
174
- sunholo-0.140.11.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
175
- sunholo-0.140.11.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
176
- sunholo-0.140.11.dist-info/RECORD,,
171
+ sunholo-0.140.13.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
172
+ sunholo-0.140.13.dist-info/METADATA,sha256=4GvUi1znwq6b_Ohjtx4uMQMQH6o-DWF4mNg1so-sQhM,17843
173
+ sunholo-0.140.13.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
174
+ sunholo-0.140.13.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
175
+ sunholo-0.140.13.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
176
+ sunholo-0.140.13.dist-info/RECORD,,
@@ -1,249 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: sunholo
3
- Version: 0.140.11
4
- Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
5
- Author-email: Holosun ApS <multivac@sunholo.com>
6
- License: Apache License, Version 2.0
7
- Project-URL: Homepage, https://github.com/sunholo-data/sunholo-py
8
- Project-URL: Download, https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz
9
- Keywords: llms,devops,google_cloud_platform
10
- Classifier: Development Status :: 3 - Alpha
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Topic :: Software Development :: Build Tools
13
- Classifier: License :: OSI Approved :: Apache Software License
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Requires-Python: >=3.10
19
- Description-Content-Type: text/markdown
20
- License-File: LICENSE.txt
21
- Requires-Dist: aiohttp
22
- Requires-Dist: google-auth
23
- Requires-Dist: ollama>=0.4.7
24
- Requires-Dist: pillow>=11.0.0
25
- Requires-Dist: pydantic
26
- Requires-Dist: requests
27
- Requires-Dist: ruamel.yaml
28
- Requires-Dist: tenacity
29
- Provides-Extra: test
30
- Requires-Dist: pytest; extra == "test"
31
- Requires-Dist: pytest-cov; extra == "test"
32
- Provides-Extra: all
33
- Requires-Dist: aiofiles; extra == "all"
34
- Requires-Dist: aiohttp; extra == "all"
35
- Requires-Dist: anthropic[vertex]; extra == "all"
36
- Requires-Dist: asyncpg; extra == "all"
37
- Requires-Dist: azure-identity; extra == "all"
38
- Requires-Dist: azure-storage-blob; extra == "all"
39
- Requires-Dist: fastapi; extra == "all"
40
- Requires-Dist: flask; extra == "all"
41
- Requires-Dist: google-auth; extra == "all"
42
- Requires-Dist: google-auth-httplib2; extra == "all"
43
- Requires-Dist: google-auth-oauthlib; extra == "all"
44
- Requires-Dist: google-cloud-aiplatform>=1.58.0; extra == "all"
45
- Requires-Dist: google-api-python-client; extra == "all"
46
- Requires-Dist: google-cloud-alloydb-connector[pg8000]; extra == "all"
47
- Requires-Dist: google-cloud-bigquery; extra == "all"
48
- Requires-Dist: google-cloud-build; extra == "all"
49
- Requires-Dist: google-cloud-service-control; extra == "all"
50
- Requires-Dist: google-cloud-logging; extra == "all"
51
- Requires-Dist: google-cloud-storage; extra == "all"
52
- Requires-Dist: google-cloud-pubsub; extra == "all"
53
- Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "all"
54
- Requires-Dist: google-cloud-texttospeech; extra == "all"
55
- Requires-Dist: google-generativeai>=0.7.1; extra == "all"
56
- Requires-Dist: google-genai>=0.2.2; extra == "all"
57
- Requires-Dist: gunicorn; extra == "all"
58
- Requires-Dist: httpcore; extra == "all"
59
- Requires-Dist: httpx; extra == "all"
60
- Requires-Dist: jsonschema; extra == "all"
61
- Requires-Dist: lancedb; extra == "all"
62
- Requires-Dist: langchain>=0.2.16; extra == "all"
63
- Requires-Dist: langchain-experimental>=0.0.61; extra == "all"
64
- Requires-Dist: langchain-community>=0.2.11; extra == "all"
65
- Requires-Dist: langchain-openai>=0.3.2; extra == "all"
66
- Requires-Dist: langchain-google-genai>=2.0.9; extra == "all"
67
- Requires-Dist: langchain_google_alloydb_pg; extra == "all"
68
- Requires-Dist: langchain-anthropic>=0.1.23; extra == "all"
69
- Requires-Dist: langchain-google-vertexai; extra == "all"
70
- Requires-Dist: langchain-unstructured; extra == "all"
71
- Requires-Dist: langfuse; extra == "all"
72
- Requires-Dist: mcp; extra == "all"
73
- Requires-Dist: numpy; extra == "all"
74
- Requires-Dist: opencv-python; extra == "all"
75
- Requires-Dist: pg8000; extra == "all"
76
- Requires-Dist: pgvector; extra == "all"
77
- Requires-Dist: pillow; extra == "all"
78
- Requires-Dist: playwright; extra == "all"
79
- Requires-Dist: psutil; extra == "all"
80
- Requires-Dist: psycopg2-binary; extra == "all"
81
- Requires-Dist: pydantic; extra == "all"
82
- Requires-Dist: pypdf; extra == "all"
83
- Requires-Dist: python-hcl2; extra == "all"
84
- Requires-Dist: python-socketio; extra == "all"
85
- Requires-Dist: pytesseract; extra == "all"
86
- Requires-Dist: requests; extra == "all"
87
- Requires-Dist: rich; extra == "all"
88
- Requires-Dist: sounddevice; extra == "all"
89
- Requires-Dist: supabase; extra == "all"
90
- Requires-Dist: tabulate; extra == "all"
91
- Requires-Dist: tantivy; extra == "all"
92
- Requires-Dist: tenacity; extra == "all"
93
- Requires-Dist: tiktoken; extra == "all"
94
- Requires-Dist: unstructured[all-docs,local-inference]; extra == "all"
95
- Requires-Dist: xlwings; extra == "all"
96
- Provides-Extra: langchain
97
- Requires-Dist: langchain; extra == "langchain"
98
- Requires-Dist: langchain_experimental; extra == "langchain"
99
- Requires-Dist: langchain-community; extra == "langchain"
100
- Requires-Dist: langsmith; extra == "langchain"
101
- Requires-Dist: langchain-unstructured; extra == "langchain"
102
- Provides-Extra: azure
103
- Requires-Dist: azure-identity; extra == "azure"
104
- Requires-Dist: azure-storage-blob; extra == "azure"
105
- Provides-Extra: cli
106
- Requires-Dist: jsonschema>=4.21.1; extra == "cli"
107
- Requires-Dist: rich; extra == "cli"
108
- Provides-Extra: database
109
- Requires-Dist: asyncpg; extra == "database"
110
- Requires-Dist: supabase; extra == "database"
111
- Requires-Dist: sqlalchemy; extra == "database"
112
- Requires-Dist: pg8000; extra == "database"
113
- Requires-Dist: pgvector; extra == "database"
114
- Requires-Dist: psycopg2-binary; extra == "database"
115
- Requires-Dist: lancedb; extra == "database"
116
- Requires-Dist: tantivy; extra == "database"
117
- Provides-Extra: pipeline
118
- Requires-Dist: GitPython; extra == "pipeline"
119
- Requires-Dist: lark; extra == "pipeline"
120
- Requires-Dist: langchain>=0.2.16; extra == "pipeline"
121
- Requires-Dist: langchain-unstructured; extra == "pipeline"
122
- Requires-Dist: psutil; extra == "pipeline"
123
- Requires-Dist: pypdf; extra == "pipeline"
124
- Requires-Dist: pytesseract; extra == "pipeline"
125
- Requires-Dist: tabulate; extra == "pipeline"
126
- Requires-Dist: unstructured[all-docs,local-inference]; extra == "pipeline"
127
- Provides-Extra: gcp
128
- Requires-Dist: aiofiles; extra == "gcp"
129
- Requires-Dist: anthropic[vertex]; extra == "gcp"
130
- Requires-Dist: google-api-python-client; extra == "gcp"
131
- Requires-Dist: google-auth-httplib2; extra == "gcp"
132
- Requires-Dist: google-auth-oauthlib; extra == "gcp"
133
- Requires-Dist: google-cloud-alloydb-connector[pg8000]; extra == "gcp"
134
- Requires-Dist: google-cloud-aiplatform>=1.58.0; extra == "gcp"
135
- Requires-Dist: google-cloud-bigquery; extra == "gcp"
136
- Requires-Dist: google-cloud-build; extra == "gcp"
137
- Requires-Dist: google-cloud-service-control; extra == "gcp"
138
- Requires-Dist: google-cloud-storage; extra == "gcp"
139
- Requires-Dist: google-cloud-logging; extra == "gcp"
140
- Requires-Dist: google-cloud-pubsub; extra == "gcp"
141
- Requires-Dist: google-cloud-discoveryengine>=0.13.4; extra == "gcp"
142
- Requires-Dist: google-cloud-texttospeech; extra == "gcp"
143
- Requires-Dist: google-genai>=0.2.2; extra == "gcp"
144
- Requires-Dist: google-generativeai>=0.8.3; extra == "gcp"
145
- Requires-Dist: langchain; extra == "gcp"
146
- Requires-Dist: langchain-google-genai>=2.0.0; extra == "gcp"
147
- Requires-Dist: langchain_google_alloydb_pg>=0.2.2; extra == "gcp"
148
- Requires-Dist: langchain-google-vertexai; extra == "gcp"
149
- Requires-Dist: pillow; extra == "gcp"
150
- Provides-Extra: ollama
151
- Requires-Dist: pillow; extra == "ollama"
152
- Requires-Dist: ollama>=0.4.7; extra == "ollama"
153
- Provides-Extra: openai
154
- Requires-Dist: langchain-openai>=0.3.2; extra == "openai"
155
- Requires-Dist: tiktoken; extra == "openai"
156
- Provides-Extra: anthropic
157
- Requires-Dist: langchain-anthropic>=0.1.23; extra == "anthropic"
158
- Requires-Dist: mcp; extra == "anthropic"
159
- Provides-Extra: tools
160
- Requires-Dist: openapi-spec-validator; extra == "tools"
161
- Requires-Dist: playwright; extra == "tools"
162
- Provides-Extra: http
163
- Requires-Dist: fastapi; extra == "http"
164
- Requires-Dist: flask; extra == "http"
165
- Requires-Dist: gunicorn; extra == "http"
166
- Requires-Dist: httpcore; extra == "http"
167
- Requires-Dist: httpx; extra == "http"
168
- Requires-Dist: langchain; extra == "http"
169
- Requires-Dist: langfuse; extra == "http"
170
- Requires-Dist: python-socketio; extra == "http"
171
- Requires-Dist: requests; extra == "http"
172
- Requires-Dist: tenacity; extra == "http"
173
- Provides-Extra: excel
174
- Requires-Dist: xlwings; extra == "excel"
175
- Requires-Dist: requests; extra == "excel"
176
- Requires-Dist: rich; extra == "excel"
177
- Provides-Extra: iac
178
- Requires-Dist: python-hcl2; extra == "iac"
179
- Provides-Extra: tts
180
- Requires-Dist: google-cloud-texttospeech; extra == "tts"
181
- Requires-Dist: numpy; extra == "tts"
182
- Requires-Dist: sounddevice; extra == "tts"
183
- Provides-Extra: video
184
- Requires-Dist: opencv-python; extra == "video"
185
- Dynamic: license-file
186
-
187
- [![PyPi Version](https://img.shields.io/pypi/v/sunholo.svg)](https://pypi.python.org/pypi/sunholo/)
188
-
189
- ## Introduction
190
- This is the Sunholo Python project, a comprehensive toolkit for working with language models and vector stores on Google Cloud Platform. It provides a wide range of functionalities and utilities to facilitate the development and deployment of language model applications.
191
-
192
- Please refer to the website for full documentation at https://dev.sunholo.com/
193
-
194
- ## Listen to the audio file:
195
-
196
- A [NotebookLM](https://notebooklm.google/) generated podcast of the codebase that may help give you an overview of what the library is capable of:
197
-
198
- [Listen to the audio file from Google Drive](https://drive.google.com/file/d/1GvwRmiYDjPjN2hXQ8plhnVDByu6TmgCQ/view?usp=drive_link) or on the website at https://dev.sunholo.com/docs/
199
-
200
- > "Ever wish you could build your own AI?..."
201
-
202
- ## Tests via pytest
203
-
204
- If loading from GitHub, run tests:
205
-
206
- ```bash
207
- pip install pytest
208
- pip install . --use-feature=in-tree-build
209
- pytest tests
210
- ```
211
-
212
- ## Local dev
213
-
214
- ```sh
215
- uv tool install --from "sunholo[cli]" sunholo --with ".[all]"
216
- ```
217
-
218
- ## Demos
219
-
220
- Using https://github.com/charmbracelet/vhs
221
-
222
- ```sh
223
- vhs record > cassette.tape
224
- ```
225
-
226
- Then make gif:
227
-
228
- ```sh
229
- vhs docs/tapes/config-list.tape
230
- ```
231
-
232
-
233
-
234
- ```
235
- Copyright [2024] [Holosun ApS]
236
-
237
- Licensed under the Apache License, Version 2.0 (the "License");
238
- you may not use this file except in compliance with the License.
239
- You may obtain a copy of the License at
240
-
241
- http://www.apache.org/licenses/LICENSE-2.0
242
-
243
- Unless required by applicable law or agreed to in writing, software
244
- distributed under the License is distributed on an "AS IS" BASIS,
245
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
246
- See the License for the specific language governing permissions and
247
- limitations under the License.
248
- ```
249
-