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.
Files changed (47) hide show
  1. synth_ai/__init__.py +3 -0
  2. synth_ai/zyk/__init__.py +3 -0
  3. synth_ai/zyk/lms/__init__.py +0 -0
  4. synth_ai/zyk/lms/caching/__init__.py +0 -0
  5. synth_ai/zyk/lms/caching/constants.py +1 -0
  6. synth_ai/zyk/lms/caching/dbs.py +0 -0
  7. synth_ai/zyk/lms/caching/ephemeral.py +72 -0
  8. synth_ai/zyk/lms/caching/handler.py +137 -0
  9. synth_ai/zyk/lms/caching/initialize.py +13 -0
  10. synth_ai/zyk/lms/caching/persistent.py +83 -0
  11. synth_ai/zyk/lms/config.py +10 -0
  12. synth_ai/zyk/lms/constants.py +22 -0
  13. synth_ai/zyk/lms/core/__init__.py +0 -0
  14. synth_ai/zyk/lms/core/all.py +47 -0
  15. synth_ai/zyk/lms/core/exceptions.py +9 -0
  16. synth_ai/zyk/lms/core/main.py +268 -0
  17. synth_ai/zyk/lms/core/vendor_clients.py +85 -0
  18. synth_ai/zyk/lms/cost/__init__.py +0 -0
  19. synth_ai/zyk/lms/cost/monitor.py +1 -0
  20. synth_ai/zyk/lms/cost/statefulness.py +1 -0
  21. synth_ai/zyk/lms/structured_outputs/__init__.py +0 -0
  22. synth_ai/zyk/lms/structured_outputs/handler.py +441 -0
  23. synth_ai/zyk/lms/structured_outputs/inject.py +314 -0
  24. synth_ai/zyk/lms/structured_outputs/rehabilitate.py +187 -0
  25. synth_ai/zyk/lms/tools/base.py +118 -0
  26. synth_ai/zyk/lms/vendors/__init__.py +0 -0
  27. synth_ai/zyk/lms/vendors/base.py +31 -0
  28. synth_ai/zyk/lms/vendors/core/__init__.py +0 -0
  29. synth_ai/zyk/lms/vendors/core/anthropic_api.py +365 -0
  30. synth_ai/zyk/lms/vendors/core/gemini_api.py +282 -0
  31. synth_ai/zyk/lms/vendors/core/mistral_api.py +331 -0
  32. synth_ai/zyk/lms/vendors/core/openai_api.py +187 -0
  33. synth_ai/zyk/lms/vendors/local/__init__.py +0 -0
  34. synth_ai/zyk/lms/vendors/local/ollama.py +0 -0
  35. synth_ai/zyk/lms/vendors/openai_standard.py +345 -0
  36. synth_ai/zyk/lms/vendors/retries.py +3 -0
  37. synth_ai/zyk/lms/vendors/supported/__init__.py +0 -0
  38. synth_ai/zyk/lms/vendors/supported/deepseek.py +73 -0
  39. synth_ai/zyk/lms/vendors/supported/groq.py +16 -0
  40. synth_ai/zyk/lms/vendors/supported/ollama.py +14 -0
  41. synth_ai/zyk/lms/vendors/supported/together.py +11 -0
  42. {synth_ai-0.1.0.dev49.dist-info → synth_ai-0.1.0.dev51.dist-info}/METADATA +1 -1
  43. synth_ai-0.1.0.dev51.dist-info/RECORD +46 -0
  44. synth_ai-0.1.0.dev49.dist-info/RECORD +0 -6
  45. {synth_ai-0.1.0.dev49.dist-info → synth_ai-0.1.0.dev51.dist-info}/WHEEL +0 -0
  46. {synth_ai-0.1.0.dev49.dist-info → synth_ai-0.1.0.dev51.dist-info}/licenses/LICENSE +0 -0
  47. {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
@@ -0,0 +1,3 @@
1
+ from synth_ai.zyk.lms.core.main import LM
2
+ from synth_ai.zyk.lms.vendors.base import BaseLMResponse
3
+ __all__ = ["LM", "BaseLMResponse"]
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__()
@@ -0,0 +1,9 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Callable, Dict, List, Literal, Optional, Union
3
+
4
+
5
+ class StructuredOutputCoercionFailureException(Exception):
6
+ pass
7
+
8
+
9
+ # ... rest of imports ...