agenta 0.27.0a9__py3-none-any.whl → 0.27.0a12__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.
Potentially problematic release.
This version of agenta might be problematic. Click here for more details.
- agenta/__init__.py +21 -3
- agenta/client/backend/__init__.py +14 -0
- agenta/client/backend/apps/client.py +28 -20
- agenta/client/backend/client.py +25 -2
- agenta/client/backend/containers/client.py +5 -1
- agenta/client/backend/core/__init__.py +2 -1
- agenta/client/backend/core/client_wrapper.py +6 -6
- agenta/client/backend/core/file.py +33 -11
- agenta/client/backend/core/http_client.py +24 -18
- agenta/client/backend/core/pydantic_utilities.py +144 -29
- agenta/client/backend/core/request_options.py +3 -0
- agenta/client/backend/core/serialization.py +139 -42
- agenta/client/backend/evaluations/client.py +7 -2
- agenta/client/backend/evaluators/client.py +349 -1
- agenta/client/backend/observability/client.py +11 -2
- agenta/client/backend/testsets/client.py +10 -10
- agenta/client/backend/types/__init__.py +14 -0
- agenta/client/backend/types/app.py +1 -0
- agenta/client/backend/types/app_variant_response.py +3 -1
- agenta/client/backend/types/config_dto.py +32 -0
- agenta/client/backend/types/config_response_model.py +32 -0
- agenta/client/backend/types/create_span.py +3 -2
- agenta/client/backend/types/environment_output.py +1 -0
- agenta/client/backend/types/environment_output_extended.py +1 -0
- agenta/client/backend/types/evaluation.py +1 -2
- agenta/client/backend/types/evaluator.py +2 -0
- agenta/client/backend/types/evaluator_config.py +1 -0
- agenta/client/backend/types/evaluator_mapping_output_interface.py +21 -0
- agenta/client/backend/types/evaluator_output_interface.py +21 -0
- agenta/client/backend/types/human_evaluation.py +1 -2
- agenta/client/backend/types/lifecycle_dto.py +24 -0
- agenta/client/backend/types/llm_tokens.py +2 -2
- agenta/client/backend/types/reference_dto.py +23 -0
- agenta/client/backend/types/reference_request_model.py +23 -0
- agenta/client/backend/types/span.py +1 -0
- agenta/client/backend/types/span_detail.py +7 -1
- agenta/client/backend/types/test_set_output_response.py +5 -2
- agenta/client/backend/types/trace_detail.py +7 -1
- agenta/client/backend/types/with_pagination.py +4 -2
- agenta/client/backend/variants/client.py +1565 -272
- agenta/sdk/__init__.py +19 -5
- agenta/sdk/agenta_init.py +21 -7
- agenta/sdk/context/routing.py +6 -5
- agenta/sdk/decorators/routing.py +16 -5
- agenta/sdk/decorators/tracing.py +16 -9
- agenta/sdk/litellm/litellm.py +47 -36
- agenta/sdk/managers/__init__.py +6 -0
- agenta/sdk/managers/config.py +318 -0
- agenta/sdk/managers/deployment.py +45 -0
- agenta/sdk/managers/shared.py +639 -0
- agenta/sdk/managers/variant.py +182 -0
- agenta/sdk/tracing/exporters.py +0 -1
- agenta/sdk/tracing/inline.py +45 -0
- agenta/sdk/tracing/processors.py +0 -1
- agenta/sdk/types.py +47 -2
- agenta/sdk/utils/exceptions.py +31 -1
- {agenta-0.27.0a9.dist-info → agenta-0.27.0a12.dist-info}/METADATA +1 -1
- {agenta-0.27.0a9.dist-info → agenta-0.27.0a12.dist-info}/RECORD +60 -49
- agenta/sdk/config_manager.py +0 -205
- {agenta-0.27.0a9.dist-info → agenta-0.27.0a12.dist-info}/WHEEL +0 -0
- {agenta-0.27.0a9.dist-info → agenta-0.27.0a12.dist-info}/entry_points.txt +0 -0
|
@@ -10,6 +10,7 @@ import typing_extensions
|
|
|
10
10
|
import pydantic
|
|
11
11
|
|
|
12
12
|
from .datetime_utils import serialize_datetime
|
|
13
|
+
from .serialization import convert_and_respect_annotation_metadata
|
|
13
14
|
|
|
14
15
|
IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
|
|
15
16
|
|
|
@@ -56,11 +57,14 @@ Model = typing.TypeVar("Model", bound=pydantic.BaseModel)
|
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T:
|
|
60
|
+
dealiased_object = convert_and_respect_annotation_metadata(
|
|
61
|
+
object_=object_, annotation=type_, direction="read"
|
|
62
|
+
)
|
|
59
63
|
if IS_PYDANTIC_V2:
|
|
60
64
|
adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2
|
|
61
|
-
return adapter.validate_python(
|
|
65
|
+
return adapter.validate_python(dealiased_object)
|
|
62
66
|
else:
|
|
63
|
-
return pydantic.parse_obj_as(type_,
|
|
67
|
+
return pydantic.parse_obj_as(type_, dealiased_object)
|
|
64
68
|
|
|
65
69
|
|
|
66
70
|
def to_jsonable_with_fallback(
|
|
@@ -75,9 +79,53 @@ def to_jsonable_with_fallback(
|
|
|
75
79
|
|
|
76
80
|
|
|
77
81
|
class UniversalBaseModel(pydantic.BaseModel):
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
if IS_PYDANTIC_V2:
|
|
83
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(
|
|
84
|
+
# Allow fields begining with `model_` to be used in the model
|
|
85
|
+
protected_namespaces=(),
|
|
86
|
+
) # type: ignore # Pydantic v2
|
|
87
|
+
|
|
88
|
+
@pydantic.model_serializer(mode="wrap", when_used="json") # type: ignore # Pydantic v2
|
|
89
|
+
def serialize_model(
|
|
90
|
+
self, handler: pydantic.SerializerFunctionWrapHandler
|
|
91
|
+
) -> typing.Any: # type: ignore # Pydantic v2
|
|
92
|
+
serialized = handler(self)
|
|
93
|
+
data = {
|
|
94
|
+
k: serialize_datetime(v) if isinstance(v, dt.datetime) else v
|
|
95
|
+
for k, v in serialized.items()
|
|
96
|
+
}
|
|
97
|
+
return data
|
|
98
|
+
|
|
99
|
+
else:
|
|
100
|
+
|
|
101
|
+
class Config:
|
|
102
|
+
smart_union = True
|
|
103
|
+
json_encoders = {dt.datetime: serialize_datetime}
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def model_construct(
|
|
107
|
+
cls: typing.Type["Model"],
|
|
108
|
+
_fields_set: typing.Optional[typing.Set[str]] = None,
|
|
109
|
+
**values: typing.Any,
|
|
110
|
+
) -> "Model":
|
|
111
|
+
dealiased_object = convert_and_respect_annotation_metadata(
|
|
112
|
+
object_=values, annotation=cls, direction="read"
|
|
113
|
+
)
|
|
114
|
+
return cls.construct(_fields_set, **dealiased_object)
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def construct(
|
|
118
|
+
cls: typing.Type["Model"],
|
|
119
|
+
_fields_set: typing.Optional[typing.Set[str]] = None,
|
|
120
|
+
**values: typing.Any,
|
|
121
|
+
) -> "Model":
|
|
122
|
+
dealiased_object = convert_and_respect_annotation_metadata(
|
|
123
|
+
object_=values, annotation=cls, direction="read"
|
|
124
|
+
)
|
|
125
|
+
if IS_PYDANTIC_V2:
|
|
126
|
+
return super().model_construct(_fields_set, **dealiased_object) # type: ignore # Pydantic v2
|
|
127
|
+
else:
|
|
128
|
+
return super().construct(_fields_set, **dealiased_object)
|
|
81
129
|
|
|
82
130
|
def json(self, **kwargs: typing.Any) -> str:
|
|
83
131
|
kwargs_with_defaults: typing.Any = {
|
|
@@ -95,30 +143,97 @@ class UniversalBaseModel(pydantic.BaseModel):
|
|
|
95
143
|
Override the default dict method to `exclude_unset` by default. This function patches
|
|
96
144
|
`exclude_unset` to work include fields within non-None default values.
|
|
97
145
|
"""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for
|
|
102
|
-
|
|
103
|
-
default = _get_field_default(field)
|
|
104
|
-
|
|
105
|
-
# If the default values are non-null act like they've been set
|
|
106
|
-
# This effectively allows exclude_unset to work like exclude_none where
|
|
107
|
-
# the latter passes through intentionally set none values.
|
|
108
|
-
if default != None:
|
|
109
|
-
_fields_set.add(name)
|
|
110
|
-
|
|
111
|
-
kwargs_with_defaults_exclude_unset: typing.Any = {
|
|
112
|
-
"by_alias": True,
|
|
113
|
-
"exclude_unset": True,
|
|
114
|
-
"include": _fields_set,
|
|
115
|
-
**kwargs,
|
|
116
|
-
}
|
|
117
|
-
|
|
146
|
+
# Note: the logic here is multi-plexed given the levers exposed in Pydantic V1 vs V2
|
|
147
|
+
# Pydantic V1's .dict can be extremely slow, so we do not want to call it twice.
|
|
148
|
+
#
|
|
149
|
+
# We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models
|
|
150
|
+
# that we have less control over, and this is less intrusive than custom serializers for now.
|
|
118
151
|
if IS_PYDANTIC_V2:
|
|
119
|
-
|
|
152
|
+
kwargs_with_defaults_exclude_unset: typing.Any = {
|
|
153
|
+
**kwargs,
|
|
154
|
+
"by_alias": True,
|
|
155
|
+
"exclude_unset": True,
|
|
156
|
+
"exclude_none": False,
|
|
157
|
+
}
|
|
158
|
+
kwargs_with_defaults_exclude_none: typing.Any = {
|
|
159
|
+
**kwargs,
|
|
160
|
+
"by_alias": True,
|
|
161
|
+
"exclude_none": True,
|
|
162
|
+
"exclude_unset": False,
|
|
163
|
+
}
|
|
164
|
+
dict_dump = deep_union_pydantic_dicts(
|
|
165
|
+
super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2
|
|
166
|
+
super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
else:
|
|
170
|
+
_fields_set = self.__fields_set__.copy()
|
|
171
|
+
|
|
172
|
+
fields = _get_model_fields(self.__class__)
|
|
173
|
+
for name, field in fields.items():
|
|
174
|
+
if name not in _fields_set:
|
|
175
|
+
default = _get_field_default(field)
|
|
176
|
+
|
|
177
|
+
# If the default values are non-null act like they've been set
|
|
178
|
+
# This effectively allows exclude_unset to work like exclude_none where
|
|
179
|
+
# the latter passes through intentionally set none values.
|
|
180
|
+
if default is not None or (
|
|
181
|
+
"exclude_unset" in kwargs and not kwargs["exclude_unset"]
|
|
182
|
+
):
|
|
183
|
+
_fields_set.add(name)
|
|
184
|
+
|
|
185
|
+
if default is not None:
|
|
186
|
+
self.__fields_set__.add(name)
|
|
187
|
+
|
|
188
|
+
kwargs_with_defaults_exclude_unset_include_fields: typing.Any = {
|
|
189
|
+
"by_alias": True,
|
|
190
|
+
"exclude_unset": True,
|
|
191
|
+
"include": _fields_set,
|
|
192
|
+
**kwargs,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
dict_dump = super().dict(
|
|
196
|
+
**kwargs_with_defaults_exclude_unset_include_fields
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return convert_and_respect_annotation_metadata(
|
|
200
|
+
object_=dict_dump, annotation=self.__class__, direction="write"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _union_list_of_pydantic_dicts(
|
|
205
|
+
source: typing.List[typing.Any], destination: typing.List[typing.Any]
|
|
206
|
+
) -> typing.List[typing.Any]:
|
|
207
|
+
converted_list: typing.List[typing.Any] = []
|
|
208
|
+
for i, item in enumerate(source):
|
|
209
|
+
destination_value = destination[i] # type: ignore
|
|
210
|
+
if isinstance(item, dict):
|
|
211
|
+
converted_list.append(deep_union_pydantic_dicts(item, destination_value))
|
|
212
|
+
elif isinstance(item, list):
|
|
213
|
+
converted_list.append(
|
|
214
|
+
_union_list_of_pydantic_dicts(item, destination_value)
|
|
215
|
+
)
|
|
120
216
|
else:
|
|
121
|
-
|
|
217
|
+
converted_list.append(item)
|
|
218
|
+
return converted_list
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def deep_union_pydantic_dicts(
|
|
222
|
+
source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any]
|
|
223
|
+
) -> typing.Dict[str, typing.Any]:
|
|
224
|
+
for key, value in source.items():
|
|
225
|
+
node = destination.setdefault(key, {})
|
|
226
|
+
if isinstance(value, dict):
|
|
227
|
+
deep_union_pydantic_dicts(value, node)
|
|
228
|
+
# Note: we do not do this same processing for sets given we do not have sets of models
|
|
229
|
+
# and given the sets are unordered, the processing of the set and matching objects would
|
|
230
|
+
# be non-trivial.
|
|
231
|
+
elif isinstance(value, list):
|
|
232
|
+
destination[key] = _union_list_of_pydantic_dicts(value, node)
|
|
233
|
+
else:
|
|
234
|
+
destination[key] = value
|
|
235
|
+
|
|
236
|
+
return destination
|
|
122
237
|
|
|
123
238
|
|
|
124
239
|
if IS_PYDANTIC_V2:
|
|
@@ -145,11 +260,11 @@ def encode_by_type(o: typing.Any) -> typing.Any:
|
|
|
145
260
|
return encoder(o)
|
|
146
261
|
|
|
147
262
|
|
|
148
|
-
def update_forward_refs(model: typing.Type["Model"]) -> None:
|
|
263
|
+
def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None:
|
|
149
264
|
if IS_PYDANTIC_V2:
|
|
150
265
|
model.model_rebuild(raise_errors=False) # type: ignore # Pydantic v2
|
|
151
266
|
else:
|
|
152
|
-
model.update_forward_refs()
|
|
267
|
+
model.update_forward_refs(**localns)
|
|
153
268
|
|
|
154
269
|
|
|
155
270
|
# Mirrors Pydantic's internal typing
|
|
@@ -23,6 +23,8 @@ class RequestOptions(typing.TypedDict, total=False):
|
|
|
23
23
|
- additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict
|
|
24
24
|
|
|
25
25
|
- additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict
|
|
26
|
+
|
|
27
|
+
- chunk_size: int. The size, in bytes, to process each chunk of data being streamed back within the response. This equates to leveraging `chunk_size` within `requests` or `httpx`, and is only leveraged for file downloads.
|
|
26
28
|
"""
|
|
27
29
|
|
|
28
30
|
timeout_in_seconds: NotRequired[int]
|
|
@@ -30,3 +32,4 @@ class RequestOptions(typing.TypedDict, total=False):
|
|
|
30
32
|
additional_headers: NotRequired[typing.Dict[str, typing.Any]]
|
|
31
33
|
additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]]
|
|
32
34
|
additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]]
|
|
35
|
+
chunk_size: NotRequired[int]
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# This file was auto-generated by Fern from our API Definition.
|
|
2
2
|
|
|
3
3
|
import collections
|
|
4
|
+
import inspect
|
|
4
5
|
import typing
|
|
5
6
|
|
|
6
7
|
import typing_extensions
|
|
7
8
|
|
|
9
|
+
import pydantic
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
class FieldMetadata:
|
|
10
13
|
"""
|
|
@@ -29,6 +32,7 @@ def convert_and_respect_annotation_metadata(
|
|
|
29
32
|
object_: typing.Any,
|
|
30
33
|
annotation: typing.Any,
|
|
31
34
|
inner_type: typing.Optional[typing.Any] = None,
|
|
35
|
+
direction: typing.Literal["read", "write"],
|
|
32
36
|
) -> typing.Any:
|
|
33
37
|
"""
|
|
34
38
|
Respect the metadata annotations on a field, such as aliasing. This function effectively
|
|
@@ -56,49 +60,79 @@ def convert_and_respect_annotation_metadata(
|
|
|
56
60
|
inner_type = annotation
|
|
57
61
|
|
|
58
62
|
clean_type = _remove_annotations(inner_type)
|
|
63
|
+
# Pydantic models
|
|
64
|
+
if (
|
|
65
|
+
inspect.isclass(clean_type)
|
|
66
|
+
and issubclass(clean_type, pydantic.BaseModel)
|
|
67
|
+
and isinstance(object_, typing.Mapping)
|
|
68
|
+
):
|
|
69
|
+
return _convert_mapping(object_, clean_type, direction)
|
|
70
|
+
# TypedDicts
|
|
59
71
|
if typing_extensions.is_typeddict(clean_type) and isinstance(
|
|
60
72
|
object_, typing.Mapping
|
|
61
73
|
):
|
|
62
|
-
return
|
|
74
|
+
return _convert_mapping(object_, clean_type, direction)
|
|
63
75
|
|
|
64
76
|
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
typing_extensions.get_origin(clean_type) == typing.Set
|
|
79
|
-
or typing_extensions.get_origin(clean_type) == set
|
|
80
|
-
or clean_type == typing.Set
|
|
81
|
-
)
|
|
82
|
-
and isinstance(object_, typing.Set)
|
|
77
|
+
typing_extensions.get_origin(clean_type) == typing.Dict
|
|
78
|
+
or typing_extensions.get_origin(clean_type) == dict
|
|
79
|
+
or clean_type == typing.Dict
|
|
80
|
+
) and isinstance(object_, typing.Dict):
|
|
81
|
+
key_type = typing_extensions.get_args(clean_type)[0]
|
|
82
|
+
value_type = typing_extensions.get_args(clean_type)[1]
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
key: convert_and_respect_annotation_metadata(
|
|
86
|
+
object_=value,
|
|
87
|
+
annotation=annotation,
|
|
88
|
+
inner_type=value_type,
|
|
89
|
+
direction=direction,
|
|
83
90
|
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
for key, value in object_.items()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# If you're iterating on a string, do not bother to coerce it to a sequence.
|
|
95
|
+
if not isinstance(object_, str):
|
|
96
|
+
if (
|
|
97
|
+
typing_extensions.get_origin(clean_type) == typing.Set
|
|
98
|
+
or typing_extensions.get_origin(clean_type) == set
|
|
99
|
+
or clean_type == typing.Set
|
|
100
|
+
) and isinstance(object_, typing.Set):
|
|
101
|
+
inner_type = typing_extensions.get_args(clean_type)[0]
|
|
102
|
+
return {
|
|
103
|
+
convert_and_respect_annotation_metadata(
|
|
104
|
+
object_=item,
|
|
105
|
+
annotation=annotation,
|
|
106
|
+
inner_type=inner_type,
|
|
107
|
+
direction=direction,
|
|
90
108
|
)
|
|
91
|
-
|
|
109
|
+
for item in object_
|
|
110
|
+
}
|
|
111
|
+
elif (
|
|
112
|
+
(
|
|
113
|
+
typing_extensions.get_origin(clean_type) == typing.List
|
|
114
|
+
or typing_extensions.get_origin(clean_type) == list
|
|
115
|
+
or clean_type == typing.List
|
|
92
116
|
)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
and isinstance(object_, typing.List)
|
|
118
|
+
) or (
|
|
119
|
+
(
|
|
120
|
+
typing_extensions.get_origin(clean_type) == typing.Sequence
|
|
121
|
+
or typing_extensions.get_origin(clean_type) == collections.abc.Sequence
|
|
122
|
+
or clean_type == typing.Sequence
|
|
99
123
|
)
|
|
100
|
-
|
|
101
|
-
|
|
124
|
+
and isinstance(object_, typing.Sequence)
|
|
125
|
+
):
|
|
126
|
+
inner_type = typing_extensions.get_args(clean_type)[0]
|
|
127
|
+
return [
|
|
128
|
+
convert_and_respect_annotation_metadata(
|
|
129
|
+
object_=item,
|
|
130
|
+
annotation=annotation,
|
|
131
|
+
inner_type=inner_type,
|
|
132
|
+
direction=direction,
|
|
133
|
+
)
|
|
134
|
+
for item in object_
|
|
135
|
+
]
|
|
102
136
|
|
|
103
137
|
if typing_extensions.get_origin(clean_type) == typing.Union:
|
|
104
138
|
# We should be able to ~relatively~ safely try to convert keys against all
|
|
@@ -107,7 +141,10 @@ def convert_and_respect_annotation_metadata(
|
|
|
107
141
|
# Or if another member aliases a field of the same name that another member does not.
|
|
108
142
|
for member in typing_extensions.get_args(clean_type):
|
|
109
143
|
object_ = convert_and_respect_annotation_metadata(
|
|
110
|
-
object_=object_,
|
|
144
|
+
object_=object_,
|
|
145
|
+
annotation=annotation,
|
|
146
|
+
inner_type=member,
|
|
147
|
+
direction=direction,
|
|
111
148
|
)
|
|
112
149
|
return object_
|
|
113
150
|
|
|
@@ -120,19 +157,37 @@ def convert_and_respect_annotation_metadata(
|
|
|
120
157
|
return object_
|
|
121
158
|
|
|
122
159
|
|
|
123
|
-
def
|
|
124
|
-
object_: typing.Mapping[str, object],
|
|
160
|
+
def _convert_mapping(
|
|
161
|
+
object_: typing.Mapping[str, object],
|
|
162
|
+
expected_type: typing.Any,
|
|
163
|
+
direction: typing.Literal["read", "write"],
|
|
125
164
|
) -> typing.Mapping[str, object]:
|
|
126
165
|
converted_object: typing.Dict[str, object] = {}
|
|
127
166
|
annotations = typing_extensions.get_type_hints(expected_type, include_extras=True)
|
|
167
|
+
aliases_to_field_names = _get_alias_to_field_name(annotations)
|
|
128
168
|
for key, value in object_.items():
|
|
129
|
-
|
|
169
|
+
if direction == "read" and key in aliases_to_field_names:
|
|
170
|
+
dealiased_key = aliases_to_field_names.get(key)
|
|
171
|
+
if dealiased_key is not None:
|
|
172
|
+
type_ = annotations.get(dealiased_key)
|
|
173
|
+
else:
|
|
174
|
+
type_ = annotations.get(key)
|
|
175
|
+
# Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map
|
|
176
|
+
#
|
|
177
|
+
# So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias
|
|
178
|
+
# then we can just pass the value through as is
|
|
130
179
|
if type_ is None:
|
|
131
180
|
converted_object[key] = value
|
|
181
|
+
elif direction == "read" and key not in aliases_to_field_names:
|
|
182
|
+
converted_object[key] = convert_and_respect_annotation_metadata(
|
|
183
|
+
object_=value, annotation=type_, direction=direction
|
|
184
|
+
)
|
|
132
185
|
else:
|
|
133
186
|
converted_object[
|
|
134
|
-
_alias_key(key, type_)
|
|
135
|
-
] = convert_and_respect_annotation_metadata(
|
|
187
|
+
_alias_key(key, type_, direction, aliases_to_field_names)
|
|
188
|
+
] = convert_and_respect_annotation_metadata(
|
|
189
|
+
object_=value, annotation=type_, direction=direction
|
|
190
|
+
)
|
|
136
191
|
return converted_object
|
|
137
192
|
|
|
138
193
|
|
|
@@ -165,7 +220,39 @@ def _remove_annotations(type_: typing.Any) -> typing.Any:
|
|
|
165
220
|
return type_
|
|
166
221
|
|
|
167
222
|
|
|
168
|
-
def
|
|
223
|
+
def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]:
|
|
224
|
+
annotations = typing_extensions.get_type_hints(type_, include_extras=True)
|
|
225
|
+
return _get_alias_to_field_name(annotations)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]:
|
|
229
|
+
annotations = typing_extensions.get_type_hints(type_, include_extras=True)
|
|
230
|
+
return _get_field_to_alias_name(annotations)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _get_alias_to_field_name(
|
|
234
|
+
field_to_hint: typing.Dict[str, typing.Any],
|
|
235
|
+
) -> typing.Dict[str, str]:
|
|
236
|
+
aliases = {}
|
|
237
|
+
for field, hint in field_to_hint.items():
|
|
238
|
+
maybe_alias = _get_alias_from_type(hint)
|
|
239
|
+
if maybe_alias is not None:
|
|
240
|
+
aliases[maybe_alias] = field
|
|
241
|
+
return aliases
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _get_field_to_alias_name(
|
|
245
|
+
field_to_hint: typing.Dict[str, typing.Any],
|
|
246
|
+
) -> typing.Dict[str, str]:
|
|
247
|
+
aliases = {}
|
|
248
|
+
for field, hint in field_to_hint.items():
|
|
249
|
+
maybe_alias = _get_alias_from_type(hint)
|
|
250
|
+
if maybe_alias is not None:
|
|
251
|
+
aliases[field] = maybe_alias
|
|
252
|
+
return aliases
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]:
|
|
169
256
|
maybe_annotated_type = _get_annotation(type_)
|
|
170
257
|
|
|
171
258
|
if maybe_annotated_type is not None:
|
|
@@ -175,5 +262,15 @@ def _alias_key(key: str, type_: typing.Any) -> str:
|
|
|
175
262
|
for annotation in annotations:
|
|
176
263
|
if isinstance(annotation, FieldMetadata) and annotation.alias is not None:
|
|
177
264
|
return annotation.alias
|
|
265
|
+
return None
|
|
266
|
+
|
|
178
267
|
|
|
179
|
-
|
|
268
|
+
def _alias_key(
|
|
269
|
+
key: str,
|
|
270
|
+
type_: typing.Any,
|
|
271
|
+
direction: typing.Literal["read", "write"],
|
|
272
|
+
aliases_to_field_names: typing.Dict[str, str],
|
|
273
|
+
) -> str:
|
|
274
|
+
if direction == "read":
|
|
275
|
+
return aliases_to_field_names.get(key, key)
|
|
276
|
+
return _get_alias_from_type(type_=type_) or key
|
|
@@ -10,6 +10,7 @@ from json.decoder import JSONDecodeError
|
|
|
10
10
|
from ..core.api_error import ApiError
|
|
11
11
|
from ..types.evaluation import Evaluation
|
|
12
12
|
from ..types.llm_run_rate_limit import LlmRunRateLimit
|
|
13
|
+
from ..core.serialization import convert_and_respect_annotation_metadata
|
|
13
14
|
from ..core.jsonable_encoder import jsonable_encoder
|
|
14
15
|
from ..types.evaluation_scenario import EvaluationScenario
|
|
15
16
|
from ..core.client_wrapper import AsyncClientWrapper
|
|
@@ -249,7 +250,9 @@ class EvaluationsClient:
|
|
|
249
250
|
"variant_ids": variant_ids,
|
|
250
251
|
"evaluators_configs": evaluators_configs,
|
|
251
252
|
"testset_id": testset_id,
|
|
252
|
-
"rate_limit":
|
|
253
|
+
"rate_limit": convert_and_respect_annotation_metadata(
|
|
254
|
+
object_=rate_limit, annotation=LlmRunRateLimit, direction="write"
|
|
255
|
+
),
|
|
253
256
|
"lm_providers_keys": lm_providers_keys,
|
|
254
257
|
"correct_answer_column": correct_answer_column,
|
|
255
258
|
},
|
|
@@ -959,7 +962,9 @@ class AsyncEvaluationsClient:
|
|
|
959
962
|
"variant_ids": variant_ids,
|
|
960
963
|
"evaluators_configs": evaluators_configs,
|
|
961
964
|
"testset_id": testset_id,
|
|
962
|
-
"rate_limit":
|
|
965
|
+
"rate_limit": convert_and_respect_annotation_metadata(
|
|
966
|
+
object_=rate_limit, annotation=LlmRunRateLimit, direction="write"
|
|
967
|
+
),
|
|
963
968
|
"lm_providers_keys": lm_providers_keys,
|
|
964
969
|
"correct_answer_column": correct_answer_column,
|
|
965
970
|
},
|