synth-ai 0.1.0.dev14__tar.gz → 0.1.0.dev15__tar.gz
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.
Potentially problematic release.
This version of synth-ai might be problematic. Click here for more details.
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/PKG-INFO +1 -1
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/pyproject.toml +1 -1
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/setup.py +1 -1
- synth_ai-0.1.0.dev15/synth_ai/zyk/lms/structured_outputs/inject.py +314 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai.egg-info/PKG-INFO +1 -1
- synth_ai-0.1.0.dev14/synth_ai/zyk/lms/structured_outputs/inject.py +0 -185
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/LICENSE +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/README.md +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/private_tests/try_synth_sdk.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/public_tests/test_agent.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/public_tests/test_all_structured_outputs.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/public_tests/test_recursive_structured_outputs.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/public_tests/test_structured_outputs.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/public_tests/test_synth_sdk.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/setup.cfg +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/caching/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/caching/constants.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/caching/dbs.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/caching/ephemeral.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/caching/handler.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/caching/initialize.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/caching/persistent.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/config.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/core/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/core/all.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/core/exceptions.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/core/main.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/core/vendor_clients.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/cost/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/cost/monitor.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/cost/statefulness.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/structured_outputs/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/structured_outputs/handler.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/structured_outputs/rehabilitate.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/base.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/constants.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/core/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/core/anthropic_api.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/core/gemini_api.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/core/mistral_api.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/core/openai_api.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/local/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/local/ollama.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/openai_standard.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/retries.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/supported/__init__.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/supported/deepseek.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/supported/groq.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/supported/ollama.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/supported/together.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai.egg-info/SOURCES.txt +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai.egg-info/dependency_links.txt +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai.egg-info/requires.txt +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai.egg-info/top_level.txt +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/tests/test_agent.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/tests/test_recursive_structured_outputs.py +0 -0
- {synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/tests/test_structured_outputs.py +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
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,185 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Any, Dict, List, Optional, Tuple, Type, get_type_hints, get_args, get_origin, Union
|
|
3
|
-
from pydantic import BaseModel
|
|
4
|
-
import warnings
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def generate_type_map() -> Dict[Any, str]:
|
|
8
|
-
base_types = {
|
|
9
|
-
int: "int",
|
|
10
|
-
float: "float",
|
|
11
|
-
bool: "bool",
|
|
12
|
-
str: "str",
|
|
13
|
-
Any: "Any",
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
collection_types = {
|
|
17
|
-
List: "List",
|
|
18
|
-
Dict: "Dict",
|
|
19
|
-
Optional: "Optional",
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type_map = {}
|
|
23
|
-
for base_type, name in base_types.items():
|
|
24
|
-
type_map[base_type] = name
|
|
25
|
-
for collection, collection_name in collection_types.items():
|
|
26
|
-
if collection is Optional:
|
|
27
|
-
type_map[Optional[base_type]] = name
|
|
28
|
-
elif collection is Dict:
|
|
29
|
-
# Handle generic Dict type
|
|
30
|
-
type_map[Dict] = "Dict[Any,Any]"
|
|
31
|
-
# Provide both key and value types for Dict
|
|
32
|
-
type_map[Dict[base_type, base_type]] = f"{collection_name}[{name},{name}]"
|
|
33
|
-
# Handle Dict[Any, Any] explicitly
|
|
34
|
-
type_map[Dict[Any, Any]] = "Dict[Any,Any]"
|
|
35
|
-
else:
|
|
36
|
-
type_map[collection[base_type]] = f"{collection_name}[{name}]"
|
|
37
|
-
return type_map
|
|
38
|
-
|
|
39
|
-
def generate_example_dict() -> Dict[str, Any]:
|
|
40
|
-
example_values = {
|
|
41
|
-
"str": "<Your type-str response here>",
|
|
42
|
-
"int": "<Your type-int response here>",
|
|
43
|
-
"float": "<Your type-float response here>",
|
|
44
|
-
"bool": "<Your type-bool response here>",
|
|
45
|
-
"Any": "<Your response here (infer the type from context)>",
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
example_dict = {}
|
|
49
|
-
for key, value in example_values.items():
|
|
50
|
-
example_dict[key] = value
|
|
51
|
-
example_dict[f"List[{key}]"] = [value]
|
|
52
|
-
example_dict[f"List[List[{key}]]"] = [[value]]
|
|
53
|
-
if key == "Dict[str,str]":
|
|
54
|
-
example_dict[f"Dict[str,{key.split('[')[1]}"] = {value: value}
|
|
55
|
-
elif key.startswith("Dict"):
|
|
56
|
-
example_dict[key] = {value: value}
|
|
57
|
-
|
|
58
|
-
# Add example for Dict[Any,Any]
|
|
59
|
-
example_dict["Dict[Any,Any]"] = {"<key>": "<value>"}
|
|
60
|
-
|
|
61
|
-
return example_dict
|
|
62
|
-
|
|
63
|
-
base_type_examples = {
|
|
64
|
-
int: ("int", 42),
|
|
65
|
-
float: ("float", 3.14),
|
|
66
|
-
bool: ("bool", True),
|
|
67
|
-
str: ("str", "example"),
|
|
68
|
-
Any: ("Any", "<Any value>"),
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
def get_type_string(type_hint):
|
|
72
|
-
origin = get_origin(type_hint)
|
|
73
|
-
args = get_args(type_hint)
|
|
74
|
-
|
|
75
|
-
if origin is None:
|
|
76
|
-
if isinstance(type_hint, type) and issubclass(type_hint, BaseModel):
|
|
77
|
-
return type_hint.__name__
|
|
78
|
-
else:
|
|
79
|
-
return base_type_examples.get(type_hint, ("Unknown", "unknown"))[0]
|
|
80
|
-
elif origin in (list, List):
|
|
81
|
-
elem_type = get_type_string(args[0])
|
|
82
|
-
return f"List[{elem_type}]"
|
|
83
|
-
elif origin in (dict, Dict):
|
|
84
|
-
key_type = get_type_string(args[0])
|
|
85
|
-
value_type = get_type_string(args[1])
|
|
86
|
-
return f"Dict[{key_type}, {value_type}]"
|
|
87
|
-
elif origin is Union:
|
|
88
|
-
non_none_types = [t for t in args if t is not type(None)]
|
|
89
|
-
if len(non_none_types) == 1:
|
|
90
|
-
return f"Optional[{get_type_string(non_none_types[0])}]"
|
|
91
|
-
else:
|
|
92
|
-
union_types = ", ".join(get_type_string(t) for t in non_none_types)
|
|
93
|
-
return f"Union[{union_types}]"
|
|
94
|
-
else:
|
|
95
|
-
return "Any"
|
|
96
|
-
|
|
97
|
-
def get_example_value(type_hint):
|
|
98
|
-
origin = get_origin(type_hint)
|
|
99
|
-
args = get_args(type_hint)
|
|
100
|
-
|
|
101
|
-
if origin is None:
|
|
102
|
-
if isinstance(type_hint, type) and issubclass(type_hint, BaseModel):
|
|
103
|
-
example = {}
|
|
104
|
-
for field_name, field_info in type_hint.model_fields.items():
|
|
105
|
-
# Updated attribute from type_ to annotation
|
|
106
|
-
example[field_name] = get_example_value(field_info.annotation)
|
|
107
|
-
return example
|
|
108
|
-
else:
|
|
109
|
-
return base_type_examples.get(type_hint, ("Unknown", "unknown"))[1]
|
|
110
|
-
elif origin in (list, List):
|
|
111
|
-
return [get_example_value(args[0])]
|
|
112
|
-
elif origin in (dict, Dict):
|
|
113
|
-
if not args or len(args) < 2:
|
|
114
|
-
warnings.warn(
|
|
115
|
-
f"Dictionary type hint {type_hint} missing type arguments. "
|
|
116
|
-
"Defaulting to Dict[str, Any].",
|
|
117
|
-
UserWarning
|
|
118
|
-
)
|
|
119
|
-
return {"example_key": "<Any value>"} # Default for Dict[str, Any]
|
|
120
|
-
return {get_example_value(args[0]): get_example_value(args[1])}
|
|
121
|
-
elif origin is Union:
|
|
122
|
-
non_none_types = [t for t in args if t is not type(None)]
|
|
123
|
-
return get_example_value(non_none_types[0])
|
|
124
|
-
else:
|
|
125
|
-
return "<Unknown>"
|
|
126
|
-
|
|
127
|
-
def add_json_instructions_to_messages(
|
|
128
|
-
system_message,
|
|
129
|
-
user_message,
|
|
130
|
-
response_model: Optional[Type[BaseModel]] = None,
|
|
131
|
-
previously_failed_error_messages: List[str] = [],
|
|
132
|
-
) -> Tuple[str, str]:
|
|
133
|
-
if response_model:
|
|
134
|
-
type_hints = get_type_hints(response_model)
|
|
135
|
-
stringified_fields = {}
|
|
136
|
-
for key, type_hint in type_hints.items():
|
|
137
|
-
example_value = get_example_value(type_hint)
|
|
138
|
-
field = response_model.model_fields[key] # Updated for Pydantic v2
|
|
139
|
-
|
|
140
|
-
# Adjusted for Pydantic v2
|
|
141
|
-
field_description = field.description if hasattr(field, 'description') else None
|
|
142
|
-
|
|
143
|
-
if field_description:
|
|
144
|
-
stringified_fields[key] = (example_value, field_description)
|
|
145
|
-
else:
|
|
146
|
-
stringified_fields[key] = example_value
|
|
147
|
-
example_json = json.dumps(
|
|
148
|
-
{k: v[0] if isinstance(v, tuple) else v for k, v in stringified_fields.items()},
|
|
149
|
-
indent=4
|
|
150
|
-
)
|
|
151
|
-
description_comments = "\n".join(
|
|
152
|
-
f'// {k}: {v[1]}' for k, v in stringified_fields.items() if isinstance(v, tuple)
|
|
153
|
-
)
|
|
154
|
-
system_message += f"""\n\n
|
|
155
|
-
Please deliver your response in the following JSON format:
|
|
156
|
-
```json
|
|
157
|
-
{example_json}
|
|
158
|
-
```
|
|
159
|
-
{description_comments}
|
|
160
|
-
"""
|
|
161
|
-
if len(previously_failed_error_messages) != 0:
|
|
162
|
-
system_message += f"""\n\n
|
|
163
|
-
Please take special care to follow the format exactly.
|
|
164
|
-
Keep in mind the following:
|
|
165
|
-
- Always use double quotes for strings
|
|
166
|
-
|
|
167
|
-
Here are some error traces from previous attempts:
|
|
168
|
-
{previously_failed_error_messages}
|
|
169
|
-
"""
|
|
170
|
-
return system_message, user_message
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def inject_structured_output_instructions(
|
|
174
|
-
messages: List[Dict[str, str]],
|
|
175
|
-
response_model: Optional[Type[BaseModel]] = None,
|
|
176
|
-
previously_failed_error_messages: List[str] = [],
|
|
177
|
-
) -> List[Dict[str, str]]:
|
|
178
|
-
prev_system_message_content = messages[0]["content"]
|
|
179
|
-
prev_user_message_content = messages[1]["content"]
|
|
180
|
-
system_message, user_message = add_json_instructions_to_messages(
|
|
181
|
-
prev_system_message_content, prev_user_message_content, response_model, previously_failed_error_messages
|
|
182
|
-
)
|
|
183
|
-
messages[0]["content"] = system_message
|
|
184
|
-
messages[1]["content"] = user_message
|
|
185
|
-
return messages
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/public_tests/test_recursive_structured_outputs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/structured_outputs/__init__.py
RENAMED
|
File without changes
|
{synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/structured_outputs/handler.py
RENAMED
|
File without changes
|
{synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/structured_outputs/rehabilitate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/core/anthropic_api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/supported/__init__.py
RENAMED
|
File without changes
|
{synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/supported/deepseek.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{synth_ai-0.1.0.dev14 → synth_ai-0.1.0.dev15}/synth_ai/zyk/lms/vendors/supported/together.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|