synth-ai 0.2.4.dev5__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 +21 -17
- 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 +29 -0
- synth_ai/environments/examples/wordle/engine.py +398 -0
- synth_ai/environments/examples/wordle/environment.py +159 -0
- synth_ai/environments/examples/wordle/helpers/generate_instances_wordfreq.py +75 -0
- synth_ai/environments/examples/wordle/taskset.py +230 -0
- synth_ai/environments/reproducibility/core.py +1 -1
- synth_ai/environments/reproducibility/tree.py +21 -21
- synth_ai/environments/service/app.py +11 -2
- synth_ai/environments/service/core_routes.py +137 -105
- 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 +168 -0
- synth_ai/learning/prompts/hello_world_in_context_injection_ex.py +213 -0
- synth_ai/learning/prompts/mipro.py +282 -1
- synth_ai/learning/prompts/random_search.py +246 -0
- synth_ai/learning/prompts/run_mipro_banking77.py +172 -0
- synth_ai/learning/prompts/run_random_search_banking77.py +324 -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 +20 -10
- synth_ai/lm/core/vendor_clients.py +18 -17
- synth_ai/lm/injection.py +80 -0
- synth_ai/lm/overrides.py +206 -0
- synth_ai/lm/provider_support/__init__.py +1 -1
- synth_ai/lm/provider_support/anthropic.py +51 -24
- synth_ai/lm/provider_support/openai.py +51 -22
- 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 +50 -25
- 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 +144 -88
- 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.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/METADATA +2 -11
- synth_ai-0.2.4.dev7.dist-info/RECORD +299 -0
- synth_ai-0.2.4.dev5.dist-info/RECORD +0 -287
- {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.4.dev5.dist-info → synth_ai-0.2.4.dev7.dist-info}/top_level.txt +0 -0
@@ -5,14 +5,11 @@ This module contains the Responses API and Harmony encoding methods
|
|
5
5
|
that extend the OpenAIStandard class functionality.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
9
8
|
import uuid
|
10
|
-
from
|
9
|
+
from typing import Any
|
11
10
|
|
12
11
|
from synth_ai.lm.tools.base import BaseTool
|
13
12
|
from synth_ai.lm.vendors.base import BaseLMResponse
|
14
|
-
from synth_ai.lm.vendors.retries import MAX_BACKOFF
|
15
|
-
import backoff
|
16
13
|
|
17
14
|
|
18
15
|
def _silent_backoff_handler(_details):
|
@@ -27,23 +24,23 @@ DEFAULT_EXCEPTIONS_TO_RETRY = (
|
|
27
24
|
|
28
25
|
class OpenAIResponsesAPIMixin:
|
29
26
|
"""Mixin class providing Responses API functionality for OpenAI vendors."""
|
30
|
-
|
27
|
+
|
31
28
|
async def _hit_api_async_responses(
|
32
29
|
self,
|
33
30
|
model: str,
|
34
|
-
messages:
|
35
|
-
lm_config:
|
36
|
-
previous_response_id:
|
31
|
+
messages: list[dict[str, Any]],
|
32
|
+
lm_config: dict[str, Any],
|
33
|
+
previous_response_id: str | None = None,
|
37
34
|
use_ephemeral_cache_only: bool = False,
|
38
|
-
tools:
|
35
|
+
tools: list[BaseTool] | None = None,
|
39
36
|
) -> BaseLMResponse:
|
40
37
|
"""Use OpenAI Responses API for supported models."""
|
41
|
-
|
38
|
+
|
42
39
|
print(f"🔍 RESPONSES API: Called for model {model}")
|
43
40
|
print(f"🔍 RESPONSES API: previous_response_id = {previous_response_id}")
|
44
|
-
|
41
|
+
|
45
42
|
# Check if the client has responses attribute
|
46
|
-
if not hasattr(self.async_client,
|
43
|
+
if not hasattr(self.async_client, "responses"):
|
47
44
|
print("🔍 RESPONSES API: Client doesn't have responses attribute, using fallback")
|
48
45
|
# Fallback - use chat completions with simulated response_id
|
49
46
|
response = await self._hit_api_async(
|
@@ -53,21 +50,22 @@ class OpenAIResponsesAPIMixin:
|
|
53
50
|
use_ephemeral_cache_only=use_ephemeral_cache_only,
|
54
51
|
tools=tools,
|
55
52
|
)
|
56
|
-
|
53
|
+
|
57
54
|
# Add Responses API fields
|
58
55
|
if not response.response_id:
|
59
56
|
import uuid
|
57
|
+
|
60
58
|
response.response_id = str(uuid.uuid4())
|
61
59
|
response.api_type = "responses"
|
62
60
|
return response
|
63
|
-
|
61
|
+
|
64
62
|
# Use the official Responses API
|
65
63
|
try:
|
66
64
|
# Common API call params for Responses API
|
67
65
|
api_params = {
|
68
66
|
"model": model,
|
69
67
|
}
|
70
|
-
|
68
|
+
|
71
69
|
# For Responses API, we use 'input' parameter
|
72
70
|
if previous_response_id:
|
73
71
|
# Continue existing thread
|
@@ -92,35 +90,37 @@ class OpenAIResponsesAPIMixin:
|
|
92
90
|
elif role == "assistant":
|
93
91
|
input_parts.append(f"Assistant: {content}")
|
94
92
|
api_params["input"] = "\n".join(input_parts)
|
95
|
-
|
93
|
+
|
96
94
|
# Add tools if provided
|
97
95
|
if tools and all(isinstance(tool, BaseTool) for tool in tools):
|
98
96
|
api_params["tools"] = [tool.to_openai_tool() for tool in tools]
|
99
97
|
elif tools:
|
100
98
|
api_params["tools"] = tools
|
101
|
-
|
99
|
+
|
102
100
|
# Add other parameters from lm_config if needed
|
103
101
|
if "max_tokens" in lm_config:
|
104
102
|
api_params["max_tokens"] = lm_config["max_tokens"]
|
105
|
-
|
103
|
+
|
106
104
|
print(f"🔍 RESPONSES API: Calling with params: {list(api_params.keys())}")
|
107
|
-
|
105
|
+
|
108
106
|
# Call the Responses API
|
109
107
|
response = await self.async_client.responses.create(**api_params)
|
110
|
-
|
108
|
+
|
111
109
|
print(f"🔍 RESPONSES API: Response received, type: {type(response)}")
|
112
|
-
|
110
|
+
|
113
111
|
# Extract fields from response
|
114
|
-
output_text = getattr(response,
|
115
|
-
reasoning_obj = getattr(response,
|
116
|
-
response_id = getattr(response,
|
117
|
-
|
112
|
+
output_text = getattr(response, "output_text", getattr(response, "content", ""))
|
113
|
+
reasoning_obj = getattr(response, "reasoning", None)
|
114
|
+
response_id = getattr(response, "id", None)
|
115
|
+
|
118
116
|
# Debug reasoning type (only first time)
|
119
|
-
if reasoning_obj and not hasattr(self,
|
117
|
+
if reasoning_obj and not hasattr(self, "_reasoning_logged"):
|
120
118
|
print(f"🔍 RESPONSES API: Reasoning type: {type(reasoning_obj)}")
|
121
|
-
print(
|
119
|
+
print(
|
120
|
+
f"🔍 RESPONSES API: Reasoning attributes: {[x for x in dir(reasoning_obj) if not x.startswith('_')]}"
|
121
|
+
)
|
122
122
|
self._reasoning_logged = True
|
123
|
-
|
123
|
+
|
124
124
|
# Handle reasoning - it might be an object or a string
|
125
125
|
reasoning = None
|
126
126
|
if reasoning_obj:
|
@@ -130,22 +130,23 @@ class OpenAIResponsesAPIMixin:
|
|
130
130
|
else:
|
131
131
|
# OpenAI returns a Reasoning object
|
132
132
|
# Try to get summary first, but preserve entire object if no summary
|
133
|
-
if hasattr(reasoning_obj,
|
133
|
+
if hasattr(reasoning_obj, "summary") and reasoning_obj.summary:
|
134
134
|
reasoning = reasoning_obj.summary
|
135
135
|
else:
|
136
136
|
# Preserve the full object structure as JSON
|
137
137
|
# This includes effort level and any other fields
|
138
|
-
if hasattr(reasoning_obj,
|
138
|
+
if hasattr(reasoning_obj, "model_dump_json"):
|
139
139
|
reasoning = reasoning_obj.model_dump_json()
|
140
|
-
elif hasattr(reasoning_obj,
|
140
|
+
elif hasattr(reasoning_obj, "to_dict"):
|
141
141
|
import json
|
142
|
+
|
142
143
|
reasoning = json.dumps(reasoning_obj.to_dict())
|
143
144
|
else:
|
144
145
|
reasoning = str(reasoning_obj)
|
145
|
-
|
146
|
+
|
146
147
|
# Handle tool calls if present
|
147
148
|
tool_calls = None
|
148
|
-
if hasattr(response,
|
149
|
+
if hasattr(response, "tool_calls") and response.tool_calls:
|
149
150
|
tool_calls = [
|
150
151
|
{
|
151
152
|
"id": tc.id,
|
@@ -157,9 +158,9 @@ class OpenAIResponsesAPIMixin:
|
|
157
158
|
}
|
158
159
|
for tc in response.tool_calls
|
159
160
|
]
|
160
|
-
|
161
|
+
|
161
162
|
print(f"🔍 RESPONSES API: Extracted response_id = {response_id}")
|
162
|
-
|
163
|
+
|
163
164
|
return BaseLMResponse(
|
164
165
|
raw_response=output_text,
|
165
166
|
response_id=response_id,
|
@@ -167,7 +168,7 @@ class OpenAIResponsesAPIMixin:
|
|
167
168
|
api_type="responses",
|
168
169
|
tool_calls=tool_calls,
|
169
170
|
)
|
170
|
-
|
171
|
+
|
171
172
|
except (AttributeError, Exception) as e:
|
172
173
|
print(f"🔍 RESPONSES API: Error calling Responses API: {e}")
|
173
174
|
# No fallback - raise the error
|
@@ -176,68 +177,80 @@ class OpenAIResponsesAPIMixin:
|
|
176
177
|
async def _hit_api_async_harmony(
|
177
178
|
self,
|
178
179
|
model: str,
|
179
|
-
messages:
|
180
|
-
lm_config:
|
181
|
-
previous_response_id:
|
180
|
+
messages: list[dict[str, Any]],
|
181
|
+
lm_config: dict[str, Any],
|
182
|
+
previous_response_id: str | None = None,
|
182
183
|
use_ephemeral_cache_only: bool = False,
|
183
|
-
tools:
|
184
|
+
tools: list[BaseTool] | None = None,
|
184
185
|
) -> BaseLMResponse:
|
185
186
|
"""Use Harmony encoding for OSS-GPT models."""
|
186
187
|
if not self.harmony_available:
|
187
|
-
raise ImportError(
|
188
|
-
|
189
|
-
|
190
|
-
|
188
|
+
raise ImportError(
|
189
|
+
"openai-harmony package required for OSS-GPT models. Install with: pip install openai-harmony"
|
190
|
+
)
|
191
|
+
|
192
|
+
from openai_harmony import Conversation, Message, Role
|
193
|
+
|
191
194
|
# Convert messages to Harmony format
|
192
195
|
harmony_messages = []
|
193
196
|
for msg in messages:
|
194
|
-
role =
|
195
|
-
Role.
|
197
|
+
role = (
|
198
|
+
Role.SYSTEM
|
199
|
+
if msg["role"] == "system"
|
200
|
+
else (Role.USER if msg["role"] == "user" else Role.ASSISTANT)
|
196
201
|
)
|
197
202
|
content = msg["content"]
|
198
203
|
# Handle multimodal content
|
199
204
|
if isinstance(content, list):
|
200
205
|
# Extract text content for now
|
201
|
-
text_parts = [
|
206
|
+
text_parts = [
|
207
|
+
part.get("text", "") for part in content if part.get("type") == "text"
|
208
|
+
]
|
202
209
|
content = " ".join(text_parts)
|
203
210
|
harmony_messages.append(Message.from_role_and_content(role, content))
|
204
|
-
|
211
|
+
|
205
212
|
conv = Conversation.from_messages(harmony_messages)
|
206
213
|
tokens = self.harmony_enc.render_conversation_for_completion(conv, Role.ASSISTANT)
|
207
|
-
|
214
|
+
|
208
215
|
# For now, we'll need to integrate with Synth GPU endpoint
|
209
216
|
# This would require the actual endpoint to be configured
|
210
217
|
# Placeholder for actual Synth GPU call
|
211
|
-
import aiohttp
|
212
218
|
import os
|
213
|
-
|
219
|
+
|
220
|
+
import aiohttp
|
221
|
+
|
214
222
|
synth_gpu_endpoint = os.getenv("SYNTH_GPU_HARMONY_ENDPOINT")
|
215
223
|
if not synth_gpu_endpoint:
|
216
224
|
raise ValueError("SYNTH_GPU_HARMONY_ENDPOINT environment variable not set")
|
217
|
-
|
218
|
-
async with aiohttp.ClientSession() as session
|
219
|
-
async with session.post(
|
225
|
+
|
226
|
+
async with aiohttp.ClientSession() as session, session.post(
|
220
227
|
f"{synth_gpu_endpoint}/v1/completions",
|
221
228
|
json={
|
222
229
|
"model": model,
|
223
230
|
"prompt": tokens,
|
224
231
|
"max_tokens": lm_config.get("max_tokens", 4096),
|
225
232
|
"temperature": lm_config.get("temperature", 0.8),
|
226
|
-
}
|
233
|
+
},
|
227
234
|
) as resp:
|
228
235
|
result = await resp.json()
|
229
|
-
|
236
|
+
|
230
237
|
# Parse response using Harmony
|
231
238
|
response_tokens = result.get("choices", [{}])[0].get("text", "")
|
232
|
-
parsed = self.harmony_enc.parse_messages_from_completion_tokens(
|
233
|
-
|
239
|
+
parsed = self.harmony_enc.parse_messages_from_completion_tokens(
|
240
|
+
response_tokens, Role.ASSISTANT
|
241
|
+
)
|
242
|
+
|
234
243
|
if parsed:
|
235
|
-
assistant_msg =
|
244
|
+
assistant_msg = (
|
245
|
+
parsed[-1].content_text()
|
246
|
+
if hasattr(parsed[-1], "content_text")
|
247
|
+
else str(parsed[-1])
|
248
|
+
)
|
236
249
|
else:
|
237
250
|
assistant_msg = response_tokens
|
238
|
-
|
251
|
+
|
239
252
|
return BaseLMResponse(
|
240
253
|
raw_response=assistant_msg,
|
241
254
|
response_id=previous_response_id or str(uuid.uuid4()),
|
242
255
|
api_type="harmony",
|
243
|
-
)
|
256
|
+
)
|
synth_ai/lm/vendors/retries.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
import backoff
|
2
1
|
import os
|
3
2
|
|
3
|
+
import backoff
|
4
|
+
|
4
5
|
# Number of retry attempts that some legacy decorators rely on.
|
5
6
|
BACKOFF_TOLERANCE: int = 20
|
6
7
|
|
@@ -12,3 +13,10 @@ try:
|
|
12
13
|
MAX_BACKOFF: int = max(1, int(os.getenv("SYNTH_AI_MAX_BACKOFF", "120")))
|
13
14
|
except ValueError:
|
14
15
|
MAX_BACKOFF = 120
|
16
|
+
|
17
|
+
# Re-export backoff for convenient import patterns elsewhere
|
18
|
+
__all__ = [
|
19
|
+
"BACKOFF_TOLERANCE",
|
20
|
+
"MAX_BACKOFF",
|
21
|
+
"backoff",
|
22
|
+
]
|
@@ -1,22 +1,22 @@
|
|
1
|
-
import re
|
2
|
-
import os
|
3
|
-
import json
|
4
1
|
import asyncio
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import random
|
5
|
+
import re
|
5
6
|
import time
|
6
|
-
from typing import Any
|
7
|
-
|
7
|
+
from typing import Any
|
8
|
+
|
8
9
|
import httpx
|
10
|
+
import requests
|
9
11
|
from requests.adapters import HTTPAdapter
|
10
12
|
from urllib3.util.retry import Retry
|
11
|
-
import random
|
12
|
-
from urllib.parse import urlparse
|
13
13
|
|
14
|
-
from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
|
15
|
-
from synth_ai.lm.tools.base import BaseTool
|
16
14
|
from synth_ai.lm.caching.initialize import get_cache_handler
|
15
|
+
from synth_ai.lm.tools.base import BaseTool
|
16
|
+
from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
|
17
17
|
|
18
18
|
# Exception types for retry
|
19
|
-
CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY:
|
19
|
+
CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY: tuple[type[Exception], ...] = (
|
20
20
|
requests.RequestException,
|
21
21
|
requests.Timeout,
|
22
22
|
httpx.RequestError,
|
@@ -28,7 +28,7 @@ class CustomEndpointAPI(VendorBase):
|
|
28
28
|
"""Generic vendor client for custom OpenAI-compatible endpoints."""
|
29
29
|
|
30
30
|
used_for_structured_outputs: bool = False
|
31
|
-
exceptions_to_retry:
|
31
|
+
exceptions_to_retry: list = list(CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY)
|
32
32
|
|
33
33
|
def __init__(self, endpoint_url: str):
|
34
34
|
# Validate and sanitize URL
|
@@ -89,7 +89,7 @@ class CustomEndpointAPI(VendorBase):
|
|
89
89
|
|
90
90
|
# Limit URL length
|
91
91
|
if len(url) > 256:
|
92
|
-
raise ValueError(
|
92
|
+
raise ValueError("Endpoint URL too long (max 256 chars)")
|
93
93
|
|
94
94
|
# Basic URL format check
|
95
95
|
if not re.match(r"^[a-zA-Z0-9\-._~:/?#\[\]@!$&\'()*+,;=]+$", url):
|
@@ -123,13 +123,13 @@ class CustomEndpointAPI(VendorBase):
|
|
123
123
|
)
|
124
124
|
return self.async_client
|
125
125
|
|
126
|
-
def _get_timeout(self, lm_config:
|
126
|
+
def _get_timeout(self, lm_config: dict[str, Any]) -> float:
|
127
127
|
"""Get timeout with per-call override support."""
|
128
128
|
return lm_config.get(
|
129
129
|
"timeout", float(os.environ.get("CUSTOM_ENDPOINT_REQUEST_TIMEOUT", "30"))
|
130
130
|
)
|
131
131
|
|
132
|
-
def _get_temperature_override(self) ->
|
132
|
+
def _get_temperature_override(self) -> float | None:
|
133
133
|
"""Get temperature override from environment for this specific endpoint."""
|
134
134
|
# Create a safe env var key from the endpoint URL
|
135
135
|
# e.g., "example.com/api" -> "CUSTOM_ENDPOINT_TEMP_EXAMPLE_COM_API"
|
@@ -140,7 +140,7 @@ class CustomEndpointAPI(VendorBase):
|
|
140
140
|
temp_str = os.environ.get(env_key)
|
141
141
|
return float(temp_str) if temp_str else None
|
142
142
|
|
143
|
-
def _compress_tool_schema(self, schema:
|
143
|
+
def _compress_tool_schema(self, schema: dict[str, Any]) -> dict[str, Any]:
|
144
144
|
"""Compress JSON schema to reduce token usage."""
|
145
145
|
if isinstance(schema, dict):
|
146
146
|
# Remove verbose keys
|
@@ -157,7 +157,7 @@ class CustomEndpointAPI(VendorBase):
|
|
157
157
|
return [self._compress_tool_schema(item) for item in schema]
|
158
158
|
return schema
|
159
159
|
|
160
|
-
def _inject_tools_into_prompt(self, system_message: str, tools:
|
160
|
+
def _inject_tools_into_prompt(self, system_message: str, tools: list[BaseTool]) -> str:
|
161
161
|
"""Inject tool definitions with compressed schemas and clear output format."""
|
162
162
|
if not tools:
|
163
163
|
return system_message
|
@@ -185,8 +185,8 @@ IMPORTANT: To use a tool, respond with JSON wrapped in ```json fences:
|
|
185
185
|
For regular responses, just respond normally without JSON fences."""
|
186
186
|
|
187
187
|
def _extract_tool_calls(
|
188
|
-
self, content: str, tools:
|
189
|
-
) -> tuple[
|
188
|
+
self, content: str, tools: list[BaseTool]
|
189
|
+
) -> tuple[list | None, str]:
|
190
190
|
"""Extract and validate tool calls from response."""
|
191
191
|
# Look for JSON fenced blocks
|
192
192
|
json_pattern = r"```json\s*(\{.*?\})\s*```"
|
@@ -242,11 +242,11 @@ For regular responses, just respond normally without JSON fences."""
|
|
242
242
|
async def _hit_api_async(
|
243
243
|
self,
|
244
244
|
model: str,
|
245
|
-
messages:
|
246
|
-
lm_config:
|
245
|
+
messages: list[dict[str, Any]],
|
246
|
+
lm_config: dict[str, Any],
|
247
247
|
use_ephemeral_cache_only: bool = False,
|
248
248
|
reasoning_effort: str = "low",
|
249
|
-
tools:
|
249
|
+
tools: list[BaseTool] | None = None,
|
250
250
|
) -> BaseLMResponse:
|
251
251
|
"""Async API call with comprehensive error handling and streaming support."""
|
252
252
|
|
@@ -314,7 +314,7 @@ For regular responses, just respond normally without JSON fences."""
|
|
314
314
|
|
315
315
|
return lm_response
|
316
316
|
|
317
|
-
except (httpx.RequestError, httpx.TimeoutException)
|
317
|
+
except (httpx.RequestError, httpx.TimeoutException):
|
318
318
|
if attempt == 2: # Last attempt
|
319
319
|
raise
|
320
320
|
await asyncio.sleep(self._exponential_backoff_with_jitter(attempt))
|
@@ -322,11 +322,11 @@ For regular responses, just respond normally without JSON fences."""
|
|
322
322
|
def _hit_api_sync(
|
323
323
|
self,
|
324
324
|
model: str,
|
325
|
-
messages:
|
326
|
-
lm_config:
|
325
|
+
messages: list[dict[str, Any]],
|
326
|
+
lm_config: dict[str, Any],
|
327
327
|
use_ephemeral_cache_only: bool = False,
|
328
328
|
reasoning_effort: str = "low",
|
329
|
-
tools:
|
329
|
+
tools: list[BaseTool] | None = None,
|
330
330
|
) -> BaseLMResponse:
|
331
331
|
"""Sync version with same logic as async."""
|
332
332
|
|
@@ -393,7 +393,7 @@ For regular responses, just respond normally without JSON fences."""
|
|
393
393
|
|
394
394
|
return lm_response
|
395
395
|
|
396
|
-
except (requests.RequestException, requests.Timeout)
|
396
|
+
except (requests.RequestException, requests.Timeout):
|
397
397
|
if attempt == 2: # Last attempt
|
398
398
|
raise
|
399
399
|
time.sleep(self._exponential_backoff_with_jitter(attempt))
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import os
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any
|
3
3
|
|
4
4
|
from openai import AsyncOpenAI, OpenAI
|
5
5
|
|
@@ -19,18 +19,18 @@ class DeepSeekAPI(OpenAIStandard):
|
|
19
19
|
base_url="https://api.deepseek.com",
|
20
20
|
)
|
21
21
|
|
22
|
-
def _convert_tools_to_openai_format(self, tools:
|
22
|
+
def _convert_tools_to_openai_format(self, tools: list[BaseTool]) -> list[dict]:
|
23
23
|
return [tool.to_openai_tool() for tool in tools]
|
24
24
|
|
25
25
|
async def _private_request_async(
|
26
26
|
self,
|
27
|
-
messages:
|
27
|
+
messages: list[dict],
|
28
28
|
temperature: float = 0,
|
29
29
|
model_name: str = "deepseek-chat",
|
30
30
|
reasoning_effort: str = "high",
|
31
|
-
tools:
|
32
|
-
lm_config:
|
33
|
-
) ->
|
31
|
+
tools: list[BaseTool] | None = None,
|
32
|
+
lm_config: dict[str, Any] | None = None,
|
33
|
+
) -> tuple[str, list[dict] | None]:
|
34
34
|
request_params = {
|
35
35
|
"model": model_name,
|
36
36
|
"messages": messages,
|
@@ -47,13 +47,13 @@ class DeepSeekAPI(OpenAIStandard):
|
|
47
47
|
|
48
48
|
def _private_request_sync(
|
49
49
|
self,
|
50
|
-
messages:
|
50
|
+
messages: list[dict],
|
51
51
|
temperature: float = 0,
|
52
52
|
model_name: str = "deepseek-chat",
|
53
53
|
reasoning_effort: str = "high",
|
54
|
-
tools:
|
55
|
-
lm_config:
|
56
|
-
) ->
|
54
|
+
tools: list[BaseTool] | None = None,
|
55
|
+
lm_config: dict[str, Any] | None = None,
|
56
|
+
) -> tuple[str, list[dict] | None]:
|
57
57
|
request_params = {
|
58
58
|
"model": model_name,
|
59
59
|
"messages": messages,
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import os
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any
|
3
3
|
|
4
4
|
from openai import AsyncOpenAI, OpenAI
|
5
5
|
|
@@ -19,7 +19,7 @@ class GrokAPI(OpenAIStandard):
|
|
19
19
|
def __init__(
|
20
20
|
self,
|
21
21
|
*,
|
22
|
-
api_key:
|
22
|
+
api_key: str | None = None,
|
23
23
|
base_url: str = "https://api.x.ai/v1",
|
24
24
|
) -> None:
|
25
25
|
api_key = api_key or os.getenv("XAI_API_KEY")
|
@@ -35,11 +35,11 @@ class GrokAPI(OpenAIStandard):
|
|
35
35
|
async def _hit_api_async(
|
36
36
|
self,
|
37
37
|
model: str,
|
38
|
-
messages:
|
39
|
-
lm_config:
|
38
|
+
messages: list[dict[str, Any]],
|
39
|
+
lm_config: dict[str, Any],
|
40
40
|
use_ephemeral_cache_only: bool = False,
|
41
41
|
reasoning_effort: str = "high",
|
42
|
-
tools:
|
42
|
+
tools: list[BaseTool] | None = None,
|
43
43
|
):
|
44
44
|
if not model:
|
45
45
|
raise ValueError("Model name is required for Grok API calls")
|
@@ -56,11 +56,11 @@ class GrokAPI(OpenAIStandard):
|
|
56
56
|
def _hit_api_sync(
|
57
57
|
self,
|
58
58
|
model: str,
|
59
|
-
messages:
|
60
|
-
lm_config:
|
59
|
+
messages: list[dict[str, Any]],
|
60
|
+
lm_config: dict[str, Any],
|
61
61
|
use_ephemeral_cache_only: bool = False,
|
62
62
|
reasoning_effort: str = "high",
|
63
|
-
tools:
|
63
|
+
tools: list[BaseTool] | None = None,
|
64
64
|
):
|
65
65
|
if not model:
|
66
66
|
raise ValueError("Model name is required for Grok API calls")
|
@@ -1,9 +1,11 @@
|
|
1
1
|
import os
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any
|
3
|
+
|
3
4
|
from openai import AsyncOpenAI, OpenAI
|
4
|
-
|
5
|
-
from synth_ai.lm.vendors.base import BaseLMResponse
|
5
|
+
|
6
6
|
from synth_ai.lm.tools.base import BaseTool
|
7
|
+
from synth_ai.lm.vendors.base import BaseLMResponse
|
8
|
+
from synth_ai.lm.vendors.openai_standard import OpenAIStandard
|
7
9
|
|
8
10
|
|
9
11
|
class OpenRouterAPI(OpenAIStandard):
|
@@ -44,11 +46,11 @@ class OpenRouterAPI(OpenAIStandard):
|
|
44
46
|
async def _hit_api_async(
|
45
47
|
self,
|
46
48
|
model: str,
|
47
|
-
messages:
|
48
|
-
lm_config:
|
49
|
+
messages: list[dict[str, Any]],
|
50
|
+
lm_config: dict[str, Any],
|
49
51
|
use_ephemeral_cache_only: bool = False,
|
50
52
|
reasoning_effort: str = "high",
|
51
|
-
tools:
|
53
|
+
tools: list[BaseTool] | None = None,
|
52
54
|
) -> BaseLMResponse:
|
53
55
|
# Strip the 'openrouter/' prefix before calling the API
|
54
56
|
model = self._strip_prefix(model)
|
@@ -59,11 +61,11 @@ class OpenRouterAPI(OpenAIStandard):
|
|
59
61
|
def _hit_api_sync(
|
60
62
|
self,
|
61
63
|
model: str,
|
62
|
-
messages:
|
63
|
-
lm_config:
|
64
|
+
messages: list[dict[str, Any]],
|
65
|
+
lm_config: dict[str, Any],
|
64
66
|
use_ephemeral_cache_only: bool = False,
|
65
67
|
reasoning_effort: str = "high",
|
66
|
-
tools:
|
68
|
+
tools: list[BaseTool] | None = None,
|
67
69
|
) -> BaseLMResponse:
|
68
70
|
# Strip the 'openrouter/' prefix before calling the API
|
69
71
|
model = self._strip_prefix(model)
|