synth-ai 0.1.3__py3-none-any.whl → 0.1.5__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.
- synth_ai/__init__.py +1 -3
- synth_ai/zyk/lms/core/all.py +6 -0
- synth_ai/zyk/lms/core/vendor_clients.py +17 -4
- synth_ai/zyk/lms/tools/__init__.py +3 -0
- synth_ai/zyk/lms/vendors/supported/custom_endpoint.py +394 -0
- synth_ai-0.1.5.dist-info/METADATA +37 -0
- {synth_ai-0.1.3.dist-info → synth_ai-0.1.5.dist-info}/RECORD +10 -8
- synth_ai-0.1.3.dist-info/METADATA +0 -67
- {synth_ai-0.1.3.dist-info → synth_ai-0.1.5.dist-info}/WHEEL +0 -0
- {synth_ai-0.1.3.dist-info → synth_ai-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.1.3.dist-info → synth_ai-0.1.5.dist-info}/top_level.txt +0 -0
synth_ai/__init__.py
CHANGED
@@ -2,9 +2,7 @@
|
|
2
2
|
Synth AI - Software for aiding the best and multiplying the will.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from importlib.metadata import version
|
6
|
-
|
7
5
|
from synth_ai.zyk import LM # Assuming LM is in zyk.py in the same directory
|
8
6
|
|
9
|
-
__version__ =
|
7
|
+
__version__ = "0.1.5"
|
10
8
|
__all__ = ["LM"] # Explicitly define public API
|
synth_ai/zyk/lms/core/all.py
CHANGED
@@ -8,6 +8,7 @@ from synth_ai.zyk.lms.vendors.supported.deepseek import DeepSeekAPI
|
|
8
8
|
from synth_ai.zyk.lms.vendors.supported.together import TogetherAPI
|
9
9
|
from synth_ai.zyk.lms.vendors.supported.groq import GroqAPI
|
10
10
|
from synth_ai.zyk.lms.vendors.core.mistral_api import MistralAPI
|
11
|
+
from synth_ai.zyk.lms.vendors.supported.custom_endpoint import CustomEndpointAPI
|
11
12
|
|
12
13
|
|
13
14
|
class OpenAIClient(OpenAIPrivate):
|
@@ -45,3 +46,8 @@ class GroqClient(GroqAPI):
|
|
45
46
|
class MistralClient(MistralAPI):
|
46
47
|
def __init__(self):
|
47
48
|
super().__init__()
|
49
|
+
|
50
|
+
|
51
|
+
class CustomEndpointClient(CustomEndpointAPI):
|
52
|
+
def __init__(self, endpoint_url: str):
|
53
|
+
super().__init__(endpoint_url=endpoint_url)
|
@@ -5,11 +5,12 @@ from synth_ai.zyk.lms.core.all import (
|
|
5
5
|
AnthropicClient,
|
6
6
|
DeepSeekClient,
|
7
7
|
GeminiClient,
|
8
|
-
|
9
|
-
|
8
|
+
GroqClient,
|
9
|
+
MistralClient,
|
10
10
|
# OpenAIClient,
|
11
11
|
OpenAIStructuredOutputClient,
|
12
12
|
TogetherClient,
|
13
|
+
CustomEndpointClient,
|
13
14
|
)
|
14
15
|
|
15
16
|
openai_naming_regexes: List[Pattern] = [
|
@@ -53,6 +54,15 @@ mistral_naming_regexes: List[Pattern] = [
|
|
53
54
|
re.compile(r"^mistral-.*$"),
|
54
55
|
]
|
55
56
|
|
57
|
+
# Custom endpoint patterns - check these before generic patterns
|
58
|
+
custom_endpoint_naming_regexes: List[Pattern] = [
|
59
|
+
# Modal endpoints: org--app.modal.run
|
60
|
+
re.compile(r"^[a-zA-Z0-9\-]+--[a-zA-Z0-9\-]+\.modal\.run$"),
|
61
|
+
# Generic domain patterns for custom endpoints
|
62
|
+
re.compile(r"^[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]+\.[a-zA-Z]+$"), # domain.tld
|
63
|
+
re.compile(r"^[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-]+\.[a-zA-Z]+\/[a-zA-Z0-9\-\/]+$"), # domain.tld/path
|
64
|
+
]
|
65
|
+
|
56
66
|
|
57
67
|
def get_client(
|
58
68
|
model_name: str,
|
@@ -79,9 +89,12 @@ def get_client(
|
|
79
89
|
elif any(regex.match(model_name) for regex in deepseek_naming_regexes):
|
80
90
|
return DeepSeekClient()
|
81
91
|
elif any(regex.match(model_name) for regex in groq_naming_regexes):
|
82
|
-
return
|
92
|
+
return GroqClient()
|
83
93
|
elif any(regex.match(model_name) for regex in mistral_naming_regexes):
|
84
|
-
return
|
94
|
+
return MistralClient()
|
95
|
+
elif any(regex.match(model_name) for regex in custom_endpoint_naming_regexes):
|
96
|
+
# Custom endpoints are passed as the endpoint URL
|
97
|
+
return CustomEndpointClient(endpoint_url=model_name)
|
85
98
|
elif any(regex.match(model_name) for regex in together_naming_regexes):
|
86
99
|
return TogetherClient()
|
87
100
|
else:
|
@@ -0,0 +1,394 @@
|
|
1
|
+
import re
|
2
|
+
import os
|
3
|
+
import json
|
4
|
+
import asyncio
|
5
|
+
import time
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple, Type
|
7
|
+
import requests
|
8
|
+
import httpx
|
9
|
+
from requests.adapters import HTTPAdapter
|
10
|
+
from urllib3.util.retry import Retry
|
11
|
+
import random
|
12
|
+
from urllib.parse import urlparse
|
13
|
+
|
14
|
+
from synth_ai.zyk.lms.vendors.base import BaseLMResponse, VendorBase
|
15
|
+
from synth_ai.zyk.lms.tools.base import BaseTool
|
16
|
+
from synth_ai.zyk.lms.caching.initialize import get_cache_handler
|
17
|
+
|
18
|
+
# Exception types for retry
|
19
|
+
CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY: Tuple[Type[Exception], ...] = (
|
20
|
+
requests.RequestException,
|
21
|
+
requests.Timeout,
|
22
|
+
httpx.RequestError,
|
23
|
+
httpx.TimeoutException
|
24
|
+
)
|
25
|
+
|
26
|
+
class CustomEndpointAPI(VendorBase):
|
27
|
+
"""Generic vendor client for custom OpenAI-compatible endpoints."""
|
28
|
+
|
29
|
+
used_for_structured_outputs: bool = False
|
30
|
+
exceptions_to_retry: List = list(CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY)
|
31
|
+
|
32
|
+
def __init__(self, endpoint_url: str):
|
33
|
+
# Validate and sanitize URL
|
34
|
+
self._validate_endpoint_url(endpoint_url)
|
35
|
+
self.endpoint_url = endpoint_url
|
36
|
+
|
37
|
+
# Construct full chat completions URL
|
38
|
+
if endpoint_url.endswith('/'):
|
39
|
+
endpoint_url = endpoint_url[:-1]
|
40
|
+
self.chat_completions_url = f"https://{endpoint_url}/chat/completions"
|
41
|
+
self.health_url = f"https://{endpoint_url}/health"
|
42
|
+
|
43
|
+
# Setup session with connection pooling and retries
|
44
|
+
self.session = self._create_session()
|
45
|
+
self.async_client = None # Lazy init
|
46
|
+
|
47
|
+
# Get auth token from environment (generic support for any auth)
|
48
|
+
self.auth_token = os.environ.get("CUSTOM_ENDPOINT_API_TOKEN")
|
49
|
+
|
50
|
+
def _validate_endpoint_url(self, url: str) -> None:
|
51
|
+
"""Validate endpoint URL format and prevent SSRF."""
|
52
|
+
# Block dangerous URL patterns
|
53
|
+
dangerous_patterns = [
|
54
|
+
"file://", "ftp://", "gopher://",
|
55
|
+
"localhost", "127.", "0.0.0.0",
|
56
|
+
"10.", "192.168.", "172.16.", "172.17.", "172.18.", "172.19.",
|
57
|
+
"172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.",
|
58
|
+
"172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31.",
|
59
|
+
"169.254.", # link-local
|
60
|
+
"::1", "fc00:", "fd00:", "fe80:", # IPv6 private
|
61
|
+
]
|
62
|
+
|
63
|
+
for pattern in dangerous_patterns:
|
64
|
+
if pattern in url.lower():
|
65
|
+
raise ValueError(f"Blocked URL pattern for security: {pattern}")
|
66
|
+
|
67
|
+
# Limit URL length
|
68
|
+
if len(url) > 256:
|
69
|
+
raise ValueError(f"Endpoint URL too long (max 256 chars)")
|
70
|
+
|
71
|
+
# Basic URL format check
|
72
|
+
if not re.match(r'^[a-zA-Z0-9\-._~:/?#\[\]@!$&\'()*+,;=]+$', url):
|
73
|
+
raise ValueError(f"Invalid URL format: {url}")
|
74
|
+
|
75
|
+
def _create_session(self) -> requests.Session:
|
76
|
+
"""Create session with retry strategy and connection pooling."""
|
77
|
+
session = requests.Session()
|
78
|
+
|
79
|
+
# Exponential backoff with jitter
|
80
|
+
retry_strategy = Retry(
|
81
|
+
total=3,
|
82
|
+
backoff_factor=1,
|
83
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
84
|
+
allowed_methods=["POST", "GET"]
|
85
|
+
)
|
86
|
+
|
87
|
+
adapter = HTTPAdapter(
|
88
|
+
max_retries=retry_strategy,
|
89
|
+
pool_connections=10,
|
90
|
+
pool_maxsize=20
|
91
|
+
)
|
92
|
+
|
93
|
+
session.mount("http://", adapter)
|
94
|
+
session.mount("https://", adapter)
|
95
|
+
|
96
|
+
return session
|
97
|
+
|
98
|
+
async def _get_async_client(self) -> httpx.AsyncClient:
|
99
|
+
"""Lazy init async client with shared retry logic."""
|
100
|
+
if self.async_client is None:
|
101
|
+
self.async_client = httpx.AsyncClient(
|
102
|
+
timeout=httpx.Timeout(30.0),
|
103
|
+
limits=httpx.Limits(max_connections=10, max_keepalive_connections=5)
|
104
|
+
)
|
105
|
+
return self.async_client
|
106
|
+
|
107
|
+
def _get_timeout(self, lm_config: Dict[str, Any]) -> float:
|
108
|
+
"""Get timeout with per-call override support."""
|
109
|
+
return lm_config.get("timeout",
|
110
|
+
float(os.environ.get("CUSTOM_ENDPOINT_REQUEST_TIMEOUT", "30")))
|
111
|
+
|
112
|
+
def _get_temperature_override(self) -> Optional[float]:
|
113
|
+
"""Get temperature override from environment for this specific endpoint."""
|
114
|
+
# Create a safe env var key from the endpoint URL
|
115
|
+
# e.g., "example.com/api" -> "CUSTOM_ENDPOINT_TEMP_EXAMPLE_COM_API"
|
116
|
+
safe_key = re.sub(r'[^A-Za-z0-9]', '_', self.endpoint_url).upper()
|
117
|
+
safe_key = safe_key[:64] # Limit length
|
118
|
+
|
119
|
+
env_key = f"CUSTOM_ENDPOINT_TEMP_{safe_key}"
|
120
|
+
temp_str = os.environ.get(env_key)
|
121
|
+
return float(temp_str) if temp_str else None
|
122
|
+
|
123
|
+
def _compress_tool_schema(self, schema: Dict[str, Any]) -> Dict[str, Any]:
|
124
|
+
"""Compress JSON schema to reduce token usage."""
|
125
|
+
if isinstance(schema, dict):
|
126
|
+
# Remove verbose keys
|
127
|
+
compressed = {
|
128
|
+
k: self._compress_tool_schema(v)
|
129
|
+
for k, v in schema.items()
|
130
|
+
if k not in ["title", "$ref", "$schema"]
|
131
|
+
}
|
132
|
+
# Shorten descriptions
|
133
|
+
if "description" in compressed and len(compressed["description"]) > 50:
|
134
|
+
compressed["description"] = compressed["description"][:47] + "..."
|
135
|
+
return compressed
|
136
|
+
elif isinstance(schema, list):
|
137
|
+
return [self._compress_tool_schema(item) for item in schema]
|
138
|
+
return schema
|
139
|
+
|
140
|
+
def _inject_tools_into_prompt(self, system_message: str, tools: List[BaseTool]) -> str:
|
141
|
+
"""Inject tool definitions with compressed schemas and clear output format."""
|
142
|
+
if not tools:
|
143
|
+
return system_message
|
144
|
+
|
145
|
+
tool_descriptions = []
|
146
|
+
for tool in tools:
|
147
|
+
schema = tool.arguments.model_json_schema()
|
148
|
+
compressed_schema = self._compress_tool_schema(schema)
|
149
|
+
|
150
|
+
tool_desc = f"Tool: {tool.name}\nDesc: {tool.description}\nParams: {json.dumps(compressed_schema, separators=(',', ':'))}"
|
151
|
+
tool_descriptions.append(tool_desc)
|
152
|
+
|
153
|
+
tools_text = "\n".join(tool_descriptions)
|
154
|
+
|
155
|
+
return f"""{system_message}
|
156
|
+
|
157
|
+
Available tools:
|
158
|
+
{tools_text}
|
159
|
+
|
160
|
+
IMPORTANT: To use a tool, respond with JSON wrapped in ```json fences:
|
161
|
+
```json
|
162
|
+
{{"tool_call": {{"name": "tool_name", "arguments": {{...}}}}}}
|
163
|
+
```
|
164
|
+
|
165
|
+
For regular responses, just respond normally without JSON fences."""
|
166
|
+
|
167
|
+
def _extract_tool_calls(self, content: str, tools: List[BaseTool]) -> tuple[Optional[List], str]:
|
168
|
+
"""Extract and validate tool calls from response."""
|
169
|
+
# Look for JSON fenced blocks
|
170
|
+
json_pattern = r'```json\s*(\{.*?\})\s*```'
|
171
|
+
matches = re.findall(json_pattern, content, re.DOTALL)
|
172
|
+
|
173
|
+
if not matches:
|
174
|
+
return None, content
|
175
|
+
|
176
|
+
tool_calls = []
|
177
|
+
cleaned_content = content
|
178
|
+
|
179
|
+
for match in matches:
|
180
|
+
try:
|
181
|
+
tool_data = json.loads(match)
|
182
|
+
if "tool_call" in tool_data:
|
183
|
+
call_data = tool_data["tool_call"]
|
184
|
+
tool_name = call_data.get("name")
|
185
|
+
|
186
|
+
# Validate against available tools
|
187
|
+
matching_tool = next((t for t in tools if t.name == tool_name), None)
|
188
|
+
if matching_tool:
|
189
|
+
# Validate arguments with pydantic
|
190
|
+
validated_args = matching_tool.arguments(**call_data.get("arguments", {}))
|
191
|
+
tool_calls.append({
|
192
|
+
"name": tool_name,
|
193
|
+
"arguments": validated_args.model_dump()
|
194
|
+
})
|
195
|
+
|
196
|
+
# Remove tool call from content
|
197
|
+
cleaned_content = cleaned_content.replace(f"```json\n{match}\n```", "").strip()
|
198
|
+
|
199
|
+
except (json.JSONDecodeError, Exception):
|
200
|
+
# Fall back to treating as normal text if validation fails
|
201
|
+
continue
|
202
|
+
|
203
|
+
return tool_calls if tool_calls else None, cleaned_content
|
204
|
+
|
205
|
+
def _exponential_backoff_with_jitter(self, attempt: int) -> float:
|
206
|
+
"""Calculate backoff time with jitter to prevent thundering herd."""
|
207
|
+
base_delay = min(2 ** attempt, 32) # Cap at 32 seconds
|
208
|
+
jitter = random.uniform(0, 1)
|
209
|
+
return base_delay + jitter
|
210
|
+
|
211
|
+
def _handle_rate_limit(self, response: requests.Response) -> None:
|
212
|
+
"""Extract and propagate rate limit information."""
|
213
|
+
if response.status_code == 429:
|
214
|
+
retry_after = response.headers.get("Retry-After")
|
215
|
+
if retry_after:
|
216
|
+
# Bubble up to synth-ai scheduler
|
217
|
+
raise requests.exceptions.RetryError(f"Rate limited. Retry after {retry_after}s")
|
218
|
+
|
219
|
+
async def _hit_api_async(
|
220
|
+
self,
|
221
|
+
model: str,
|
222
|
+
messages: List[Dict[str, Any]],
|
223
|
+
lm_config: Dict[str, Any],
|
224
|
+
use_ephemeral_cache_only: bool = False,
|
225
|
+
reasoning_effort: str = "low",
|
226
|
+
tools: Optional[List[BaseTool]] = None,
|
227
|
+
) -> BaseLMResponse:
|
228
|
+
"""Async API call with comprehensive error handling and streaming support."""
|
229
|
+
|
230
|
+
# Cache integration - check first
|
231
|
+
used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
|
232
|
+
cache_result = used_cache_handler.hit_managed_cache(
|
233
|
+
model, messages, lm_config=lm_config, tools=tools
|
234
|
+
)
|
235
|
+
if cache_result:
|
236
|
+
return cache_result
|
237
|
+
|
238
|
+
# Apply tool injection
|
239
|
+
if tools and messages:
|
240
|
+
messages = messages.copy()
|
241
|
+
if messages and messages[0].get("role") == "system":
|
242
|
+
messages[0]["content"] = self._inject_tools_into_prompt(
|
243
|
+
messages[0]["content"], tools
|
244
|
+
)
|
245
|
+
|
246
|
+
# Prepare request
|
247
|
+
headers = {"Content-Type": "application/json"}
|
248
|
+
if self.auth_token:
|
249
|
+
headers["Authorization"] = f"Bearer {self.auth_token}"
|
250
|
+
|
251
|
+
# Apply temperature override
|
252
|
+
temp_override = self._get_temperature_override()
|
253
|
+
request_temp = temp_override if temp_override else lm_config.get("temperature", 0.7)
|
254
|
+
|
255
|
+
payload = {
|
256
|
+
"model": model, # Pass through the model name
|
257
|
+
"messages": messages,
|
258
|
+
"temperature": request_temp,
|
259
|
+
"stream": lm_config.get("stream", False)
|
260
|
+
}
|
261
|
+
|
262
|
+
timeout = self._get_timeout(lm_config)
|
263
|
+
client = await self._get_async_client()
|
264
|
+
|
265
|
+
# Make request with retry logic
|
266
|
+
for attempt in range(3):
|
267
|
+
try:
|
268
|
+
response = await client.post(
|
269
|
+
self.chat_completions_url,
|
270
|
+
json=payload,
|
271
|
+
headers=headers,
|
272
|
+
timeout=timeout
|
273
|
+
)
|
274
|
+
|
275
|
+
if response.status_code == 429:
|
276
|
+
self._handle_rate_limit(response)
|
277
|
+
|
278
|
+
response.raise_for_status()
|
279
|
+
|
280
|
+
response_data = response.json()
|
281
|
+
content = response_data["choices"][0]["message"]["content"]
|
282
|
+
|
283
|
+
# Extract tool calls
|
284
|
+
tool_calls, clean_content = self._extract_tool_calls(content, tools or [])
|
285
|
+
|
286
|
+
lm_response = BaseLMResponse(
|
287
|
+
raw_response=clean_content,
|
288
|
+
structured_output=None,
|
289
|
+
tool_calls=tool_calls
|
290
|
+
)
|
291
|
+
|
292
|
+
# Add to cache
|
293
|
+
used_cache_handler.add_to_managed_cache(
|
294
|
+
model, messages, lm_config=lm_config, output=lm_response, tools=tools
|
295
|
+
)
|
296
|
+
|
297
|
+
return lm_response
|
298
|
+
|
299
|
+
except (httpx.RequestError, httpx.TimeoutException) as e:
|
300
|
+
if attempt == 2: # Last attempt
|
301
|
+
raise
|
302
|
+
await asyncio.sleep(self._exponential_backoff_with_jitter(attempt))
|
303
|
+
|
304
|
+
def _hit_api_sync(
|
305
|
+
self,
|
306
|
+
model: str,
|
307
|
+
messages: List[Dict[str, Any]],
|
308
|
+
lm_config: Dict[str, Any],
|
309
|
+
use_ephemeral_cache_only: bool = False,
|
310
|
+
reasoning_effort: str = "low",
|
311
|
+
tools: Optional[List[BaseTool]] = None,
|
312
|
+
) -> BaseLMResponse:
|
313
|
+
"""Sync version with same logic as async."""
|
314
|
+
|
315
|
+
# Cache integration - check first
|
316
|
+
used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
|
317
|
+
cache_result = used_cache_handler.hit_managed_cache(
|
318
|
+
model, messages, lm_config=lm_config, tools=tools
|
319
|
+
)
|
320
|
+
if cache_result:
|
321
|
+
return cache_result
|
322
|
+
|
323
|
+
# Apply tool injection
|
324
|
+
if tools and messages:
|
325
|
+
messages = messages.copy()
|
326
|
+
if messages and messages[0].get("role") == "system":
|
327
|
+
messages[0]["content"] = self._inject_tools_into_prompt(
|
328
|
+
messages[0]["content"], tools
|
329
|
+
)
|
330
|
+
|
331
|
+
# Prepare request
|
332
|
+
headers = {"Content-Type": "application/json"}
|
333
|
+
if self.auth_token:
|
334
|
+
headers["Authorization"] = f"Bearer {self.auth_token}"
|
335
|
+
|
336
|
+
# Apply temperature override
|
337
|
+
temp_override = self._get_temperature_override()
|
338
|
+
request_temp = temp_override if temp_override else lm_config.get("temperature", 0.7)
|
339
|
+
|
340
|
+
payload = {
|
341
|
+
"model": model, # Pass through the model name
|
342
|
+
"messages": messages,
|
343
|
+
"temperature": request_temp,
|
344
|
+
"stream": lm_config.get("stream", False)
|
345
|
+
}
|
346
|
+
|
347
|
+
timeout = self._get_timeout(lm_config)
|
348
|
+
|
349
|
+
# Make request with retry logic
|
350
|
+
for attempt in range(3):
|
351
|
+
try:
|
352
|
+
response = self.session.post(
|
353
|
+
self.chat_completions_url,
|
354
|
+
json=payload,
|
355
|
+
headers=headers,
|
356
|
+
timeout=timeout
|
357
|
+
)
|
358
|
+
|
359
|
+
if response.status_code == 429:
|
360
|
+
self._handle_rate_limit(response)
|
361
|
+
|
362
|
+
response.raise_for_status()
|
363
|
+
|
364
|
+
response_data = response.json()
|
365
|
+
content = response_data["choices"][0]["message"]["content"]
|
366
|
+
|
367
|
+
# Extract tool calls
|
368
|
+
tool_calls, clean_content = self._extract_tool_calls(content, tools or [])
|
369
|
+
|
370
|
+
lm_response = BaseLMResponse(
|
371
|
+
raw_response=clean_content,
|
372
|
+
structured_output=None,
|
373
|
+
tool_calls=tool_calls
|
374
|
+
)
|
375
|
+
|
376
|
+
# Add to cache
|
377
|
+
used_cache_handler.add_to_managed_cache(
|
378
|
+
model, messages, lm_config=lm_config, output=lm_response, tools=tools
|
379
|
+
)
|
380
|
+
|
381
|
+
return lm_response
|
382
|
+
|
383
|
+
except (requests.RequestException, requests.Timeout) as e:
|
384
|
+
if attempt == 2: # Last attempt
|
385
|
+
raise
|
386
|
+
time.sleep(self._exponential_backoff_with_jitter(attempt))
|
387
|
+
|
388
|
+
def __del__(self):
|
389
|
+
"""Cleanup resources."""
|
390
|
+
if hasattr(self, 'session'):
|
391
|
+
self.session.close()
|
392
|
+
if hasattr(self, 'async_client') and self.async_client:
|
393
|
+
# Schedule cleanup for async client
|
394
|
+
pass
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: synth-ai
|
3
|
+
Version: 0.1.5
|
4
|
+
Home-page: https://github.com/synth-laboratories/synth-ai
|
5
|
+
Author: Josh Purtell
|
6
|
+
Author-email: josh@usesynth.com
|
7
|
+
License: MIT
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
License-File: LICENSE
|
10
|
+
Requires-Dist: openai
|
11
|
+
Requires-Dist: pydantic>=2.9.2
|
12
|
+
Requires-Dist: diskcache
|
13
|
+
Requires-Dist: backoff>=2.2.1
|
14
|
+
Requires-Dist: anthropic>=0.34.2
|
15
|
+
Requires-Dist: google>=3.0.0
|
16
|
+
Requires-Dist: google-generativeai>=0.8.1
|
17
|
+
Requires-Dist: together>=1.2.12
|
18
|
+
Dynamic: author
|
19
|
+
Dynamic: author-email
|
20
|
+
Dynamic: description
|
21
|
+
Dynamic: description-content-type
|
22
|
+
Dynamic: home-page
|
23
|
+
Dynamic: license
|
24
|
+
Dynamic: license-file
|
25
|
+
Dynamic: requires-dist
|
26
|
+
|
27
|
+
AI Infra used by the Synth AI Team
|
28
|
+
```
|
29
|
+
_____ _ _ _____
|
30
|
+
/ ____| | | | | /\ |_ _|
|
31
|
+
| (___ _ _ _ __ | |_| |__ / \ | |
|
32
|
+
\___ \| | | | '_ \| __| '_ \ / /\ \ | |
|
33
|
+
____) | |_| | | | | |_| | | | / ____ \ _| |_
|
34
|
+
|_____/ \__, |_| |_|\__|_| |_| /_/ \_\_____|
|
35
|
+
__/ |
|
36
|
+
|___/
|
37
|
+
```
|
@@ -1,4 +1,4 @@
|
|
1
|
-
synth_ai/__init__.py,sha256
|
1
|
+
synth_ai/__init__.py,sha256=-qLyQqdy3njK2e_kHYslyaPezwEs9XgpovFi4ZyJWBs,225
|
2
2
|
synth_ai/zyk/__init__.py,sha256=kGMD-drlBVdsyT-QFODMwaZUtxPCJ9mg58GKQUvFqo0,134
|
3
3
|
synth_ai/zyk/lms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
synth_ai/zyk/lms/config.py,sha256=UBMi0DIFQDBV_eGPK5vG8R7VwxXcV10BPGq1iV8vVjg,282
|
@@ -11,10 +11,10 @@ synth_ai/zyk/lms/caching/handler.py,sha256=HEo_e8q3kqDSVW0hN1AA6Rx5cBvlEeizi-kiO
|
|
11
11
|
synth_ai/zyk/lms/caching/initialize.py,sha256=zZls6RKAax6Z-8oJInGaSg_RPN_fEZ6e_RCX64lMLJw,416
|
12
12
|
synth_ai/zyk/lms/caching/persistent.py,sha256=ZaY1A9qhvfNKzcAI9FnwbIrgMKvVeIfb_yCyl3M8dxE,2860
|
13
13
|
synth_ai/zyk/lms/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
synth_ai/zyk/lms/core/all.py,sha256=
|
14
|
+
synth_ai/zyk/lms/core/all.py,sha256=MAlhGk5LBK7En-oUzFBoemGl_GZbenmvqhlkSQ7cfGc,1410
|
15
15
|
synth_ai/zyk/lms/core/exceptions.py,sha256=K0BVdAzxVIchsvYZAaHEH1GAWBZvpxhFi-SPcJOjyPQ,205
|
16
16
|
synth_ai/zyk/lms/core/main.py,sha256=pLz7COTdvDWQivYaA1iYYF2onUOosD_sFaPJG48bdKM,10598
|
17
|
-
synth_ai/zyk/lms/core/vendor_clients.py,sha256=
|
17
|
+
synth_ai/zyk/lms/core/vendor_clients.py,sha256=7D30Ol-eVaJ8pZnbqeKADJ6q_Nz5buOd2qQJrrn8Rlk,3647
|
18
18
|
synth_ai/zyk/lms/cost/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
synth_ai/zyk/lms/cost/monitor.py,sha256=cSKIvw6WdPZIRubADWxQoh1MdB40T8-jjgfNUeUHIn0,5
|
20
20
|
synth_ai/zyk/lms/cost/statefulness.py,sha256=TOsuXL8IjtKOYJ2aJQF8TwJVqn_wQ7AIwJJmdhMye7U,36
|
@@ -22,6 +22,7 @@ synth_ai/zyk/lms/structured_outputs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQ
|
|
22
22
|
synth_ai/zyk/lms/structured_outputs/handler.py,sha256=4DboLNZyXpqNB5YNCSgGHBsNbWqFk-8uwV944nOYNo8,16919
|
23
23
|
synth_ai/zyk/lms/structured_outputs/inject.py,sha256=Fy-zDeleRxOZ8ZRM6IuZ6CP2XZnMe4K2PEn4Q9c_KPY,11777
|
24
24
|
synth_ai/zyk/lms/structured_outputs/rehabilitate.py,sha256=ecKGWrgWYUSplqHzK40KdohwaN8gBV0xl4LUReLN_vg,7910
|
25
|
+
synth_ai/zyk/lms/tools/__init__.py,sha256=3JM5vqZqKloaeHaxuO49C8A_0qljys3pQ1yt70WKhho,51
|
25
26
|
synth_ai/zyk/lms/tools/base.py,sha256=i-AIVRlitiQ4JMJ_BBFRSpUcWgxWIUYoHxAqfxHN_7E,4056
|
26
27
|
synth_ai/zyk/lms/vendors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
28
|
synth_ai/zyk/lms/vendors/base.py,sha256=aK4PEtkMLt_o3qD22kW-x3HJUEKdIk06zlH4kX0VkAE,760
|
@@ -35,12 +36,13 @@ synth_ai/zyk/lms/vendors/core/openai_api.py,sha256=O5KbRpy0pDDofVjxgZdeU69ueUl6S
|
|
35
36
|
synth_ai/zyk/lms/vendors/local/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
37
|
synth_ai/zyk/lms/vendors/local/ollama.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
38
|
synth_ai/zyk/lms/vendors/supported/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
|
+
synth_ai/zyk/lms/vendors/supported/custom_endpoint.py,sha256=awluDoUgcRROsZdH_o_Whe2D0MjQfOZOnmgWbESAbIQ,15523
|
38
40
|
synth_ai/zyk/lms/vendors/supported/deepseek.py,sha256=BElW0NGpkSA62wOqzzMtDw8XR36rSNXK5LldeHJkQrc,2430
|
39
41
|
synth_ai/zyk/lms/vendors/supported/groq.py,sha256=Fbi7QvhdLx0F-VHO5PY-uIQlPR0bo3C9h1MvIOx8nz0,388
|
40
42
|
synth_ai/zyk/lms/vendors/supported/ollama.py,sha256=K30VBFRTd7NYyPmyBVRZS2sm0UB651AHp9i3wd55W64,469
|
41
43
|
synth_ai/zyk/lms/vendors/supported/together.py,sha256=Ni_jBqqGPN0PkkY-Ew64s3gNKk51k3FCpLSwlNhKbf0,342
|
42
|
-
synth_ai-0.1.
|
43
|
-
synth_ai-0.1.
|
44
|
-
synth_ai-0.1.
|
45
|
-
synth_ai-0.1.
|
46
|
-
synth_ai-0.1.
|
44
|
+
synth_ai-0.1.5.dist-info/licenses/LICENSE,sha256=ynhjRQUfqA_RdGRATApfFA_fBAy9cno04sLtLUqxVFM,1069
|
45
|
+
synth_ai-0.1.5.dist-info/METADATA,sha256=Ip_93PnkbnO2yIKXxg-Pzqi7oBF7s4DrM5UYlXnUSBE,1084
|
46
|
+
synth_ai-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
47
|
+
synth_ai-0.1.5.dist-info/top_level.txt,sha256=fBmtZyVHuKaGa29oHBaaUkrUIWTqSpoVMPiVdCDP3k8,9
|
48
|
+
synth_ai-0.1.5.dist-info/RECORD,,
|
@@ -1,67 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: synth-ai
|
3
|
-
Version: 0.1.3
|
4
|
-
Summary: Software for aiding the best and multiplying the will.
|
5
|
-
Home-page: https://github.com/synth-laboratories/synth-ai
|
6
|
-
Author: Josh Purtell
|
7
|
-
Author-email: Josh Purtell <josh@usesynth.ai>
|
8
|
-
License: MIT License
|
9
|
-
|
10
|
-
Copyright (c) 2024 Josh Purtell
|
11
|
-
|
12
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
13
|
-
of this software and associated documentation files (the "Software"), to deal
|
14
|
-
in the Software without restriction, including without limitation the rights
|
15
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
16
|
-
copies of the Software, and to permit persons to whom the Software is
|
17
|
-
furnished to do so, subject to the following conditions:
|
18
|
-
|
19
|
-
The above copyright notice and this permission notice shall be included in all
|
20
|
-
copies or substantial portions of the Software.
|
21
|
-
|
22
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
23
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
24
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
25
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
26
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
27
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
28
|
-
SOFTWARE.
|
29
|
-
|
30
|
-
Project-URL: Homepage, https://github.com/synth-laboratories/synth-ai
|
31
|
-
Keywords: synth-ai
|
32
|
-
Classifier: License :: OSI Approved :: MIT License
|
33
|
-
Classifier: Programming Language :: Python
|
34
|
-
Classifier: Programming Language :: Python :: 3
|
35
|
-
Requires-Python: >=3.10
|
36
|
-
Description-Content-Type: text/markdown
|
37
|
-
License-File: LICENSE
|
38
|
-
Requires-Dist: openai>=1.0.0
|
39
|
-
Requires-Dist: pydantic>=2.0.0
|
40
|
-
Requires-Dist: diskcache>=5.0.0
|
41
|
-
Requires-Dist: backoff>=2.2.1
|
42
|
-
Requires-Dist: anthropic>=0.34.2
|
43
|
-
Requires-Dist: google>=3.0.0
|
44
|
-
Requires-Dist: google-api-core
|
45
|
-
Requires-Dist: google-generativeai
|
46
|
-
Requires-Dist: google-genai
|
47
|
-
Requires-Dist: together>=1.2.12
|
48
|
-
Requires-Dist: langfuse<3.0.0,>=2.53.9
|
49
|
-
Requires-Dist: datasets>=3.2.0
|
50
|
-
Requires-Dist: groq>=0.18.0
|
51
|
-
Requires-Dist: pytest-timeout>=2.3.1
|
52
|
-
Requires-Dist: mistralai
|
53
|
-
Dynamic: author
|
54
|
-
Dynamic: home-page
|
55
|
-
Dynamic: license-file
|
56
|
-
|
57
|
-
AI Infra used by the Synth AI Team
|
58
|
-
```
|
59
|
-
_____ _ _ _____
|
60
|
-
/ ____| | | | | /\ |_ _|
|
61
|
-
| (___ _ _ _ __ | |_| |__ / \ | |
|
62
|
-
\___ \| | | | '_ \| __| '_ \ / /\ \ | |
|
63
|
-
____) | |_| | | | | |_| | | | / ____ \ _| |_
|
64
|
-
|_____/ \__, |_| |_|\__|_| |_| /_/ \_\_____|
|
65
|
-
__/ |
|
66
|
-
|___/
|
67
|
-
```
|
File without changes
|
File without changes
|
File without changes
|