synth-ai 0.1.0.dev38__py3-none-any.whl → 0.1.0.dev49__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 +3 -1
- {synth_ai-0.1.0.dev38.dist-info → synth_ai-0.1.0.dev49.dist-info}/METADATA +12 -11
- synth_ai-0.1.0.dev49.dist-info/RECORD +6 -0
- {synth_ai-0.1.0.dev38.dist-info → synth_ai-0.1.0.dev49.dist-info}/WHEEL +1 -1
- synth_ai-0.1.0.dev49.dist-info/top_level.txt +1 -0
- private_tests/try_synth_sdk.py +0 -1
- public_tests/test_agent.py +0 -538
- public_tests/test_all_structured_outputs.py +0 -196
- public_tests/test_anthropic_structured_outputs.py +0 -0
- public_tests/test_deepseek_structured_outputs.py +0 -0
- public_tests/test_deepseek_tools.py +0 -64
- public_tests/test_gemini_output.py +0 -188
- public_tests/test_gemini_structured_outputs.py +0 -106
- public_tests/test_models.py +0 -183
- public_tests/test_openai_structured_outputs.py +0 -106
- public_tests/test_reasoning_effort.py +0 -75
- public_tests/test_reasoning_models.py +0 -92
- public_tests/test_recursive_structured_outputs.py +0 -180
- public_tests/test_structured.py +0 -137
- public_tests/test_structured_outputs.py +0 -109
- public_tests/test_synth_sdk.py +0 -384
- public_tests/test_text.py +0 -160
- public_tests/test_tools.py +0 -319
- synth_ai/zyk/__init__.py +0 -3
- synth_ai/zyk/lms/__init__.py +0 -0
- synth_ai/zyk/lms/caching/__init__.py +0 -0
- synth_ai/zyk/lms/caching/constants.py +0 -1
- synth_ai/zyk/lms/caching/dbs.py +0 -0
- synth_ai/zyk/lms/caching/ephemeral.py +0 -72
- synth_ai/zyk/lms/caching/handler.py +0 -142
- synth_ai/zyk/lms/caching/initialize.py +0 -13
- synth_ai/zyk/lms/caching/persistent.py +0 -83
- synth_ai/zyk/lms/config.py +0 -8
- synth_ai/zyk/lms/core/__init__.py +0 -0
- synth_ai/zyk/lms/core/all.py +0 -47
- synth_ai/zyk/lms/core/exceptions.py +0 -9
- synth_ai/zyk/lms/core/main.py +0 -314
- synth_ai/zyk/lms/core/vendor_clients.py +0 -85
- synth_ai/zyk/lms/cost/__init__.py +0 -0
- synth_ai/zyk/lms/cost/monitor.py +0 -1
- synth_ai/zyk/lms/cost/statefulness.py +0 -1
- synth_ai/zyk/lms/structured_outputs/__init__.py +0 -0
- synth_ai/zyk/lms/structured_outputs/handler.py +0 -442
- synth_ai/zyk/lms/structured_outputs/inject.py +0 -314
- synth_ai/zyk/lms/structured_outputs/rehabilitate.py +0 -187
- synth_ai/zyk/lms/tools/base.py +0 -104
- synth_ai/zyk/lms/vendors/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/base.py +0 -31
- synth_ai/zyk/lms/vendors/constants.py +0 -22
- synth_ai/zyk/lms/vendors/core/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/core/anthropic_api.py +0 -413
- synth_ai/zyk/lms/vendors/core/gemini_api.py +0 -306
- synth_ai/zyk/lms/vendors/core/mistral_api.py +0 -327
- synth_ai/zyk/lms/vendors/core/openai_api.py +0 -185
- synth_ai/zyk/lms/vendors/local/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/local/ollama.py +0 -0
- synth_ai/zyk/lms/vendors/openai_standard.py +0 -375
- synth_ai/zyk/lms/vendors/retries.py +0 -3
- synth_ai/zyk/lms/vendors/supported/__init__.py +0 -0
- synth_ai/zyk/lms/vendors/supported/deepseek.py +0 -73
- synth_ai/zyk/lms/vendors/supported/groq.py +0 -16
- synth_ai/zyk/lms/vendors/supported/ollama.py +0 -14
- synth_ai/zyk/lms/vendors/supported/together.py +0 -11
- synth_ai-0.1.0.dev38.dist-info/RECORD +0 -67
- synth_ai-0.1.0.dev38.dist-info/top_level.txt +0 -4
- tests/test_agent.py +0 -538
- tests/test_recursive_structured_outputs.py +0 -180
- tests/test_structured_outputs.py +0 -100
- {synth_ai-0.1.0.dev38.dist-info → synth_ai-0.1.0.dev49.dist-info}/licenses/LICENSE +0 -0
@@ -1,314 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from typing import (
|
3
|
-
Any,
|
4
|
-
Dict,
|
5
|
-
List,
|
6
|
-
Optional,
|
7
|
-
Tuple,
|
8
|
-
Type,
|
9
|
-
get_type_hints,
|
10
|
-
get_args,
|
11
|
-
get_origin,
|
12
|
-
Union,
|
13
|
-
Literal,
|
14
|
-
)
|
15
|
-
from pydantic import BaseModel
|
16
|
-
import warnings
|
17
|
-
|
18
|
-
|
19
|
-
def generate_type_map() -> Dict[Any, str]:
|
20
|
-
base_types = {
|
21
|
-
int: "int",
|
22
|
-
float: "float",
|
23
|
-
bool: "bool",
|
24
|
-
str: "str",
|
25
|
-
Any: "Any",
|
26
|
-
}
|
27
|
-
|
28
|
-
collection_types = {
|
29
|
-
List: "List",
|
30
|
-
Dict: "Dict",
|
31
|
-
Optional: "Optional",
|
32
|
-
}
|
33
|
-
|
34
|
-
type_map = {}
|
35
|
-
for base_type, name in base_types.items():
|
36
|
-
type_map[base_type] = name
|
37
|
-
for collection, collection_name in collection_types.items():
|
38
|
-
if collection is Optional:
|
39
|
-
type_map[Optional[base_type]] = name
|
40
|
-
elif collection is Dict:
|
41
|
-
# Handle generic Dict type
|
42
|
-
type_map[Dict] = "Dict[Any,Any]"
|
43
|
-
# Provide both key and value types for Dict
|
44
|
-
type_map[Dict[base_type, base_type]] = (
|
45
|
-
f"{collection_name}[{name},{name}]"
|
46
|
-
)
|
47
|
-
# Handle Dict[Any, Any] explicitly
|
48
|
-
type_map[Dict[Any, Any]] = "Dict[Any,Any]"
|
49
|
-
else:
|
50
|
-
type_map[collection[base_type]] = f"{collection_name}[{name}]"
|
51
|
-
return type_map
|
52
|
-
|
53
|
-
|
54
|
-
def generate_example_dict() -> Dict[str, Any]:
|
55
|
-
example_values = {
|
56
|
-
"str": "<Your type-str response here>",
|
57
|
-
"int": "<Your type-int response here>",
|
58
|
-
"float": "<Your type-float response here>",
|
59
|
-
"bool": "<Your type-bool response here>",
|
60
|
-
"Any": "<Your response here (infer the type from context)>",
|
61
|
-
}
|
62
|
-
|
63
|
-
example_dict = {}
|
64
|
-
for key, value in example_values.items():
|
65
|
-
example_dict[key] = value
|
66
|
-
example_dict[f"List[{key}]"] = [value]
|
67
|
-
example_dict[f"List[List[{key}]]"] = [[value]]
|
68
|
-
if key == "Dict[str,str]":
|
69
|
-
example_dict[f"Dict[str,{key.split('[')[1]}"] = {value: value}
|
70
|
-
elif key.startswith("Dict"):
|
71
|
-
example_dict[key] = {value: value}
|
72
|
-
|
73
|
-
# Add example for Dict[Any,Any]
|
74
|
-
example_dict["Dict[Any,Any]"] = {"<key>": "<value>"}
|
75
|
-
|
76
|
-
return example_dict
|
77
|
-
|
78
|
-
|
79
|
-
base_type_examples = {
|
80
|
-
int: ("int", 42),
|
81
|
-
float: ("float", 3.14),
|
82
|
-
bool: ("bool", True),
|
83
|
-
str: ("str", "example"),
|
84
|
-
Any: ("Any", "<Any value>"),
|
85
|
-
}
|
86
|
-
|
87
|
-
|
88
|
-
def get_type_string(type_hint):
|
89
|
-
origin = get_origin(type_hint)
|
90
|
-
args = get_args(type_hint)
|
91
|
-
|
92
|
-
if origin is None:
|
93
|
-
if isinstance(type_hint, type) and issubclass(type_hint, BaseModel):
|
94
|
-
# For Pydantic models, create a dictionary of field types
|
95
|
-
field_types = {}
|
96
|
-
for field_name, field_info in type_hint.model_fields.items():
|
97
|
-
field_type = get_type_string(field_info.annotation)
|
98
|
-
# Check for Literal type by looking at the origin
|
99
|
-
if get_origin(field_info.annotation) is Literal:
|
100
|
-
literal_args = get_args(field_info.annotation)
|
101
|
-
field_type = f"Literal[{repr(literal_args[0])}]"
|
102
|
-
field_types[field_name] = field_type
|
103
|
-
return f"{type_hint.__name__}({', '.join(f'{k}: {v}' for k, v in field_types.items())})"
|
104
|
-
else:
|
105
|
-
return base_type_examples.get(type_hint, ("Unknown", "unknown"))[0]
|
106
|
-
elif origin in (list, List):
|
107
|
-
elem_type = get_type_string(args[0])
|
108
|
-
return f"List[{elem_type}]"
|
109
|
-
elif origin in (dict, Dict):
|
110
|
-
key_type = get_type_string(args[0])
|
111
|
-
value_type = get_type_string(args[1])
|
112
|
-
return f"Dict[{key_type}, {value_type}]"
|
113
|
-
elif origin is Union:
|
114
|
-
non_none_types = [t for t in args if t is not type(None)]
|
115
|
-
if len(non_none_types) == 1:
|
116
|
-
return f"Optional[{get_type_string(non_none_types[0])}]"
|
117
|
-
else:
|
118
|
-
# For unions of Pydantic models (like tool calls), show each variant
|
119
|
-
union_types = []
|
120
|
-
for t in non_none_types:
|
121
|
-
if isinstance(t, type) and issubclass(t, BaseModel):
|
122
|
-
# Include discriminator field if present
|
123
|
-
discriminator = None
|
124
|
-
for field_name, field_info in t.model_fields.items():
|
125
|
-
if get_origin(field_info.annotation) is Literal:
|
126
|
-
literal_args = get_args(field_info.annotation)
|
127
|
-
discriminator = f"{field_name}={repr(literal_args[0])}"
|
128
|
-
break
|
129
|
-
type_str = t.__name__
|
130
|
-
if discriminator:
|
131
|
-
type_str += f"({discriminator})"
|
132
|
-
union_types.append(type_str)
|
133
|
-
else:
|
134
|
-
union_types.append(get_type_string(t))
|
135
|
-
return f"Union[{', '.join(union_types)}]"
|
136
|
-
elif origin is Literal:
|
137
|
-
# Handle Literal type directly
|
138
|
-
return f"Literal[{repr(args[0])}]"
|
139
|
-
else:
|
140
|
-
return "Any"
|
141
|
-
|
142
|
-
|
143
|
-
def get_example_value(type_hint):
|
144
|
-
origin = get_origin(type_hint)
|
145
|
-
args = get_args(type_hint)
|
146
|
-
|
147
|
-
if origin is None:
|
148
|
-
if isinstance(type_hint, type) and issubclass(type_hint, BaseModel):
|
149
|
-
example = {}
|
150
|
-
union_docs = []
|
151
|
-
for field_name, field_info in type_hint.model_fields.items():
|
152
|
-
field_value, field_docs = get_example_value(field_info.annotation)
|
153
|
-
if field_docs:
|
154
|
-
union_docs.extend(field_docs)
|
155
|
-
|
156
|
-
# Handle literal fields by checking origin
|
157
|
-
if get_origin(field_info.annotation) is Literal:
|
158
|
-
literal_args = get_args(field_info.annotation)
|
159
|
-
field_value = literal_args[0]
|
160
|
-
|
161
|
-
# Include field description if available
|
162
|
-
if field_info.description:
|
163
|
-
example[field_name] = {
|
164
|
-
"value": field_value,
|
165
|
-
"description": field_info.description,
|
166
|
-
}
|
167
|
-
else:
|
168
|
-
example[field_name] = field_value
|
169
|
-
return example, union_docs
|
170
|
-
else:
|
171
|
-
return base_type_examples.get(type_hint, ("Unknown", "unknown"))[1], []
|
172
|
-
elif origin in (list, List):
|
173
|
-
value, docs = get_example_value(args[0])
|
174
|
-
return [value], docs
|
175
|
-
elif origin in (dict, Dict):
|
176
|
-
if not args or len(args) < 2:
|
177
|
-
warnings.warn(
|
178
|
-
f"Dictionary type hint {type_hint} missing type arguments. "
|
179
|
-
"Defaulting to Dict[str, Any].",
|
180
|
-
UserWarning,
|
181
|
-
)
|
182
|
-
return {"example_key": "<Any value>"}, [] # Default for Dict[str, Any]
|
183
|
-
key_value, key_docs = get_example_value(args[0])
|
184
|
-
value_value, value_docs = get_example_value(args[1])
|
185
|
-
return {key_value: value_value}, key_docs + value_docs
|
186
|
-
elif origin is Union:
|
187
|
-
non_none_types = [t for t in args if t is not type(None)]
|
188
|
-
# For unions of tool calls, use the first one but preserve the discriminator
|
189
|
-
first_type = non_none_types[0]
|
190
|
-
union_docs = []
|
191
|
-
|
192
|
-
if all(
|
193
|
-
isinstance(t, type) and issubclass(t, BaseModel) for t in non_none_types
|
194
|
-
):
|
195
|
-
# Generate examples for all union variants
|
196
|
-
for t in non_none_types:
|
197
|
-
example = {}
|
198
|
-
for field_name, field_info in t.model_fields.items():
|
199
|
-
field_value, _ = get_example_value(field_info.annotation)
|
200
|
-
if get_origin(field_info.annotation) is Literal:
|
201
|
-
literal_args = get_args(field_info.annotation)
|
202
|
-
field_value = literal_args[0]
|
203
|
-
example[field_name] = field_value
|
204
|
-
union_docs.append(f"\nExample {t.__name__}:")
|
205
|
-
union_docs.append(json.dumps(example, indent=2))
|
206
|
-
|
207
|
-
# Return first type as main example
|
208
|
-
if isinstance(first_type, type) and issubclass(first_type, BaseModel):
|
209
|
-
example, _ = get_example_value(first_type)
|
210
|
-
# Ensure tool_type or other discriminator is preserved
|
211
|
-
for field_name, field_info in first_type.model_fields.items():
|
212
|
-
if get_origin(field_info.annotation) is Literal:
|
213
|
-
literal_args = get_args(field_info.annotation)
|
214
|
-
if (
|
215
|
-
isinstance(example[field_name], dict)
|
216
|
-
and "value" in example[field_name]
|
217
|
-
):
|
218
|
-
example[field_name]["value"] = literal_args[0]
|
219
|
-
else:
|
220
|
-
example[field_name] = literal_args[0]
|
221
|
-
return example, union_docs
|
222
|
-
main_example, docs = get_example_value(first_type)
|
223
|
-
return main_example, docs + union_docs
|
224
|
-
elif origin is Literal:
|
225
|
-
# Handle Literal type directly
|
226
|
-
return args[0], []
|
227
|
-
else:
|
228
|
-
return "<Unknown>", []
|
229
|
-
|
230
|
-
|
231
|
-
def add_json_instructions_to_messages(
|
232
|
-
system_message,
|
233
|
-
user_message,
|
234
|
-
response_model: Optional[Type[BaseModel]] = None,
|
235
|
-
previously_failed_error_messages: List[str] = [],
|
236
|
-
) -> Tuple[str, str]:
|
237
|
-
if response_model:
|
238
|
-
type_hints = get_type_hints(response_model)
|
239
|
-
# print("Type hints", type_hints)
|
240
|
-
stringified_fields = {}
|
241
|
-
union_docs = []
|
242
|
-
for key, type_hint in type_hints.items():
|
243
|
-
# print("Key", key, "Type hint", type_hint)
|
244
|
-
example_value, docs = get_example_value(type_hint)
|
245
|
-
union_docs.extend(docs)
|
246
|
-
field = response_model.model_fields[key] # Updated for Pydantic v2
|
247
|
-
|
248
|
-
# Adjusted for Pydantic v2
|
249
|
-
field_description = (
|
250
|
-
field.description if hasattr(field, "description") else None
|
251
|
-
)
|
252
|
-
|
253
|
-
if field_description:
|
254
|
-
stringified_fields[key] = (example_value, field_description)
|
255
|
-
else:
|
256
|
-
stringified_fields[key] = example_value
|
257
|
-
example_json = json.dumps(
|
258
|
-
{
|
259
|
-
k: v[0] if isinstance(v, tuple) else v
|
260
|
-
for k, v in stringified_fields.items()
|
261
|
-
},
|
262
|
-
indent=4,
|
263
|
-
)
|
264
|
-
description_comments = "\n".join(
|
265
|
-
f"// {k}: {v[1]}"
|
266
|
-
for k, v in stringified_fields.items()
|
267
|
-
if isinstance(v, tuple)
|
268
|
-
)
|
269
|
-
|
270
|
-
# print("Example JSON", example_json)
|
271
|
-
# print("Description comments", description_comments)
|
272
|
-
# print("Union documentation", "\n".join(union_docs))
|
273
|
-
# raise Exception("Stop here")
|
274
|
-
|
275
|
-
system_message += f"""\n\n
|
276
|
-
Please deliver your response in the following JSON format:
|
277
|
-
```json
|
278
|
-
{example_json}
|
279
|
-
```
|
280
|
-
{description_comments}
|
281
|
-
"""
|
282
|
-
if len(union_docs) != 0:
|
283
|
-
system_message += f"""\n\n
|
284
|
-
NOTE - the above example included a union type. Here are some additional examples for that union type:
|
285
|
-
{chr(10).join(union_docs)}
|
286
|
-
"""
|
287
|
-
if len(previously_failed_error_messages) != 0:
|
288
|
-
system_message += f"""\n\n
|
289
|
-
Please take special care to follow the format exactly.
|
290
|
-
Keep in mind the following:
|
291
|
-
- Always use double quotes for strings
|
292
|
-
|
293
|
-
Here are some error traces from previous attempts:
|
294
|
-
{previously_failed_error_messages}
|
295
|
-
"""
|
296
|
-
return system_message, user_message
|
297
|
-
|
298
|
-
|
299
|
-
def inject_structured_output_instructions(
|
300
|
-
messages: List[Dict[str, str]],
|
301
|
-
response_model: Optional[Type[BaseModel]] = None,
|
302
|
-
previously_failed_error_messages: List[str] = [],
|
303
|
-
) -> List[Dict[str, str]]:
|
304
|
-
prev_system_message_content = messages[0]["content"]
|
305
|
-
prev_user_message_content = messages[1]["content"]
|
306
|
-
system_message, user_message = add_json_instructions_to_messages(
|
307
|
-
prev_system_message_content,
|
308
|
-
prev_user_message_content,
|
309
|
-
response_model,
|
310
|
-
previously_failed_error_messages,
|
311
|
-
)
|
312
|
-
messages[0]["content"] = system_message
|
313
|
-
messages[1]["content"] = user_message
|
314
|
-
return messages
|
@@ -1,187 +0,0 @@
|
|
1
|
-
import ast
|
2
|
-
import json
|
3
|
-
import logging
|
4
|
-
import re
|
5
|
-
from typing import Dict, List, Type, Union
|
6
|
-
|
7
|
-
from pydantic import BaseModel
|
8
|
-
|
9
|
-
from synth_ai.zyk.lms.vendors.base import VendorBase
|
10
|
-
from synth_ai.zyk.lms.vendors.core.openai_api import OpenAIStructuredOutputClient
|
11
|
-
|
12
|
-
|
13
|
-
def pull_out_structured_output(
|
14
|
-
response_raw: str, response_model: Type[BaseModel]
|
15
|
-
) -> BaseModel:
|
16
|
-
logger = logging.getLogger(__name__)
|
17
|
-
logger.debug(f"Raw response received: {response_raw}")
|
18
|
-
|
19
|
-
assert isinstance(
|
20
|
-
response_raw, str
|
21
|
-
), f"Response raw is not a string: {type(response_raw)}"
|
22
|
-
|
23
|
-
# Use regex to extract JSON content within ```json ... ```
|
24
|
-
json_pattern = re.compile(r"```json\s*(\{.*\})\s*```", re.DOTALL)
|
25
|
-
match = json_pattern.search(response_raw)
|
26
|
-
if match:
|
27
|
-
response_prepared = match.group(1).strip()
|
28
|
-
else:
|
29
|
-
# Fallback to existing parsing if no code fencing is found
|
30
|
-
if "```" in response_raw:
|
31
|
-
response_prepared = response_raw.split("```")[1].strip()
|
32
|
-
else:
|
33
|
-
response_prepared = response_raw.strip()
|
34
|
-
|
35
|
-
# Replace "null" with '"None"' if needed (ensure this aligns with your data)
|
36
|
-
response_prepared = response_prepared.replace("null", '"None"')
|
37
|
-
|
38
|
-
logger.debug(f"Prepared response for JSON parsing: {response_prepared}")
|
39
|
-
|
40
|
-
try:
|
41
|
-
response = json.loads(response_prepared)
|
42
|
-
final = response_model(**response)
|
43
|
-
except json.JSONDecodeError as e:
|
44
|
-
# Attempt to parse using ast.literal_eval as a fallback
|
45
|
-
response_prepared = response_prepared.replace("\n", "").replace("\\n", "")
|
46
|
-
response_prepared = response_prepared.replace('\\"', '"')
|
47
|
-
try:
|
48
|
-
response = ast.literal_eval(response_prepared)
|
49
|
-
final = response_model(**response)
|
50
|
-
except Exception as inner_e:
|
51
|
-
raise ValueError(
|
52
|
-
f"Failed to parse response as {response_model}: {inner_e} - {response_prepared}"
|
53
|
-
)
|
54
|
-
except Exception as e:
|
55
|
-
raise ValueError(
|
56
|
-
f"Failed to parse response as {response_model}: {e} - {response_prepared}"
|
57
|
-
)
|
58
|
-
assert isinstance(final, BaseModel), "Structured output must be a Pydantic model"
|
59
|
-
return final
|
60
|
-
|
61
|
-
|
62
|
-
def fix_errant_stringified_json_sync(
|
63
|
-
response_raw: str,
|
64
|
-
response_model: Type[BaseModel],
|
65
|
-
models: List[str] = ["gpt-4o-mini", "gpt-4o"],
|
66
|
-
) -> BaseModel:
|
67
|
-
try:
|
68
|
-
return pull_out_structured_output(response_raw, response_model)
|
69
|
-
except ValueError as e:
|
70
|
-
# print("")
|
71
|
-
mini_client = OpenAIStructuredOutputClient()
|
72
|
-
messages = [
|
73
|
-
{
|
74
|
-
"role": "system",
|
75
|
-
"content": "An AI attempted to generate stringified json that could be extracted into the provided Pydantic model. This json cannot be extracted, and an error message is provided to elucidate why. Please review the information and return a corrected response. Do not materially change the content of the response, just formatting where necessary.",
|
76
|
-
},
|
77
|
-
{
|
78
|
-
"role": "user",
|
79
|
-
"content": f"# Errant Attempt\n{response_raw}\n# Response Model\n {response_model.model_json_schema()}\n# Error Message\n {str(e)}.",
|
80
|
-
},
|
81
|
-
]
|
82
|
-
for model in models:
|
83
|
-
if model == "gpt-4o":
|
84
|
-
print(
|
85
|
-
"Warning - using gpt-4o for structured output correction - this could add up over time (latency, cost)"
|
86
|
-
)
|
87
|
-
try:
|
88
|
-
fixed_response = mini_client._hit_api_sync(
|
89
|
-
model, messages=messages, lm_config={"temperature": 0.0}
|
90
|
-
)
|
91
|
-
return pull_out_structured_output(fixed_response, response_model)
|
92
|
-
except Exception as e:
|
93
|
-
pass
|
94
|
-
raise ValueError("Failed to fix response using any model")
|
95
|
-
|
96
|
-
|
97
|
-
async def fix_errant_stringified_json_async(
|
98
|
-
response_raw: str,
|
99
|
-
response_model: Type[BaseModel],
|
100
|
-
models: List[str] = ["gpt-4o-mini", "gpt-4o"],
|
101
|
-
) -> BaseModel:
|
102
|
-
try:
|
103
|
-
return pull_out_structured_output(response_raw, response_model)
|
104
|
-
except ValueError as e:
|
105
|
-
mini_client = OpenAIStructuredOutputClient()
|
106
|
-
messages = [
|
107
|
-
{
|
108
|
-
"role": "system",
|
109
|
-
"content": "An AI attempted to generate stringified json that could be extracted into the provided Pydantic model. This json cannot be extracted, and an error message is provided to elucidate why. Please review the information and return a corrected response. Do not materially change the content of the response, just formatting where necessary.",
|
110
|
-
},
|
111
|
-
{
|
112
|
-
"role": "user",
|
113
|
-
"content": f"# Errant Attempt\n{response_raw}\n# Response Model\n {response_model.model_json_schema()}\n# Error Message\n {str(e)}.",
|
114
|
-
},
|
115
|
-
]
|
116
|
-
for model in models:
|
117
|
-
if model == "gpt-4o":
|
118
|
-
print(
|
119
|
-
"Warning - using gpt-4o for structured output correction - this could add up over time (latency, cost)"
|
120
|
-
)
|
121
|
-
try:
|
122
|
-
fixed_response = await mini_client._hit_api_async(
|
123
|
-
model, messages=messages, lm_config={"temperature": 0.0}
|
124
|
-
)
|
125
|
-
return pull_out_structured_output(fixed_response, response_model)
|
126
|
-
except Exception as e:
|
127
|
-
pass
|
128
|
-
raise ValueError("Failed to fix response using any model")
|
129
|
-
|
130
|
-
|
131
|
-
async def fix_errant_forced_async(
|
132
|
-
messages: List[Dict],
|
133
|
-
response_raw: str,
|
134
|
-
response_model: Type[BaseModel],
|
135
|
-
model: str,
|
136
|
-
) -> BaseModel:
|
137
|
-
try:
|
138
|
-
return pull_out_structured_output(response_raw, response_model)
|
139
|
-
except ValueError as e:
|
140
|
-
client = OpenAIStructuredOutputClient()
|
141
|
-
messages = [
|
142
|
-
{
|
143
|
-
"role": "system",
|
144
|
-
"content": "An AI attempted to generate stringified json that could be extracted into the provided Pydantic model. This json cannot be extracted, and an error message is provided to elucidate why. Please review the information and return a corrected response. Do not materially change the content of the response, just formatting where necessary.",
|
145
|
-
},
|
146
|
-
{
|
147
|
-
"role": "user",
|
148
|
-
"content": f"<previous messages>\n<system_message>{messages[0]['content']}</system_message>\n<user_message>{messages[1]['content']}</user_message></previous messages> # Errant Attempt\n{response_raw}\n# Response Model\n {response_model.model_json_schema()}\n# Error Message\n {str(e)}.",
|
149
|
-
},
|
150
|
-
]
|
151
|
-
# print("Broken response:")
|
152
|
-
# print(response_raw)
|
153
|
-
fixed_response = await client._hit_api_async_structured_output(
|
154
|
-
model=model,
|
155
|
-
messages=messages,
|
156
|
-
response_model=response_model,
|
157
|
-
temperature=0.0,
|
158
|
-
)
|
159
|
-
# print("Fixed response:")
|
160
|
-
# print(fixed_response)
|
161
|
-
return fixed_response.structured_output
|
162
|
-
|
163
|
-
|
164
|
-
def fix_errant_forced_sync(
|
165
|
-
response_raw: str,
|
166
|
-
response_model: Type[BaseModel],
|
167
|
-
model: str,
|
168
|
-
) -> BaseModel:
|
169
|
-
client = OpenAIStructuredOutputClient()
|
170
|
-
messages = [
|
171
|
-
{
|
172
|
-
"role": "system",
|
173
|
-
"content": "An AI attempted to generate a response that could be extracted into the provided Pydantic model. This response cannot be extracted. Please review the information and return a corrected response.",
|
174
|
-
},
|
175
|
-
{
|
176
|
-
"role": "user",
|
177
|
-
"content": f"# Errant Attempt\n{response_raw}\n# Response Model\n {response_model.model_json_schema()}",
|
178
|
-
},
|
179
|
-
]
|
180
|
-
# print("Broken response:")
|
181
|
-
# print(response_raw)
|
182
|
-
fixed_response = client._hit_api_sync_structured_output(
|
183
|
-
model=model, messages=messages, response_model=response_model, temperature=0.0
|
184
|
-
)
|
185
|
-
# print("Fixed response:")
|
186
|
-
# print(fixed_response)
|
187
|
-
return fixed_response.structured_output
|
synth_ai/zyk/lms/tools/base.py
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
from typing import Type
|
2
|
-
|
3
|
-
from pydantic import BaseModel
|
4
|
-
|
5
|
-
|
6
|
-
class BaseTool(BaseModel):
|
7
|
-
name: str
|
8
|
-
arguments: Type[BaseModel]
|
9
|
-
description: str = ""
|
10
|
-
strict: bool = True
|
11
|
-
|
12
|
-
def to_openai_tool(self):
|
13
|
-
schema = self.arguments.model_json_schema()
|
14
|
-
schema["additionalProperties"] = False
|
15
|
-
|
16
|
-
return {
|
17
|
-
"type": "function",
|
18
|
-
"function": {
|
19
|
-
"name": self.name,
|
20
|
-
"description": self.description,
|
21
|
-
"parameters": schema,
|
22
|
-
"strict": self.strict,
|
23
|
-
},
|
24
|
-
}
|
25
|
-
|
26
|
-
def to_anthropic_tool(self):
|
27
|
-
schema = self.arguments.model_json_schema()
|
28
|
-
schema["additionalProperties"] = False
|
29
|
-
|
30
|
-
return {
|
31
|
-
"name": self.name,
|
32
|
-
"description": self.description,
|
33
|
-
"input_schema": {
|
34
|
-
"type": "object",
|
35
|
-
"properties": schema["properties"],
|
36
|
-
"required": schema.get("required", []),
|
37
|
-
},
|
38
|
-
}
|
39
|
-
|
40
|
-
def to_mistral_tool(self):
|
41
|
-
schema = self.arguments.model_json_schema()
|
42
|
-
properties = {}
|
43
|
-
for prop_name, prop in schema.get("properties", {}).items():
|
44
|
-
prop_type = prop["type"]
|
45
|
-
if prop_type == "array" and "items" in prop:
|
46
|
-
properties[prop_name] = {
|
47
|
-
"type": "array",
|
48
|
-
"items": prop["items"],
|
49
|
-
"description": prop.get("description", ""),
|
50
|
-
}
|
51
|
-
continue
|
52
|
-
|
53
|
-
properties[prop_name] = {
|
54
|
-
"type": prop_type,
|
55
|
-
"description": prop.get("description", ""),
|
56
|
-
}
|
57
|
-
if "enum" in prop:
|
58
|
-
properties[prop_name]["enum"] = prop["enum"]
|
59
|
-
|
60
|
-
parameters = {
|
61
|
-
"type": "object",
|
62
|
-
"properties": properties,
|
63
|
-
"required": schema.get("required", []),
|
64
|
-
"additionalProperties": False,
|
65
|
-
}
|
66
|
-
return {
|
67
|
-
"type": "function",
|
68
|
-
"function": {
|
69
|
-
"name": self.name,
|
70
|
-
"description": self.description,
|
71
|
-
"parameters": parameters,
|
72
|
-
},
|
73
|
-
}
|
74
|
-
|
75
|
-
def to_gemini_tool(self):
|
76
|
-
schema = self.arguments.model_json_schema()
|
77
|
-
# Convert Pydantic schema types to Gemini schema types
|
78
|
-
properties = {}
|
79
|
-
for name, prop in schema["properties"].items():
|
80
|
-
prop_type = prop.get("type", "string")
|
81
|
-
if prop_type == "array" and "items" in prop:
|
82
|
-
properties[name] = {
|
83
|
-
"type": "array",
|
84
|
-
"items": prop["items"],
|
85
|
-
"description": prop.get("description", ""),
|
86
|
-
}
|
87
|
-
continue
|
88
|
-
|
89
|
-
properties[name] = {
|
90
|
-
"type": prop_type,
|
91
|
-
"description": prop.get("description", ""),
|
92
|
-
}
|
93
|
-
if "enum" in prop:
|
94
|
-
properties[name]["enum"] = prop["enum"]
|
95
|
-
|
96
|
-
return {
|
97
|
-
"name": self.name,
|
98
|
-
"description": self.description,
|
99
|
-
"parameters": {
|
100
|
-
"type": "object",
|
101
|
-
"properties": properties,
|
102
|
-
"required": schema.get("required", []),
|
103
|
-
},
|
104
|
-
}
|
File without changes
|
synth_ai/zyk/lms/vendors/base.py
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
from typing import Any, Dict, List, Optional
|
3
|
-
|
4
|
-
from pydantic import BaseModel
|
5
|
-
|
6
|
-
|
7
|
-
class BaseLMResponse(BaseModel):
|
8
|
-
raw_response: str
|
9
|
-
structured_output: Optional[BaseModel] = None
|
10
|
-
tool_calls: Optional[List[Dict]] = None
|
11
|
-
|
12
|
-
|
13
|
-
class VendorBase(ABC):
|
14
|
-
used_for_structured_outputs: bool = False
|
15
|
-
exceptions_to_retry: List[Exception] = []
|
16
|
-
|
17
|
-
@abstractmethod
|
18
|
-
async def _hit_api_async(
|
19
|
-
self,
|
20
|
-
messages: List[Dict[str, Any]],
|
21
|
-
response_model_override: Optional[BaseModel] = None,
|
22
|
-
) -> str:
|
23
|
-
pass
|
24
|
-
|
25
|
-
@abstractmethod
|
26
|
-
def _hit_api_sync(
|
27
|
-
self,
|
28
|
-
messages: List[Dict[str, Any]],
|
29
|
-
response_model_override: Optional[BaseModel] = None,
|
30
|
-
) -> str:
|
31
|
-
pass
|
@@ -1,22 +0,0 @@
|
|
1
|
-
SPECIAL_BASE_TEMPS = {
|
2
|
-
"claude-3-5-sonnet-20240620": 0.7,
|
3
|
-
"o1-mini": 1,
|
4
|
-
"o1-preview": 1,
|
5
|
-
"o1": 1,
|
6
|
-
"o3-mini": 1,
|
7
|
-
"o3": 1,
|
8
|
-
"o4-mini": 1,
|
9
|
-
"o4": 1,
|
10
|
-
}
|
11
|
-
|
12
|
-
openai_reasoners = [
|
13
|
-
"o1-mini",
|
14
|
-
"o1-preview",
|
15
|
-
"o1",
|
16
|
-
"o3-mini",
|
17
|
-
"o3",
|
18
|
-
"o4-mini",
|
19
|
-
"o4",
|
20
|
-
"o5-mini",
|
21
|
-
"o5",
|
22
|
-
]
|
File without changes
|