synth-ai 0.1.9__py3-none-any.whl → 0.2.1.dev0__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 +28 -2
- synth_ai/core/system.py +4 -0
- synth_ai/environments/__init__.py +35 -0
- synth_ai/environments/environment/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/__init__.py +1 -0
- synth_ai/environments/environment/artifacts/base.py +50 -0
- synth_ai/environments/environment/core.py +22 -0
- synth_ai/environments/environment/db/__init__.py +1 -0
- synth_ai/environments/environment/db/sqlite.py +45 -0
- synth_ai/environments/environment/registry.py +24 -0
- synth_ai/environments/environment/resources/sqlite.py +46 -0
- synth_ai/environments/environment/results.py +1 -0
- synth_ai/environments/environment/rewards/__init__.py +1 -0
- synth_ai/environments/environment/rewards/core.py +28 -0
- synth_ai/environments/environment/shared_engine.py +26 -0
- synth_ai/environments/environment/tools/__init__.py +34 -0
- synth_ai/environments/examples/__init__.py +1 -0
- synth_ai/environments/examples/crafter_classic/__init__.py +8 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_comprehensive_evaluation.py +58 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_browser.py +152 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_evaluation_framework.py +1194 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_quick_evaluation.py +51 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_react_agent.py +872 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1412 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +1110 -0
- synth_ai/environments/examples/crafter_classic/config_logging.py +111 -0
- synth_ai/environments/examples/crafter_classic/engine.py +502 -0
- synth_ai/environments/examples/crafter_classic/engine_deterministic_patch.py +63 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/action_map.py +5 -0
- synth_ai/environments/examples/crafter_classic/engine_helpers/serialization.py +74 -0
- synth_ai/environments/examples/crafter_classic/environment.py +255 -0
- synth_ai/environments/examples/crafter_classic/taskset.py +228 -0
- synth_ai/environments/examples/enron/agent_demos/test_synth_react.py +535 -0
- synth_ai/environments/examples/enron/art_helpers/email_search_tools.py +156 -0
- synth_ai/environments/examples/enron/art_helpers/local_email_db.py +280 -0
- synth_ai/environments/examples/enron/art_helpers/types_enron.py +24 -0
- synth_ai/environments/examples/enron/engine.py +291 -0
- synth_ai/environments/examples/enron/environment.py +165 -0
- synth_ai/environments/examples/enron/taskset.py +112 -0
- synth_ai/environments/examples/enron/units/keyword_stats.py +111 -0
- synth_ai/environments/examples/enron/units/test_email_index.py +8 -0
- synth_ai/environments/examples/minigrid/__init__.py +48 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_evaluation_framework.py +1188 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_quick_evaluation.py +47 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_react_agent.py +562 -0
- synth_ai/environments/examples/minigrid/agent_demos/minigrid_trace_evaluation.py +220 -0
- synth_ai/environments/examples/minigrid/agent_demos/test_minigrid_react_agent.py +393 -0
- synth_ai/environments/examples/minigrid/engine.py +589 -0
- synth_ai/environments/examples/minigrid/environment.py +274 -0
- synth_ai/environments/examples/minigrid/environment_mapping.py +242 -0
- synth_ai/environments/examples/minigrid/puzzle_loader.py +416 -0
- synth_ai/environments/examples/minigrid/taskset.py +583 -0
- synth_ai/environments/examples/minigrid/units/test_action_behavior.py +226 -0
- synth_ai/environments/examples/minigrid/units/test_debug_messages.py +83 -0
- synth_ai/environments/examples/minigrid/units/test_exploration.py +120 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_engine.py +214 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment.py +238 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_environment_mapping.py +301 -0
- synth_ai/environments/examples/minigrid/units/test_minigrid_taskset.py +210 -0
- synth_ai/environments/examples/nethack/__init__.py +7 -0
- synth_ai/environments/examples/nethack/achievements.py +337 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_evaluation_framework.py +981 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_quick_evaluation.py +74 -0
- synth_ai/environments/examples/nethack/agent_demos/nethack_react_agent.py +832 -0
- synth_ai/environments/examples/nethack/agent_demos/test_nethack_react_agent.py +1112 -0
- synth_ai/environments/examples/nethack/engine.py +738 -0
- synth_ai/environments/examples/nethack/environment.py +255 -0
- synth_ai/environments/examples/nethack/helpers/__init__.py +42 -0
- synth_ai/environments/examples/nethack/helpers/action_mapping.py +301 -0
- synth_ai/environments/examples/nethack/helpers/nle_wrapper.py +401 -0
- synth_ai/environments/examples/nethack/helpers/observation_utils.py +433 -0
- synth_ai/environments/examples/nethack/helpers/recording_wrapper.py +201 -0
- synth_ai/environments/examples/nethack/helpers/trajectory_recorder.py +268 -0
- synth_ai/environments/examples/nethack/helpers/visualization/replay_viewer.py +308 -0
- synth_ai/environments/examples/nethack/helpers/visualization/visualizer.py +430 -0
- synth_ai/environments/examples/nethack/taskset.py +323 -0
- synth_ai/environments/examples/nethack/units/test_nethack_engine.py +277 -0
- synth_ai/environments/examples/nethack/units/test_nethack_environment.py +281 -0
- synth_ai/environments/examples/nethack/units/test_nethack_taskset.py +213 -0
- synth_ai/environments/examples/nethack/units/test_recording.py +307 -0
- synth_ai/environments/examples/red/__init__.py +7 -0
- synth_ai/environments/examples/red/agent_demos/__init__.py +1 -0
- synth_ai/environments/examples/red/agent_demos/test_synth_react.py +1471 -0
- synth_ai/environments/examples/red/config_logging.py +110 -0
- synth_ai/environments/examples/red/engine.py +693 -0
- synth_ai/environments/examples/red/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/red/engine_helpers/memory_map.py +28 -0
- synth_ai/environments/examples/red/engine_helpers/reward_components.py +275 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/__init__.py +142 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/adaptive_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/battle_rewards.py +283 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/composite_rewards.py +149 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/economy_rewards.py +137 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/efficiency_rewards.py +56 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/exploration_rewards.py +330 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/novelty_rewards.py +120 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pallet_town_rewards.py +558 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/pokemon_rewards.py +312 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/social_rewards.py +147 -0
- synth_ai/environments/examples/red/engine_helpers/reward_library/story_rewards.py +246 -0
- synth_ai/environments/examples/red/engine_helpers/screen_analysis.py +367 -0
- synth_ai/environments/examples/red/engine_helpers/state_extraction.py +139 -0
- synth_ai/environments/examples/red/environment.py +235 -0
- synth_ai/environments/examples/red/taskset.py +77 -0
- synth_ai/environments/examples/red/test_fixes.py +125 -0
- synth_ai/environments/examples/red/test_fixes_mock.py +148 -0
- synth_ai/environments/examples/red/units/__init__.py +1 -0
- synth_ai/environments/examples/red/units/test_basic_functionality.py +97 -0
- synth_ai/environments/examples/red/units/test_button_press_requirements.py +217 -0
- synth_ai/environments/examples/red/units/test_engine.py +192 -0
- synth_ai/environments/examples/red/units/test_environment.py +455 -0
- synth_ai/environments/examples/red/units/test_exploration_strategy.py +227 -0
- synth_ai/environments/examples/red/units/test_integration.py +217 -0
- synth_ai/environments/examples/red/units/test_memory_extraction.py +111 -0
- synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +1100 -0
- synth_ai/environments/examples/red/units/test_movement_debug.py +255 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_debug.py +163 -0
- synth_ai/environments/examples/red/units/test_pokemon_mcts_verbose.py +117 -0
- synth_ai/environments/examples/red/units/test_red_basic.py +145 -0
- synth_ai/environments/examples/red/units/test_red_comprehensive.py +323 -0
- synth_ai/environments/examples/red/units/test_retry_movement.py +195 -0
- synth_ai/environments/examples/red/units/test_reward_components.py +186 -0
- synth_ai/environments/examples/red/units/test_rom_integration.py +260 -0
- synth_ai/environments/examples/red/units/test_taskset.py +116 -0
- synth_ai/environments/examples/red/units/test_tree.py +448 -0
- synth_ai/environments/examples/sokoban/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/sokoban_full_eval.py +900 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_dspy_react.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_sokoban_react_agent.py +498 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_lats.py +1 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_locally.py +748 -0
- synth_ai/environments/examples/sokoban/agent_demos/test_synth_react_service.py +296 -0
- synth_ai/environments/examples/sokoban/engine.py +675 -0
- synth_ai/environments/examples/sokoban/engine_helpers/__init__.py +1 -0
- synth_ai/environments/examples/sokoban/engine_helpers/room_utils.py +656 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/__init__.py +17 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/__init__.py +3 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/boxoban_env.py +129 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/render_utils.py +370 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/room_utils.py +331 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env.py +305 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_fixed_targets.py +66 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_pull.py +114 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_two_player.py +122 -0
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/sokoban_env_variations.py +394 -0
- synth_ai/environments/examples/sokoban/environment.py +228 -0
- synth_ai/environments/examples/sokoban/generate_verified_puzzles.py +438 -0
- synth_ai/environments/examples/sokoban/puzzle_loader.py +311 -0
- synth_ai/environments/examples/sokoban/taskset.py +425 -0
- synth_ai/environments/examples/sokoban/units/astar_common.py +94 -0
- synth_ai/environments/examples/sokoban/units/test_building_task_set.py +49 -0
- synth_ai/environments/examples/sokoban/units/test_false_positive.py +120 -0
- synth_ai/environments/examples/sokoban/units/test_simple_run_through_environment.py +119 -0
- synth_ai/environments/examples/sokoban/units/test_sokoban_environment.py +98 -0
- synth_ai/environments/examples/sokoban/units/test_tree.py +364 -0
- synth_ai/environments/examples/tictactoe/__init__.py +1 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_synth_react.py +266 -0
- synth_ai/environments/examples/tictactoe/agent_demos/test_tictactoe_react_agent.py +470 -0
- synth_ai/environments/examples/tictactoe/engine.py +368 -0
- synth_ai/environments/examples/tictactoe/environment.py +239 -0
- synth_ai/environments/examples/tictactoe/taskset.py +214 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_engine.py +393 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_environment.py +493 -0
- synth_ai/environments/examples/tictactoe/units/test_tictactoe_taskset.py +191 -0
- synth_ai/environments/examples/verilog/__init__.py +10 -0
- synth_ai/environments/examples/verilog/agent_demos/test_synth_react.py +520 -0
- synth_ai/environments/examples/verilog/engine.py +328 -0
- synth_ai/environments/examples/verilog/environment.py +349 -0
- synth_ai/environments/examples/verilog/taskset.py +418 -0
- synth_ai/environments/examples/verilog/units/test_verilog_engine.py +466 -0
- synth_ai/environments/examples/verilog/units/test_verilog_environment.py +585 -0
- synth_ai/environments/examples/verilog/units/test_verilog_integration.py +383 -0
- synth_ai/environments/examples/verilog/units/test_verilog_taskset.py +457 -0
- synth_ai/environments/reproducibility/core.py +42 -0
- synth_ai/environments/reproducibility/tree.py +364 -0
- synth_ai/environments/service/app.py +78 -0
- synth_ai/environments/service/core_routes.py +775 -0
- synth_ai/environments/service/external_registry.py +57 -0
- synth_ai/environments/service/registry.py +9 -0
- synth_ai/environments/stateful/__init__.py +1 -0
- synth_ai/environments/stateful/core.py +28 -0
- synth_ai/environments/stateful/engine.py +21 -0
- synth_ai/environments/stateful/state.py +7 -0
- synth_ai/environments/tasks/api.py +19 -0
- synth_ai/environments/tasks/core.py +78 -0
- synth_ai/environments/tasks/filters.py +39 -0
- synth_ai/environments/tasks/utils.py +89 -0
- synth_ai/environments/v0_observability/history.py +3 -0
- synth_ai/environments/v0_observability/log.py +2 -0
- synth_ai/lm/caching/constants.py +1 -0
- synth_ai/{zyk/lms → lm}/caching/ephemeral.py +4 -8
- synth_ai/{zyk/lms → lm}/caching/handler.py +15 -15
- synth_ai/{zyk/lms → lm}/caching/initialize.py +2 -4
- synth_ai/{zyk/lms → lm}/caching/persistent.py +4 -10
- synth_ai/{zyk/lms → lm}/config.py +2 -1
- synth_ai/{zyk/lms → lm}/constants.py +2 -2
- synth_ai/{zyk/lms → lm}/core/all.py +10 -10
- synth_ai/{zyk/lms → lm}/core/main.py +57 -33
- synth_ai/{zyk/lms → lm}/core/vendor_clients.py +12 -10
- synth_ai/lm/cost/monitor.py +1 -0
- synth_ai/lm/cost/statefulness.py +1 -0
- synth_ai/lm/provider_support/__init__.py +8 -0
- synth_ai/lm/provider_support/anthropic.py +945 -0
- synth_ai/lm/provider_support/openai.py +1115 -0
- synth_ai/lm/provider_support/suppress_logging.py +31 -0
- synth_ai/{zyk/lms → lm}/structured_outputs/handler.py +58 -80
- synth_ai/{zyk/lms → lm}/structured_outputs/inject.py +6 -20
- synth_ai/{zyk/lms → lm}/structured_outputs/rehabilitate.py +6 -12
- synth_ai/{zyk/lms → lm}/vendors/core/anthropic_api.py +21 -30
- synth_ai/{zyk/lms → lm}/vendors/core/gemini_api.py +37 -32
- synth_ai/{zyk/lms → lm}/vendors/core/mistral_api.py +19 -28
- synth_ai/{zyk/lms → lm}/vendors/core/openai_api.py +26 -36
- synth_ai/{zyk/lms → lm}/vendors/openai_standard.py +29 -33
- synth_ai/{zyk/lms → lm}/vendors/retries.py +1 -1
- synth_ai/lm/vendors/supported/__init__.py +0 -0
- synth_ai/{zyk/lms → lm}/vendors/supported/custom_endpoint.py +131 -118
- synth_ai/{zyk/lms → lm}/vendors/supported/deepseek.py +4 -8
- synth_ai/{zyk/lms → lm}/vendors/supported/grok.py +6 -8
- synth_ai/{zyk/lms → lm}/vendors/supported/groq.py +1 -1
- synth_ai/{zyk/lms → lm}/vendors/supported/ollama.py +2 -2
- synth_ai/{zyk/lms → lm}/vendors/supported/openrouter.py +18 -16
- synth_ai/{zyk/lms → lm}/vendors/supported/together.py +1 -1
- synth_ai/tracing/__init__.py +0 -0
- synth_ai/tracing/abstractions.py +224 -0
- synth_ai/tracing/base_client.py +91 -0
- synth_ai/tracing/client_manager.py +131 -0
- synth_ai/tracing/config.py +140 -0
- synth_ai/tracing/context.py +146 -0
- synth_ai/tracing/decorators.py +679 -0
- synth_ai/tracing/events/__init__.py +0 -0
- synth_ai/tracing/events/manage.py +147 -0
- synth_ai/tracing/events/scope.py +86 -0
- synth_ai/tracing/events/store.py +227 -0
- synth_ai/tracing/immediate_client.py +152 -0
- synth_ai/tracing/local.py +18 -0
- synth_ai/tracing/log_client_base.py +74 -0
- synth_ai/tracing/retry_queue.py +187 -0
- synth_ai/tracing/trackers.py +515 -0
- synth_ai/tracing/upload.py +504 -0
- synth_ai/tracing/utils.py +9 -0
- synth_ai/zyk/__init__.py +28 -2
- synth_ai-0.2.1.dev0.dist-info/METADATA +349 -0
- synth_ai-0.2.1.dev0.dist-info/RECORD +261 -0
- synth_ai/zyk/lms/caching/constants.py +0 -1
- synth_ai/zyk/lms/cost/monitor.py +0 -1
- synth_ai/zyk/lms/cost/statefulness.py +0 -1
- synth_ai-0.1.9.dist-info/METADATA +0 -37
- synth_ai-0.1.9.dist-info/RECORD +0 -50
- /synth_ai/{zyk/lms/__init__.py → environments/reproducibility/helpers.py} +0 -0
- /synth_ai/{zyk/lms/caching → lm}/__init__.py +0 -0
- /synth_ai/{zyk/lms/core → lm/caching}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/caching/dbs.py +0 -0
- /synth_ai/{zyk/lms/cost → lm/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/core/exceptions.py +0 -0
- /synth_ai/{zyk/lms/structured_outputs → lm/cost}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors → lm/structured_outputs}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/tools/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/tools/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/core → lm/vendors}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/vendors/base.py +0 -0
- /synth_ai/{zyk/lms/vendors/local → lm/vendors/core}/__init__.py +0 -0
- /synth_ai/{zyk/lms/vendors/supported → lm/vendors/local}/__init__.py +0 -0
- /synth_ai/{zyk/lms → lm}/vendors/local/ollama.py +0 -0
- {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/WHEEL +0 -0
- {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.1.9.dist-info → synth_ai-0.2.1.dev0.dist-info}/top_level.txt +0 -0
@@ -11,122 +11,142 @@ from urllib3.util.retry import Retry
|
|
11
11
|
import random
|
12
12
|
from urllib.parse import urlparse
|
13
13
|
|
14
|
-
from synth_ai.
|
15
|
-
from synth_ai.
|
16
|
-
from synth_ai.
|
14
|
+
from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
|
15
|
+
from synth_ai.lm.tools.base import BaseTool
|
16
|
+
from synth_ai.lm.caching.initialize import get_cache_handler
|
17
17
|
|
18
18
|
# Exception types for retry
|
19
19
|
CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY: Tuple[Type[Exception], ...] = (
|
20
|
-
requests.RequestException,
|
20
|
+
requests.RequestException,
|
21
21
|
requests.Timeout,
|
22
22
|
httpx.RequestError,
|
23
|
-
httpx.TimeoutException
|
23
|
+
httpx.TimeoutException,
|
24
24
|
)
|
25
25
|
|
26
|
+
|
26
27
|
class CustomEndpointAPI(VendorBase):
|
27
28
|
"""Generic vendor client for custom OpenAI-compatible endpoints."""
|
28
|
-
|
29
|
+
|
29
30
|
used_for_structured_outputs: bool = False
|
30
31
|
exceptions_to_retry: List = list(CUSTOM_ENDPOINT_EXCEPTIONS_TO_RETRY)
|
31
|
-
|
32
|
+
|
32
33
|
def __init__(self, endpoint_url: str):
|
33
34
|
# Validate and sanitize URL
|
34
35
|
self._validate_endpoint_url(endpoint_url)
|
35
36
|
self.endpoint_url = endpoint_url
|
36
|
-
|
37
|
+
|
37
38
|
# Construct full chat completions URL
|
38
|
-
if endpoint_url.endswith(
|
39
|
+
if endpoint_url.endswith("/"):
|
39
40
|
endpoint_url = endpoint_url[:-1]
|
40
41
|
self.chat_completions_url = f"https://{endpoint_url}/chat/completions"
|
41
42
|
self.health_url = f"https://{endpoint_url}/health"
|
42
|
-
|
43
|
+
|
43
44
|
# Setup session with connection pooling and retries
|
44
45
|
self.session = self._create_session()
|
45
46
|
self.async_client = None # Lazy init
|
46
|
-
|
47
|
+
|
47
48
|
# Get auth token from environment (generic support for any auth)
|
48
49
|
self.auth_token = os.environ.get("CUSTOM_ENDPOINT_API_TOKEN")
|
49
|
-
|
50
|
+
|
50
51
|
def _validate_endpoint_url(self, url: str) -> None:
|
51
52
|
"""Validate endpoint URL format and prevent SSRF."""
|
52
53
|
# Block dangerous URL patterns
|
53
54
|
dangerous_patterns = [
|
54
|
-
"file://",
|
55
|
-
"
|
56
|
-
"
|
57
|
-
"
|
58
|
-
"
|
55
|
+
"file://",
|
56
|
+
"ftp://",
|
57
|
+
"gopher://",
|
58
|
+
"localhost",
|
59
|
+
"127.",
|
60
|
+
"0.0.0.0",
|
61
|
+
"10.",
|
62
|
+
"192.168.",
|
63
|
+
"172.16.",
|
64
|
+
"172.17.",
|
65
|
+
"172.18.",
|
66
|
+
"172.19.",
|
67
|
+
"172.20.",
|
68
|
+
"172.21.",
|
69
|
+
"172.22.",
|
70
|
+
"172.23.",
|
71
|
+
"172.24.",
|
72
|
+
"172.25.",
|
73
|
+
"172.26.",
|
74
|
+
"172.27.",
|
75
|
+
"172.28.",
|
76
|
+
"172.29.",
|
77
|
+
"172.30.",
|
78
|
+
"172.31.",
|
59
79
|
"169.254.", # link-local
|
60
|
-
"::1",
|
80
|
+
"::1",
|
81
|
+
"fc00:",
|
82
|
+
"fd00:",
|
83
|
+
"fe80:", # IPv6 private
|
61
84
|
]
|
62
|
-
|
85
|
+
|
63
86
|
for pattern in dangerous_patterns:
|
64
87
|
if pattern in url.lower():
|
65
88
|
raise ValueError(f"Blocked URL pattern for security: {pattern}")
|
66
|
-
|
89
|
+
|
67
90
|
# Limit URL length
|
68
91
|
if len(url) > 256:
|
69
92
|
raise ValueError(f"Endpoint URL too long (max 256 chars)")
|
70
|
-
|
93
|
+
|
71
94
|
# Basic URL format check
|
72
|
-
if not re.match(r
|
95
|
+
if not re.match(r"^[a-zA-Z0-9\-._~:/?#\[\]@!$&\'()*+,;=]+$", url):
|
73
96
|
raise ValueError(f"Invalid URL format: {url}")
|
74
|
-
|
97
|
+
|
75
98
|
def _create_session(self) -> requests.Session:
|
76
99
|
"""Create session with retry strategy and connection pooling."""
|
77
100
|
session = requests.Session()
|
78
|
-
|
101
|
+
|
79
102
|
# Exponential backoff with jitter
|
80
103
|
retry_strategy = Retry(
|
81
104
|
total=3,
|
82
105
|
backoff_factor=1,
|
83
106
|
status_forcelist=[429, 500, 502, 503, 504],
|
84
|
-
allowed_methods=["POST", "GET"]
|
107
|
+
allowed_methods=["POST", "GET"],
|
85
108
|
)
|
86
|
-
|
87
|
-
adapter = HTTPAdapter(
|
88
|
-
|
89
|
-
pool_connections=10,
|
90
|
-
pool_maxsize=20
|
91
|
-
)
|
92
|
-
|
109
|
+
|
110
|
+
adapter = HTTPAdapter(max_retries=retry_strategy, pool_connections=10, pool_maxsize=20)
|
111
|
+
|
93
112
|
session.mount("http://", adapter)
|
94
113
|
session.mount("https://", adapter)
|
95
|
-
|
114
|
+
|
96
115
|
return session
|
97
|
-
|
116
|
+
|
98
117
|
async def _get_async_client(self) -> httpx.AsyncClient:
|
99
118
|
"""Lazy init async client with shared retry logic."""
|
100
119
|
if self.async_client is None:
|
101
120
|
self.async_client = httpx.AsyncClient(
|
102
121
|
timeout=httpx.Timeout(30.0),
|
103
|
-
limits=httpx.Limits(max_connections=10, max_keepalive_connections=5)
|
122
|
+
limits=httpx.Limits(max_connections=10, max_keepalive_connections=5),
|
104
123
|
)
|
105
124
|
return self.async_client
|
106
|
-
|
125
|
+
|
107
126
|
def _get_timeout(self, lm_config: Dict[str, Any]) -> float:
|
108
127
|
"""Get timeout with per-call override support."""
|
109
|
-
return lm_config.get(
|
110
|
-
|
111
|
-
|
128
|
+
return lm_config.get(
|
129
|
+
"timeout", float(os.environ.get("CUSTOM_ENDPOINT_REQUEST_TIMEOUT", "30"))
|
130
|
+
)
|
131
|
+
|
112
132
|
def _get_temperature_override(self) -> Optional[float]:
|
113
133
|
"""Get temperature override from environment for this specific endpoint."""
|
114
134
|
# Create a safe env var key from the endpoint URL
|
115
135
|
# e.g., "example.com/api" -> "CUSTOM_ENDPOINT_TEMP_EXAMPLE_COM_API"
|
116
|
-
safe_key = re.sub(r
|
136
|
+
safe_key = re.sub(r"[^A-Za-z0-9]", "_", self.endpoint_url).upper()
|
117
137
|
safe_key = safe_key[:64] # Limit length
|
118
|
-
|
138
|
+
|
119
139
|
env_key = f"CUSTOM_ENDPOINT_TEMP_{safe_key}"
|
120
140
|
temp_str = os.environ.get(env_key)
|
121
141
|
return float(temp_str) if temp_str else None
|
122
|
-
|
142
|
+
|
123
143
|
def _compress_tool_schema(self, schema: Dict[str, Any]) -> Dict[str, Any]:
|
124
144
|
"""Compress JSON schema to reduce token usage."""
|
125
145
|
if isinstance(schema, dict):
|
126
146
|
# Remove verbose keys
|
127
147
|
compressed = {
|
128
|
-
k: self._compress_tool_schema(v)
|
129
|
-
for k, v in schema.items()
|
148
|
+
k: self._compress_tool_schema(v)
|
149
|
+
for k, v in schema.items()
|
130
150
|
if k not in ["title", "$ref", "$schema"]
|
131
151
|
}
|
132
152
|
# Shorten descriptions
|
@@ -136,22 +156,22 @@ class CustomEndpointAPI(VendorBase):
|
|
136
156
|
elif isinstance(schema, list):
|
137
157
|
return [self._compress_tool_schema(item) for item in schema]
|
138
158
|
return schema
|
139
|
-
|
159
|
+
|
140
160
|
def _inject_tools_into_prompt(self, system_message: str, tools: List[BaseTool]) -> str:
|
141
161
|
"""Inject tool definitions with compressed schemas and clear output format."""
|
142
162
|
if not tools:
|
143
163
|
return system_message
|
144
|
-
|
164
|
+
|
145
165
|
tool_descriptions = []
|
146
166
|
for tool in tools:
|
147
167
|
schema = tool.arguments.model_json_schema()
|
148
168
|
compressed_schema = self._compress_tool_schema(schema)
|
149
|
-
|
169
|
+
|
150
170
|
tool_desc = f"Tool: {tool.name}\nDesc: {tool.description}\nParams: {json.dumps(compressed_schema, separators=(',', ':'))}"
|
151
171
|
tool_descriptions.append(tool_desc)
|
152
|
-
|
172
|
+
|
153
173
|
tools_text = "\n".join(tool_descriptions)
|
154
|
-
|
174
|
+
|
155
175
|
return f"""{system_message}
|
156
176
|
|
157
177
|
Available tools:
|
@@ -164,50 +184,53 @@ IMPORTANT: To use a tool, respond with JSON wrapped in ```json fences:
|
|
164
184
|
|
165
185
|
For regular responses, just respond normally without JSON fences."""
|
166
186
|
|
167
|
-
def _extract_tool_calls(
|
187
|
+
def _extract_tool_calls(
|
188
|
+
self, content: str, tools: List[BaseTool]
|
189
|
+
) -> tuple[Optional[List], str]:
|
168
190
|
"""Extract and validate tool calls from response."""
|
169
191
|
# Look for JSON fenced blocks
|
170
|
-
json_pattern = r
|
192
|
+
json_pattern = r"```json\s*(\{.*?\})\s*```"
|
171
193
|
matches = re.findall(json_pattern, content, re.DOTALL)
|
172
|
-
|
194
|
+
|
173
195
|
if not matches:
|
174
196
|
return None, content
|
175
|
-
|
197
|
+
|
176
198
|
tool_calls = []
|
177
199
|
cleaned_content = content
|
178
|
-
|
200
|
+
|
179
201
|
for match in matches:
|
180
202
|
try:
|
181
203
|
tool_data = json.loads(match)
|
182
204
|
if "tool_call" in tool_data:
|
183
205
|
call_data = tool_data["tool_call"]
|
184
206
|
tool_name = call_data.get("name")
|
185
|
-
|
207
|
+
|
186
208
|
# Validate against available tools
|
187
209
|
matching_tool = next((t for t in tools if t.name == tool_name), None)
|
188
210
|
if matching_tool:
|
189
211
|
# Validate arguments with pydantic
|
190
212
|
validated_args = matching_tool.arguments(**call_data.get("arguments", {}))
|
191
|
-
tool_calls.append(
|
192
|
-
"name": tool_name,
|
193
|
-
|
194
|
-
|
195
|
-
|
213
|
+
tool_calls.append(
|
214
|
+
{"name": tool_name, "arguments": validated_args.model_dump()}
|
215
|
+
)
|
216
|
+
|
196
217
|
# Remove tool call from content
|
197
|
-
cleaned_content = cleaned_content.replace(
|
198
|
-
|
218
|
+
cleaned_content = cleaned_content.replace(
|
219
|
+
f"```json\n{match}\n```", ""
|
220
|
+
).strip()
|
221
|
+
|
199
222
|
except (json.JSONDecodeError, Exception):
|
200
223
|
# Fall back to treating as normal text if validation fails
|
201
224
|
continue
|
202
|
-
|
225
|
+
|
203
226
|
return tool_calls if tool_calls else None, cleaned_content
|
204
|
-
|
227
|
+
|
205
228
|
def _exponential_backoff_with_jitter(self, attempt: int) -> float:
|
206
229
|
"""Calculate backoff time with jitter to prevent thundering herd."""
|
207
|
-
base_delay = min(2
|
230
|
+
base_delay = min(2**attempt, 32) # Cap at 32 seconds
|
208
231
|
jitter = random.uniform(0, 1)
|
209
232
|
return base_delay + jitter
|
210
|
-
|
233
|
+
|
211
234
|
def _handle_rate_limit(self, response: requests.Response) -> None:
|
212
235
|
"""Extract and propagate rate limit information."""
|
213
236
|
if response.status_code == 429:
|
@@ -215,7 +238,7 @@ For regular responses, just respond normally without JSON fences."""
|
|
215
238
|
if retry_after:
|
216
239
|
# Bubble up to synth-ai scheduler
|
217
240
|
raise requests.exceptions.RetryError(f"Rate limited. Retry after {retry_after}s")
|
218
|
-
|
241
|
+
|
219
242
|
async def _hit_api_async(
|
220
243
|
self,
|
221
244
|
model: str,
|
@@ -226,7 +249,7 @@ For regular responses, just respond normally without JSON fences."""
|
|
226
249
|
tools: Optional[List[BaseTool]] = None,
|
227
250
|
) -> BaseLMResponse:
|
228
251
|
"""Async API call with comprehensive error handling and streaming support."""
|
229
|
-
|
252
|
+
|
230
253
|
# Cache integration - check first
|
231
254
|
used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
|
232
255
|
cache_result = used_cache_handler.hit_managed_cache(
|
@@ -234,7 +257,7 @@ For regular responses, just respond normally without JSON fences."""
|
|
234
257
|
)
|
235
258
|
if cache_result:
|
236
259
|
return cache_result
|
237
|
-
|
260
|
+
|
238
261
|
# Apply tool injection
|
239
262
|
if tools and messages:
|
240
263
|
messages = messages.copy()
|
@@ -242,65 +265,60 @@ For regular responses, just respond normally without JSON fences."""
|
|
242
265
|
messages[0]["content"] = self._inject_tools_into_prompt(
|
243
266
|
messages[0]["content"], tools
|
244
267
|
)
|
245
|
-
|
268
|
+
|
246
269
|
# Prepare request
|
247
270
|
headers = {"Content-Type": "application/json"}
|
248
271
|
if self.auth_token:
|
249
272
|
headers["Authorization"] = f"Bearer {self.auth_token}"
|
250
|
-
|
273
|
+
|
251
274
|
# Apply temperature override
|
252
275
|
temp_override = self._get_temperature_override()
|
253
276
|
request_temp = temp_override if temp_override else lm_config.get("temperature", 0.7)
|
254
|
-
|
277
|
+
|
255
278
|
payload = {
|
256
279
|
"model": model, # Pass through the model name
|
257
280
|
"messages": messages,
|
258
281
|
"temperature": request_temp,
|
259
|
-
"stream": lm_config.get("stream", False)
|
282
|
+
"stream": lm_config.get("stream", False),
|
260
283
|
}
|
261
|
-
|
284
|
+
|
262
285
|
timeout = self._get_timeout(lm_config)
|
263
286
|
client = await self._get_async_client()
|
264
|
-
|
287
|
+
|
265
288
|
# Make request with retry logic
|
266
289
|
for attempt in range(3):
|
267
290
|
try:
|
268
291
|
response = await client.post(
|
269
|
-
self.chat_completions_url,
|
270
|
-
json=payload,
|
271
|
-
headers=headers,
|
272
|
-
timeout=timeout
|
292
|
+
self.chat_completions_url, json=payload, headers=headers, timeout=timeout
|
273
293
|
)
|
274
|
-
|
294
|
+
|
275
295
|
if response.status_code == 429:
|
276
296
|
self._handle_rate_limit(response)
|
277
|
-
|
297
|
+
|
278
298
|
response.raise_for_status()
|
279
|
-
|
299
|
+
|
280
300
|
response_data = response.json()
|
281
301
|
content = response_data["choices"][0]["message"]["content"]
|
282
|
-
|
302
|
+
|
283
303
|
# Extract tool calls
|
284
304
|
tool_calls, clean_content = self._extract_tool_calls(content, tools or [])
|
285
|
-
|
305
|
+
|
286
306
|
lm_response = BaseLMResponse(
|
287
|
-
raw_response=clean_content,
|
288
|
-
structured_output=None,
|
289
|
-
tool_calls=tool_calls
|
307
|
+
raw_response=clean_content, structured_output=None, tool_calls=tool_calls
|
290
308
|
)
|
291
|
-
|
309
|
+
|
292
310
|
# Add to cache
|
293
311
|
used_cache_handler.add_to_managed_cache(
|
294
312
|
model, messages, lm_config=lm_config, output=lm_response, tools=tools
|
295
313
|
)
|
296
|
-
|
314
|
+
|
297
315
|
return lm_response
|
298
|
-
|
316
|
+
|
299
317
|
except (httpx.RequestError, httpx.TimeoutException) as e:
|
300
318
|
if attempt == 2: # Last attempt
|
301
319
|
raise
|
302
320
|
await asyncio.sleep(self._exponential_backoff_with_jitter(attempt))
|
303
|
-
|
321
|
+
|
304
322
|
def _hit_api_sync(
|
305
323
|
self,
|
306
324
|
model: str,
|
@@ -311,7 +329,7 @@ For regular responses, just respond normally without JSON fences."""
|
|
311
329
|
tools: Optional[List[BaseTool]] = None,
|
312
330
|
) -> BaseLMResponse:
|
313
331
|
"""Sync version with same logic as async."""
|
314
|
-
|
332
|
+
|
315
333
|
# Cache integration - check first
|
316
334
|
used_cache_handler = get_cache_handler(use_ephemeral_cache_only)
|
317
335
|
cache_result = used_cache_handler.hit_managed_cache(
|
@@ -319,7 +337,7 @@ For regular responses, just respond normally without JSON fences."""
|
|
319
337
|
)
|
320
338
|
if cache_result:
|
321
339
|
return cache_result
|
322
|
-
|
340
|
+
|
323
341
|
# Apply tool injection
|
324
342
|
if tools and messages:
|
325
343
|
messages = messages.copy()
|
@@ -327,68 +345,63 @@ For regular responses, just respond normally without JSON fences."""
|
|
327
345
|
messages[0]["content"] = self._inject_tools_into_prompt(
|
328
346
|
messages[0]["content"], tools
|
329
347
|
)
|
330
|
-
|
348
|
+
|
331
349
|
# Prepare request
|
332
350
|
headers = {"Content-Type": "application/json"}
|
333
351
|
if self.auth_token:
|
334
352
|
headers["Authorization"] = f"Bearer {self.auth_token}"
|
335
|
-
|
353
|
+
|
336
354
|
# Apply temperature override
|
337
355
|
temp_override = self._get_temperature_override()
|
338
356
|
request_temp = temp_override if temp_override else lm_config.get("temperature", 0.7)
|
339
|
-
|
357
|
+
|
340
358
|
payload = {
|
341
359
|
"model": model, # Pass through the model name
|
342
360
|
"messages": messages,
|
343
361
|
"temperature": request_temp,
|
344
|
-
"stream": lm_config.get("stream", False)
|
362
|
+
"stream": lm_config.get("stream", False),
|
345
363
|
}
|
346
|
-
|
364
|
+
|
347
365
|
timeout = self._get_timeout(lm_config)
|
348
|
-
|
366
|
+
|
349
367
|
# Make request with retry logic
|
350
368
|
for attempt in range(3):
|
351
369
|
try:
|
352
370
|
response = self.session.post(
|
353
|
-
self.chat_completions_url,
|
354
|
-
json=payload,
|
355
|
-
headers=headers,
|
356
|
-
timeout=timeout
|
371
|
+
self.chat_completions_url, json=payload, headers=headers, timeout=timeout
|
357
372
|
)
|
358
|
-
|
373
|
+
|
359
374
|
if response.status_code == 429:
|
360
375
|
self._handle_rate_limit(response)
|
361
|
-
|
376
|
+
|
362
377
|
response.raise_for_status()
|
363
|
-
|
378
|
+
|
364
379
|
response_data = response.json()
|
365
380
|
content = response_data["choices"][0]["message"]["content"]
|
366
|
-
|
381
|
+
|
367
382
|
# Extract tool calls
|
368
383
|
tool_calls, clean_content = self._extract_tool_calls(content, tools or [])
|
369
|
-
|
384
|
+
|
370
385
|
lm_response = BaseLMResponse(
|
371
|
-
raw_response=clean_content,
|
372
|
-
structured_output=None,
|
373
|
-
tool_calls=tool_calls
|
386
|
+
raw_response=clean_content, structured_output=None, tool_calls=tool_calls
|
374
387
|
)
|
375
|
-
|
388
|
+
|
376
389
|
# Add to cache
|
377
390
|
used_cache_handler.add_to_managed_cache(
|
378
391
|
model, messages, lm_config=lm_config, output=lm_response, tools=tools
|
379
392
|
)
|
380
|
-
|
393
|
+
|
381
394
|
return lm_response
|
382
|
-
|
395
|
+
|
383
396
|
except (requests.RequestException, requests.Timeout) as e:
|
384
397
|
if attempt == 2: # Last attempt
|
385
398
|
raise
|
386
399
|
time.sleep(self._exponential_backoff_with_jitter(attempt))
|
387
|
-
|
400
|
+
|
388
401
|
def __del__(self):
|
389
402
|
"""Cleanup resources."""
|
390
|
-
if hasattr(self,
|
403
|
+
if hasattr(self, "session"):
|
391
404
|
self.session.close()
|
392
|
-
if hasattr(self,
|
405
|
+
if hasattr(self, "async_client") and self.async_client:
|
393
406
|
# Schedule cleanup for async client
|
394
|
-
pass
|
407
|
+
pass
|
@@ -3,8 +3,8 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
3
3
|
|
4
4
|
from openai import AsyncOpenAI, OpenAI
|
5
5
|
|
6
|
-
from synth_ai.
|
7
|
-
from synth_ai.
|
6
|
+
from synth_ai.lm.tools.base import BaseTool
|
7
|
+
from synth_ai.lm.vendors.openai_standard import OpenAIStandard
|
8
8
|
|
9
9
|
|
10
10
|
class DeepSeekAPI(OpenAIStandard):
|
@@ -43,9 +43,7 @@ class DeepSeekAPI(OpenAIStandard):
|
|
43
43
|
response = await self.async_client.chat.completions.create(**request_params)
|
44
44
|
message = response.choices[0].message
|
45
45
|
|
46
|
-
return message.content, message.tool_calls if hasattr(
|
47
|
-
message, "tool_calls"
|
48
|
-
) else None
|
46
|
+
return message.content, message.tool_calls if hasattr(message, "tool_calls") else None
|
49
47
|
|
50
48
|
def _private_request_sync(
|
51
49
|
self,
|
@@ -68,6 +66,4 @@ class DeepSeekAPI(OpenAIStandard):
|
|
68
66
|
response = self.sync_client.chat.completions.create(**request_params)
|
69
67
|
message = response.choices[0].message
|
70
68
|
|
71
|
-
return message.content, message.tool_calls if hasattr(
|
72
|
-
message, "tool_calls"
|
73
|
-
) else None
|
69
|
+
return message.content, message.tool_calls if hasattr(message, "tool_calls") else None
|
@@ -3,8 +3,8 @@ from typing import Any, Dict, List, Optional
|
|
3
3
|
|
4
4
|
from openai import AsyncOpenAI, OpenAI
|
5
5
|
|
6
|
-
from synth_ai.
|
7
|
-
from synth_ai.
|
6
|
+
from synth_ai.lm.tools.base import BaseTool
|
7
|
+
from synth_ai.lm.vendors.openai_standard import OpenAIStandard
|
8
8
|
|
9
9
|
|
10
10
|
class GrokAPI(OpenAIStandard):
|
@@ -24,9 +24,7 @@ class GrokAPI(OpenAIStandard):
|
|
24
24
|
) -> None:
|
25
25
|
api_key = api_key or os.getenv("XAI_API_KEY")
|
26
26
|
if not api_key:
|
27
|
-
raise ValueError(
|
28
|
-
"Set the XAI_API_KEY environment variable or pass api_key explicitly."
|
29
|
-
)
|
27
|
+
raise ValueError("Set the XAI_API_KEY environment variable or pass api_key explicitly.")
|
30
28
|
|
31
29
|
super().__init__(
|
32
30
|
sync_client=OpenAI(api_key=api_key, base_url=base_url),
|
@@ -45,7 +43,7 @@ class GrokAPI(OpenAIStandard):
|
|
45
43
|
):
|
46
44
|
if not model:
|
47
45
|
raise ValueError("Model name is required for Grok API calls")
|
48
|
-
|
46
|
+
|
49
47
|
return await super()._hit_api_async(
|
50
48
|
model,
|
51
49
|
messages,
|
@@ -66,7 +64,7 @@ class GrokAPI(OpenAIStandard):
|
|
66
64
|
):
|
67
65
|
if not model:
|
68
66
|
raise ValueError("Model name is required for Grok API calls")
|
69
|
-
|
67
|
+
|
70
68
|
return super()._hit_api_sync(
|
71
69
|
model,
|
72
70
|
messages,
|
@@ -74,4 +72,4 @@ class GrokAPI(OpenAIStandard):
|
|
74
72
|
use_ephemeral_cache_only=use_ephemeral_cache_only,
|
75
73
|
reasoning_effort=reasoning_effort,
|
76
74
|
tools=tools,
|
77
|
-
)
|
75
|
+
)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from openai import OpenAI, AsyncOpenAI
|
2
|
-
from synth_ai.
|
2
|
+
from synth_ai.lm.vendors.openai_standard import OpenAIStandard
|
3
3
|
|
4
4
|
|
5
5
|
class OllamaAPI(OpenAIStandard):
|
@@ -11,4 +11,4 @@ class OllamaAPI(OpenAIStandard):
|
|
11
11
|
self.async_client = AsyncOpenAI(
|
12
12
|
base_url="http://localhost:11434/v1",
|
13
13
|
api_key="ollama", # required, but unused
|
14
|
-
)
|
14
|
+
)
|
@@ -1,44 +1,46 @@
|
|
1
1
|
import os
|
2
2
|
from typing import Any, Dict, List, Optional
|
3
3
|
from openai import AsyncOpenAI, OpenAI
|
4
|
-
from synth_ai.
|
5
|
-
from synth_ai.
|
6
|
-
from synth_ai.
|
4
|
+
from synth_ai.lm.vendors.openai_standard import OpenAIStandard
|
5
|
+
from synth_ai.lm.vendors.base import BaseLMResponse
|
6
|
+
from synth_ai.lm.tools.base import BaseTool
|
7
7
|
|
8
8
|
|
9
9
|
class OpenRouterAPI(OpenAIStandard):
|
10
10
|
"""OpenRouter API client for accessing various models through OpenRouter's unified API."""
|
11
|
-
|
11
|
+
|
12
12
|
def __init__(self):
|
13
13
|
api_key = os.getenv("OPENROUTER_API_KEY")
|
14
14
|
if not api_key:
|
15
15
|
raise ValueError("OPENROUTER_API_KEY environment variable is not set")
|
16
|
-
|
16
|
+
|
17
17
|
# OpenRouter requires specific headers
|
18
18
|
default_headers = {
|
19
|
-
"HTTP-Referer": os.getenv(
|
20
|
-
|
19
|
+
"HTTP-Referer": os.getenv(
|
20
|
+
"OPENROUTER_APP_URL", "https://github.com/synth-laboratories/synth-ai"
|
21
|
+
),
|
22
|
+
"X-Title": os.getenv("OPENROUTER_APP_TITLE", "synth-ai"),
|
21
23
|
}
|
22
|
-
|
24
|
+
|
23
25
|
super().__init__(
|
24
26
|
sync_client=OpenAI(
|
25
27
|
api_key=api_key,
|
26
28
|
base_url="https://openrouter.ai/api/v1",
|
27
|
-
default_headers=default_headers
|
29
|
+
default_headers=default_headers,
|
28
30
|
),
|
29
31
|
async_client=AsyncOpenAI(
|
30
32
|
api_key=api_key,
|
31
33
|
base_url="https://openrouter.ai/api/v1",
|
32
|
-
default_headers=default_headers
|
33
|
-
)
|
34
|
+
default_headers=default_headers,
|
35
|
+
),
|
34
36
|
)
|
35
|
-
|
37
|
+
|
36
38
|
def _strip_prefix(self, model: str) -> str:
|
37
39
|
"""Remove the 'openrouter/' prefix from model names."""
|
38
40
|
if model.startswith("openrouter/"):
|
39
|
-
return model[len("openrouter/"):]
|
41
|
+
return model[len("openrouter/") :]
|
40
42
|
return model
|
41
|
-
|
43
|
+
|
42
44
|
async def _hit_api_async(
|
43
45
|
self,
|
44
46
|
model: str,
|
@@ -53,7 +55,7 @@ class OpenRouterAPI(OpenAIStandard):
|
|
53
55
|
return await super()._hit_api_async(
|
54
56
|
model, messages, lm_config, use_ephemeral_cache_only, reasoning_effort, tools
|
55
57
|
)
|
56
|
-
|
58
|
+
|
57
59
|
def _hit_api_sync(
|
58
60
|
self,
|
59
61
|
model: str,
|
@@ -67,4 +69,4 @@ class OpenRouterAPI(OpenAIStandard):
|
|
67
69
|
model = self._strip_prefix(model)
|
68
70
|
return super()._hit_api_sync(
|
69
71
|
model, messages, lm_config, use_ephemeral_cache_only, reasoning_effort, tools
|
70
|
-
)
|
72
|
+
)
|