agenta 0.27.0a9__py3-none-any.whl → 0.27.0a13__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.

Files changed (62) hide show
  1. agenta/__init__.py +21 -3
  2. agenta/client/backend/__init__.py +14 -0
  3. agenta/client/backend/apps/client.py +28 -20
  4. agenta/client/backend/client.py +25 -2
  5. agenta/client/backend/containers/client.py +5 -1
  6. agenta/client/backend/core/__init__.py +2 -1
  7. agenta/client/backend/core/client_wrapper.py +6 -6
  8. agenta/client/backend/core/file.py +33 -11
  9. agenta/client/backend/core/http_client.py +24 -18
  10. agenta/client/backend/core/pydantic_utilities.py +144 -29
  11. agenta/client/backend/core/request_options.py +3 -0
  12. agenta/client/backend/core/serialization.py +139 -42
  13. agenta/client/backend/evaluations/client.py +7 -2
  14. agenta/client/backend/evaluators/client.py +349 -1
  15. agenta/client/backend/observability/client.py +11 -2
  16. agenta/client/backend/testsets/client.py +10 -10
  17. agenta/client/backend/types/__init__.py +14 -0
  18. agenta/client/backend/types/app.py +1 -0
  19. agenta/client/backend/types/app_variant_response.py +3 -1
  20. agenta/client/backend/types/config_dto.py +32 -0
  21. agenta/client/backend/types/config_response_model.py +32 -0
  22. agenta/client/backend/types/create_span.py +3 -2
  23. agenta/client/backend/types/environment_output.py +1 -0
  24. agenta/client/backend/types/environment_output_extended.py +1 -0
  25. agenta/client/backend/types/evaluation.py +1 -2
  26. agenta/client/backend/types/evaluator.py +2 -0
  27. agenta/client/backend/types/evaluator_config.py +1 -0
  28. agenta/client/backend/types/evaluator_mapping_output_interface.py +21 -0
  29. agenta/client/backend/types/evaluator_output_interface.py +21 -0
  30. agenta/client/backend/types/human_evaluation.py +1 -2
  31. agenta/client/backend/types/lifecycle_dto.py +24 -0
  32. agenta/client/backend/types/llm_tokens.py +2 -2
  33. agenta/client/backend/types/reference_dto.py +23 -0
  34. agenta/client/backend/types/reference_request_model.py +23 -0
  35. agenta/client/backend/types/span.py +1 -0
  36. agenta/client/backend/types/span_detail.py +7 -1
  37. agenta/client/backend/types/test_set_output_response.py +5 -2
  38. agenta/client/backend/types/trace_detail.py +7 -1
  39. agenta/client/backend/types/with_pagination.py +4 -2
  40. agenta/client/backend/variants/client.py +1565 -272
  41. agenta/docker/docker-assets/Dockerfile.cloud.template +1 -1
  42. agenta/sdk/__init__.py +19 -5
  43. agenta/sdk/agenta_init.py +21 -7
  44. agenta/sdk/context/routing.py +6 -5
  45. agenta/sdk/decorators/routing.py +16 -5
  46. agenta/sdk/decorators/tracing.py +16 -9
  47. agenta/sdk/litellm/litellm.py +47 -36
  48. agenta/sdk/managers/__init__.py +6 -0
  49. agenta/sdk/managers/config.py +318 -0
  50. agenta/sdk/managers/deployment.py +45 -0
  51. agenta/sdk/managers/shared.py +639 -0
  52. agenta/sdk/managers/variant.py +182 -0
  53. agenta/sdk/tracing/exporters.py +0 -1
  54. agenta/sdk/tracing/inline.py +46 -1
  55. agenta/sdk/tracing/processors.py +0 -1
  56. agenta/sdk/types.py +47 -2
  57. agenta/sdk/utils/exceptions.py +31 -1
  58. {agenta-0.27.0a9.dist-info → agenta-0.27.0a13.dist-info}/METADATA +1 -1
  59. {agenta-0.27.0a9.dist-info → agenta-0.27.0a13.dist-info}/RECORD +61 -50
  60. agenta/sdk/config_manager.py +0 -205
  61. {agenta-0.27.0a9.dist-info → agenta-0.27.0a13.dist-info}/WHEEL +0 -0
  62. {agenta-0.27.0a9.dist-info → agenta-0.27.0a13.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(object_)
65
+ return adapter.validate_python(dealiased_object)
62
66
  else:
63
- return pydantic.parse_obj_as(type_, object_)
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
- class Config:
79
- populate_by_name = True
80
- json_encoders = {dt.datetime: serialize_datetime}
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
- _fields_set = self.__fields_set__
99
-
100
- fields = _get_model_fields(self.__class__)
101
- for name, field in fields.items():
102
- if name not in _fields_set:
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
- return super().model_dump(**kwargs_with_defaults_exclude_unset) # type: ignore # Pydantic v2
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
- return super().dict(**kwargs_with_defaults_exclude_unset)
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 _convert_typeddict(object_, clean_type)
74
+ return _convert_mapping(object_, clean_type, direction)
63
75
 
64
76
  if (
65
- # If you're iterating on a string, do not bother to coerce it to a sequence.
66
- (not isinstance(object_, str))
67
- and (
68
- (
69
- (
70
- typing_extensions.get_origin(clean_type) == typing.List
71
- or typing_extensions.get_origin(clean_type) == list
72
- or clean_type == typing.List
73
- )
74
- and isinstance(object_, typing.List)
75
- )
76
- or (
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
- or (
85
- (
86
- typing_extensions.get_origin(clean_type) == typing.Sequence
87
- or typing_extensions.get_origin(clean_type)
88
- == collections.abc.Sequence
89
- or clean_type == typing.Sequence
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
- and isinstance(object_, typing.Sequence)
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
- inner_type = typing_extensions.get_args(clean_type)[0]
96
- return [
97
- convert_and_respect_annotation_metadata(
98
- object_=item, annotation=annotation, inner_type=inner_type
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
- for item in object_
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_, annotation=annotation, inner_type=member
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 _convert_typeddict(
124
- object_: typing.Mapping[str, object], expected_type: typing.Any
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
- type_ = annotations.get(key)
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(object_=value, annotation=type_)
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 _alias_key(key: str, type_: typing.Any) -> str:
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
- return key
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": 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": 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
  },