synth-ai 0.1.0.dev49__py3-none-any.whl → 0.1.0.dev51__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 +3 -0
- synth_ai/zyk/__init__.py +3 -0
- synth_ai/zyk/lms/__init__.py +0 -0
- synth_ai/zyk/lms/caching/__init__.py +0 -0
- synth_ai/zyk/lms/caching/constants.py +1 -0
- synth_ai/zyk/lms/caching/dbs.py +0 -0
- synth_ai/zyk/lms/caching/ephemeral.py +72 -0
- synth_ai/zyk/lms/caching/handler.py +137 -0
- synth_ai/zyk/lms/caching/initialize.py +13 -0
- synth_ai/zyk/lms/caching/persistent.py +83 -0
- synth_ai/zyk/lms/config.py +10 -0
- synth_ai/zyk/lms/constants.py +22 -0
- synth_ai/zyk/lms/core/__init__.py +0 -0
- synth_ai/zyk/lms/core/all.py +47 -0
- synth_ai/zyk/lms/core/exceptions.py +9 -0
- synth_ai/zyk/lms/core/main.py +268 -0
- synth_ai/zyk/lms/core/vendor_clients.py +85 -0
- synth_ai/zyk/lms/cost/__init__.py +0 -0
- synth_ai/zyk/lms/cost/monitor.py +1 -0
- synth_ai/zyk/lms/cost/statefulness.py +1 -0
- synth_ai/zyk/lms/structured_outputs/__init__.py +0 -0
- synth_ai/zyk/lms/structured_outputs/handler.py +441 -0
- synth_ai/zyk/lms/structured_outputs/inject.py +314 -0
- synth_ai/zyk/lms/structured_outputs/rehabilitate.py +187 -0
- synth_ai/zyk/lms/tools/base.py +118 -0
- synth_ai/zyk/lms/vendors/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/base.py +31 -0
- synth_ai/zyk/lms/vendors/core/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/core/anthropic_api.py +365 -0
- synth_ai/zyk/lms/vendors/core/gemini_api.py +282 -0
- synth_ai/zyk/lms/vendors/core/mistral_api.py +331 -0
- synth_ai/zyk/lms/vendors/core/openai_api.py +187 -0
- synth_ai/zyk/lms/vendors/local/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/local/ollama.py +0 -0
- synth_ai/zyk/lms/vendors/openai_standard.py +345 -0
- synth_ai/zyk/lms/vendors/retries.py +3 -0
- synth_ai/zyk/lms/vendors/supported/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/supported/deepseek.py +73 -0
- synth_ai/zyk/lms/vendors/supported/groq.py +16 -0
- synth_ai/zyk/lms/vendors/supported/ollama.py +14 -0
- synth_ai/zyk/lms/vendors/supported/together.py +11 -0
- {synth_ai-0.1.0.dev49.dist-info → synth_ai-0.1.0.dev51.dist-info}/METADATA +1 -1
- synth_ai-0.1.0.dev51.dist-info/RECORD +46 -0
- synth_ai-0.1.0.dev49.dist-info/RECORD +0 -6
- {synth_ai-0.1.0.dev49.dist-info → synth_ai-0.1.0.dev51.dist-info}/WHEEL +0 -0
- {synth_ai-0.1.0.dev49.dist-info → synth_ai-0.1.0.dev51.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.1.0.dev49.dist-info → synth_ai-0.1.0.dev51.dist-info}/top_level.txt +0 -0
synth_ai/__init__.py
CHANGED
@@ -4,4 +4,7 @@ Synth AI - Software for aiding the best and multiplying the will.
|
|
4
4
|
|
5
5
|
from importlib.metadata import version
|
6
6
|
|
7
|
+
from synth_ai.zyk import LM # Assuming LM is in zyk.py in the same directory
|
8
|
+
|
7
9
|
__version__ = version("synth-ai") # Gets version from installed package metadata
|
10
|
+
__all__ = ["LM"] # Explicitly define public API
|
synth_ai/zyk/__init__.py
ADDED
File without changes
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
DISKCACHE_SIZE_LIMIT = 10 * 1024 * 1024 * 1024 #10GB
|
File without changes
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import os
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import Optional, Union
|
4
|
+
|
5
|
+
from diskcache import Cache
|
6
|
+
from pydantic import BaseModel
|
7
|
+
|
8
|
+
from synth_ai.zyk.lms.caching.constants import DISKCACHE_SIZE_LIMIT
|
9
|
+
from synth_ai.zyk.lms.vendors.base import BaseLMResponse
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class EphemeralCache:
|
14
|
+
def __init__(self, fast_cache_dir: str = ".cache/ephemeral_cache"):
|
15
|
+
os.makedirs(fast_cache_dir, exist_ok=True)
|
16
|
+
self.fast_cache = Cache(fast_cache_dir, size_limit=DISKCACHE_SIZE_LIMIT)
|
17
|
+
|
18
|
+
def hit_cache(
|
19
|
+
self, key: str, response_model: Optional[BaseModel] = None
|
20
|
+
) -> Optional[BaseLMResponse]:
|
21
|
+
if key not in self.fast_cache:
|
22
|
+
return None
|
23
|
+
|
24
|
+
try:
|
25
|
+
cache_data = self.fast_cache[key]
|
26
|
+
except AttributeError:
|
27
|
+
return None
|
28
|
+
|
29
|
+
if not isinstance(cache_data, dict):
|
30
|
+
return BaseLMResponse(
|
31
|
+
raw_response=cache_data, structured_output=None, tool_calls=None
|
32
|
+
)
|
33
|
+
|
34
|
+
raw_response = cache_data.get("raw_response")
|
35
|
+
tool_calls = cache_data.get("tool_calls")
|
36
|
+
structured_output = cache_data.get("structured_output")
|
37
|
+
|
38
|
+
if response_model and structured_output:
|
39
|
+
structured_output = response_model(**structured_output)
|
40
|
+
|
41
|
+
return BaseLMResponse(
|
42
|
+
raw_response=raw_response,
|
43
|
+
structured_output=structured_output,
|
44
|
+
tool_calls=tool_calls,
|
45
|
+
)
|
46
|
+
|
47
|
+
def add_to_cache(self, key: str, response: Union[BaseLMResponse, str]) -> None:
|
48
|
+
if isinstance(response, str):
|
49
|
+
self.fast_cache[key] = response
|
50
|
+
return
|
51
|
+
|
52
|
+
if isinstance(response, BaseLMResponse):
|
53
|
+
cache_data = {
|
54
|
+
"raw_response": response.raw_response
|
55
|
+
if response.raw_response is not None
|
56
|
+
else None,
|
57
|
+
"tool_calls": response.tool_calls
|
58
|
+
if response.tool_calls is not None
|
59
|
+
else None,
|
60
|
+
"structured_output": (
|
61
|
+
response.structured_output.model_dump()
|
62
|
+
if response.structured_output is not None
|
63
|
+
else None
|
64
|
+
),
|
65
|
+
}
|
66
|
+
self.fast_cache[key] = cache_data
|
67
|
+
return
|
68
|
+
|
69
|
+
raise ValueError(f"Invalid response type: {type(response)}")
|
70
|
+
|
71
|
+
def close(self):
|
72
|
+
self.fast_cache.close()
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import hashlib
|
2
|
+
from typing import Any, Dict, List, Optional, Type
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
from synth_ai.zyk.lms.caching.ephemeral import EphemeralCache
|
7
|
+
from synth_ai.zyk.lms.caching.persistent import PersistentCache
|
8
|
+
from synth_ai.zyk.lms.tools.base import BaseTool
|
9
|
+
from synth_ai.zyk.lms.vendors.base import BaseLMResponse
|
10
|
+
|
11
|
+
persistent_cache = PersistentCache()
|
12
|
+
ephemeral_cache = EphemeralCache()
|
13
|
+
|
14
|
+
import logging
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
def map_params_to_key(
|
18
|
+
messages: List[Dict],
|
19
|
+
model: str,
|
20
|
+
temperature: float,
|
21
|
+
response_model: Optional[Type[BaseModel]],
|
22
|
+
tools: Optional[List[BaseTool]] = None,
|
23
|
+
reasoning_effort: str = "low",
|
24
|
+
) -> str:
|
25
|
+
if any(m is None for m in messages):
|
26
|
+
raise ValueError("Messages cannot contain None values - messages: ", messages)
|
27
|
+
if not all([isinstance(msg["content"], str) for msg in messages]):
|
28
|
+
normalized_messages = "".join([str(msg["content"]) for msg in messages])
|
29
|
+
else:
|
30
|
+
normalized_messages = "".join([msg["content"] for msg in messages])
|
31
|
+
normalized_model = model
|
32
|
+
normalized_temperature = f"{temperature:.2f}"[:4]
|
33
|
+
normalized_response_model = str(response_model.schema()) if response_model else ""
|
34
|
+
normalized_reasoning_effort = reasoning_effort if reasoning_effort else ""
|
35
|
+
|
36
|
+
# Normalize tools if present
|
37
|
+
normalized_tools = ""
|
38
|
+
if tools and all(isinstance(tool, BaseTool) for tool in tools):
|
39
|
+
tool_schemas = []
|
40
|
+
for tool in sorted(tools, key=lambda x: x.name): # Sort by name for consistency
|
41
|
+
tool_schema = {
|
42
|
+
"name": tool.name,
|
43
|
+
"description": tool.description,
|
44
|
+
"arguments": tool.arguments.schema(),
|
45
|
+
}
|
46
|
+
tool_schemas.append(str(tool_schema))
|
47
|
+
#logger.error(f"Tool schemas: {tool_schemas}")
|
48
|
+
normalized_tools = "".join(tool_schemas)
|
49
|
+
elif tools:
|
50
|
+
logger.error(f"Tools: {tools}")
|
51
|
+
normalized_tools = "".join([str(tool) for tool in tools])
|
52
|
+
|
53
|
+
key_str = ""
|
54
|
+
components = [
|
55
|
+
normalized_messages,
|
56
|
+
normalized_model,
|
57
|
+
normalized_temperature,
|
58
|
+
normalized_response_model,
|
59
|
+
normalized_tools,
|
60
|
+
normalized_reasoning_effort
|
61
|
+
]
|
62
|
+
for component in components:
|
63
|
+
if component:
|
64
|
+
key_str += str(component)
|
65
|
+
|
66
|
+
return hashlib.sha256(key_str.encode()).hexdigest()
|
67
|
+
|
68
|
+
|
69
|
+
class CacheHandler:
|
70
|
+
use_persistent_store: bool = False
|
71
|
+
use_ephemeral_store: bool = True
|
72
|
+
|
73
|
+
def __init__(
|
74
|
+
self, use_persistent_store: bool = False, use_ephemeral_store: bool = True
|
75
|
+
):
|
76
|
+
self.use_persistent_store = use_persistent_store
|
77
|
+
self.use_ephemeral_store = use_ephemeral_store
|
78
|
+
|
79
|
+
def _validate_messages(self, messages: List[Dict[str, Any]]) -> None:
|
80
|
+
"""Validate that messages are in the correct format."""
|
81
|
+
assert all(
|
82
|
+
[type(msg["content"]) == str for msg in messages]
|
83
|
+
), "All message contents must be strings"
|
84
|
+
|
85
|
+
def hit_managed_cache(
|
86
|
+
self,
|
87
|
+
model: str,
|
88
|
+
messages: List[Dict[str, Any]],
|
89
|
+
lm_config: Dict[str, Any],
|
90
|
+
tools: Optional[List[BaseTool]] = None,
|
91
|
+
) -> Optional[BaseLMResponse]:
|
92
|
+
"""Hit the cache with the given key."""
|
93
|
+
self._validate_messages(messages)
|
94
|
+
assert type(lm_config) == dict, "lm_config must be a dictionary"
|
95
|
+
key = map_params_to_key(
|
96
|
+
messages,
|
97
|
+
model,
|
98
|
+
lm_config.get("temperature", 0.0),
|
99
|
+
lm_config.get("response_model", None),
|
100
|
+
tools,
|
101
|
+
lm_config.get("reasoning_effort", "low"),
|
102
|
+
)
|
103
|
+
if self.use_persistent_store:
|
104
|
+
return persistent_cache.hit_cache(
|
105
|
+
key=key, response_model=lm_config.get("response_model", None)
|
106
|
+
)
|
107
|
+
elif self.use_ephemeral_store:
|
108
|
+
return ephemeral_cache.hit_cache(
|
109
|
+
key=key, response_model=lm_config.get("response_model", None)
|
110
|
+
)
|
111
|
+
else:
|
112
|
+
return None
|
113
|
+
|
114
|
+
def add_to_managed_cache(
|
115
|
+
self,
|
116
|
+
model: str,
|
117
|
+
messages: List[Dict[str, Any]],
|
118
|
+
lm_config: Dict[str, Any],
|
119
|
+
output: BaseLMResponse,
|
120
|
+
tools: Optional[List[BaseTool]] = None,
|
121
|
+
) -> None:
|
122
|
+
"""Add the given output to the cache."""
|
123
|
+
self._validate_messages(messages)
|
124
|
+
assert type(output) == BaseLMResponse, "output must be a BaseLMResponse"
|
125
|
+
assert type(lm_config) == dict, "lm_config must be a dictionary"
|
126
|
+
key = map_params_to_key(
|
127
|
+
messages,
|
128
|
+
model,
|
129
|
+
lm_config.get("temperature", 0.0),
|
130
|
+
lm_config.get("response_model", None),
|
131
|
+
tools,
|
132
|
+
lm_config.get("reasoning_effort", "low"),
|
133
|
+
)
|
134
|
+
if self.use_persistent_store:
|
135
|
+
persistent_cache.add_to_cache(key, output)
|
136
|
+
if self.use_ephemeral_store:
|
137
|
+
ephemeral_cache.add_to_cache(key, output)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from synth_ai.zyk.lms.caching.handler import CacheHandler
|
2
|
+
|
3
|
+
cache_handler = CacheHandler(use_ephemeral_store=True, use_persistent_store=True)
|
4
|
+
ephemeral_cache_handler = CacheHandler(
|
5
|
+
use_ephemeral_store=True, use_persistent_store=False
|
6
|
+
)
|
7
|
+
|
8
|
+
|
9
|
+
def get_cache_handler(use_ephemeral_cache_only: bool = False):
|
10
|
+
if use_ephemeral_cache_only:
|
11
|
+
return ephemeral_cache_handler
|
12
|
+
else:
|
13
|
+
return cache_handler
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
import sqlite3
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Optional, Type, Union
|
6
|
+
|
7
|
+
from pydantic import BaseModel
|
8
|
+
|
9
|
+
from synth_ai.zyk.lms.vendors.base import BaseLMResponse
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class PersistentCache:
|
14
|
+
def __init__(self, db_path: str = ".cache/persistent_cache.db"):
|
15
|
+
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
16
|
+
self.conn = sqlite3.connect(db_path)
|
17
|
+
self.cursor = self.conn.cursor()
|
18
|
+
self.cursor.execute("""CREATE TABLE IF NOT EXISTS cache
|
19
|
+
(key TEXT PRIMARY KEY, response TEXT)""")
|
20
|
+
self.conn.commit()
|
21
|
+
|
22
|
+
def hit_cache(
|
23
|
+
self, key: str, response_model: Optional[Type[BaseModel]] = None
|
24
|
+
) -> Optional[BaseLMResponse]:
|
25
|
+
self.cursor.execute("SELECT response FROM cache WHERE key = ?", (key,))
|
26
|
+
result = self.cursor.fetchone()
|
27
|
+
if not result:
|
28
|
+
return None
|
29
|
+
|
30
|
+
try:
|
31
|
+
cache_data = json.loads(result[0])
|
32
|
+
except json.JSONDecodeError:
|
33
|
+
# Handle legacy string responses
|
34
|
+
return BaseLMResponse(
|
35
|
+
raw_response=result[0], structured_output=None, tool_calls=None
|
36
|
+
)
|
37
|
+
|
38
|
+
if not isinstance(cache_data, dict):
|
39
|
+
return BaseLMResponse(
|
40
|
+
raw_response=cache_data, structured_output=None, tool_calls=None
|
41
|
+
)
|
42
|
+
|
43
|
+
raw_response = cache_data.get("raw_response")
|
44
|
+
tool_calls = cache_data.get("tool_calls")
|
45
|
+
structured_output = cache_data.get("structured_output")
|
46
|
+
|
47
|
+
if response_model and structured_output:
|
48
|
+
structured_output = response_model(**structured_output)
|
49
|
+
|
50
|
+
return BaseLMResponse(
|
51
|
+
raw_response=raw_response,
|
52
|
+
structured_output=structured_output,
|
53
|
+
tool_calls=tool_calls,
|
54
|
+
)
|
55
|
+
|
56
|
+
def add_to_cache(self, key: str, response: Union[BaseLMResponse, str]) -> None:
|
57
|
+
if isinstance(response, str):
|
58
|
+
cache_data = response
|
59
|
+
elif isinstance(response, BaseLMResponse):
|
60
|
+
cache_data = {
|
61
|
+
"raw_response": response.raw_response
|
62
|
+
if response.raw_response is not None
|
63
|
+
else None,
|
64
|
+
"tool_calls": response.tool_calls
|
65
|
+
if response.tool_calls is not None
|
66
|
+
else None,
|
67
|
+
"structured_output": (
|
68
|
+
response.structured_output.model_dump()
|
69
|
+
if response.structured_output is not None
|
70
|
+
else None
|
71
|
+
),
|
72
|
+
}
|
73
|
+
else:
|
74
|
+
raise ValueError(f"Invalid response type: {type(response)}")
|
75
|
+
|
76
|
+
self.cursor.execute(
|
77
|
+
"INSERT OR REPLACE INTO cache (key, response) VALUES (?, ?)",
|
78
|
+
(key, json.dumps(cache_data)),
|
79
|
+
)
|
80
|
+
self.conn.commit()
|
81
|
+
|
82
|
+
def close(self) -> None:
|
83
|
+
self.conn.close()
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import os
|
2
|
+
from dotenv import load_dotenv
|
3
|
+
|
4
|
+
|
5
|
+
def should_use_cache() -> bool:
|
6
|
+
load_dotenv()
|
7
|
+
cache_env = os.getenv("USE_ZYK_CACHE", "true").lower()
|
8
|
+
return cache_env not in ("false", "0", "no")
|
9
|
+
|
10
|
+
reasoning_models = ["o1","o3-mini", "o3", "o4-mini", "claude-3-7-sonnet-latest"]
|
@@ -0,0 +1,22 @@
|
|
1
|
+
OPENAI_REASONING_MODELS = ["o4", "o4-mini", "o3","o3-mini", "o1-mini", "o1"]
|
2
|
+
CLAUDE_REASONING_MODELS = ["claude-3-7-sonnet-latest"]
|
3
|
+
GEMINI_REASONING_MODELS = ["gemini-2.5-flash", "gemini-2.5-pro"]
|
4
|
+
|
5
|
+
# Gemini models that support thinking
|
6
|
+
GEMINI_REASONING_MODELS = ["gemini-2.5-flash", "gemini-2.5-pro"]
|
7
|
+
GEMINI_THINKING_BUDGETS = {
|
8
|
+
"high": 10000,
|
9
|
+
"medium": 5000,
|
10
|
+
"low": 2500,
|
11
|
+
}
|
12
|
+
|
13
|
+
# Anthropic Sonnet 3.7 budgets
|
14
|
+
SONNET_37_BUDGETS = {
|
15
|
+
"high": 8192,
|
16
|
+
"medium": 4096,
|
17
|
+
"low": 2048,
|
18
|
+
}
|
19
|
+
|
20
|
+
REASONING_MODELS = OPENAI_REASONING_MODELS + CLAUDE_REASONING_MODELS + GEMINI_REASONING_MODELS
|
21
|
+
|
22
|
+
SPECIAL_BASE_TEMPS = {model: 1 for model in REASONING_MODELS}
|
File without changes
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from synth_ai.zyk.lms.vendors.core.anthropic_api import AnthropicAPI
|
2
|
+
from synth_ai.zyk.lms.vendors.core.gemini_api import GeminiAPI
|
3
|
+
from synth_ai.zyk.lms.vendors.core.openai_api import (
|
4
|
+
OpenAIPrivate,
|
5
|
+
OpenAIStructuredOutputClient,
|
6
|
+
)
|
7
|
+
from synth_ai.zyk.lms.vendors.supported.deepseek import DeepSeekAPI
|
8
|
+
from synth_ai.zyk.lms.vendors.supported.together import TogetherAPI
|
9
|
+
from synth_ai.zyk.lms.vendors.supported.groq import GroqAPI
|
10
|
+
from synth_ai.zyk.lms.vendors.core.mistral_api import MistralAPI
|
11
|
+
|
12
|
+
|
13
|
+
class OpenAIClient(OpenAIPrivate):
|
14
|
+
def __init__(self, synth_logging: bool = True):
|
15
|
+
super().__init__(
|
16
|
+
synth_logging=synth_logging,
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
class AnthropicClient(AnthropicAPI):
|
21
|
+
def __init__(self):
|
22
|
+
super().__init__()
|
23
|
+
|
24
|
+
|
25
|
+
class GeminiClient(GeminiAPI):
|
26
|
+
def __init__(self):
|
27
|
+
super().__init__()
|
28
|
+
|
29
|
+
|
30
|
+
class DeepSeekClient(DeepSeekAPI):
|
31
|
+
def __init__(self):
|
32
|
+
super().__init__()
|
33
|
+
|
34
|
+
|
35
|
+
class TogetherClient(TogetherAPI):
|
36
|
+
def __init__(self):
|
37
|
+
super().__init__()
|
38
|
+
|
39
|
+
|
40
|
+
class GroqClient(GroqAPI):
|
41
|
+
def __init__(self):
|
42
|
+
super().__init__()
|
43
|
+
|
44
|
+
|
45
|
+
class MistralClient(MistralAPI):
|
46
|
+
def __init__(self):
|
47
|
+
super().__init__()
|