synth-ai 0.2.2.dev0__py3-none-any.whl → 0.2.4.dev2__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/cli/__init__.py +66 -0
- synth_ai/cli/balance.py +205 -0
- synth_ai/cli/calc.py +70 -0
- synth_ai/cli/demo.py +74 -0
- synth_ai/{cli.py → cli/legacy_root_backup.py} +60 -15
- synth_ai/cli/man.py +103 -0
- synth_ai/cli/recent.py +126 -0
- synth_ai/cli/root.py +184 -0
- synth_ai/cli/status.py +126 -0
- synth_ai/cli/traces.py +136 -0
- synth_ai/cli/watch.py +508 -0
- synth_ai/config/base_url.py +53 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/analyze_semantic_words_markdown.py +252 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_duckdb_v2_backup.py +413 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/filter_traces_sft_turso.py +760 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/kick_off_ft_synth.py +34 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/test_crafter_react_agent_lm_synth.py +1740 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_modal_ft/test_crafter_react_agent_lm_synth_v2_backup.py +1318 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_duckdb_v2_backup.py +386 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/filter_traces_sft_turso.py +580 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/run_rollouts_for_models_and_compare_v2_backup.py +1352 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/run_rollouts_for_models_and_compare_v3.py +4 -4
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_openai_ft/test_crafter_react_agent_openai_v2_backup.py +2551 -0
- synth_ai/environments/examples/crafter_classic/agent_demos/crafter_trace_evaluation.py +1 -1
- synth_ai/environments/examples/crafter_classic/agent_demos/example_v3_usage.py +1 -1
- synth_ai/environments/examples/crafter_classic/agent_demos/old/traces/session_crafter_episode_16_15227b68-2906-416f-acc4-d6a9b4fa5828_20250725_001154.json +1363 -1
- synth_ai/environments/examples/crafter_classic/agent_demos/test_crafter_react_agent.py +3 -3
- synth_ai/environments/examples/crafter_classic/environment.py +1 -1
- synth_ai/environments/examples/crafter_custom/environment.py +1 -1
- synth_ai/environments/examples/enron/dataset/corbt___enron_emails_sample_questions/default/0.0.0/293c9fe8170037e01cc9cf5834e0cd5ef6f1a6bb/dataset_info.json +1 -0
- synth_ai/environments/examples/nethack/helpers/achievements.json +64 -0
- synth_ai/environments/examples/red/units/test_exploration_strategy.py +1 -1
- synth_ai/environments/examples/red/units/test_menu_bug_reproduction.py +5 -5
- synth_ai/environments/examples/red/units/test_movement_debug.py +2 -2
- synth_ai/environments/examples/red/units/test_retry_movement.py +1 -1
- synth_ai/environments/examples/sokoban/engine_helpers/vendored/envs/available_envs.json +122 -0
- synth_ai/environments/examples/sokoban/verified_puzzles.json +54987 -0
- synth_ai/environments/service/core_routes.py +1 -1
- synth_ai/experimental/synth_oss.py +446 -0
- synth_ai/learning/core.py +21 -0
- synth_ai/learning/gateway.py +4 -0
- synth_ai/learning/prompts/gepa.py +0 -0
- synth_ai/learning/prompts/mipro.py +8 -0
- synth_ai/lm/__init__.py +3 -0
- synth_ai/lm/core/main.py +4 -0
- synth_ai/lm/core/main_v3.py +238 -122
- synth_ai/lm/core/vendor_clients.py +4 -0
- synth_ai/lm/provider_support/openai.py +11 -2
- synth_ai/lm/vendors/base.py +7 -0
- synth_ai/lm/vendors/openai_standard.py +339 -4
- synth_ai/lm/vendors/openai_standard_responses.py +243 -0
- synth_ai/lm/vendors/synth_client.py +155 -5
- synth_ai/lm/warmup.py +54 -17
- synth_ai/tracing/__init__.py +18 -0
- synth_ai/tracing_v1/__init__.py +29 -14
- synth_ai/tracing_v3/__init__.py +2 -2
- synth_ai/tracing_v3/abstractions.py +62 -17
- synth_ai/tracing_v3/config.py +13 -7
- synth_ai/tracing_v3/db_config.py +6 -6
- synth_ai/tracing_v3/hooks.py +1 -1
- synth_ai/tracing_v3/llm_call_record_helpers.py +350 -0
- synth_ai/tracing_v3/lm_call_record_abstractions.py +257 -0
- synth_ai/tracing_v3/session_tracer.py +5 -5
- synth_ai/tracing_v3/tests/test_concurrent_operations.py +1 -1
- synth_ai/tracing_v3/tests/test_llm_call_records.py +672 -0
- synth_ai/tracing_v3/tests/test_session_tracer.py +43 -9
- synth_ai/tracing_v3/tests/test_turso_manager.py +1 -1
- synth_ai/tracing_v3/turso/manager.py +18 -11
- synth_ai/tracing_v3/turso/models.py +1 -0
- synth_ai/tui/__main__.py +13 -0
- synth_ai/tui/dashboard.py +329 -0
- synth_ai/v0/tracing/__init__.py +0 -0
- synth_ai/{tracing → v0/tracing}/base_client.py +3 -3
- synth_ai/{tracing → v0/tracing}/client_manager.py +1 -1
- synth_ai/{tracing → v0/tracing}/context.py +1 -1
- synth_ai/{tracing → v0/tracing}/decorators.py +11 -11
- synth_ai/v0/tracing/events/__init__.py +0 -0
- synth_ai/{tracing → v0/tracing}/events/manage.py +4 -4
- synth_ai/{tracing → v0/tracing}/events/scope.py +6 -6
- synth_ai/{tracing → v0/tracing}/events/store.py +3 -3
- synth_ai/{tracing → v0/tracing}/immediate_client.py +6 -6
- synth_ai/{tracing → v0/tracing}/log_client_base.py +2 -2
- synth_ai/{tracing → v0/tracing}/retry_queue.py +3 -3
- synth_ai/{tracing → v0/tracing}/trackers.py +2 -2
- synth_ai/{tracing → v0/tracing}/upload.py +4 -4
- synth_ai/v0/tracing_v1/__init__.py +16 -0
- synth_ai/{tracing_v1 → v0/tracing_v1}/base_client.py +3 -3
- synth_ai/{tracing_v1 → v0/tracing_v1}/client_manager.py +1 -1
- synth_ai/{tracing_v1 → v0/tracing_v1}/context.py +1 -1
- synth_ai/{tracing_v1 → v0/tracing_v1}/decorators.py +11 -11
- synth_ai/v0/tracing_v1/events/__init__.py +0 -0
- synth_ai/{tracing_v1 → v0/tracing_v1}/events/manage.py +4 -4
- synth_ai/{tracing_v1 → v0/tracing_v1}/events/scope.py +6 -6
- synth_ai/{tracing_v1 → v0/tracing_v1}/events/store.py +3 -3
- synth_ai/{tracing_v1 → v0/tracing_v1}/immediate_client.py +6 -6
- synth_ai/{tracing_v1 → v0/tracing_v1}/log_client_base.py +2 -2
- synth_ai/{tracing_v1 → v0/tracing_v1}/retry_queue.py +3 -3
- synth_ai/{tracing_v1 → v0/tracing_v1}/trackers.py +2 -2
- synth_ai/{tracing_v1 → v0/tracing_v1}/upload.py +4 -4
- {synth_ai-0.2.2.dev0.dist-info → synth_ai-0.2.4.dev2.dist-info}/METADATA +100 -5
- {synth_ai-0.2.2.dev0.dist-info → synth_ai-0.2.4.dev2.dist-info}/RECORD +115 -75
- /synth_ai/{tracing/events/__init__.py → compound/cais.py} +0 -0
- /synth_ai/{tracing_v1/events/__init__.py → environments/examples/crafter_classic/debug_translation.py} +0 -0
- /synth_ai/{tracing → v0/tracing}/abstractions.py +0 -0
- /synth_ai/{tracing → v0/tracing}/config.py +0 -0
- /synth_ai/{tracing → v0/tracing}/local.py +0 -0
- /synth_ai/{tracing → v0/tracing}/utils.py +0 -0
- /synth_ai/{tracing_v1 → v0/tracing_v1}/abstractions.py +0 -0
- /synth_ai/{tracing_v1 → v0/tracing_v1}/config.py +0 -0
- /synth_ai/{tracing_v1 → v0/tracing_v1}/local.py +0 -0
- /synth_ai/{tracing_v1 → v0/tracing_v1}/utils.py +0 -0
- {synth_ai-0.2.2.dev0.dist-info → synth_ai-0.2.4.dev2.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.2.dev0.dist-info → synth_ai-0.2.4.dev2.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.2.dev0.dist-info → synth_ai-0.2.4.dev2.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.2.dev0.dist-info → synth_ai-0.2.4.dev2.dist-info}/top_level.txt +0 -0
synth_ai/lm/core/main_v3.py
CHANGED
@@ -5,37 +5,39 @@ This module provides the LM class with async v3 tracing support,
|
|
5
5
|
replacing the v2 DuckDB-based implementation.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import Any,
|
9
|
-
import os
|
10
|
-
import functools
|
8
|
+
from typing import Any, Literal
|
11
9
|
import asyncio
|
12
10
|
import time
|
13
11
|
|
14
|
-
from pydantic import BaseModel
|
12
|
+
from pydantic import BaseModel
|
15
13
|
|
16
|
-
from synth_ai.lm.
|
14
|
+
from synth_ai.lm.config import reasoning_models
|
17
15
|
from synth_ai.lm.core.vendor_clients import (
|
18
16
|
anthropic_naming_regexes,
|
19
17
|
get_client,
|
20
18
|
openai_naming_regexes,
|
21
19
|
)
|
22
20
|
from synth_ai.lm.structured_outputs.handler import StructuredOutputHandler
|
23
|
-
from synth_ai.lm.vendors.base import VendorBase, BaseLMResponse
|
24
21
|
from synth_ai.lm.tools.base import BaseTool
|
25
|
-
from synth_ai.lm.
|
22
|
+
from synth_ai.lm.vendors.base import BaseLMResponse, VendorBase
|
26
23
|
|
27
24
|
# V3 tracing imports
|
28
|
-
from synth_ai.tracing_v3.session_tracer import SessionTracer
|
29
|
-
from synth_ai.tracing_v3.decorators import set_session_id, set_turn_number, set_session_tracer
|
30
25
|
from synth_ai.tracing_v3.abstractions import LMCAISEvent, TimeRecord
|
26
|
+
from synth_ai.tracing_v3.decorators import set_turn_number
|
27
|
+
from synth_ai.tracing_v3.llm_call_record_helpers import (
|
28
|
+
compute_aggregates_from_call_records,
|
29
|
+
create_llm_call_record_from_response,
|
30
|
+
)
|
31
|
+
from synth_ai.tracing_v3.session_tracer import SessionTracer
|
31
32
|
|
32
33
|
|
33
34
|
def build_messages(
|
34
35
|
sys_msg: str,
|
35
36
|
user_msg: str,
|
36
|
-
images_bytes:
|
37
|
-
model_name:
|
38
|
-
) ->
|
37
|
+
images_bytes: list | None = None,
|
38
|
+
model_name: str | None = None,
|
39
|
+
) -> list[dict]:
|
40
|
+
images_bytes = images_bytes or []
|
39
41
|
if len(images_bytes) > 0 and any(regex.match(model_name) for regex in openai_naming_regexes):
|
40
42
|
return [
|
41
43
|
{"role": "system", "content": sys_msg},
|
@@ -51,9 +53,7 @@ def build_messages(
|
|
51
53
|
],
|
52
54
|
},
|
53
55
|
]
|
54
|
-
elif len(images_bytes) > 0 and any(
|
55
|
-
regex.match(model_name) for regex in anthropic_naming_regexes
|
56
|
-
):
|
56
|
+
elif len(images_bytes) > 0 and any(regex.match(model_name) for regex in anthropic_naming_regexes):
|
57
57
|
return [
|
58
58
|
{"role": "system", "content": sys_msg},
|
59
59
|
{
|
@@ -84,24 +84,27 @@ class LM:
|
|
84
84
|
|
85
85
|
def __init__(
|
86
86
|
self,
|
87
|
-
vendor:
|
88
|
-
model:
|
87
|
+
vendor: str | None = None,
|
88
|
+
model: str | None = None,
|
89
89
|
# v2 compatibility parameters
|
90
|
-
model_name:
|
91
|
-
formatting_model_name:
|
92
|
-
provider:
|
90
|
+
model_name: str | None = None, # Alias for model
|
91
|
+
formatting_model_name: str | None = None, # For structured outputs
|
92
|
+
provider: str | None = None, # Alias for vendor
|
93
93
|
synth_logging: bool = True, # v2 compatibility
|
94
94
|
max_retries: Literal["None", "Few", "Many"] = "Few", # v2 compatibility
|
95
95
|
# v3 parameters
|
96
|
-
is_structured:
|
97
|
-
structured_outputs_vendor:
|
98
|
-
response_format:
|
96
|
+
is_structured: bool | None = None,
|
97
|
+
structured_outputs_vendor: str | None = None,
|
98
|
+
response_format: type[BaseModel] | dict[str, Any] | None = None,
|
99
99
|
json_mode: bool = False,
|
100
100
|
temperature: float = 0.8,
|
101
|
-
session_tracer:
|
102
|
-
system_id:
|
101
|
+
session_tracer: SessionTracer | None = None,
|
102
|
+
system_id: str | None = None,
|
103
103
|
enable_v3_tracing: bool = True,
|
104
|
-
enable_v2_tracing:
|
104
|
+
enable_v2_tracing: bool | None = None, # v2 compatibility
|
105
|
+
# Responses API parameters
|
106
|
+
auto_store_responses: bool = True,
|
107
|
+
use_responses_api: bool | None = None,
|
105
108
|
**additional_params,
|
106
109
|
):
|
107
110
|
# Handle v2 compatibility parameters
|
@@ -116,14 +119,14 @@ class LM:
|
|
116
119
|
if vendor is None and model is not None:
|
117
120
|
# Import vendor detection logic
|
118
121
|
from synth_ai.lm.core.vendor_clients import (
|
119
|
-
openai_naming_regexes,
|
120
122
|
anthropic_naming_regexes,
|
121
|
-
|
123
|
+
custom_endpoint_naming_regexes,
|
122
124
|
deepseek_naming_regexes,
|
123
|
-
|
125
|
+
gemini_naming_regexes,
|
124
126
|
grok_naming_regexes,
|
127
|
+
groq_naming_regexes,
|
128
|
+
openai_naming_regexes,
|
125
129
|
openrouter_naming_regexes,
|
126
|
-
custom_endpoint_naming_regexes,
|
127
130
|
together_naming_regexes,
|
128
131
|
)
|
129
132
|
|
@@ -160,18 +163,52 @@ class LM:
|
|
160
163
|
self.system_id = system_id or f"lm_{self.vendor or 'unknown'}_{self.model or 'unknown'}"
|
161
164
|
self.enable_v3_tracing = enable_v3_tracing
|
162
165
|
self.additional_params = additional_params
|
166
|
+
|
167
|
+
# Initialize vendor wrapper early, before any potential usage
|
168
|
+
# (e.g., within StructuredOutputHandler initialization below)
|
169
|
+
self._vendor_wrapper = None
|
170
|
+
|
171
|
+
# Responses API thread management
|
172
|
+
self.auto_store_responses = auto_store_responses
|
173
|
+
self.use_responses_api = use_responses_api
|
174
|
+
self._last_response_id: str | None = None
|
163
175
|
|
164
176
|
# Set structured output handler if needed
|
165
177
|
if self.response_format:
|
166
178
|
self.is_structured = True
|
179
|
+
# Choose mode automatically: prefer forced_json for OpenAI/reasoning models
|
180
|
+
forced_json_preferred = (self.vendor == "openai") or (
|
181
|
+
self.model in reasoning_models if self.model else False
|
182
|
+
)
|
183
|
+
structured_output_mode = "forced_json" if forced_json_preferred else "stringified_json"
|
184
|
+
|
185
|
+
# Build core and formatting clients
|
186
|
+
core_client = get_client(
|
187
|
+
self.model,
|
188
|
+
with_formatting=(structured_output_mode == "forced_json"),
|
189
|
+
provider=self.vendor,
|
190
|
+
)
|
191
|
+
formatting_model = formatting_model_name or self.model
|
192
|
+
formatting_client = get_client(
|
193
|
+
formatting_model,
|
194
|
+
with_formatting=True,
|
195
|
+
provider=self.vendor if self.vendor != "custom_endpoint" else None,
|
196
|
+
)
|
197
|
+
|
198
|
+
# Map retries
|
199
|
+
max_retries_dict = {"None": 0, "Few": 2, "Many": 5}
|
200
|
+
handler_params = {"max_retries": max_retries_dict.get(max_retries, 2)}
|
201
|
+
|
167
202
|
self.structured_output_handler = StructuredOutputHandler(
|
168
|
-
|
203
|
+
core_client,
|
204
|
+
formatting_client,
|
205
|
+
structured_output_mode,
|
206
|
+
handler_params,
|
169
207
|
)
|
170
208
|
else:
|
171
209
|
self.structured_output_handler = None
|
172
210
|
|
173
|
-
#
|
174
|
-
self._vendor_wrapper = None
|
211
|
+
# Vendor wrapper lazy-instantiated via get_vendor_wrapper()
|
175
212
|
|
176
213
|
def get_vendor_wrapper(self) -> VendorBase:
|
177
214
|
"""Get or create the vendor wrapper."""
|
@@ -180,31 +217,68 @@ class LM:
|
|
180
217
|
self._vendor_wrapper = get_client(self.model, provider=self.vendor)
|
181
218
|
return self._vendor_wrapper
|
182
219
|
|
220
|
+
def _should_use_responses_api(self) -> bool:
|
221
|
+
"""Determine if Responses API should be used."""
|
222
|
+
if self.use_responses_api is not None:
|
223
|
+
return self.use_responses_api
|
224
|
+
|
225
|
+
# Auto-detect based on model
|
226
|
+
responses_models = {
|
227
|
+
"o4-mini", "o3", "o3-mini", # Supported Synth-hosted models
|
228
|
+
"gpt-oss-120b", "gpt-oss-20b" # OSS models via Synth
|
229
|
+
}
|
230
|
+
return self.model in responses_models or (self.model and self.model in reasoning_models)
|
231
|
+
|
232
|
+
def _should_use_harmony(self) -> bool:
|
233
|
+
"""Determine if Harmony encoding should be used for OSS models."""
|
234
|
+
# Only use Harmony for OSS models when NOT using OpenAI vendor
|
235
|
+
# OpenAI hosts these models directly via Responses API
|
236
|
+
harmony_models = {"gpt-oss-120b", "gpt-oss-20b"}
|
237
|
+
return self.model in harmony_models and self.vendor != "openai"
|
238
|
+
|
183
239
|
async def respond_async(
|
184
240
|
self,
|
185
|
-
system_message:
|
186
|
-
user_message:
|
187
|
-
messages:
|
188
|
-
images_bytes:
|
189
|
-
images_as_bytes:
|
190
|
-
response_model:
|
191
|
-
tools:
|
192
|
-
turn_number:
|
241
|
+
system_message: str | None = None,
|
242
|
+
user_message: str | None = None,
|
243
|
+
messages: list[dict] | None = None, # v2 compatibility
|
244
|
+
images_bytes: list[bytes] | None = None,
|
245
|
+
images_as_bytes: list[bytes] | None = None, # v2 compatibility
|
246
|
+
response_model: type[BaseModel] | None = None, # v2 compatibility
|
247
|
+
tools: list[BaseTool] | None = None,
|
248
|
+
turn_number: int | None = None,
|
249
|
+
previous_response_id: str | None = None, # Responses API thread management
|
193
250
|
**kwargs,
|
194
251
|
) -> BaseLMResponse:
|
195
252
|
"""Async method to get LM response with v3 tracing."""
|
196
253
|
start_time = time.time()
|
197
254
|
|
198
255
|
# Handle v2 compatibility
|
199
|
-
if images_as_bytes is not None
|
200
|
-
images_bytes = images_as_bytes
|
256
|
+
images_bytes = images_as_bytes if images_as_bytes is not None else (images_bytes or [])
|
201
257
|
|
202
|
-
# Handle response_model for structured outputs
|
258
|
+
# Handle response_model for structured outputs (runtime-provided)
|
203
259
|
if response_model and not self.response_format:
|
204
260
|
self.response_format = response_model
|
205
261
|
self.is_structured = True
|
262
|
+
# Mirror initialization logic from __init__
|
263
|
+
forced_json_preferred = (self.vendor == "openai") or (
|
264
|
+
self.model in reasoning_models if self.model else False
|
265
|
+
)
|
266
|
+
structured_output_mode = "forced_json" if forced_json_preferred else "stringified_json"
|
267
|
+
core_client = get_client(
|
268
|
+
self.model,
|
269
|
+
with_formatting=(structured_output_mode == "forced_json"),
|
270
|
+
provider=self.vendor,
|
271
|
+
)
|
272
|
+
formatting_client = get_client(
|
273
|
+
self.model,
|
274
|
+
with_formatting=True,
|
275
|
+
provider=self.vendor if self.vendor != "custom_endpoint" else None,
|
276
|
+
)
|
206
277
|
self.structured_output_handler = StructuredOutputHandler(
|
207
|
-
|
278
|
+
core_client,
|
279
|
+
formatting_client,
|
280
|
+
structured_output_mode,
|
281
|
+
{"max_retries": 2},
|
208
282
|
)
|
209
283
|
|
210
284
|
# Set turn number if provided
|
@@ -227,57 +301,94 @@ class LM:
|
|
227
301
|
)
|
228
302
|
messages_to_use = build_messages(system_message, user_message, images_bytes, self.model)
|
229
303
|
|
230
|
-
#
|
231
|
-
|
232
|
-
|
233
|
-
# Prepare parameters based on vendor type
|
234
|
-
if hasattr(vendor_wrapper, "_hit_api_async"):
|
235
|
-
# OpenAIStandard expects lm_config
|
236
|
-
lm_config = {"temperature": self.temperature, **self.additional_params, **kwargs}
|
237
|
-
if self.json_mode:
|
238
|
-
lm_config["response_format"] = {"type": "json_object"}
|
239
|
-
|
240
|
-
params = {"model": self.model, "messages": messages_to_use, "lm_config": lm_config}
|
304
|
+
# If using structured outputs, route through the handler
|
305
|
+
if self.structured_output_handler and self.response_format:
|
241
306
|
if tools:
|
242
|
-
|
307
|
+
raise ValueError("Tools are not supported with structured output mode")
|
308
|
+
response = await self.structured_output_handler.call_async(
|
309
|
+
messages=messages_to_use,
|
310
|
+
model=self.model,
|
311
|
+
response_model=self.response_format,
|
312
|
+
use_ephemeral_cache_only=False,
|
313
|
+
lm_config={"temperature": self.temperature, **self.additional_params, **kwargs},
|
314
|
+
reasoning_effort="high",
|
315
|
+
)
|
243
316
|
else:
|
244
|
-
#
|
245
|
-
|
246
|
-
"model": self.model,
|
247
|
-
"messages": messages_to_use,
|
248
|
-
"temperature": self.temperature,
|
249
|
-
**self.additional_params,
|
250
|
-
**kwargs,
|
251
|
-
}
|
317
|
+
# Get vendor wrapper
|
318
|
+
vendor_wrapper = self.get_vendor_wrapper()
|
252
319
|
|
253
|
-
|
254
|
-
|
320
|
+
# Determine API type to use
|
321
|
+
use_responses = self._should_use_responses_api()
|
322
|
+
use_harmony = self._should_use_harmony()
|
255
323
|
|
256
|
-
|
257
|
-
|
324
|
+
# Decide response ID to use for thread management
|
325
|
+
response_id_to_use = None
|
326
|
+
if previous_response_id:
|
327
|
+
response_id_to_use = previous_response_id # Manual override
|
328
|
+
elif self.auto_store_responses and self._last_response_id:
|
329
|
+
response_id_to_use = self._last_response_id # Auto-chain
|
258
330
|
|
259
|
-
|
260
|
-
try:
|
261
|
-
# Try the standard method names
|
331
|
+
# Prepare parameters based on vendor type
|
262
332
|
if hasattr(vendor_wrapper, "_hit_api_async"):
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
333
|
+
# OpenAIStandard expects lm_config
|
334
|
+
lm_config = {"temperature": self.temperature, **self.additional_params, **kwargs}
|
335
|
+
if self.json_mode:
|
336
|
+
lm_config["response_format"] = {"type": "json_object"}
|
337
|
+
|
338
|
+
params = {"model": self.model, "messages": messages_to_use, "lm_config": lm_config}
|
339
|
+
if tools:
|
340
|
+
params["tools"] = tools
|
270
341
|
else:
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
342
|
+
# Other vendors use flat params
|
343
|
+
params = {
|
344
|
+
"model": self.model,
|
345
|
+
"messages": messages_to_use,
|
346
|
+
"temperature": self.temperature,
|
347
|
+
**self.additional_params,
|
348
|
+
**kwargs,
|
349
|
+
}
|
350
|
+
|
351
|
+
if tools:
|
352
|
+
params["tools"] = [tool.to_dict() for tool in tools]
|
353
|
+
|
354
|
+
if self.json_mode:
|
355
|
+
params["response_format"] = {"type": "json_object"}
|
356
|
+
|
357
|
+
# Call vendor with appropriate API type
|
358
|
+
try:
|
359
|
+
# Route to appropriate API
|
360
|
+
if use_harmony and hasattr(vendor_wrapper, "_hit_api_async_harmony"):
|
361
|
+
params["previous_response_id"] = response_id_to_use
|
362
|
+
response = await vendor_wrapper._hit_api_async_harmony(**params)
|
363
|
+
elif use_responses and hasattr(vendor_wrapper, "_hit_api_async_responses"):
|
364
|
+
params["previous_response_id"] = response_id_to_use
|
365
|
+
response = await vendor_wrapper._hit_api_async_responses(**params)
|
366
|
+
else:
|
367
|
+
# Standard chat completions API
|
368
|
+
if hasattr(vendor_wrapper, "_hit_api_async"):
|
369
|
+
response = await vendor_wrapper._hit_api_async(**params)
|
370
|
+
elif hasattr(vendor_wrapper, "respond_async"):
|
371
|
+
response = await vendor_wrapper.respond_async(**params)
|
372
|
+
elif hasattr(vendor_wrapper, "respond"):
|
373
|
+
# Fallback to sync in executor
|
374
|
+
loop = asyncio.get_event_loop()
|
375
|
+
response = await loop.run_in_executor(None, vendor_wrapper.respond, params)
|
376
|
+
else:
|
377
|
+
raise AttributeError(
|
378
|
+
f"Vendor wrapper {type(vendor_wrapper).__name__} has no suitable response method"
|
379
|
+
)
|
380
|
+
if not hasattr(response, 'api_type'):
|
381
|
+
response.api_type = "chat"
|
277
382
|
|
278
|
-
|
279
|
-
|
280
|
-
|
383
|
+
# Update stored response ID if auto-storing
|
384
|
+
if self.auto_store_responses and hasattr(response, 'response_id') and response.response_id:
|
385
|
+
self._last_response_id = response.response_id
|
386
|
+
|
387
|
+
except Exception as e:
|
388
|
+
print(f"Error calling vendor: {e}")
|
389
|
+
raise
|
390
|
+
|
391
|
+
# No additional post-processing needed for structured outputs here
|
281
392
|
|
282
393
|
# Record tracing event if enabled
|
283
394
|
if (
|
@@ -286,36 +397,40 @@ class LM:
|
|
286
397
|
and hasattr(self.session_tracer, "current_session")
|
287
398
|
):
|
288
399
|
latency_ms = int((time.time() - start_time) * 1000)
|
400
|
+
|
401
|
+
# Create LLMCallRecord from the response
|
402
|
+
from datetime import datetime
|
403
|
+
started_at = datetime.utcnow()
|
404
|
+
completed_at = datetime.utcnow()
|
405
|
+
|
406
|
+
call_record = create_llm_call_record_from_response(
|
407
|
+
response=response,
|
408
|
+
model_name=self.model or self.vendor,
|
409
|
+
provider=self.vendor,
|
410
|
+
messages=messages_to_use,
|
411
|
+
temperature=self.temperature,
|
412
|
+
request_params={**self.additional_params, **kwargs},
|
413
|
+
tools=tools,
|
414
|
+
started_at=started_at,
|
415
|
+
completed_at=completed_at,
|
416
|
+
latency_ms=latency_ms,
|
417
|
+
)
|
418
|
+
|
419
|
+
# Compute aggregates from the call record
|
420
|
+
aggregates = compute_aggregates_from_call_records([call_record])
|
289
421
|
|
290
|
-
#
|
291
|
-
usage_info = {}
|
292
|
-
if hasattr(response, "usage") and response.usage:
|
293
|
-
usage_info = {
|
294
|
-
"input_tokens": response.usage.get("input_tokens", 0),
|
295
|
-
"output_tokens": response.usage.get("output_tokens", 0),
|
296
|
-
"total_tokens": response.usage.get("total_tokens", 0),
|
297
|
-
"cost_usd": response.usage.get("cost_usd", 0.0),
|
298
|
-
}
|
299
|
-
else:
|
300
|
-
# Default values when usage is not available
|
301
|
-
usage_info = {
|
302
|
-
"input_tokens": 0,
|
303
|
-
"output_tokens": 0,
|
304
|
-
"total_tokens": 0,
|
305
|
-
"cost_usd": 0.0,
|
306
|
-
}
|
307
|
-
|
308
|
-
# Create LM event
|
422
|
+
# Create LM event with call_records
|
309
423
|
lm_event = LMCAISEvent(
|
310
424
|
system_instance_id=self.system_id,
|
311
425
|
time_record=TimeRecord(event_time=time.time(), message_time=turn_number),
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
426
|
+
# Aggregates at event level
|
427
|
+
input_tokens=aggregates["input_tokens"],
|
428
|
+
output_tokens=aggregates["output_tokens"],
|
429
|
+
total_tokens=aggregates["total_tokens"],
|
430
|
+
cost_usd=aggregates["cost_usd"],
|
431
|
+
latency_ms=aggregates["latency_ms"],
|
432
|
+
# Store the call record
|
433
|
+
call_records=[call_record],
|
319
434
|
metadata={
|
320
435
|
"temperature": self.temperature,
|
321
436
|
"json_mode": self.json_mode,
|
@@ -363,14 +478,15 @@ class LM:
|
|
363
478
|
|
364
479
|
def respond(
|
365
480
|
self,
|
366
|
-
system_message:
|
367
|
-
user_message:
|
368
|
-
messages:
|
369
|
-
images_bytes:
|
370
|
-
images_as_bytes:
|
371
|
-
response_model:
|
372
|
-
tools:
|
373
|
-
|
481
|
+
system_message: str | None = None,
|
482
|
+
user_message: str | None = None,
|
483
|
+
messages: list[dict] | None = None, # v2 compatibility
|
484
|
+
images_bytes: list[bytes] | None = None,
|
485
|
+
images_as_bytes: list[bytes] | None = None, # v2 compatibility
|
486
|
+
response_model: type[BaseModel] | None = None, # v2 compatibility
|
487
|
+
tools: list[BaseTool] | None = None,
|
488
|
+
previous_response_id: str | None = None, # Responses API thread management
|
489
|
+
turn_number: int | None = None,
|
374
490
|
**kwargs,
|
375
491
|
) -> BaseLMResponse:
|
376
492
|
"""Synchronous wrapper for respond_async."""
|
@@ -68,6 +68,10 @@ grok_naming_regexes: List[Pattern] = [
|
|
68
68
|
]
|
69
69
|
|
70
70
|
|
71
|
+
openrouter_naming_regexes: List[Pattern] = [
|
72
|
+
re.compile(r"^openrouter/.*$"), # openrouter/model-name pattern
|
73
|
+
]
|
74
|
+
|
71
75
|
openrouter_naming_regexes: List[Pattern] = [
|
72
76
|
re.compile(r"^openrouter/.*$"), # openrouter/model-name pattern
|
73
77
|
]
|
@@ -103,7 +103,7 @@ OPENAI_METHODS_V1 = [
|
|
103
103
|
sync=False,
|
104
104
|
),
|
105
105
|
OpenAiDefinition(
|
106
|
-
module="openai.resources.
|
106
|
+
module="openai.resources.chat.completions",
|
107
107
|
object="Completions",
|
108
108
|
method="parse",
|
109
109
|
type="chat",
|
@@ -111,7 +111,7 @@ OPENAI_METHODS_V1 = [
|
|
111
111
|
min_version="1.50.0",
|
112
112
|
),
|
113
113
|
OpenAiDefinition(
|
114
|
-
module="openai.resources.
|
114
|
+
module="openai.resources.chat.completions",
|
115
115
|
object="AsyncCompletions",
|
116
116
|
method="parse",
|
117
117
|
type="chat",
|
@@ -776,6 +776,15 @@ class OpenAILangfuse:
|
|
776
776
|
):
|
777
777
|
continue
|
778
778
|
|
779
|
+
# Check if the method actually exists before trying to wrap it
|
780
|
+
try:
|
781
|
+
module = __import__(resource.module, fromlist=[resource.object])
|
782
|
+
obj = getattr(module, resource.object, None)
|
783
|
+
if obj and not hasattr(obj, resource.method):
|
784
|
+
continue # Skip if method doesn't exist
|
785
|
+
except (ImportError, AttributeError):
|
786
|
+
continue # Skip if module or object doesn't exist
|
787
|
+
|
779
788
|
wrap_function_wrapper(
|
780
789
|
resource.module,
|
781
790
|
f"{resource.object}.{resource.method}",
|
synth_ai/lm/vendors/base.py
CHANGED
@@ -18,10 +18,17 @@ class BaseLMResponse(BaseModel):
|
|
18
18
|
raw_response: The raw text response from the model
|
19
19
|
structured_output: Optional parsed Pydantic model if structured output was requested
|
20
20
|
tool_calls: Optional list of tool calls if tools were provided
|
21
|
+
response_id: Optional response ID for thread management (Responses API)
|
22
|
+
reasoning: Optional reasoning trace from the model (o1 models)
|
23
|
+
api_type: Optional API type used ("chat", "responses", or "harmony")
|
21
24
|
"""
|
22
25
|
raw_response: str
|
23
26
|
structured_output: Optional[BaseModel] = None
|
24
27
|
tool_calls: Optional[List[Dict]] = None
|
28
|
+
response_id: Optional[str] = None
|
29
|
+
reasoning: Optional[str] = None
|
30
|
+
api_type: Optional[str] = None
|
31
|
+
usage: Optional[Dict[str, Any]] = None
|
25
32
|
|
26
33
|
|
27
34
|
class VendorBase(ABC):
|