synth-ai 0.2.4.dev6__py3-none-any.whl → 0.2.4.dev8__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 +18 -9
- synth_ai/cli/__init__.py +10 -5
- synth_ai/cli/balance.py +25 -32
- synth_ai/cli/calc.py +2 -3
- synth_ai/cli/demo.py +3 -5
- synth_ai/cli/legacy_root_backup.py +58 -32
- synth_ai/cli/man.py +22 -19
- synth_ai/cli/recent.py +9 -8
- synth_ai/cli/root.py +58 -13
- synth_ai/cli/status.py +13 -6
- synth_ai/cli/traces.py +45 -21
- synth_ai/cli/watch.py +40 -37
- synth_ai/config/base_url.py +47 -2
- synth_ai/core/experiment.py +1 -2
- synth_ai/environments/__init__.py +2 -6
- synth_ai/environments/environment/artifacts/base.py +3 -1
- synth_ai/environments/environment/db/sqlite.py +1 -1
- synth_ai/environments/environment/registry.py +19 -20
- synth_ai/environments/environment/resources/sqlite.py +2 -3
- synth_ai/environments/environment/rewards/core.py +3 -2
- synth_ai/environments/environment/tools/__init__.py +6 -4
- synth_ai/environments/examples/crafter_classic/__init__.py +1 -1
- synth_ai/environments/examples/crafter_classic/engine.py +13 -13
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +1 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +2 -1
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +2 -1
- synth_ai/environments/examples/crafter_classic/engine_serialization_patch_v3.py +3 -2
- synth_ai/environments/examples/crafter_classic/environment.py +16 -15
- synth_ai/environments/examples/crafter_classic/taskset.py +2 -2
- synth_ai/environments/examples/crafter_classic/trace_hooks_v3.py +2 -3
- synth_ai/environments/examples/crafter_classic/world_config_patch_simple.py +2 -1
- synth_ai/environments/examples/crafter_custom/crafter/__init__.py +2 -2
- synth_ai/environments/examples/crafter_custom/crafter/config.py +2 -2
- synth_ai/environments/examples/crafter_custom/crafter/env.py +1 -5
- synth_ai/environments/examples/crafter_custom/crafter/objects.py +1 -2
- synth_ai/environments/examples/crafter_custom/crafter/worldgen.py +1 -2
- synth_ai/environments/examples/crafter_custom/dataset_builder.py +5 -5
- synth_ai/environments/examples/crafter_custom/environment.py +13 -13
- synth_ai/environments/examples/crafter_custom/run_dataset.py +5 -5
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +2 -2
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +5 -4
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +2 -1
- synth_ai/environments/examples/enron/engine.py +18 -14
- synth_ai/environments/examples/enron/environment.py +12 -11
- synth_ai/environments/examples/enron/taskset.py +7 -7
- synth_ai/environments/examples/minigrid/__init__.py +6 -6
- synth_ai/environments/examples/minigrid/engine.py +6 -6
- synth_ai/environments/examples/minigrid/environment.py +6 -6
- synth_ai/environments/examples/minigrid/puzzle_loader.py +3 -2
- synth_ai/environments/examples/minigrid/taskset.py +13 -13
- synth_ai/environments/examples/nethack/achievements.py +1 -1
- synth_ai/environments/examples/nethack/engine.py +8 -7
- synth_ai/environments/examples/nethack/environment.py +10 -9
- synth_ai/environments/examples/nethack/helpers/__init__.py +8 -9
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +1 -1
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +2 -1
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +1 -1
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +3 -4
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +6 -5
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +5 -5
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +7 -6
- synth_ai/environments/examples/nethack/taskset.py +5 -5
- synth_ai/environments/examples/red/engine.py +9 -8
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +7 -7
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +2 -1
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +3 -2
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +2 -1
- synth_ai/environments/examples/red/environment.py +18 -15
- synth_ai/environments/examples/red/taskset.py +5 -3
- synth_ai/environments/examples/sokoban/engine.py +16 -13
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +3 -2
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +2 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +1 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +7 -5
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +1 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +2 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +5 -4
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +3 -2
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +2 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +5 -4
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +1 -1
- synth_ai/environments/examples/sokoban/environment.py +15 -14
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +5 -3
- synth_ai/environments/examples/sokoban/puzzle_loader.py +3 -2
- synth_ai/environments/examples/sokoban/taskset.py +13 -10
- synth_ai/environments/examples/tictactoe/engine.py +6 -6
- synth_ai/environments/examples/tictactoe/environment.py +8 -7
- synth_ai/environments/examples/tictactoe/taskset.py +6 -5
- synth_ai/environments/examples/verilog/engine.py +4 -3
- synth_ai/environments/examples/verilog/environment.py +11 -10
- synth_ai/environments/examples/verilog/taskset.py +14 -12
- synth_ai/environments/examples/wordle/__init__.py +5 -5
- synth_ai/environments/examples/wordle/engine.py +32 -25
- synth_ai/environments/examples/wordle/environment.py +21 -16
- synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +6 -6
- synth_ai/environments/examples/wordle/taskset.py +20 -12
- synth_ai/environments/reproducibility/core.py +1 -1
- synth_ai/environments/reproducibility/tree.py +21 -21
- synth_ai/environments/service/app.py +3 -2
- synth_ai/environments/service/core_routes.py +104 -110
- synth_ai/environments/service/external_registry.py +1 -2
- synth_ai/environments/service/registry.py +1 -1
- synth_ai/environments/stateful/core.py +1 -2
- synth_ai/environments/stateful/engine.py +1 -1
- synth_ai/environments/tasks/api.py +4 -4
- synth_ai/environments/tasks/core.py +14 -12
- synth_ai/environments/tasks/filters.py +6 -4
- synth_ai/environments/tasks/utils.py +13 -11
- synth_ai/evals/base.py +2 -3
- synth_ai/experimental/synth_oss.py +4 -4
- synth_ai/http.py +102 -0
- synth_ai/inference/__init__.py +7 -0
- synth_ai/inference/client.py +20 -0
- synth_ai/jobs/client.py +246 -0
- synth_ai/learning/__init__.py +24 -0
- synth_ai/learning/client.py +149 -0
- synth_ai/learning/config.py +43 -0
- synth_ai/learning/constants.py +29 -0
- synth_ai/learning/ft_client.py +59 -0
- synth_ai/learning/gateway.py +1 -3
- synth_ai/learning/health.py +43 -0
- synth_ai/learning/jobs.py +205 -0
- synth_ai/learning/prompts/banking77_injection_eval.py +15 -10
- synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +26 -14
- synth_ai/learning/prompts/mipro.py +61 -52
- synth_ai/learning/prompts/random_search.py +42 -43
- synth_ai/learning/prompts/run_mipro_banking77.py +32 -20
- synth_ai/learning/prompts/run_random_search_banking77.py +71 -52
- synth_ai/learning/rl_client.py +256 -0
- synth_ai/learning/sse.py +58 -0
- synth_ai/learning/validators.py +48 -0
- synth_ai/lm/__init__.py +5 -5
- synth_ai/lm/caching/ephemeral.py +9 -9
- synth_ai/lm/caching/handler.py +20 -20
- synth_ai/lm/caching/persistent.py +10 -10
- synth_ai/lm/config.py +3 -3
- synth_ai/lm/constants.py +7 -7
- synth_ai/lm/core/all.py +17 -3
- synth_ai/lm/core/exceptions.py +0 -2
- synth_ai/lm/core/main.py +26 -41
- synth_ai/lm/core/main_v3.py +33 -10
- synth_ai/lm/core/synth_models.py +48 -0
- synth_ai/lm/core/vendor_clients.py +26 -22
- synth_ai/lm/injection.py +7 -8
- synth_ai/lm/overrides.py +21 -19
- synth_ai/lm/provider_support/__init__.py +1 -1
- synth_ai/lm/provider_support/anthropic.py +15 -15
- synth_ai/lm/provider_support/openai.py +23 -21
- synth_ai/lm/structured_outputs/handler.py +34 -32
- synth_ai/lm/structured_outputs/inject.py +24 -27
- synth_ai/lm/structured_outputs/rehabilitate.py +19 -15
- synth_ai/lm/tools/base.py +17 -16
- synth_ai/lm/unified_interface.py +17 -18
- synth_ai/lm/vendors/base.py +20 -18
- synth_ai/lm/vendors/core/anthropic_api.py +36 -27
- synth_ai/lm/vendors/core/gemini_api.py +31 -36
- synth_ai/lm/vendors/core/mistral_api.py +19 -19
- synth_ai/lm/vendors/core/openai_api.py +42 -13
- synth_ai/lm/vendors/openai_standard.py +158 -101
- synth_ai/lm/vendors/openai_standard_responses.py +74 -61
- synth_ai/lm/vendors/retries.py +9 -1
- synth_ai/lm/vendors/supported/custom_endpoint.py +38 -28
- synth_ai/lm/vendors/supported/deepseek.py +10 -10
- synth_ai/lm/vendors/supported/grok.py +8 -8
- synth_ai/lm/vendors/supported/ollama.py +2 -1
- synth_ai/lm/vendors/supported/openrouter.py +11 -9
- synth_ai/lm/vendors/synth_client.py +425 -75
- synth_ai/lm/warmup.py +8 -7
- synth_ai/rl/__init__.py +30 -0
- synth_ai/rl/contracts.py +32 -0
- synth_ai/rl/env_keys.py +137 -0
- synth_ai/rl/secrets.py +19 -0
- synth_ai/scripts/verify_rewards.py +100 -0
- synth_ai/task/__init__.py +10 -0
- synth_ai/task/contracts.py +120 -0
- synth_ai/task/health.py +28 -0
- synth_ai/task/validators.py +12 -0
- synth_ai/tracing/__init__.py +22 -10
- synth_ai/tracing_v1/__init__.py +22 -20
- synth_ai/tracing_v3/__init__.py +7 -7
- synth_ai/tracing_v3/abstractions.py +56 -52
- synth_ai/tracing_v3/config.py +4 -2
- synth_ai/tracing_v3/db_config.py +6 -8
- synth_ai/tracing_v3/decorators.py +29 -30
- synth_ai/tracing_v3/examples/basic_usage.py +12 -12
- synth_ai/tracing_v3/hooks.py +24 -22
- synth_ai/tracing_v3/llm_call_record_helpers.py +85 -98
- synth_ai/tracing_v3/lm_call_record_abstractions.py +2 -4
- synth_ai/tracing_v3/migration_helper.py +3 -5
- synth_ai/tracing_v3/replica_sync.py +30 -32
- synth_ai/tracing_v3/session_tracer.py +158 -31
- synth_ai/tracing_v3/storage/__init__.py +1 -1
- synth_ai/tracing_v3/storage/base.py +8 -7
- synth_ai/tracing_v3/storage/config.py +4 -4
- synth_ai/tracing_v3/storage/factory.py +4 -4
- synth_ai/tracing_v3/storage/utils.py +9 -9
- synth_ai/tracing_v3/turso/__init__.py +3 -3
- synth_ai/tracing_v3/turso/daemon.py +9 -9
- synth_ai/tracing_v3/turso/manager.py +278 -48
- synth_ai/tracing_v3/turso/models.py +77 -19
- synth_ai/tracing_v3/utils.py +5 -5
- synth_ai/v0/tracing/abstractions.py +28 -28
- synth_ai/v0/tracing/base_client.py +9 -9
- synth_ai/v0/tracing/client_manager.py +7 -7
- synth_ai/v0/tracing/config.py +7 -7
- synth_ai/v0/tracing/context.py +6 -6
- synth_ai/v0/tracing/decorators.py +6 -5
- synth_ai/v0/tracing/events/manage.py +1 -1
- synth_ai/v0/tracing/events/store.py +5 -4
- synth_ai/v0/tracing/immediate_client.py +4 -5
- synth_ai/v0/tracing/local.py +3 -3
- synth_ai/v0/tracing/log_client_base.py +4 -5
- synth_ai/v0/tracing/retry_queue.py +5 -6
- synth_ai/v0/tracing/trackers.py +25 -25
- synth_ai/v0/tracing/upload.py +6 -0
- synth_ai/v0/tracing_v1/__init__.py +1 -1
- synth_ai/v0/tracing_v1/abstractions.py +28 -28
- synth_ai/v0/tracing_v1/base_client.py +9 -9
- synth_ai/v0/tracing_v1/client_manager.py +7 -7
- synth_ai/v0/tracing_v1/config.py +7 -7
- synth_ai/v0/tracing_v1/context.py +6 -6
- synth_ai/v0/tracing_v1/decorators.py +7 -6
- synth_ai/v0/tracing_v1/events/manage.py +1 -1
- synth_ai/v0/tracing_v1/events/store.py +5 -4
- synth_ai/v0/tracing_v1/immediate_client.py +4 -5
- synth_ai/v0/tracing_v1/local.py +3 -3
- synth_ai/v0/tracing_v1/log_client_base.py +4 -5
- synth_ai/v0/tracing_v1/retry_queue.py +5 -6
- synth_ai/v0/tracing_v1/trackers.py +25 -25
- synth_ai/v0/tracing_v1/upload.py +25 -24
- synth_ai/zyk/__init__.py +1 -0
- synth_ai-0.2.4.dev8.dist-info/METADATA +635 -0
- synth_ai-0.2.4.dev8.dist-info/RECORD +317 -0
- synth_ai/tui/__init__.py +0 -1
- synth_ai/tui/__main__.py +0 -13
- synth_ai/tui/cli/__init__.py +0 -1
- synth_ai/tui/cli/query_experiments.py +0 -165
- synth_ai/tui/cli/query_experiments_v3.py +0 -165
- synth_ai/tui/dashboard.py +0 -329
- synth_ai-0.2.4.dev6.dist-info/METADATA +0 -203
- synth_ai-0.2.4.dev6.dist-info/RECORD +0 -299
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev8.dist-info}/top_level.txt +0 -0
synth_ai/lm/caching/ephemeral.py
CHANGED
@@ -7,7 +7,6 @@ of the application run, useful for avoiding redundant API calls within a session
|
|
7
7
|
|
8
8
|
import os
|
9
9
|
from dataclasses import dataclass
|
10
|
-
from typing import Optional, Union
|
11
10
|
|
12
11
|
from diskcache import Cache
|
13
12
|
from pydantic import BaseModel
|
@@ -20,24 +19,25 @@ from synth_ai.lm.vendors.base import BaseLMResponse
|
|
20
19
|
class EphemeralCache:
|
21
20
|
"""
|
22
21
|
Ephemeral cache implementation using diskcache.
|
23
|
-
|
22
|
+
|
24
23
|
This cache stores LM responses temporarily on disk with a size limit.
|
25
24
|
The cache is cleared when the application restarts.
|
26
25
|
"""
|
26
|
+
|
27
27
|
def __init__(self, fast_cache_dir: str = ".cache/ephemeral_cache"):
|
28
28
|
os.makedirs(fast_cache_dir, exist_ok=True)
|
29
29
|
self.fast_cache = Cache(fast_cache_dir, size_limit=DISKCACHE_SIZE_LIMIT)
|
30
30
|
|
31
31
|
def hit_cache(
|
32
|
-
self, key: str, response_model:
|
33
|
-
) ->
|
32
|
+
self, key: str, response_model: BaseModel | None = None
|
33
|
+
) -> BaseLMResponse | None:
|
34
34
|
"""
|
35
35
|
Check if a response exists in cache for the given key.
|
36
|
-
|
36
|
+
|
37
37
|
Args:
|
38
38
|
key: Cache key to look up
|
39
39
|
response_model: Optional Pydantic model to reconstruct structured output
|
40
|
-
|
40
|
+
|
41
41
|
Returns:
|
42
42
|
BaseLMResponse if found in cache, None otherwise
|
43
43
|
"""
|
@@ -65,14 +65,14 @@ class EphemeralCache:
|
|
65
65
|
tool_calls=tool_calls,
|
66
66
|
)
|
67
67
|
|
68
|
-
def add_to_cache(self, key: str, response:
|
68
|
+
def add_to_cache(self, key: str, response: BaseLMResponse | str) -> None:
|
69
69
|
"""
|
70
70
|
Add a response to the cache.
|
71
|
-
|
71
|
+
|
72
72
|
Args:
|
73
73
|
key: Cache key to store under
|
74
74
|
response: Either a BaseLMResponse object or raw string response
|
75
|
-
|
75
|
+
|
76
76
|
Raises:
|
77
77
|
ValueError: If response type is not supported
|
78
78
|
"""
|
synth_ai/lm/caching/handler.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import hashlib
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any
|
3
3
|
|
4
4
|
from pydantic import BaseModel
|
5
5
|
|
@@ -17,11 +17,11 @@ logger = logging.getLogger(__name__)
|
|
17
17
|
|
18
18
|
|
19
19
|
def map_params_to_key(
|
20
|
-
messages:
|
20
|
+
messages: list[dict],
|
21
21
|
model: str,
|
22
22
|
temperature: float,
|
23
|
-
response_model:
|
24
|
-
tools:
|
23
|
+
response_model: type[BaseModel] | None,
|
24
|
+
tools: list[BaseTool] | None = None,
|
25
25
|
reasoning_effort: str = "low",
|
26
26
|
) -> str:
|
27
27
|
if any(m is None for m in messages):
|
@@ -76,37 +76,37 @@ class CacheHandler:
|
|
76
76
|
self.use_persistent_store = use_persistent_store
|
77
77
|
self.use_ephemeral_store = use_ephemeral_store
|
78
78
|
|
79
|
-
def _validate_messages(self, messages:
|
79
|
+
def _validate_messages(self, messages: list[dict[str, Any]]) -> None:
|
80
80
|
"""Validate that messages are in the correct format."""
|
81
|
-
assert all(
|
81
|
+
assert all(isinstance(msg["content"], str) for msg in messages), (
|
82
82
|
"All message contents must be strings"
|
83
83
|
)
|
84
84
|
|
85
85
|
def hit_managed_cache(
|
86
86
|
self,
|
87
87
|
model: str,
|
88
|
-
messages:
|
89
|
-
lm_config:
|
90
|
-
tools:
|
91
|
-
) ->
|
88
|
+
messages: list[dict[str, Any]],
|
89
|
+
lm_config: dict[str, Any],
|
90
|
+
tools: list[BaseTool] | None = None,
|
91
|
+
) -> BaseLMResponse | None:
|
92
92
|
"""Hit the cache with the given key."""
|
93
93
|
self._validate_messages(messages)
|
94
|
-
assert
|
94
|
+
assert isinstance(lm_config, dict), "lm_config must be a dictionary"
|
95
95
|
key = map_params_to_key(
|
96
96
|
messages,
|
97
97
|
model,
|
98
98
|
lm_config.get("temperature", 0.0),
|
99
|
-
lm_config.get("response_model"
|
99
|
+
lm_config.get("response_model"),
|
100
100
|
tools,
|
101
101
|
lm_config.get("reasoning_effort", "low"),
|
102
102
|
)
|
103
103
|
if self.use_persistent_store:
|
104
104
|
return persistent_cache.hit_cache(
|
105
|
-
key=key, response_model=lm_config.get("response_model"
|
105
|
+
key=key, response_model=lm_config.get("response_model")
|
106
106
|
)
|
107
107
|
elif self.use_ephemeral_store:
|
108
108
|
return ephemeral_cache.hit_cache(
|
109
|
-
key=key, response_model=lm_config.get("response_model"
|
109
|
+
key=key, response_model=lm_config.get("response_model")
|
110
110
|
)
|
111
111
|
else:
|
112
112
|
return None
|
@@ -114,20 +114,20 @@ class CacheHandler:
|
|
114
114
|
def add_to_managed_cache(
|
115
115
|
self,
|
116
116
|
model: str,
|
117
|
-
messages:
|
118
|
-
lm_config:
|
117
|
+
messages: list[dict[str, Any]],
|
118
|
+
lm_config: dict[str, Any],
|
119
119
|
output: BaseLMResponse,
|
120
|
-
tools:
|
120
|
+
tools: list[BaseTool] | None = None,
|
121
121
|
) -> None:
|
122
122
|
"""Add the given output to the cache."""
|
123
123
|
self._validate_messages(messages)
|
124
|
-
assert
|
125
|
-
assert
|
124
|
+
assert isinstance(output, BaseLMResponse), "output must be a BaseLMResponse"
|
125
|
+
assert isinstance(lm_config, dict), "lm_config must be a dictionary"
|
126
126
|
key = map_params_to_key(
|
127
127
|
messages,
|
128
128
|
model,
|
129
129
|
lm_config.get("temperature", 0.0),
|
130
|
-
lm_config.get("response_model"
|
130
|
+
lm_config.get("response_model"),
|
131
131
|
tools,
|
132
132
|
lm_config.get("reasoning_effort", "low"),
|
133
133
|
)
|
@@ -9,7 +9,6 @@ import json
|
|
9
9
|
import os
|
10
10
|
import sqlite3
|
11
11
|
from dataclasses import dataclass
|
12
|
-
from typing import Optional, Type, Union
|
13
12
|
|
14
13
|
from pydantic import BaseModel
|
15
14
|
|
@@ -20,10 +19,11 @@ from synth_ai.lm.vendors.base import BaseLMResponse
|
|
20
19
|
class PersistentCache:
|
21
20
|
"""
|
22
21
|
Persistent cache implementation using SQLite.
|
23
|
-
|
22
|
+
|
24
23
|
This cache stores LM responses in a SQLite database that persists
|
25
24
|
across application restarts.
|
26
25
|
"""
|
26
|
+
|
27
27
|
def __init__(self, db_path: str = ".cache/persistent_cache.db"):
|
28
28
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
29
29
|
self.conn = sqlite3.connect(db_path)
|
@@ -33,15 +33,15 @@ class PersistentCache:
|
|
33
33
|
self.conn.commit()
|
34
34
|
|
35
35
|
def hit_cache(
|
36
|
-
self, key: str, response_model:
|
37
|
-
) ->
|
36
|
+
self, key: str, response_model: type[BaseModel] | None = None
|
37
|
+
) -> BaseLMResponse | None:
|
38
38
|
"""
|
39
39
|
Check if a response exists in cache for the given key.
|
40
|
-
|
40
|
+
|
41
41
|
Args:
|
42
42
|
key: Cache key to look up
|
43
43
|
response_model: Optional Pydantic model class to reconstruct structured output
|
44
|
-
|
44
|
+
|
45
45
|
Returns:
|
46
46
|
BaseLMResponse if found in cache, None otherwise
|
47
47
|
"""
|
@@ -72,17 +72,17 @@ class PersistentCache:
|
|
72
72
|
tool_calls=tool_calls,
|
73
73
|
)
|
74
74
|
|
75
|
-
def add_to_cache(self, key: str, response:
|
75
|
+
def add_to_cache(self, key: str, response: BaseLMResponse | str) -> None:
|
76
76
|
"""
|
77
77
|
Add a response to the cache.
|
78
|
-
|
78
|
+
|
79
79
|
Args:
|
80
80
|
key: Cache key to store under
|
81
81
|
response: Either a BaseLMResponse object or raw string response
|
82
|
-
|
82
|
+
|
83
83
|
Raises:
|
84
84
|
ValueError: If response type is not supported
|
85
|
-
|
85
|
+
|
86
86
|
Note:
|
87
87
|
Uses INSERT OR REPLACE to update existing cache entries.
|
88
88
|
"""
|
synth_ai/lm/config.py
CHANGED
@@ -4,8 +4,8 @@ Loads sensitive configuration from environment variables.
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import os
|
7
|
-
from typing import Optional
|
8
7
|
from dataclasses import dataclass
|
8
|
+
|
9
9
|
from dotenv import load_dotenv
|
10
10
|
|
11
11
|
# Load environment variables from .env file
|
@@ -15,10 +15,10 @@ load_dotenv()
|
|
15
15
|
def should_use_cache() -> bool:
|
16
16
|
"""
|
17
17
|
Check if caching should be enabled based on environment variable.
|
18
|
-
|
18
|
+
|
19
19
|
Returns:
|
20
20
|
bool: True if caching is enabled (default), False if explicitly disabled.
|
21
|
-
|
21
|
+
|
22
22
|
Note:
|
23
23
|
Caching is controlled by the USE_ZYK_CACHE environment variable.
|
24
24
|
Set to "false", "0", or "no" to disable caching.
|
synth_ai/lm/constants.py
CHANGED
@@ -13,20 +13,20 @@ GEMINI_REASONING_MODELS = ["gemini-2.5-flash", "gemini-2.5-pro"]
|
|
13
13
|
# Gemini models that support thinking
|
14
14
|
GEMINI_REASONING_MODELS = ["gemini-2.5-flash", "gemini-2.5-pro"]
|
15
15
|
GEMINI_THINKING_BUDGETS = {
|
16
|
-
"high": 10000,
|
17
|
-
"medium": 5000,
|
18
|
-
"low": 2500,
|
16
|
+
"high": 10000, # High thinking budget for complex reasoning
|
17
|
+
"medium": 5000, # Medium thinking budget for standard reasoning
|
18
|
+
"low": 2500, # Low thinking budget for simple reasoning
|
19
19
|
}
|
20
20
|
|
21
21
|
# Anthropic Sonnet 3.7 budgets
|
22
22
|
SONNET_37_BUDGETS = {
|
23
|
-
"high": 8192,
|
24
|
-
"medium": 4096,
|
25
|
-
"low": 2048,
|
23
|
+
"high": 8192, # High budget for complex tasks
|
24
|
+
"medium": 4096, # Medium budget for standard tasks
|
25
|
+
"low": 2048, # Low budget for simple tasks
|
26
26
|
}
|
27
27
|
|
28
28
|
# Combined list of all reasoning models
|
29
29
|
REASONING_MODELS = OPENAI_REASONING_MODELS + CLAUDE_REASONING_MODELS + GEMINI_REASONING_MODELS
|
30
30
|
|
31
31
|
# Special base temperatures for reasoning models (all set to 1.0)
|
32
|
-
SPECIAL_BASE_TEMPS =
|
32
|
+
SPECIAL_BASE_TEMPS = dict.fromkeys(REASONING_MODELS, 1)
|
synth_ai/lm/core/all.py
CHANGED
@@ -4,12 +4,12 @@ from synth_ai.lm.vendors.core.openai_api import (
|
|
4
4
|
OpenAIPrivate,
|
5
5
|
OpenAIStructuredOutputClient,
|
6
6
|
)
|
7
|
+
from synth_ai.lm.vendors.supported.custom_endpoint import CustomEndpointAPI
|
7
8
|
from synth_ai.lm.vendors.supported.deepseek import DeepSeekAPI
|
8
|
-
from synth_ai.lm.vendors.supported.together import TogetherAPI
|
9
|
-
from synth_ai.lm.vendors.supported.groq import GroqAPI
|
10
9
|
from synth_ai.lm.vendors.supported.grok import GrokAPI
|
11
|
-
from synth_ai.lm.vendors.supported.
|
10
|
+
from synth_ai.lm.vendors.supported.groq import GroqAPI
|
12
11
|
from synth_ai.lm.vendors.supported.openrouter import OpenRouterAPI
|
12
|
+
from synth_ai.lm.vendors.supported.together import TogetherAPI
|
13
13
|
|
14
14
|
|
15
15
|
class OpenAIClient(OpenAIPrivate):
|
@@ -57,3 +57,17 @@ class CustomEndpointClient(CustomEndpointAPI):
|
|
57
57
|
class OpenRouterClient(OpenRouterAPI):
|
58
58
|
def __init__(self):
|
59
59
|
super().__init__()
|
60
|
+
|
61
|
+
|
62
|
+
__all__ = [
|
63
|
+
"OpenAIClient",
|
64
|
+
"AnthropicClient",
|
65
|
+
"GeminiClient",
|
66
|
+
"DeepSeekClient",
|
67
|
+
"TogetherClient",
|
68
|
+
"GroqClient",
|
69
|
+
"GrokClient",
|
70
|
+
"CustomEndpointClient",
|
71
|
+
"OpenRouterClient",
|
72
|
+
"OpenAIStructuredOutputClient",
|
73
|
+
]
|
synth_ai/lm/core/exceptions.py
CHANGED
synth_ai/lm/core/main.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
from typing import Any, Dict, List, Literal, Optional, Union
|
2
1
|
import os
|
2
|
+
from typing import Any, Literal
|
3
3
|
|
4
4
|
from pydantic import BaseModel, Field
|
5
5
|
|
6
|
+
from synth_ai.lm.config import reasoning_models
|
6
7
|
from synth_ai.lm.core.exceptions import StructuredOutputCoercionFailureException
|
7
8
|
from synth_ai.lm.core.vendor_clients import (
|
8
9
|
anthropic_naming_regexes,
|
@@ -10,29 +11,28 @@ from synth_ai.lm.core.vendor_clients import (
|
|
10
11
|
openai_naming_regexes,
|
11
12
|
)
|
12
13
|
from synth_ai.lm.structured_outputs.handler import StructuredOutputHandler
|
13
|
-
from synth_ai.lm.vendors.base import VendorBase
|
14
14
|
from synth_ai.lm.tools.base import BaseTool
|
15
|
-
from synth_ai.lm.
|
15
|
+
from synth_ai.lm.vendors.base import VendorBase
|
16
16
|
|
17
17
|
|
18
18
|
def build_messages(
|
19
19
|
sys_msg: str,
|
20
20
|
user_msg: str,
|
21
|
-
images_bytes:
|
22
|
-
model_name:
|
23
|
-
) ->
|
21
|
+
images_bytes: list[bytes] = [],
|
22
|
+
model_name: str | None = None,
|
23
|
+
) -> list[dict]:
|
24
24
|
"""
|
25
25
|
Build a messages list for API calls, handling image formatting based on the model provider.
|
26
|
-
|
26
|
+
|
27
27
|
Args:
|
28
28
|
sys_msg: System message content
|
29
29
|
user_msg: User message content
|
30
30
|
images_bytes: List of base64-encoded image bytes
|
31
31
|
model_name: Model name to determine proper image format (OpenAI vs Anthropic)
|
32
|
-
|
32
|
+
|
33
33
|
Returns:
|
34
34
|
List[Dict]: Formatted messages list ready for API calls
|
35
|
-
|
35
|
+
|
36
36
|
Note:
|
37
37
|
Different providers require different image formats:
|
38
38
|
- OpenAI: Uses "image_url" with data URL format
|
@@ -102,7 +102,7 @@ class LM:
|
|
102
102
|
# if str
|
103
103
|
model_name: str
|
104
104
|
client: VendorBase
|
105
|
-
lm_config:
|
105
|
+
lm_config: dict[str, Any]
|
106
106
|
structured_output_handler: StructuredOutputHandler
|
107
107
|
|
108
108
|
def __init__(
|
@@ -113,23 +113,8 @@ class LM:
|
|
113
113
|
max_retries: Literal["None", "Few", "Many"] = "Few",
|
114
114
|
structured_output_mode: Literal["stringified_json", "forced_json"] = "stringified_json",
|
115
115
|
synth_logging: bool = True,
|
116
|
-
provider:
|
117
|
-
|
118
|
-
Literal[
|
119
|
-
"openai",
|
120
|
-
"anthropic",
|
121
|
-
"groq",
|
122
|
-
"gemini",
|
123
|
-
"deepseek",
|
124
|
-
"grok",
|
125
|
-
"mistral",
|
126
|
-
"openrouter",
|
127
|
-
"together",
|
128
|
-
],
|
129
|
-
str,
|
130
|
-
]
|
131
|
-
] = None,
|
132
|
-
enable_thinking: Optional[bool] = None,
|
116
|
+
provider: Literal["openai", "anthropic", "groq", "gemini", "deepseek", "grok", "mistral", "openrouter", "together"] | str | None = None,
|
117
|
+
enable_thinking: bool | None = None,
|
133
118
|
):
|
134
119
|
# print("Structured output mode", structured_output_mode)
|
135
120
|
# Check for environment variable if provider is not specified
|
@@ -170,13 +155,13 @@ class LM:
|
|
170
155
|
|
171
156
|
def respond_sync(
|
172
157
|
self,
|
173
|
-
system_message:
|
174
|
-
user_message:
|
175
|
-
messages:
|
176
|
-
images_as_bytes:
|
177
|
-
response_model:
|
158
|
+
system_message: str | None = None,
|
159
|
+
user_message: str | None = None,
|
160
|
+
messages: list[dict] | None = None,
|
161
|
+
images_as_bytes: list[bytes] = [],
|
162
|
+
response_model: BaseModel | None = None,
|
178
163
|
use_ephemeral_cache_only: bool = False,
|
179
|
-
tools:
|
164
|
+
tools: list[BaseTool] | None = None,
|
180
165
|
reasoning_effort: str = "low",
|
181
166
|
):
|
182
167
|
assert (system_message is None) == (user_message is None), (
|
@@ -231,13 +216,13 @@ class LM:
|
|
231
216
|
|
232
217
|
async def respond_async(
|
233
218
|
self,
|
234
|
-
system_message:
|
235
|
-
user_message:
|
236
|
-
messages:
|
237
|
-
images_as_bytes:
|
238
|
-
response_model:
|
219
|
+
system_message: str | None = None,
|
220
|
+
user_message: str | None = None,
|
221
|
+
messages: list[dict] | None = None,
|
222
|
+
images_as_bytes: list[bytes] = [],
|
223
|
+
response_model: BaseModel | None = None,
|
239
224
|
use_ephemeral_cache_only: bool = False,
|
240
|
-
tools:
|
225
|
+
tools: list[BaseTool] | None = None,
|
241
226
|
reasoning_effort: str = "low",
|
242
227
|
):
|
243
228
|
# "In respond_async")
|
@@ -300,8 +285,8 @@ if __name__ == "__main__":
|
|
300
285
|
|
301
286
|
# Update json instructions to handle nested pydantic?
|
302
287
|
class Thought(BaseModel):
|
303
|
-
argument_keys:
|
304
|
-
argument_values:
|
288
|
+
argument_keys: list[str] = Field(description="The keys of the arguments")
|
289
|
+
argument_values: list[str] = Field(
|
305
290
|
description="Stringified JSON for the values of the arguments"
|
306
291
|
)
|
307
292
|
|
synth_ai/lm/core/main_v3.py
CHANGED
@@ -53,7 +53,9 @@ def build_messages(
|
|
53
53
|
],
|
54
54
|
},
|
55
55
|
]
|
56
|
-
elif len(images_bytes) > 0 and any(
|
56
|
+
elif len(images_bytes) > 0 and any(
|
57
|
+
regex.match(model_name) for regex in anthropic_naming_regexes
|
58
|
+
):
|
57
59
|
return [
|
58
60
|
{"role": "system", "content": sys_msg},
|
59
61
|
{
|
@@ -115,7 +117,11 @@ class LM:
|
|
115
117
|
if enable_v2_tracing is not None:
|
116
118
|
enable_v3_tracing = enable_v2_tracing
|
117
119
|
|
120
|
+
# Debug logging
|
121
|
+
print(f"🔍 LM __init__: provider={provider}, vendor={vendor}, model={model}")
|
122
|
+
|
118
123
|
# If vendor not provided, infer from model name
|
124
|
+
# But only if no explicit provider was given
|
119
125
|
if vendor is None and model is not None:
|
120
126
|
# Import vendor detection logic
|
121
127
|
from synth_ai.lm.core.vendor_clients import (
|
@@ -154,6 +160,7 @@ class LM:
|
|
154
160
|
|
155
161
|
self.vendor = vendor
|
156
162
|
self.model = model
|
163
|
+
print(f"🔍 LM final: vendor={self.vendor}, model={self.model}")
|
157
164
|
self.is_structured = is_structured
|
158
165
|
self.structured_outputs_vendor = structured_outputs_vendor
|
159
166
|
self.response_format = response_format
|
@@ -163,7 +170,7 @@ class LM:
|
|
163
170
|
self.system_id = system_id or f"lm_{self.vendor or 'unknown'}_{self.model or 'unknown'}"
|
164
171
|
self.enable_v3_tracing = enable_v3_tracing
|
165
172
|
self.additional_params = additional_params
|
166
|
-
|
173
|
+
|
167
174
|
# Initialize vendor wrapper early, before any potential usage
|
168
175
|
# (e.g., within StructuredOutputHandler initialization below)
|
169
176
|
self._vendor_wrapper = None
|
@@ -221,11 +228,14 @@ class LM:
|
|
221
228
|
"""Determine if Responses API should be used."""
|
222
229
|
if self.use_responses_api is not None:
|
223
230
|
return self.use_responses_api
|
224
|
-
|
231
|
+
|
225
232
|
# Auto-detect based on model
|
226
233
|
responses_models = {
|
227
|
-
"o4-mini",
|
228
|
-
"
|
234
|
+
"o4-mini",
|
235
|
+
"o3",
|
236
|
+
"o3-mini", # Supported Synth-hosted models
|
237
|
+
"gpt-oss-120b",
|
238
|
+
"gpt-oss-20b", # OSS models via Synth
|
229
239
|
}
|
230
240
|
return self.model in responses_models or (self.model and self.model in reasoning_models)
|
231
241
|
|
@@ -332,6 +342,14 @@ class LM:
|
|
332
342
|
if hasattr(vendor_wrapper, "_hit_api_async"):
|
333
343
|
# OpenAIStandard expects lm_config
|
334
344
|
lm_config = {"temperature": self.temperature, **self.additional_params, **kwargs}
|
345
|
+
# Map convenience enable_thinking => thinking_mode unless explicitly set
|
346
|
+
if "enable_thinking" in lm_config and "thinking_mode" not in lm_config:
|
347
|
+
try:
|
348
|
+
et = lm_config.get("enable_thinking")
|
349
|
+
if isinstance(et, bool):
|
350
|
+
lm_config["thinking_mode"] = "think" if et else "no_think"
|
351
|
+
except Exception:
|
352
|
+
pass
|
335
353
|
if self.json_mode:
|
336
354
|
lm_config["response_format"] = {"type": "json_object"}
|
337
355
|
|
@@ -377,11 +395,15 @@ class LM:
|
|
377
395
|
raise AttributeError(
|
378
396
|
f"Vendor wrapper {type(vendor_wrapper).__name__} has no suitable response method"
|
379
397
|
)
|
380
|
-
if not hasattr(response,
|
398
|
+
if not hasattr(response, "api_type"):
|
381
399
|
response.api_type = "chat"
|
382
400
|
|
383
401
|
# Update stored response ID if auto-storing
|
384
|
-
if
|
402
|
+
if (
|
403
|
+
self.auto_store_responses
|
404
|
+
and hasattr(response, "response_id")
|
405
|
+
and response.response_id
|
406
|
+
):
|
385
407
|
self._last_response_id = response.response_id
|
386
408
|
|
387
409
|
except Exception as e:
|
@@ -397,12 +419,13 @@ class LM:
|
|
397
419
|
and hasattr(self.session_tracer, "current_session")
|
398
420
|
):
|
399
421
|
latency_ms = int((time.time() - start_time) * 1000)
|
400
|
-
|
422
|
+
|
401
423
|
# Create LLMCallRecord from the response
|
402
424
|
from datetime import datetime
|
425
|
+
|
403
426
|
started_at = datetime.utcnow()
|
404
427
|
completed_at = datetime.utcnow()
|
405
|
-
|
428
|
+
|
406
429
|
call_record = create_llm_call_record_from_response(
|
407
430
|
response=response,
|
408
431
|
model_name=self.model or self.vendor,
|
@@ -415,7 +438,7 @@ class LM:
|
|
415
438
|
completed_at=completed_at,
|
416
439
|
latency_ms=latency_ms,
|
417
440
|
)
|
418
|
-
|
441
|
+
|
419
442
|
# Compute aggregates from the call record
|
420
443
|
aggregates = compute_aggregates_from_call_records([call_record])
|
421
444
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"""
|
2
|
+
Synth-supported models registry.
|
3
|
+
|
4
|
+
This module defines the specific models that are supported by Synth's infrastructure.
|
5
|
+
Models are organized by family and size for easy maintenance and extension.
|
6
|
+
|
7
|
+
MAINTENANCE GUIDE:
|
8
|
+
1. Add new model families to the appropriate lists (QWEN_MODELS, OTHER_SYNTH_MODELS)
|
9
|
+
2. Fine-tuned models (ft:) are automatically detected by regex
|
10
|
+
3. Update SYNTH_SUPPORTED_MODELS set when adding new models
|
11
|
+
4. Test changes with: pytest tests/lms/test_qwen_chat_completions.py
|
12
|
+
|
13
|
+
WHY THIS EXISTS:
|
14
|
+
- The previous regex (^.*\/.*$) was too broad and caught unintended models
|
15
|
+
- This provides explicit control over which models use Synth infrastructure
|
16
|
+
- Easier to maintain and debug model routing issues
|
17
|
+
"""
|
18
|
+
|
19
|
+
from typing import List, Set
|
20
|
+
|
21
|
+
# Qwen3 model families supported by Synth
|
22
|
+
QWEN3_MODELS: List[str] = [
|
23
|
+
# Qwen3 base models
|
24
|
+
"Qwen/Qwen3-0.6B",
|
25
|
+
"Qwen/Qwen3-1.7B",
|
26
|
+
"Qwen/Qwen3-4B",
|
27
|
+
"Qwen/Qwen3-8B",
|
28
|
+
"Qwen/Qwen3-14B",
|
29
|
+
"Qwen/Qwen3-32B",
|
30
|
+
|
31
|
+
# Qwen3 specialized variants
|
32
|
+
"Qwen/Qwen3-4B-Instruct-2507",
|
33
|
+
"Qwen/Qwen3-4B-Thinking-2507",
|
34
|
+
]
|
35
|
+
|
36
|
+
# Fine-tuned models pattern - any model starting with "ft:" is considered Synth-compatible
|
37
|
+
# These are dynamically detected, but we can add specific known ones here
|
38
|
+
FINE_TUNED_MODELS: List[str] = [
|
39
|
+
# Add specific fine-tuned models that are known to work with Synth
|
40
|
+
# Examples:
|
41
|
+
# "ft:Qwen/Qwen3-4B-Instruct-2507:ftjob-22",
|
42
|
+
]
|
43
|
+
|
44
|
+
# Combine all Synth-supported models
|
45
|
+
SYNTH_SUPPORTED_MODELS: Set[str] = set(QWEN3_MODELS + FINE_TUNED_MODELS)
|
46
|
+
|
47
|
+
# Export the main set for easy import
|
48
|
+
__all__ = ["SYNTH_SUPPORTED_MODELS", "QWEN3_MODELS", "FINE_TUNED_MODELS"]
|