synth-ai 0.1.0.dev4__py3-none-any.whl → 0.1.0.dev6__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.
Potentially problematic release.
This version of synth-ai might be problematic. Click here for more details.
- public_tests/synth_sdk.py +389 -0
- public_tests/test_agent.py +538 -0
- public_tests/test_recursive_structured_outputs.py +180 -0
- public_tests/test_structured_outputs.py +100 -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 +50 -0
- synth_ai/zyk/lms/caching/handler.py +92 -0
- synth_ai/zyk/lms/caching/initialize.py +13 -0
- synth_ai/zyk/lms/caching/persistent.py +55 -0
- synth_ai/zyk/lms/config.py +8 -0
- synth_ai/zyk/lms/core/__init__.py +0 -0
- synth_ai/zyk/lms/core/all.py +35 -0
- synth_ai/zyk/lms/core/exceptions.py +9 -0
- synth_ai/zyk/lms/core/main.py +245 -0
- synth_ai/zyk/lms/core/vendor_clients.py +60 -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 +388 -0
- synth_ai/zyk/lms/structured_outputs/inject.py +185 -0
- synth_ai/zyk/lms/structured_outputs/rehabilitate.py +186 -0
- synth_ai/zyk/lms/vendors/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/base.py +15 -0
- synth_ai/zyk/lms/vendors/constants.py +5 -0
- synth_ai/zyk/lms/vendors/core/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/core/anthropic_api.py +191 -0
- synth_ai/zyk/lms/vendors/core/gemini_api.py +146 -0
- synth_ai/zyk/lms/vendors/core/openai_api.py +145 -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 +141 -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 +18 -0
- synth_ai/zyk/lms/vendors/supported/together.py +11 -0
- {synth_ai-0.1.0.dev4.dist-info → synth_ai-0.1.0.dev6.dist-info}/METADATA +1 -1
- synth_ai-0.1.0.dev6.dist-info/RECORD +46 -0
- synth_ai-0.1.0.dev6.dist-info/top_level.txt +2 -0
- synth_ai-0.1.0.dev4.dist-info/RECORD +0 -7
- synth_ai-0.1.0.dev4.dist-info/top_level.txt +0 -1
- {synth_ai-0.1.0.dev4.dist-info → synth_ai-0.1.0.dev6.dist-info}/LICENSE +0 -0
- {synth_ai-0.1.0.dev4.dist-info → synth_ai-0.1.0.dev6.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import unittest
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from synth_ai.zyk.lms.core.main import LM
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Define example structured output models
|
|
11
|
+
class SimpleResponse(BaseModel):
|
|
12
|
+
message: str
|
|
13
|
+
confidence: float
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ComplexResponse(BaseModel):
|
|
17
|
+
title: str
|
|
18
|
+
tags: List[str]
|
|
19
|
+
content: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NestedResponse(BaseModel):
|
|
23
|
+
main_category: str
|
|
24
|
+
subcategories: List[str]
|
|
25
|
+
details: SimpleResponse
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Define nested structured output models
|
|
29
|
+
class Address(BaseModel):
|
|
30
|
+
street: str
|
|
31
|
+
city: str
|
|
32
|
+
country: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PersonalInfo(BaseModel):
|
|
36
|
+
name: str
|
|
37
|
+
age: int
|
|
38
|
+
address: Address
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class WorkInfo(BaseModel):
|
|
42
|
+
company: str
|
|
43
|
+
position: str
|
|
44
|
+
years_experience: int
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class NestedPersonResponse(BaseModel):
|
|
48
|
+
personal: PersonalInfo
|
|
49
|
+
work: WorkInfo
|
|
50
|
+
skills: List[str]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ProjectDetails(BaseModel):
|
|
54
|
+
name: str
|
|
55
|
+
description: str
|
|
56
|
+
technologies: List[str]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class NestedPortfolioResponse(BaseModel):
|
|
60
|
+
developer: PersonalInfo
|
|
61
|
+
projects: List[ProjectDetails]
|
|
62
|
+
total_experience: int
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class NestedCompanyResponse(BaseModel):
|
|
66
|
+
name: str
|
|
67
|
+
founded: int
|
|
68
|
+
headquarters: Address
|
|
69
|
+
employees: List[PersonalInfo]
|
|
70
|
+
main_products: List[str]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestLMStructuredOutputs(unittest.TestCase):
|
|
74
|
+
@classmethod
|
|
75
|
+
def setUpClass(cls):
|
|
76
|
+
# Initialize the LM once for all tests
|
|
77
|
+
cls.lm = LM(
|
|
78
|
+
model_name="gpt-4o-mini",
|
|
79
|
+
formatting_model_name="gpt-4o-mini",
|
|
80
|
+
temperature=0.7,
|
|
81
|
+
max_retries="Few",
|
|
82
|
+
structured_output_mode="forced_json",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def test_sync_simple_response(self):
|
|
86
|
+
result = self.lm.respond_sync(
|
|
87
|
+
system_message="You are a helpful assistant.",
|
|
88
|
+
user_message="Give me a short greeting and your confidence level.",
|
|
89
|
+
response_model=SimpleResponse,
|
|
90
|
+
)
|
|
91
|
+
self.assertIsInstance(result, SimpleResponse)
|
|
92
|
+
self.assertIsInstance(result.message, str)
|
|
93
|
+
self.assertIsInstance(result.confidence, float)
|
|
94
|
+
self.assertGreaterEqual(result.confidence, 0)
|
|
95
|
+
self.assertLessEqual(result.confidence, 1)
|
|
96
|
+
|
|
97
|
+
def test_sync_complex_response(self):
|
|
98
|
+
result = self.lm.respond_sync(
|
|
99
|
+
system_message="You are a content creator.",
|
|
100
|
+
user_message="Create a short blog post about AI.",
|
|
101
|
+
response_model=ComplexResponse,
|
|
102
|
+
)
|
|
103
|
+
self.assertIsInstance(result, ComplexResponse)
|
|
104
|
+
self.assertIsInstance(result.title, str)
|
|
105
|
+
self.assertIsInstance(result.tags, list)
|
|
106
|
+
self.assertIsInstance(result.content, str)
|
|
107
|
+
|
|
108
|
+
async def async_nested_response(self):
|
|
109
|
+
result = await self.lm.respond_async(
|
|
110
|
+
system_message="You are a categorization expert.",
|
|
111
|
+
user_message="Categorize 'Python' and provide a brief description.",
|
|
112
|
+
response_model=NestedResponse,
|
|
113
|
+
)
|
|
114
|
+
self.assertIsInstance(result, NestedResponse)
|
|
115
|
+
self.assertIsInstance(result.main_category, str)
|
|
116
|
+
self.assertIsInstance(result.subcategories, list)
|
|
117
|
+
self.assertIsInstance(result.details, SimpleResponse)
|
|
118
|
+
|
|
119
|
+
def test_async_nested_response(self):
|
|
120
|
+
asyncio.run(self.async_nested_response())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class TestLMNestedStructuredOutputs(unittest.TestCase):
|
|
124
|
+
@classmethod
|
|
125
|
+
def setUpClass(cls):
|
|
126
|
+
# Initialize the LM once for all tests
|
|
127
|
+
cls.lm = LM(
|
|
128
|
+
model_name="gpt-4o-mini",
|
|
129
|
+
formatting_model_name="gpt-4o-mini",
|
|
130
|
+
temperature=0.7,
|
|
131
|
+
max_retries="Few",
|
|
132
|
+
structured_output_mode="forced_json",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def test_sync_nested_person_response(self):
|
|
136
|
+
result = self.lm.respond_sync(
|
|
137
|
+
system_message="You are an HR assistant.",
|
|
138
|
+
user_message="Provide detailed information about a fictional employee named John Doe.",
|
|
139
|
+
response_model=NestedPersonResponse,
|
|
140
|
+
)
|
|
141
|
+
self.assertIsInstance(result, NestedPersonResponse)
|
|
142
|
+
self.assertIsInstance(result.personal, PersonalInfo)
|
|
143
|
+
self.assertIsInstance(result.personal.address, Address)
|
|
144
|
+
self.assertIsInstance(result.work, WorkInfo)
|
|
145
|
+
self.assertIsInstance(result.skills, list)
|
|
146
|
+
|
|
147
|
+
def test_sync_nested_portfolio_response(self):
|
|
148
|
+
result = self.lm.respond_sync(
|
|
149
|
+
system_message="You are a portfolio manager.",
|
|
150
|
+
user_message="Create a portfolio for a fictional software developer with multiple projects.",
|
|
151
|
+
response_model=NestedPortfolioResponse,
|
|
152
|
+
)
|
|
153
|
+
self.assertIsInstance(result, NestedPortfolioResponse)
|
|
154
|
+
self.assertIsInstance(result.developer, PersonalInfo)
|
|
155
|
+
self.assertIsInstance(result.developer.address, Address)
|
|
156
|
+
self.assertIsInstance(result.projects, list)
|
|
157
|
+
for project in result.projects:
|
|
158
|
+
self.assertIsInstance(project, ProjectDetails)
|
|
159
|
+
self.assertIsInstance(result.total_experience, int)
|
|
160
|
+
|
|
161
|
+
async def async_nested_company_response(self):
|
|
162
|
+
result = await self.lm.respond_async(
|
|
163
|
+
system_message="You are a company information specialist.",
|
|
164
|
+
user_message="Provide detailed information about a fictional tech company.",
|
|
165
|
+
response_model=NestedCompanyResponse,
|
|
166
|
+
)
|
|
167
|
+
self.assertIsInstance(result, NestedCompanyResponse)
|
|
168
|
+
self.assertIsInstance(result.headquarters, Address)
|
|
169
|
+
self.assertIsInstance(result.employees, list)
|
|
170
|
+
for employee in result.employees:
|
|
171
|
+
self.assertIsInstance(employee, PersonalInfo)
|
|
172
|
+
self.assertIsInstance(employee.address, Address)
|
|
173
|
+
self.assertIsInstance(result.main_products, list)
|
|
174
|
+
|
|
175
|
+
def test_async_nested_company_response(self):
|
|
176
|
+
asyncio.run(self.async_nested_company_response())
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
unittest.main()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import unittest
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from synth_ai.zyk.lms.core.main import LM
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Define example structured output models
|
|
11
|
+
class SimpleResponse(BaseModel):
|
|
12
|
+
message: str
|
|
13
|
+
confidence_between_zero_one: float = Field(
|
|
14
|
+
..., description="Confidence level between 0 and 1"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ComplexResponse(BaseModel):
|
|
19
|
+
title: str
|
|
20
|
+
tags: List[str]
|
|
21
|
+
content: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NestedResponse(BaseModel):
|
|
25
|
+
main_category: str
|
|
26
|
+
subcategories: List[str]
|
|
27
|
+
details: SimpleResponse
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestLMStructuredOutputs(unittest.TestCase):
|
|
31
|
+
@classmethod
|
|
32
|
+
def setUpClass(cls):
|
|
33
|
+
# Initialize LMs for both forced_json and stringified_json modes
|
|
34
|
+
cls.lm_forced_json = LM(
|
|
35
|
+
model_name="gpt-4o-mini",
|
|
36
|
+
formatting_model_name="gpt-4o-mini",
|
|
37
|
+
temperature=0.7,
|
|
38
|
+
max_retries="Few",
|
|
39
|
+
structured_output_mode="forced_json",
|
|
40
|
+
)
|
|
41
|
+
cls.lm_stringified_json = LM(
|
|
42
|
+
model_name="gpt-4o-mini",
|
|
43
|
+
formatting_model_name="gpt-4o-mini",
|
|
44
|
+
temperature=0.7,
|
|
45
|
+
max_retries="Few",
|
|
46
|
+
structured_output_mode="stringified_json",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def test_sync_simple_response(self):
|
|
50
|
+
for lm in [self.lm_forced_json, self.lm_stringified_json]:
|
|
51
|
+
with self.subTest(
|
|
52
|
+
mode=lm.structured_output_handler.handler.structured_output_mode
|
|
53
|
+
):
|
|
54
|
+
result = lm.respond_sync(
|
|
55
|
+
system_message="You are a helpful assistant.",
|
|
56
|
+
user_message="Give me a short greeting and your confidence level.",
|
|
57
|
+
response_model=SimpleResponse,
|
|
58
|
+
)
|
|
59
|
+
self.assertIsInstance(result, SimpleResponse)
|
|
60
|
+
self.assertIsInstance(result.message, str)
|
|
61
|
+
self.assertIsInstance(result.confidence_between_zero_one, float)
|
|
62
|
+
self.assertGreaterEqual(result.confidence_between_zero_one, 0)
|
|
63
|
+
self.assertLessEqual(result.confidence_between_zero_one, 1)
|
|
64
|
+
|
|
65
|
+
def test_sync_complex_response(self):
|
|
66
|
+
for lm in [self.lm_forced_json, self.lm_stringified_json]:
|
|
67
|
+
with self.subTest(
|
|
68
|
+
mode=lm.structured_output_handler.handler.structured_output_mode
|
|
69
|
+
):
|
|
70
|
+
result = lm.respond_sync(
|
|
71
|
+
system_message="You are a content creator.",
|
|
72
|
+
user_message="Create a short blog post about AI.",
|
|
73
|
+
response_model=ComplexResponse,
|
|
74
|
+
)
|
|
75
|
+
self.assertIsInstance(result, ComplexResponse)
|
|
76
|
+
self.assertIsInstance(result.title, str)
|
|
77
|
+
self.assertIsInstance(result.tags, list)
|
|
78
|
+
self.assertIsInstance(result.content, str)
|
|
79
|
+
|
|
80
|
+
async def async_nested_response(self, lm):
|
|
81
|
+
result = await lm.respond_async(
|
|
82
|
+
system_message="You are a categorization expert.",
|
|
83
|
+
user_message="Categorize 'Python' and provide a brief description.",
|
|
84
|
+
response_model=NestedResponse,
|
|
85
|
+
)
|
|
86
|
+
self.assertIsInstance(result, NestedResponse)
|
|
87
|
+
self.assertIsInstance(result.main_category, str)
|
|
88
|
+
self.assertIsInstance(result.subcategories, list)
|
|
89
|
+
self.assertIsInstance(result.details, SimpleResponse)
|
|
90
|
+
|
|
91
|
+
def test_async_nested_response(self):
|
|
92
|
+
for lm in [self.lm_forced_json, self.lm_stringified_json]: #
|
|
93
|
+
with self.subTest(
|
|
94
|
+
mode=lm.structured_output_handler.handler.structured_output_mode
|
|
95
|
+
):
|
|
96
|
+
asyncio.run(self.async_nested_response(lm))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DISKCACHE_SIZE_LIMIT = 10 * 1024 * 1024 * 1024 #10GB
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Dict, 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
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class EphemeralCache:
|
|
13
|
+
def __init__(self, fast_cache_dir: str = ".cache/ephemeral_cache"):
|
|
14
|
+
os.makedirs(fast_cache_dir, exist_ok=True)
|
|
15
|
+
self.fast_cache = Cache(fast_cache_dir, size_limit=DISKCACHE_SIZE_LIMIT)
|
|
16
|
+
|
|
17
|
+
def hit_cache(self, key: str, response_model: BaseModel) -> Optional[Dict]:
|
|
18
|
+
assert isinstance(response_model, BaseModel)
|
|
19
|
+
if key in self.fast_cache:
|
|
20
|
+
try:
|
|
21
|
+
cache_data = self.fast_cache[key]
|
|
22
|
+
except AttributeError:
|
|
23
|
+
return None
|
|
24
|
+
if response_model is not None:
|
|
25
|
+
if isinstance(cache_data["response"], dict):
|
|
26
|
+
response = cache_data["response"]
|
|
27
|
+
return response_model(**response)
|
|
28
|
+
if isinstance(cache_data, str):
|
|
29
|
+
return cache_data
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
def add_to_cache(self, key: str, response: Union[BaseModel, str]) -> None:
|
|
33
|
+
if isinstance(response, BaseModel):
|
|
34
|
+
response_dict = response.model_dump()
|
|
35
|
+
response_class = response.__class__.__name__
|
|
36
|
+
elif isinstance(response, str):
|
|
37
|
+
response_dict = response
|
|
38
|
+
response_class = None
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"Invalid response type: {type(response)}")
|
|
41
|
+
|
|
42
|
+
cache_data = {
|
|
43
|
+
"response": response_dict,
|
|
44
|
+
"response_class": response_class,
|
|
45
|
+
}
|
|
46
|
+
self.fast_cache[key] = cache_data
|
|
47
|
+
return key
|
|
48
|
+
|
|
49
|
+
def close(self):
|
|
50
|
+
self.fast_cache.close()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from typing import Any, Dict, List, Type, Union
|
|
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.config import should_use_cache
|
|
9
|
+
|
|
10
|
+
persistent_cache = PersistentCache()
|
|
11
|
+
ephemeral_cache = EphemeralCache()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def map_params_to_key(
|
|
15
|
+
messages: List[Dict],
|
|
16
|
+
model: str,
|
|
17
|
+
temperature: float,
|
|
18
|
+
response_model: Type[BaseModel],
|
|
19
|
+
) -> str:
|
|
20
|
+
if not all([isinstance(msg["content"], str) for msg in messages]):
|
|
21
|
+
normalized_messages = "".join([str(msg["content"]) for msg in messages])
|
|
22
|
+
else:
|
|
23
|
+
normalized_messages = "".join([msg["content"] for msg in messages])
|
|
24
|
+
normalized_model = model
|
|
25
|
+
normalized_temperature = f"{temperature:.2f}"[:4]
|
|
26
|
+
normalized_response_model = str(response_model.schema()) if response_model else ""
|
|
27
|
+
return hashlib.sha256(
|
|
28
|
+
(
|
|
29
|
+
normalized_messages
|
|
30
|
+
+ normalized_model
|
|
31
|
+
+ normalized_temperature
|
|
32
|
+
+ normalized_response_model
|
|
33
|
+
).encode()
|
|
34
|
+
).hexdigest()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CacheHandler:
|
|
38
|
+
use_persistent_store: bool = False
|
|
39
|
+
use_ephemeral_store: bool = True
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self, use_persistent_store: bool = False, use_ephemeral_store: bool = True
|
|
43
|
+
):
|
|
44
|
+
self.use_persistent_store = use_persistent_store
|
|
45
|
+
self.use_ephemeral_store = use_ephemeral_store
|
|
46
|
+
|
|
47
|
+
def hit_managed_cache(
|
|
48
|
+
self, model: str, messages: List[Dict[str, Any]], lm_config: Dict[str, Any] = {}
|
|
49
|
+
) -> str:
|
|
50
|
+
if not should_use_cache():
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
assert isinstance(lm_config, dict), "lm_config must be a dictionary"
|
|
54
|
+
key = map_params_to_key(
|
|
55
|
+
messages,
|
|
56
|
+
model,
|
|
57
|
+
lm_config.get("temperature", 0.0),
|
|
58
|
+
lm_config.get("response_model", None),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if self.use_persistent_store:
|
|
62
|
+
return persistent_cache.hit_cache(
|
|
63
|
+
key=key, response_model=lm_config.get("response_model", None)
|
|
64
|
+
)
|
|
65
|
+
elif self.use_ephemeral_store:
|
|
66
|
+
return ephemeral_cache.hit_cache(
|
|
67
|
+
key=key, response_model=lm_config.get("response_model", None)
|
|
68
|
+
)
|
|
69
|
+
else:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def add_to_managed_cache(
|
|
73
|
+
self,
|
|
74
|
+
model: str,
|
|
75
|
+
messages: List[Dict[str, Any]],
|
|
76
|
+
lm_config: Dict[str, Any],
|
|
77
|
+
output: Union[str, BaseModel],
|
|
78
|
+
) -> None:
|
|
79
|
+
if not should_use_cache():
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
assert isinstance(lm_config, dict), "lm_config must be a dictionary"
|
|
83
|
+
key = map_params_to_key(
|
|
84
|
+
messages,
|
|
85
|
+
model,
|
|
86
|
+
lm_config.get("temperature", 0.0),
|
|
87
|
+
lm_config.get("response_model", None),
|
|
88
|
+
)
|
|
89
|
+
if self.use_persistent_store:
|
|
90
|
+
persistent_cache.add_to_cache(key, output)
|
|
91
|
+
if self.use_ephemeral_store:
|
|
92
|
+
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,55 @@
|
|
|
1
|
+
from typing import Dict, Optional, Type, Union
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
import json
|
|
5
|
+
import sqlite3
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class PersistentCache:
|
|
10
|
+
def __init__(self, db_path: str = ".cache/persistent_cache.db"):
|
|
11
|
+
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
12
|
+
self.conn = sqlite3.connect(db_path)
|
|
13
|
+
self.cursor = self.conn.cursor()
|
|
14
|
+
self.cursor.execute("""CREATE TABLE IF NOT EXISTS cache
|
|
15
|
+
(key TEXT PRIMARY KEY, response TEXT)""")
|
|
16
|
+
self.conn.commit()
|
|
17
|
+
|
|
18
|
+
def hit_cache(self, key: str, response_model: Type[BaseModel]) -> Optional[Dict]:
|
|
19
|
+
self.cursor.execute("SELECT response FROM cache WHERE key = ?", (key,))
|
|
20
|
+
result = self.cursor.fetchone()
|
|
21
|
+
if not result:
|
|
22
|
+
return None
|
|
23
|
+
result = json.loads(result[0])
|
|
24
|
+
if result and response_model and "response" not in result:
|
|
25
|
+
return response_model(**result)
|
|
26
|
+
elif result and response_model:
|
|
27
|
+
return response_model(**json.loads(result["response"]))
|
|
28
|
+
elif result and not isinstance(result, dict):
|
|
29
|
+
return result
|
|
30
|
+
elif result and isinstance(result, dict) and "response" in result:
|
|
31
|
+
return result["response"]
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
def add_to_cache(self, key: str, response: Union[BaseModel, str]) -> None:
|
|
35
|
+
if isinstance(response, BaseModel):
|
|
36
|
+
response_dict = response.model_dump()
|
|
37
|
+
response_class = response.__class__.__name__
|
|
38
|
+
elif isinstance(response, str):
|
|
39
|
+
response_dict = response
|
|
40
|
+
response_class = None
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError(f"Invalid response type: {type(response)}")
|
|
43
|
+
|
|
44
|
+
cache_data = {
|
|
45
|
+
"response": response_dict,
|
|
46
|
+
"response_class": response_class,
|
|
47
|
+
}
|
|
48
|
+
self.cursor.execute(
|
|
49
|
+
"INSERT OR REPLACE INTO cache (key, response) VALUES (?, ?)",
|
|
50
|
+
(key, json.dumps(cache_data)),
|
|
51
|
+
)
|
|
52
|
+
self.conn.commit()
|
|
53
|
+
|
|
54
|
+
def close(self) -> None:
|
|
55
|
+
self.conn.close()
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
|
|
10
|
+
|
|
11
|
+
class OpenAIClient(OpenAIPrivate):
|
|
12
|
+
def __init__(self, synth_logging: bool = True):
|
|
13
|
+
super().__init__(
|
|
14
|
+
synth_logging=synth_logging,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AnthropicClient(AnthropicAPI):
|
|
19
|
+
def __init__(self):
|
|
20
|
+
super().__init__()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class GeminiClient(GeminiAPI):
|
|
24
|
+
def __init__(self):
|
|
25
|
+
super().__init__()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DeepSeekClient(DeepSeekAPI):
|
|
29
|
+
def __init__(self):
|
|
30
|
+
super().__init__()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TogetherClient(TogetherAPI):
|
|
34
|
+
def __init__(self):
|
|
35
|
+
super().__init__()
|