synth-ai 0.2.4.dev6__py3-none-any.whl → 0.2.4.dev7__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 +22 -17
- 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 +1 -3
- 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/learning/gateway.py +1 -3
- 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/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 +20 -10
- synth_ai/lm/core/vendor_clients.py +18 -17
- 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 +11 -10
- synth_ai/lm/vendors/openai_standard.py +113 -87
- 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 +26 -26
- 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 +69 -63
- synth_ai/lm/warmup.py +8 -7
- 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 +21 -21
- 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 +35 -29
- 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 +60 -48
- synth_ai/tracing_v3/turso/models.py +24 -19
- synth_ai/tracing_v3/utils.py +5 -5
- synth_ai/tui/__main__.py +1 -1
- synth_ai/tui/cli/query_experiments.py +2 -3
- synth_ai/tui/cli/query_experiments_v3.py +2 -3
- synth_ai/tui/dashboard.py +97 -86
- 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.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/METADATA +1 -11
- synth_ai-0.2.4.dev7.dist-info/RECORD +299 -0
- synth_ai-0.2.4.dev6.dist-info/RECORD +0 -299
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.4.dev6.dist-info → synth_ai-0.2.4.dev7.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,19 @@
|
|
1
1
|
import json
|
2
|
+
import warnings
|
2
3
|
from typing import (
|
3
4
|
Any,
|
4
|
-
|
5
|
-
List,
|
5
|
+
Literal,
|
6
6
|
Optional,
|
7
|
-
|
8
|
-
Type,
|
9
|
-
get_type_hints,
|
7
|
+
Union,
|
10
8
|
get_args,
|
11
9
|
get_origin,
|
12
|
-
|
13
|
-
Literal,
|
10
|
+
get_type_hints,
|
14
11
|
)
|
12
|
+
|
15
13
|
from pydantic import BaseModel
|
16
|
-
import warnings
|
17
14
|
|
18
15
|
|
19
|
-
def generate_type_map() ->
|
16
|
+
def generate_type_map() -> dict[Any, str]:
|
20
17
|
base_types = {
|
21
18
|
int: "int",
|
22
19
|
float: "float",
|
@@ -26,8 +23,8 @@ def generate_type_map() -> Dict[Any, str]:
|
|
26
23
|
}
|
27
24
|
|
28
25
|
collection_types = {
|
29
|
-
|
30
|
-
|
26
|
+
list: "List",
|
27
|
+
dict: "Dict",
|
31
28
|
Optional: "Optional",
|
32
29
|
}
|
33
30
|
|
@@ -37,19 +34,19 @@ def generate_type_map() -> Dict[Any, str]:
|
|
37
34
|
for collection, collection_name in collection_types.items():
|
38
35
|
if collection is Optional:
|
39
36
|
type_map[Optional[base_type]] = name
|
40
|
-
elif collection is
|
37
|
+
elif collection is dict:
|
41
38
|
# Handle generic Dict type
|
42
|
-
type_map[
|
39
|
+
type_map[dict] = "Dict[Any,Any]"
|
43
40
|
# Provide both key and value types for Dict
|
44
|
-
type_map[
|
41
|
+
type_map[dict[base_type, base_type]] = f"{collection_name}[{name},{name}]"
|
45
42
|
# Handle Dict[Any, Any] explicitly
|
46
|
-
type_map[
|
43
|
+
type_map[dict[Any, Any]] = "Dict[Any,Any]"
|
47
44
|
else:
|
48
45
|
type_map[collection[base_type]] = f"{collection_name}[{name}]"
|
49
46
|
return type_map
|
50
47
|
|
51
48
|
|
52
|
-
def generate_example_dict() ->
|
49
|
+
def generate_example_dict() -> dict[str, Any]:
|
53
50
|
example_values = {
|
54
51
|
"str": "<Your type-str response here>",
|
55
52
|
"int": "<Your type-int response here>",
|
@@ -101,10 +98,10 @@ def get_type_string(type_hint):
|
|
101
98
|
return f"{type_hint.__name__}({', '.join(f'{k}: {v}' for k, v in field_types.items())})"
|
102
99
|
else:
|
103
100
|
return base_type_examples.get(type_hint, ("Unknown", "unknown"))[0]
|
104
|
-
elif origin in (list,
|
101
|
+
elif origin in (list, list):
|
105
102
|
elem_type = get_type_string(args[0])
|
106
103
|
return f"List[{elem_type}]"
|
107
|
-
elif origin in (dict,
|
104
|
+
elif origin in (dict, dict):
|
108
105
|
key_type = get_type_string(args[0])
|
109
106
|
value_type = get_type_string(args[1])
|
110
107
|
return f"Dict[{key_type}, {value_type}]"
|
@@ -167,10 +164,10 @@ def get_example_value(type_hint):
|
|
167
164
|
return example, union_docs
|
168
165
|
else:
|
169
166
|
return base_type_examples.get(type_hint, ("Unknown", "unknown"))[1], []
|
170
|
-
elif origin in (list,
|
167
|
+
elif origin in (list, list):
|
171
168
|
value, docs = get_example_value(args[0])
|
172
169
|
return [value], docs
|
173
|
-
elif origin in (dict,
|
170
|
+
elif origin in (dict, dict):
|
174
171
|
if not args or len(args) < 2:
|
175
172
|
warnings.warn(
|
176
173
|
f"Dictionary type hint {type_hint} missing type arguments. "
|
@@ -224,9 +221,9 @@ def get_example_value(type_hint):
|
|
224
221
|
def add_json_instructions_to_messages(
|
225
222
|
system_message,
|
226
223
|
user_message,
|
227
|
-
response_model:
|
228
|
-
previously_failed_error_messages:
|
229
|
-
) ->
|
224
|
+
response_model: type[BaseModel] | None = None,
|
225
|
+
previously_failed_error_messages: list[str] = [],
|
226
|
+
) -> tuple[str, str]:
|
230
227
|
if response_model:
|
231
228
|
type_hints = get_type_hints(response_model)
|
232
229
|
# print("Type hints", type_hints)
|
@@ -283,10 +280,10 @@ Here are some error traces from previous attempts:
|
|
283
280
|
|
284
281
|
|
285
282
|
def inject_structured_output_instructions(
|
286
|
-
messages:
|
287
|
-
response_model:
|
288
|
-
previously_failed_error_messages:
|
289
|
-
) ->
|
283
|
+
messages: list[dict[str, str]],
|
284
|
+
response_model: type[BaseModel] | None = None,
|
285
|
+
previously_failed_error_messages: list[str] = [],
|
286
|
+
) -> list[dict[str, str]]:
|
290
287
|
prev_system_message_content = messages[0]["content"]
|
291
288
|
prev_user_message_content = messages[1]["content"]
|
292
289
|
system_message, user_message = add_json_instructions_to_messages(
|
@@ -2,15 +2,13 @@ import ast
|
|
2
2
|
import json
|
3
3
|
import logging
|
4
4
|
import re
|
5
|
-
from typing import Dict, List, Type, Union
|
6
5
|
|
7
6
|
from pydantic import BaseModel
|
8
7
|
|
9
|
-
from synth_ai.lm.vendors.base import VendorBase
|
10
8
|
from synth_ai.lm.vendors.core.openai_api import OpenAIStructuredOutputClient
|
11
9
|
|
12
10
|
|
13
|
-
def pull_out_structured_output(response_raw: str, response_model:
|
11
|
+
def pull_out_structured_output(response_raw: str, response_model: type[BaseModel]) -> BaseModel:
|
14
12
|
logger = logging.getLogger(__name__)
|
15
13
|
# logger.debug(f"Raw response received: {response_raw}")
|
16
14
|
|
@@ -36,7 +34,7 @@ def pull_out_structured_output(response_raw: str, response_model: Type[BaseModel
|
|
36
34
|
try:
|
37
35
|
response = json.loads(response_prepared)
|
38
36
|
final = response_model(**response)
|
39
|
-
except json.JSONDecodeError
|
37
|
+
except json.JSONDecodeError:
|
40
38
|
# Attempt to parse using ast.literal_eval as a fallback
|
41
39
|
response_prepared = response_prepared.replace("\n", "").replace("\\n", "")
|
42
40
|
response_prepared = response_prepared.replace('\\"', '"')
|
@@ -46,18 +44,22 @@ def pull_out_structured_output(response_raw: str, response_model: Type[BaseModel
|
|
46
44
|
except Exception as inner_e:
|
47
45
|
raise ValueError(
|
48
46
|
f"Failed to parse response as {response_model}: {inner_e} - {response_prepared}"
|
49
|
-
)
|
47
|
+
) from inner_e
|
50
48
|
except Exception as e:
|
51
|
-
raise ValueError(
|
49
|
+
raise ValueError(
|
50
|
+
f"Failed to parse response as {response_model}: {e} - {response_prepared}"
|
51
|
+
) from e
|
52
52
|
assert isinstance(final, BaseModel), "Structured output must be a Pydantic model"
|
53
53
|
return final
|
54
54
|
|
55
55
|
|
56
56
|
def fix_errant_stringified_json_sync(
|
57
57
|
response_raw: str,
|
58
|
-
response_model:
|
59
|
-
models:
|
58
|
+
response_model: type[BaseModel],
|
59
|
+
models: list[str] | None = None,
|
60
60
|
) -> BaseModel:
|
61
|
+
if models is None:
|
62
|
+
models = ["gpt-4o-mini", "gpt-4o"]
|
61
63
|
try:
|
62
64
|
return pull_out_structured_output(response_raw, response_model)
|
63
65
|
except ValueError as e:
|
@@ -85,14 +87,16 @@ def fix_errant_stringified_json_sync(
|
|
85
87
|
return pull_out_structured_output(fixed_response, response_model)
|
86
88
|
except Exception as e:
|
87
89
|
pass
|
88
|
-
raise ValueError("Failed to fix response using any model")
|
90
|
+
raise ValueError("Failed to fix response using any model") from None
|
89
91
|
|
90
92
|
|
91
93
|
async def fix_errant_stringified_json_async(
|
92
94
|
response_raw: str,
|
93
|
-
response_model:
|
94
|
-
models:
|
95
|
+
response_model: type[BaseModel],
|
96
|
+
models: list[str] | None = None,
|
95
97
|
) -> BaseModel:
|
98
|
+
if models is None:
|
99
|
+
models = ["gpt-4o-mini", "gpt-4o"]
|
96
100
|
try:
|
97
101
|
return pull_out_structured_output(response_raw, response_model)
|
98
102
|
except ValueError as e:
|
@@ -119,13 +123,13 @@ async def fix_errant_stringified_json_async(
|
|
119
123
|
return pull_out_structured_output(fixed_response, response_model)
|
120
124
|
except Exception as e:
|
121
125
|
pass
|
122
|
-
raise ValueError("Failed to fix response using any model")
|
126
|
+
raise ValueError("Failed to fix response using any model") from None
|
123
127
|
|
124
128
|
|
125
129
|
async def fix_errant_forced_async(
|
126
|
-
messages:
|
130
|
+
messages: list[dict],
|
127
131
|
response_raw: str,
|
128
|
-
response_model:
|
132
|
+
response_model: type[BaseModel],
|
129
133
|
model: str,
|
130
134
|
) -> BaseModel:
|
131
135
|
try:
|
@@ -157,7 +161,7 @@ async def fix_errant_forced_async(
|
|
157
161
|
|
158
162
|
def fix_errant_forced_sync(
|
159
163
|
response_raw: str,
|
160
|
-
response_model:
|
164
|
+
response_model: type[BaseModel],
|
161
165
|
model: str,
|
162
166
|
) -> BaseModel:
|
163
167
|
client = OpenAIStructuredOutputClient()
|
synth_ai/lm/tools/base.py
CHANGED
@@ -4,7 +4,7 @@ Base class for LM tools.
|
|
4
4
|
This module provides the base class for defining tools that can be used with language models.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from typing import
|
7
|
+
from typing import Any
|
8
8
|
|
9
9
|
from pydantic import BaseModel
|
10
10
|
|
@@ -12,25 +12,26 @@ from pydantic import BaseModel
|
|
12
12
|
class BaseTool(BaseModel):
|
13
13
|
"""
|
14
14
|
Base class for defining tools that can be used with language models.
|
15
|
-
|
15
|
+
|
16
16
|
Attributes:
|
17
17
|
name: The name of the tool
|
18
18
|
arguments: Pydantic model defining the tool's arguments
|
19
19
|
description: Human-readable description of what the tool does
|
20
20
|
strict: Whether to enforce strict schema validation (default True)
|
21
21
|
"""
|
22
|
+
|
22
23
|
name: str
|
23
|
-
arguments:
|
24
|
+
arguments: type[BaseModel]
|
24
25
|
description: str = ""
|
25
26
|
strict: bool = True
|
26
27
|
|
27
|
-
def to_openai_tool(self) ->
|
28
|
+
def to_openai_tool(self) -> dict[str, Any]:
|
28
29
|
"""
|
29
30
|
Convert the tool to OpenAI's tool format.
|
30
|
-
|
31
|
+
|
31
32
|
Returns:
|
32
33
|
dict: Tool definition in OpenAI's expected format
|
33
|
-
|
34
|
+
|
34
35
|
Note:
|
35
36
|
- Ensures additionalProperties is False for strict validation
|
36
37
|
- Fixes array items that lack explicit types
|
@@ -40,7 +41,7 @@ class BaseTool(BaseModel):
|
|
40
41
|
schema["additionalProperties"] = False
|
41
42
|
|
42
43
|
if "properties" in schema:
|
43
|
-
for
|
44
|
+
for _prop_name, prop_schema in schema["properties"].items():
|
44
45
|
if prop_schema.get("type") == "array":
|
45
46
|
items_schema = prop_schema.get("items", {})
|
46
47
|
if not isinstance(items_schema, dict) or not items_schema.get("type"):
|
@@ -63,13 +64,13 @@ class BaseTool(BaseModel):
|
|
63
64
|
},
|
64
65
|
}
|
65
66
|
|
66
|
-
def to_anthropic_tool(self) ->
|
67
|
+
def to_anthropic_tool(self) -> dict[str, Any]:
|
67
68
|
"""
|
68
69
|
Convert the tool to Anthropic's tool format.
|
69
|
-
|
70
|
+
|
70
71
|
Returns:
|
71
72
|
dict: Tool definition in Anthropic's expected format
|
72
|
-
|
73
|
+
|
73
74
|
Note:
|
74
75
|
Anthropic uses a different format with input_schema instead of parameters.
|
75
76
|
"""
|
@@ -86,13 +87,13 @@ class BaseTool(BaseModel):
|
|
86
87
|
},
|
87
88
|
}
|
88
89
|
|
89
|
-
def to_mistral_tool(self) ->
|
90
|
+
def to_mistral_tool(self) -> dict[str, Any]:
|
90
91
|
"""
|
91
92
|
Convert the tool to Mistral's tool format.
|
92
|
-
|
93
|
+
|
93
94
|
Returns:
|
94
95
|
dict: Tool definition in Mistral's expected format
|
95
|
-
|
96
|
+
|
96
97
|
Note:
|
97
98
|
Mistral requires explicit handling of array types and enum values.
|
98
99
|
"""
|
@@ -130,13 +131,13 @@ class BaseTool(BaseModel):
|
|
130
131
|
},
|
131
132
|
}
|
132
133
|
|
133
|
-
def to_gemini_tool(self) ->
|
134
|
+
def to_gemini_tool(self) -> dict[str, Any]:
|
134
135
|
"""
|
135
136
|
Convert the tool to Gemini's tool format.
|
136
|
-
|
137
|
+
|
137
138
|
Returns:
|
138
139
|
dict: Tool definition in Gemini's expected format
|
139
|
-
|
140
|
+
|
140
141
|
Note:
|
141
142
|
Gemini uses a simpler format without the nested "function" key.
|
142
143
|
"""
|
synth_ai/lm/unified_interface.py
CHANGED
@@ -3,12 +3,11 @@ Unified interface for LM providers.
|
|
3
3
|
Provides a consistent API for OpenAI and Synth backends.
|
4
4
|
"""
|
5
5
|
|
6
|
-
import os
|
7
6
|
import logging
|
8
7
|
from abc import ABC, abstractmethod
|
9
|
-
from typing import
|
8
|
+
from typing import Any
|
10
9
|
|
11
|
-
from .config import
|
10
|
+
from .config import OpenAIConfig, SynthConfig
|
12
11
|
|
13
12
|
logger = logging.getLogger(__name__)
|
14
13
|
|
@@ -18,8 +17,8 @@ class UnifiedLMProvider(ABC):
|
|
18
17
|
|
19
18
|
@abstractmethod
|
20
19
|
async def create_chat_completion(
|
21
|
-
self, model: str, messages:
|
22
|
-
) ->
|
20
|
+
self, model: str, messages: list[dict[str, Any]], **kwargs
|
21
|
+
) -> dict[str, Any]:
|
23
22
|
"""Create a chat completion."""
|
24
23
|
pass
|
25
24
|
|
@@ -37,7 +36,7 @@ class UnifiedLMProvider(ABC):
|
|
37
36
|
class OpenAIProvider(UnifiedLMProvider):
|
38
37
|
"""OpenAI provider implementation."""
|
39
38
|
|
40
|
-
def __init__(self, api_key:
|
39
|
+
def __init__(self, api_key: str | None = None, **kwargs):
|
41
40
|
"""
|
42
41
|
Initialize OpenAI provider.
|
43
42
|
|
@@ -47,8 +46,8 @@ class OpenAIProvider(UnifiedLMProvider):
|
|
47
46
|
"""
|
48
47
|
try:
|
49
48
|
from openai import AsyncOpenAI
|
50
|
-
except ImportError:
|
51
|
-
raise ImportError("OpenAI package not installed. Run: pip install openai")
|
49
|
+
except ImportError as err:
|
50
|
+
raise ImportError("OpenAI package not installed. Run: pip install openai") from err
|
52
51
|
|
53
52
|
# Use provided key or load from environment
|
54
53
|
if api_key is None:
|
@@ -59,8 +58,8 @@ class OpenAIProvider(UnifiedLMProvider):
|
|
59
58
|
logger.info("Initialized OpenAI provider")
|
60
59
|
|
61
60
|
async def create_chat_completion(
|
62
|
-
self, model: str, messages:
|
63
|
-
) ->
|
61
|
+
self, model: str, messages: list[dict[str, Any]], **kwargs
|
62
|
+
) -> dict[str, Any]:
|
64
63
|
"""Create a chat completion using OpenAI."""
|
65
64
|
response = await self.client.chat.completions.create(
|
66
65
|
model=model, messages=messages, **kwargs
|
@@ -82,7 +81,7 @@ class OpenAIProvider(UnifiedLMProvider):
|
|
82
81
|
class SynthProvider(UnifiedLMProvider):
|
83
82
|
"""Synth provider implementation."""
|
84
83
|
|
85
|
-
def __init__(self, config:
|
84
|
+
def __init__(self, config: SynthConfig | None = None, **kwargs):
|
86
85
|
"""
|
87
86
|
Initialize Synth provider.
|
88
87
|
|
@@ -96,8 +95,8 @@ class SynthProvider(UnifiedLMProvider):
|
|
96
95
|
self.client = AsyncSynthClient(self.config)
|
97
96
|
|
98
97
|
async def create_chat_completion(
|
99
|
-
self, model: str, messages:
|
100
|
-
) ->
|
98
|
+
self, model: str, messages: list[dict[str, Any]], **kwargs
|
99
|
+
) -> dict[str, Any]:
|
101
100
|
"""Create a chat completion using Synth."""
|
102
101
|
return await self.client.chat_completions_create(model=model, messages=messages, **kwargs)
|
103
102
|
|
@@ -156,9 +155,9 @@ class UnifiedLMClient:
|
|
156
155
|
default_provider: Default provider to use ("openai" or "synth")
|
157
156
|
"""
|
158
157
|
self.default_provider = default_provider
|
159
|
-
self._providers:
|
158
|
+
self._providers: dict[str, UnifiedLMProvider] = {}
|
160
159
|
|
161
|
-
async def _get_provider(self, provider:
|
160
|
+
async def _get_provider(self, provider: str | None = None) -> UnifiedLMProvider:
|
162
161
|
"""Get or create a provider instance."""
|
163
162
|
provider_name = provider or self.default_provider
|
164
163
|
|
@@ -168,8 +167,8 @@ class UnifiedLMClient:
|
|
168
167
|
return self._providers[provider_name]
|
169
168
|
|
170
169
|
async def create_chat_completion(
|
171
|
-
self, model: str, messages:
|
172
|
-
) ->
|
170
|
+
self, model: str, messages: list[dict[str, Any]], provider: str | None = None, **kwargs
|
171
|
+
) -> dict[str, Any]:
|
173
172
|
"""
|
174
173
|
Create a chat completion using specified or default provider.
|
175
174
|
|
@@ -185,7 +184,7 @@ class UnifiedLMClient:
|
|
185
184
|
provider_instance = await self._get_provider(provider)
|
186
185
|
return await provider_instance.create_chat_completion(model, messages, **kwargs)
|
187
186
|
|
188
|
-
async def warmup(self, model: str, provider:
|
187
|
+
async def warmup(self, model: str, provider: str | None = None, **kwargs) -> bool:
|
189
188
|
"""Warm up a model on specified provider."""
|
190
189
|
provider_instance = await self._get_provider(provider)
|
191
190
|
return await provider_instance.warmup(model, **kwargs)
|
synth_ai/lm/vendors/base.py
CHANGED
@@ -5,7 +5,7 @@ This module provides abstract base classes for implementing language model vendo
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
from abc import ABC, abstractmethod
|
8
|
-
from typing import Any
|
8
|
+
from typing import Any
|
9
9
|
|
10
10
|
from pydantic import BaseModel
|
11
11
|
|
@@ -13,7 +13,7 @@ from pydantic import BaseModel
|
|
13
13
|
class BaseLMResponse(BaseModel):
|
14
14
|
"""
|
15
15
|
Standard response format from language model API calls.
|
16
|
-
|
16
|
+
|
17
17
|
Attributes:
|
18
18
|
raw_response: The raw text response from the model
|
19
19
|
structured_output: Optional parsed Pydantic model if structured output was requested
|
@@ -22,39 +22,41 @@ class BaseLMResponse(BaseModel):
|
|
22
22
|
reasoning: Optional reasoning trace from the model (o1 models)
|
23
23
|
api_type: Optional API type used ("chat", "responses", or "harmony")
|
24
24
|
"""
|
25
|
+
|
25
26
|
raw_response: str
|
26
|
-
structured_output:
|
27
|
-
tool_calls:
|
28
|
-
response_id:
|
29
|
-
reasoning:
|
30
|
-
api_type:
|
31
|
-
usage:
|
27
|
+
structured_output: BaseModel | None = None
|
28
|
+
tool_calls: list[dict] | None = None
|
29
|
+
response_id: str | None = None
|
30
|
+
reasoning: str | None = None
|
31
|
+
api_type: str | None = None
|
32
|
+
usage: dict[str, Any] | None = None
|
32
33
|
|
33
34
|
|
34
35
|
class VendorBase(ABC):
|
35
36
|
"""
|
36
37
|
Abstract base class for language model vendor implementations.
|
37
|
-
|
38
|
+
|
38
39
|
Attributes:
|
39
40
|
used_for_structured_outputs: Whether this vendor supports structured outputs
|
40
41
|
exceptions_to_retry: List of exceptions that should trigger retries
|
41
42
|
"""
|
43
|
+
|
42
44
|
used_for_structured_outputs: bool = False
|
43
|
-
exceptions_to_retry:
|
45
|
+
exceptions_to_retry: list[Exception] = []
|
44
46
|
|
45
47
|
@abstractmethod
|
46
48
|
async def _hit_api_async(
|
47
49
|
self,
|
48
|
-
messages:
|
49
|
-
response_model_override:
|
50
|
+
messages: list[dict[str, Any]],
|
51
|
+
response_model_override: BaseModel | None = None,
|
50
52
|
) -> str:
|
51
53
|
"""
|
52
54
|
Make an asynchronous API call to the language model.
|
53
|
-
|
55
|
+
|
54
56
|
Args:
|
55
57
|
messages: List of message dictionaries with role and content
|
56
58
|
response_model_override: Optional Pydantic model for structured output
|
57
|
-
|
59
|
+
|
58
60
|
Returns:
|
59
61
|
str: The model's response
|
60
62
|
"""
|
@@ -63,16 +65,16 @@ class VendorBase(ABC):
|
|
63
65
|
@abstractmethod
|
64
66
|
def _hit_api_sync(
|
65
67
|
self,
|
66
|
-
messages:
|
67
|
-
response_model_override:
|
68
|
+
messages: list[dict[str, Any]],
|
69
|
+
response_model_override: BaseModel | None = None,
|
68
70
|
) -> str:
|
69
71
|
"""
|
70
72
|
Make a synchronous API call to the language model.
|
71
|
-
|
73
|
+
|
72
74
|
Args:
|
73
75
|
messages: List of message dictionaries with role and content
|
74
76
|
response_model_override: Optional Pydantic model for structured output
|
75
|
-
|
77
|
+
|
76
78
|
Returns:
|
77
79
|
str: The model's response
|
78
80
|
"""
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import json
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any
|
3
3
|
|
4
4
|
import anthropic
|
5
5
|
import pydantic
|
@@ -8,27 +8,32 @@ from pydantic import BaseModel
|
|
8
8
|
from synth_ai.lm.caching.initialize import (
|
9
9
|
get_cache_handler,
|
10
10
|
)
|
11
|
+
from synth_ai.lm.constants import CLAUDE_REASONING_MODELS, SONNET_37_BUDGETS, SPECIAL_BASE_TEMPS
|
12
|
+
from synth_ai.lm.overrides import (
|
13
|
+
apply_injection as apply_injection_overrides,
|
14
|
+
)
|
15
|
+
from synth_ai.lm.overrides import (
|
16
|
+
apply_param_overrides,
|
17
|
+
use_overrides_for_messages,
|
18
|
+
)
|
11
19
|
from synth_ai.lm.tools.base import BaseTool
|
12
20
|
from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
|
13
|
-
from synth_ai.lm.constants import SPECIAL_BASE_TEMPS, CLAUDE_REASONING_MODELS, SONNET_37_BUDGETS
|
14
21
|
from synth_ai.lm.vendors.core.openai_api import OpenAIStructuredOutputClient
|
15
|
-
from synth_ai.lm.overrides import use_overrides_for_messages, apply_injection as apply_injection_overrides, apply_param_overrides
|
16
|
-
from synth_ai.lm.injection import apply_injection
|
17
22
|
|
18
|
-
ANTHROPIC_EXCEPTIONS_TO_RETRY:
|
23
|
+
ANTHROPIC_EXCEPTIONS_TO_RETRY: tuple[type[Exception], ...] = (anthropic.APIError,)
|
19
24
|
|
20
25
|
|
21
26
|
class AnthropicAPI(VendorBase):
|
22
27
|
used_for_structured_outputs: bool = True
|
23
|
-
exceptions_to_retry:
|
28
|
+
exceptions_to_retry: tuple = ANTHROPIC_EXCEPTIONS_TO_RETRY
|
24
29
|
sync_client: Any
|
25
30
|
async_client: Any
|
26
31
|
|
27
32
|
def __init__(
|
28
33
|
self,
|
29
|
-
exceptions_to_retry:
|
34
|
+
exceptions_to_retry: tuple[type[Exception], ...] = ANTHROPIC_EXCEPTIONS_TO_RETRY,
|
30
35
|
used_for_structured_outputs: bool = False,
|
31
|
-
reasoning_effort:
|
36
|
+
reasoning_effort: str | None = "high",
|
32
37
|
):
|
33
38
|
self.sync_client = anthropic.Anthropic()
|
34
39
|
self.async_client = anthropic.AsyncAnthropic()
|
@@ -46,14 +51,14 @@ class AnthropicAPI(VendorBase):
|
|
46
51
|
async def _hit_api_async(
|
47
52
|
self,
|
48
53
|
model: str,
|
49
|
-
messages:
|
50
|
-
lm_config:
|
54
|
+
messages: list[dict[str, Any]],
|
55
|
+
lm_config: dict[str, Any],
|
51
56
|
use_ephemeral_cache_only: bool = False,
|
52
57
|
reasoning_effort: str = "high",
|
53
|
-
tools:
|
54
|
-
**vendor_params:
|
58
|
+
tools: list[BaseTool] | None = None,
|
59
|
+
**vendor_params: dict[str, Any],
|
55
60
|
) -> BaseLMResponse:
|
56
|
-
assert lm_config.get("response_model"
|
61
|
+
assert lm_config.get("response_model") is None, (
|
57
62
|
"response_model is not supported for standard calls"
|
58
63
|
)
|
59
64
|
used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
|
@@ -77,6 +82,7 @@ class AnthropicAPI(VendorBase):
|
|
77
82
|
}
|
78
83
|
with use_overrides_for_messages(messages):
|
79
84
|
from synth_ai.lm.overrides import apply_tool_overrides
|
85
|
+
|
80
86
|
api_params = apply_tool_overrides(api_params)
|
81
87
|
api_params = apply_param_overrides(api_params)
|
82
88
|
|
@@ -89,8 +95,11 @@ class AnthropicAPI(VendorBase):
|
|
89
95
|
import inspect
|
90
96
|
|
91
97
|
create_sig = inspect.signature(self.async_client.messages.create)
|
92
|
-
if
|
93
|
-
|
98
|
+
if (
|
99
|
+
"thinking" in create_sig.parameters
|
100
|
+
and model in CLAUDE_REASONING_MODELS
|
101
|
+
and reasoning_effort in ["high", "medium"]
|
102
|
+
):
|
94
103
|
budget = SONNET_37_BUDGETS[reasoning_effort]
|
95
104
|
api_params["thinking"] = {
|
96
105
|
"type": "enabled",
|
@@ -144,14 +153,14 @@ class AnthropicAPI(VendorBase):
|
|
144
153
|
def _hit_api_sync(
|
145
154
|
self,
|
146
155
|
model: str,
|
147
|
-
messages:
|
148
|
-
lm_config:
|
156
|
+
messages: list[dict[str, Any]],
|
157
|
+
lm_config: dict[str, Any],
|
149
158
|
use_ephemeral_cache_only: bool = False,
|
150
159
|
reasoning_effort: str = "high",
|
151
|
-
tools:
|
152
|
-
**vendor_params:
|
160
|
+
tools: list[BaseTool] | None = None,
|
161
|
+
**vendor_params: dict[str, Any],
|
153
162
|
) -> BaseLMResponse:
|
154
|
-
assert lm_config.get("response_model"
|
163
|
+
assert lm_config.get("response_model") is None, (
|
155
164
|
"response_model is not supported for standard calls"
|
156
165
|
)
|
157
166
|
used_cache_handler = get_cache_handler(use_ephemeral_cache_only=use_ephemeral_cache_only)
|
@@ -175,6 +184,7 @@ class AnthropicAPI(VendorBase):
|
|
175
184
|
}
|
176
185
|
with use_overrides_for_messages(messages):
|
177
186
|
from synth_ai.lm.overrides import apply_tool_overrides
|
187
|
+
|
178
188
|
api_params = apply_tool_overrides(api_params)
|
179
189
|
api_params = apply_param_overrides(api_params)
|
180
190
|
|
@@ -238,12 +248,12 @@ class AnthropicAPI(VendorBase):
|
|
238
248
|
async def _hit_api_async_structured_output(
|
239
249
|
self,
|
240
250
|
model: str,
|
241
|
-
messages:
|
251
|
+
messages: list[dict[str, Any]],
|
242
252
|
response_model: BaseModel,
|
243
253
|
temperature: float,
|
244
254
|
use_ephemeral_cache_only: bool = False,
|
245
255
|
reasoning_effort: str = "high",
|
246
|
-
**vendor_params:
|
256
|
+
**vendor_params: dict[str, Any],
|
247
257
|
) -> BaseLMResponse:
|
248
258
|
try:
|
249
259
|
# First try with Anthropic
|
@@ -297,17 +307,16 @@ class AnthropicAPI(VendorBase):
|
|
297
307
|
def _hit_api_sync_structured_output(
|
298
308
|
self,
|
299
309
|
model: str,
|
300
|
-
messages:
|
310
|
+
messages: list[dict[str, Any]],
|
301
311
|
response_model: BaseModel,
|
302
312
|
temperature: float,
|
303
313
|
use_ephemeral_cache_only: bool = False,
|
304
314
|
reasoning_effort: str = "high",
|
305
|
-
**vendor_params:
|
315
|
+
**vendor_params: dict[str, Any],
|
306
316
|
) -> BaseLMResponse:
|
307
317
|
try:
|
308
318
|
# First try with Anthropic
|
309
319
|
reasoning_effort = vendor_params.get("reasoning_effort", reasoning_effort)
|
310
|
-
import time
|
311
320
|
|
312
321
|
if model in CLAUDE_REASONING_MODELS:
|
313
322
|
if reasoning_effort in ["high", "medium"]:
|
@@ -359,13 +368,13 @@ class AnthropicAPI(VendorBase):
|
|
359
368
|
|
360
369
|
async def _process_call_async(
|
361
370
|
self,
|
362
|
-
messages:
|
371
|
+
messages: list[dict[str, Any]],
|
363
372
|
model: str,
|
364
373
|
response_model: BaseModel,
|
365
374
|
api_call_method,
|
366
375
|
temperature: float = 0.0,
|
367
376
|
use_ephemeral_cache_only: bool = False,
|
368
|
-
vendor_params:
|
377
|
+
vendor_params: dict[str, Any] = None,
|
369
378
|
) -> BaseModel:
|
370
379
|
vendor_params = vendor_params or {}
|
371
380
|
# Each vendor can filter parameters they support
|