synth-ai 0.1.0.dev14__tar.gz → 0.1.0.dev51__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.
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/PKG-INFO +9 -10
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/pyproject.toml +10 -12
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/setup.py +1 -1
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/__init__.py +2 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/__init__.py +3 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/caching/ephemeral.py +72 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/caching/handler.py +137 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/caching/persistent.py +83 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/config.py +2 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/constants.py +22 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/core/main.py +36 -17
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/core/vendor_clients.py +4 -3
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/structured_outputs/handler.py +99 -46
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/structured_outputs/inject.py +314 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/structured_outputs/rehabilitate.py +4 -3
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/tools/base.py +118 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/vendors/base.py +31 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/vendors/core/anthropic_api.py +365 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/vendors/core/gemini_api.py +282 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/vendors/core/mistral_api.py +331 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/core/openai_api.py +71 -29
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/vendors/openai_standard.py +345 -0
- synth_ai-0.1.0.dev51/synth_ai/zyk/lms/vendors/supported/deepseek.py +73 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai.egg-info/PKG-INFO +9 -10
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai.egg-info/SOURCES.txt +2 -7
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai.egg-info/requires.txt +6 -8
- synth_ai-0.1.0.dev51/synth_ai.egg-info/top_level.txt +1 -0
- synth_ai-0.1.0.dev14/private_tests/try_synth_sdk.py +0 -1
- synth_ai-0.1.0.dev14/public_tests/test_all_structured_outputs.py +0 -201
- synth_ai-0.1.0.dev14/public_tests/test_synth_sdk.py +0 -389
- synth_ai-0.1.0.dev14/synth_ai/zyk/__init__.py +0 -3
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/caching/ephemeral.py +0 -50
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/caching/handler.py +0 -92
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/caching/persistent.py +0 -55
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/structured_outputs/inject.py +0 -185
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/vendors/base.py +0 -15
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/vendors/constants.py +0 -5
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/vendors/core/anthropic_api.py +0 -191
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/vendors/core/gemini_api.py +0 -146
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/vendors/core/mistral_api.py +0 -221
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/vendors/openai_standard.py +0 -144
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/vendors/supported/deepseek.py +0 -18
- synth_ai-0.1.0.dev14/synth_ai.egg-info/top_level.txt +0 -6
- synth_ai-0.1.0.dev14/tests/test_agent.py +0 -538
- synth_ai-0.1.0.dev14/tests/test_recursive_structured_outputs.py +0 -180
- synth_ai-0.1.0.dev14/tests/test_structured_outputs.py +0 -100
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/LICENSE +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/README.md +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/setup.cfg +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/caching/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/caching/constants.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/caching/dbs.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/caching/initialize.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/core/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/core/all.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/core/exceptions.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/cost/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/cost/monitor.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/cost/statefulness.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/structured_outputs/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/core/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/local/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/local/ollama.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/retries.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/supported/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/supported/groq.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/supported/ollama.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai/zyk/lms/vendors/supported/together.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev51}/synth_ai.egg-info/dependency_links.txt +0 -0
- {synth_ai-0.1.0.dev14/public_tests → synth_ai-0.1.0.dev51/tests}/test_agent.py +0 -0
- {synth_ai-0.1.0.dev14/public_tests → synth_ai-0.1.0.dev51/tests}/test_recursive_structured_outputs.py +0 -0
- {synth_ai-0.1.0.dev14/public_tests → synth_ai-0.1.0.dev51/tests}/test_structured_outputs.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: synth-ai
|
3
|
-
Version: 0.1.0.
|
3
|
+
Version: 0.1.0.dev51
|
4
4
|
Summary: Software for aiding the best and multiplying the will.
|
5
5
|
Home-page: https://github.com/synth-laboratories/synth-ai
|
6
6
|
Author: Josh Purtell
|
@@ -35,24 +35,23 @@ Classifier: Programming Language :: Python :: 3
|
|
35
35
|
Requires-Python: >=3.10
|
36
36
|
Description-Content-Type: text/markdown
|
37
37
|
License-File: LICENSE
|
38
|
-
Requires-Dist: openai
|
39
|
-
Requires-Dist: pydantic
|
40
|
-
Requires-Dist: diskcache
|
38
|
+
Requires-Dist: openai>=1.0.0
|
39
|
+
Requires-Dist: pydantic>=2.0.0
|
40
|
+
Requires-Dist: diskcache>=5.0.0
|
41
41
|
Requires-Dist: backoff>=2.2.1
|
42
42
|
Requires-Dist: anthropic>=0.34.2
|
43
43
|
Requires-Dist: google>=3.0.0
|
44
|
-
Requires-Dist: google-
|
44
|
+
Requires-Dist: google-api-core
|
45
|
+
Requires-Dist: google-generativeai
|
45
46
|
Requires-Dist: together>=1.2.12
|
46
47
|
Requires-Dist: langfuse>=2.56.1
|
47
|
-
Requires-Dist: synth-sdk>=0.3.1.dev4
|
48
48
|
Requires-Dist: datasets>=3.2.0
|
49
49
|
Requires-Dist: groq>=0.18.0
|
50
50
|
Requires-Dist: pytest-timeout>=2.3.1
|
51
|
-
Requires-Dist:
|
52
|
-
Requires-Dist: ollama>=0.4.7
|
53
|
-
Requires-Dist: mistralai>=1.5.0
|
51
|
+
Requires-Dist: mistralai
|
54
52
|
Dynamic: author
|
55
53
|
Dynamic: home-page
|
54
|
+
Dynamic: license-file
|
56
55
|
|
57
56
|
AI Infra used by the Synth AI Team
|
58
57
|
```
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "synth-ai"
|
3
|
-
version = "0.1.0.
|
3
|
+
version = "0.1.0.dev51"
|
4
4
|
description = "Software for aiding the best and multiplying the will."
|
5
5
|
readme = "README.md"
|
6
6
|
authors = [{ name = "Josh Purtell", email = "josh@usesynth.ai" }]
|
@@ -12,22 +12,20 @@ classifiers = [
|
|
12
12
|
]
|
13
13
|
keywords = ["synth-ai"]
|
14
14
|
dependencies = [
|
15
|
-
"openai",
|
16
|
-
"pydantic",
|
17
|
-
"diskcache",
|
15
|
+
"openai>=1.0.0",
|
16
|
+
"pydantic>=2.0.0",
|
17
|
+
"diskcache>=5.0.0",
|
18
18
|
"backoff>=2.2.1",
|
19
19
|
"anthropic>=0.34.2",
|
20
20
|
"google>=3.0.0",
|
21
|
-
"google-
|
21
|
+
"google-api-core",
|
22
|
+
"google-generativeai",
|
22
23
|
"together>=1.2.12",
|
23
24
|
"langfuse>=2.56.1",
|
24
|
-
"synth-sdk>=0.3.1.dev4",
|
25
25
|
"datasets>=3.2.0",
|
26
26
|
"groq>=0.18.0",
|
27
27
|
"pytest-timeout>=2.3.1",
|
28
|
-
"
|
29
|
-
"ollama>=0.4.7",
|
30
|
-
"mistralai>=1.5.0",
|
28
|
+
"mistralai",
|
31
29
|
]
|
32
30
|
requires-python = ">=3.10"
|
33
31
|
|
@@ -38,9 +36,9 @@ Homepage = "https://github.com/synth-laboratories/synth-ai"
|
|
38
36
|
requires = ["setuptools>=61.0"]
|
39
37
|
build-backend = "setuptools.build_meta"
|
40
38
|
|
41
|
-
[tool.setuptools]
|
42
|
-
|
43
|
-
|
39
|
+
[tool.setuptools.packages.find]
|
40
|
+
where = ["."] # Assumes 'synth_ai' package is in a dir named 'synth_ai' at this level
|
41
|
+
include = ["synth_ai*"]
|
44
42
|
|
45
43
|
# Explicitly exclude test directories and files
|
46
44
|
[tool.setuptools.exclude-package-data]
|
@@ -4,5 +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
|
7
8
|
|
8
9
|
__version__ = version("synth-ai") # Gets version from installed package metadata
|
10
|
+
__all__ = ["LM"] # Explicitly define public API
|
@@ -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,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,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}
|
@@ -10,9 +10,8 @@ from synth_ai.zyk.lms.core.vendor_clients import (
|
|
10
10
|
)
|
11
11
|
from synth_ai.zyk.lms.structured_outputs.handler import StructuredOutputHandler
|
12
12
|
from synth_ai.zyk.lms.vendors.base import VendorBase
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
from synth_ai.zyk.lms.tools.base import BaseTool
|
14
|
+
from synth_ai.zyk.lms.config import reasoning_models
|
16
15
|
|
17
16
|
def build_messages(
|
18
17
|
sys_msg: str,
|
@@ -108,7 +107,7 @@ class LM:
|
|
108
107
|
{"max_retries": max_retries_dict.get(max_retries, 2)},
|
109
108
|
)
|
110
109
|
# Override temperature to 1 for reasoning models
|
111
|
-
effective_temperature = 1.0 if model_name in
|
110
|
+
effective_temperature = 1.0 if model_name in reasoning_models else temperature
|
112
111
|
self.lm_config = {"temperature": effective_temperature}
|
113
112
|
self.model_name = model_name
|
114
113
|
|
@@ -120,6 +119,8 @@ class LM:
|
|
120
119
|
images_as_bytes: List[Any] = [],
|
121
120
|
response_model: Optional[BaseModel] = None,
|
122
121
|
use_ephemeral_cache_only: bool = False,
|
122
|
+
tools: Optional[List[BaseTool]] = None,
|
123
|
+
reasoning_effort: str = "low",
|
123
124
|
):
|
124
125
|
assert (system_message is None) == (
|
125
126
|
user_message is None
|
@@ -127,37 +128,45 @@ class LM:
|
|
127
128
|
assert (
|
128
129
|
(messages is None) != (system_message is None)
|
129
130
|
), "Must provide either messages or system_message/user_message pair, but not both"
|
130
|
-
|
131
|
+
assert not (response_model and tools), "Cannot provide both response_model and tools"
|
131
132
|
if messages is None:
|
132
133
|
messages = build_messages(
|
133
134
|
system_message, user_message, images_as_bytes, self.model_name
|
134
135
|
)
|
135
|
-
|
136
|
+
result = None
|
136
137
|
if response_model:
|
137
138
|
try:
|
138
|
-
|
139
|
+
result = self.structured_output_handler.call_sync(
|
139
140
|
messages,
|
140
141
|
model=self.model_name,
|
141
142
|
lm_config=self.lm_config,
|
142
143
|
response_model=response_model,
|
143
144
|
use_ephemeral_cache_only=use_ephemeral_cache_only,
|
145
|
+
reasoning_effort=reasoning_effort,
|
144
146
|
)
|
145
147
|
except StructuredOutputCoercionFailureException:
|
146
148
|
# print("Falling back to backup handler")
|
147
|
-
|
149
|
+
result = self.backup_structured_output_handler.call_sync(
|
148
150
|
messages,
|
149
151
|
model=self.model_name,
|
150
152
|
lm_config=self.lm_config,
|
151
153
|
response_model=response_model,
|
152
154
|
use_ephemeral_cache_only=use_ephemeral_cache_only,
|
155
|
+
reasoning_effort=reasoning_effort,
|
153
156
|
)
|
154
157
|
else:
|
155
|
-
|
158
|
+
result = self.client._hit_api_sync(
|
156
159
|
messages=messages,
|
157
160
|
model=self.model_name,
|
158
161
|
lm_config=self.lm_config,
|
159
162
|
use_ephemeral_cache_only=use_ephemeral_cache_only,
|
163
|
+
tools=tools,
|
164
|
+
reasoning_effort=reasoning_effort,
|
160
165
|
)
|
166
|
+
assert isinstance(result.raw_response, str), "Raw response must be a string"
|
167
|
+
assert (isinstance(result.structured_output, BaseModel) or result.structured_output is None), "Structured output must be a Pydantic model or None"
|
168
|
+
assert (isinstance(result.tool_calls, list) or result.tool_calls is None), "Tool calls must be a list or None"
|
169
|
+
return result
|
161
170
|
|
162
171
|
async def respond_async(
|
163
172
|
self,
|
@@ -167,6 +176,8 @@ class LM:
|
|
167
176
|
images_as_bytes: List[Any] = [],
|
168
177
|
response_model: Optional[BaseModel] = None,
|
169
178
|
use_ephemeral_cache_only: bool = False,
|
179
|
+
tools: Optional[List[BaseTool]] = None,
|
180
|
+
reasoning_effort: str = "low",
|
170
181
|
):
|
171
182
|
# "In respond_async")
|
172
183
|
assert (system_message is None) == (
|
@@ -176,39 +187,47 @@ class LM:
|
|
176
187
|
(messages is None) != (system_message is None)
|
177
188
|
), "Must provide either messages or system_message/user_message pair, but not both"
|
178
189
|
|
190
|
+
assert not (response_model and tools), "Cannot provide both response_model and tools"
|
179
191
|
if messages is None:
|
180
192
|
messages = build_messages(
|
181
193
|
system_message, user_message, images_as_bytes, self.model_name
|
182
194
|
)
|
183
|
-
|
195
|
+
result = None
|
184
196
|
if response_model:
|
185
197
|
try:
|
186
|
-
|
187
|
-
|
198
|
+
print("Trying structured output handler")
|
199
|
+
result = await self.structured_output_handler.call_async(
|
188
200
|
messages,
|
189
201
|
model=self.model_name,
|
190
202
|
lm_config=self.lm_config,
|
191
203
|
response_model=response_model,
|
192
204
|
use_ephemeral_cache_only=use_ephemeral_cache_only,
|
205
|
+
reasoning_effort=reasoning_effort,
|
193
206
|
)
|
194
207
|
except StructuredOutputCoercionFailureException:
|
195
|
-
|
196
|
-
|
208
|
+
print("Falling back to backup handler")
|
209
|
+
result = await self.backup_structured_output_handler.call_async(
|
197
210
|
messages,
|
198
211
|
model=self.model_name,
|
199
212
|
lm_config=self.lm_config,
|
200
213
|
response_model=response_model,
|
201
214
|
use_ephemeral_cache_only=use_ephemeral_cache_only,
|
215
|
+
reasoning_effort=reasoning_effort,
|
202
216
|
)
|
203
217
|
else:
|
204
|
-
#
|
205
|
-
|
218
|
+
#print("Calling API no response model")
|
219
|
+
result = await self.client._hit_api_async(
|
206
220
|
messages=messages,
|
207
221
|
model=self.model_name,
|
208
222
|
lm_config=self.lm_config,
|
209
223
|
use_ephemeral_cache_only=use_ephemeral_cache_only,
|
224
|
+
tools=tools,
|
225
|
+
reasoning_effort=reasoning_effort,
|
210
226
|
)
|
211
|
-
|
227
|
+
assert isinstance(result.raw_response, str), "Raw response must be a string"
|
228
|
+
assert (isinstance(result.structured_output, BaseModel) or result.structured_output is None), "Structured output must be a Pydantic model or None"
|
229
|
+
assert (isinstance(result.tool_calls, list) or result.tool_calls is None), "Tool calls must be a list or None"
|
230
|
+
return result
|
212
231
|
|
213
232
|
if __name__ == "__main__":
|
214
233
|
import asyncio
|
@@ -5,15 +5,15 @@ from synth_ai.zyk.lms.core.all import (
|
|
5
5
|
AnthropicClient,
|
6
6
|
DeepSeekClient,
|
7
7
|
GeminiClient,
|
8
|
+
GroqAPI,
|
9
|
+
MistralAPI,
|
8
10
|
# OpenAIClient,
|
9
11
|
OpenAIStructuredOutputClient,
|
10
12
|
TogetherClient,
|
11
|
-
GroqAPI,
|
12
|
-
MistralAPI,
|
13
13
|
)
|
14
14
|
|
15
15
|
openai_naming_regexes: List[Pattern] = [
|
16
|
-
re.compile(r"^(ft:)?(o[1,3](-.*)?|gpt-.*)$"),
|
16
|
+
re.compile(r"^(ft:)?(o[1,3,4](-.*)?|gpt-.*)$"),
|
17
17
|
]
|
18
18
|
openai_formatting_model_regexes: List[Pattern] = [
|
19
19
|
re.compile(r"^(ft:)?gpt-4o(-.*)?$"),
|
@@ -23,6 +23,7 @@ anthropic_naming_regexes: List[Pattern] = [
|
|
23
23
|
]
|
24
24
|
gemini_naming_regexes: List[Pattern] = [
|
25
25
|
re.compile(r"^gemini-.*$"),
|
26
|
+
re.compile(r"^gemma[2-9].*$"),
|
26
27
|
]
|
27
28
|
deepseek_naming_regexes: List[Pattern] = [
|
28
29
|
re.compile(r"^deepseek-.*$"),
|