lionagi 0.17.8__py3-none-any.whl → 0.17.10__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.
- lionagi/_types.py +2 -0
- lionagi/fields/reason.py +2 -0
- lionagi/ln/types.py +45 -0
- lionagi/models/field_model.py +393 -286
- lionagi/models/hashable_model.py +96 -14
- lionagi/models/model_params.py +271 -269
- lionagi/models/operable_model.py +8 -8
- lionagi/protocols/action/manager.py +12 -15
- lionagi/protocols/generic/pile.py +7 -4
- lionagi/protocols/operatives/operative.py +29 -6
- lionagi/service/connections/providers/claude_code_cli.py +1 -1
- lionagi/service/third_party/anthropic_models.py +2 -3
- lionagi/service/third_party/claude_code.py +53 -86
- lionagi/version.py +1 -1
- {lionagi-0.17.8.dist-info → lionagi-0.17.10.dist-info}/METADATA +2 -2
- {lionagi-0.17.8.dist-info → lionagi-0.17.10.dist-info}/RECORD +18 -18
- {lionagi-0.17.8.dist-info → lionagi-0.17.10.dist-info}/WHEEL +0 -0
- {lionagi-0.17.8.dist-info → lionagi-0.17.10.dist-info}/licenses/LICENSE +0 -0
lionagi/models/model_params.py
CHANGED
@@ -2,334 +2,336 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
"""ModelParams implementation using Params base class with aggressive caching.
|
6
|
+
|
7
|
+
This module provides ModelParams, a configuration class for dynamically creating
|
8
|
+
Pydantic models with explicit behavior and aggressive caching for performance.
|
9
|
+
"""
|
10
|
+
|
11
|
+
from __future__ import annotations
|
12
|
+
|
5
13
|
import inspect
|
14
|
+
import os
|
15
|
+
import threading
|
16
|
+
from collections import OrderedDict
|
6
17
|
from collections.abc import Callable
|
18
|
+
from dataclasses import dataclass
|
19
|
+
from dataclasses import field as dc_field
|
20
|
+
from typing import Any, ClassVar
|
7
21
|
|
8
|
-
from pydantic import
|
9
|
-
BaseModel,
|
10
|
-
Field,
|
11
|
-
PrivateAttr,
|
12
|
-
create_model,
|
13
|
-
field_validator,
|
14
|
-
model_validator,
|
15
|
-
)
|
22
|
+
from pydantic import BaseModel, create_model
|
16
23
|
from pydantic.fields import FieldInfo
|
17
|
-
|
18
|
-
|
19
|
-
from lionagi.libs.validate.common_field_validators import (
|
20
|
-
validate_boolean_field,
|
21
|
-
validate_list_dict_str_keys,
|
22
|
-
validate_model_to_type,
|
23
|
-
validate_nullable_string_field,
|
24
|
-
validate_same_dtype_flat_list,
|
25
|
-
validate_str_str_dict,
|
26
|
-
)
|
24
|
+
|
25
|
+
from lionagi.ln.types import Params
|
27
26
|
from lionagi.utils import copy
|
28
27
|
|
29
28
|
from .field_model import FieldModel
|
30
|
-
from .schema_model import SchemaModel
|
31
29
|
|
32
30
|
__all__ = ("ModelParams",)
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
32
|
+
# Global cache configuration
|
33
|
+
_MODEL_CACHE_SIZE = int(os.environ.get("LIONAGI_MODEL_CACHE_SIZE", "1000"))
|
34
|
+
_model_cache: OrderedDict[int, type[BaseModel]] = OrderedDict()
|
35
|
+
_model_cache_lock = threading.RLock()
|
36
|
+
|
37
|
+
|
38
|
+
@dataclass(slots=True, frozen=True, init=False)
|
39
|
+
class ModelParams(Params):
|
40
|
+
"""Configuration for dynamically creating Pydantic models.
|
41
|
+
|
42
|
+
This class provides a way to configure and create Pydantic models dynamically
|
43
|
+
with explicit behavior (no silent conversions) and aggressive caching for
|
44
|
+
performance optimization.
|
45
|
+
|
46
|
+
Key features:
|
47
|
+
- All unspecified fields are explicitly Unset (not None or empty)
|
48
|
+
- No silent type conversions - fails fast on incorrect types
|
49
|
+
- Aggressive caching of created models with LRU eviction
|
50
|
+
- Thread-safe model creation and caching
|
51
|
+
- Not directly instantiable - requires keyword arguments
|
52
|
+
|
53
|
+
Attributes:
|
54
|
+
name: Name for the generated model class
|
55
|
+
parameter_fields: Field definitions for the model
|
56
|
+
base_type: Base model class to inherit from
|
57
|
+
field_models: List of FieldModel definitions
|
58
|
+
exclude_fields: Fields to exclude from the final model
|
59
|
+
field_descriptions: Custom descriptions for fields
|
60
|
+
inherit_base: Whether to inherit from base_type
|
61
|
+
config_dict: Pydantic model configuration
|
62
|
+
doc: Docstring for the generated model
|
63
|
+
frozen: Whether the model should be immutable
|
64
|
+
|
65
|
+
Environment Variables:
|
66
|
+
LIONAGI_MODEL_CACHE_SIZE: Maximum number of cached models (default: 1000)
|
53
67
|
|
54
68
|
Examples:
|
55
69
|
>>> params = ModelParams(
|
56
70
|
... name="UserModel",
|
71
|
+
... frozen=True,
|
57
72
|
... field_models=[
|
58
|
-
... FieldModel(name="username"
|
59
|
-
... FieldModel(name="age",
|
73
|
+
... FieldModel(str, name="username"),
|
74
|
+
... FieldModel(int, name="age", default=0)
|
60
75
|
... ],
|
61
76
|
... doc="A user model with basic attributes."
|
62
77
|
... )
|
63
78
|
>>> UserModel = params.create_new_model()
|
64
|
-
"""
|
65
|
-
|
66
|
-
name: str | None = Field(
|
67
|
-
default=None, description="Name for the generated model class"
|
68
|
-
)
|
69
|
-
|
70
|
-
parameter_fields: dict[str, FieldInfo] = Field(
|
71
|
-
default_factory=dict, description="Field definitions for the model"
|
72
|
-
)
|
73
|
-
|
74
|
-
base_type: type[BaseModel] = Field(
|
75
|
-
default=BaseModel, description="Base model class to inherit from"
|
76
|
-
)
|
77
|
-
|
78
|
-
field_models: list[FieldModel] = Field(
|
79
|
-
default_factory=list, description="List of field model definitions"
|
80
|
-
)
|
81
|
-
|
82
|
-
exclude_fields: list = Field(
|
83
|
-
default_factory=list,
|
84
|
-
description="Fields to exclude from the final model",
|
85
|
-
)
|
86
|
-
|
87
|
-
field_descriptions: dict = Field(
|
88
|
-
default_factory=dict, description="Custom descriptions for fields"
|
89
|
-
)
|
90
|
-
|
91
|
-
inherit_base: bool = Field(
|
92
|
-
default=True, description="Whether to inherit from base_type"
|
93
|
-
)
|
94
79
|
|
95
|
-
|
96
|
-
|
97
|
-
|
80
|
+
>>> # All unspecified fields are Unset
|
81
|
+
>>> params2 = ModelParams(name="SimpleModel")
|
82
|
+
>>> assert params2.doc is Unset
|
83
|
+
>>> assert params2.frozen is Unset
|
84
|
+
"""
|
98
85
|
|
99
|
-
|
100
|
-
|
86
|
+
# Class configuration - let Params handle Unset population
|
87
|
+
_prefill_unset: ClassVar[bool] = True
|
88
|
+
_none_as_sentinel: ClassVar[bool] = True
|
89
|
+
|
90
|
+
# Public fields (all start as Unset when not provided)
|
91
|
+
name: str | None
|
92
|
+
parameter_fields: dict[str, FieldInfo]
|
93
|
+
base_type: type[BaseModel]
|
94
|
+
field_models: list[FieldModel]
|
95
|
+
exclude_fields: list[str]
|
96
|
+
field_descriptions: dict[str, str]
|
97
|
+
inherit_base: bool
|
98
|
+
config_dict: dict[str, Any] | None
|
99
|
+
doc: str | None
|
100
|
+
frozen: bool
|
101
|
+
|
102
|
+
# Private computed state
|
103
|
+
_final_fields: dict[str, FieldInfo] = dc_field(
|
104
|
+
default_factory=dict, init=False
|
101
105
|
)
|
102
|
-
|
103
|
-
|
104
|
-
default=False, description="Whether the model should be immutable"
|
106
|
+
_validators: dict[str, Callable] = dc_field(
|
107
|
+
default_factory=dict, init=False
|
105
108
|
)
|
106
|
-
_validators: dict[str, Callable] | None = PrivateAttr(default=None)
|
107
|
-
_use_keys: set[str] = PrivateAttr(default_factory=set)
|
108
109
|
|
109
|
-
|
110
|
-
|
111
|
-
"""Get field definitions to use in new model.
|
110
|
+
def _validate(self) -> None:
|
111
|
+
"""Validate types and setup model configuration.
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
Returns:
|
117
|
-
A dictionary mapping field names to tuples of (type, FieldInfo),
|
118
|
-
containing only the fields that should be included in the new model.
|
119
|
-
"""
|
120
|
-
params = {
|
121
|
-
k: v
|
122
|
-
for k, v in self.parameter_fields.items()
|
123
|
-
if k in self._use_keys
|
124
|
-
}
|
125
|
-
# Add field_models with proper type annotations
|
126
|
-
for f in self.field_models:
|
127
|
-
if f.name in self._use_keys:
|
128
|
-
params[f.name] = f.field_info
|
129
|
-
# Set the annotation from the FieldModel's base_type
|
130
|
-
params[f.name].annotation = f.base_type
|
131
|
-
|
132
|
-
return {k: (v.annotation, v) for k, v in params.items()}
|
133
|
-
|
134
|
-
@field_validator("parameter_fields", mode="before")
|
135
|
-
def _validate_parameters(cls, value) -> dict[str, FieldInfo]:
|
136
|
-
"""Validate parameter field definitions.
|
137
|
-
|
138
|
-
Args:
|
139
|
-
value: Value to validate.
|
140
|
-
|
141
|
-
Returns:
|
142
|
-
dict[str, FieldInfo]: Validated parameter fields.
|
143
|
-
|
144
|
-
Raises:
|
145
|
-
ValueError: If parameter fields are invalid.
|
146
|
-
"""
|
147
|
-
if value in [None, {}, []]:
|
148
|
-
return {}
|
149
|
-
if not isinstance(value, dict):
|
150
|
-
raise ValueError("Fields must be a dictionary.")
|
151
|
-
for k, v in value.items():
|
152
|
-
if not isinstance(k, str):
|
153
|
-
raise ValueError("Field names must be strings.")
|
154
|
-
if not isinstance(v, FieldInfo):
|
155
|
-
raise ValueError("Field values must be FieldInfo objects.")
|
156
|
-
return copy(value)
|
157
|
-
|
158
|
-
@field_validator("base_type", mode="before")
|
159
|
-
def _validate_base(cls, value) -> type[BaseModel]:
|
160
|
-
"""Validate base model type.
|
161
|
-
|
162
|
-
Args:
|
163
|
-
value: Value to validate.
|
164
|
-
|
165
|
-
Returns:
|
166
|
-
type[BaseModel]: Validated base model type.
|
113
|
+
This method performs minimal domain-specific validation, then processes
|
114
|
+
and merges field definitions from various sources.
|
167
115
|
|
168
116
|
Raises:
|
169
|
-
ValueError: If
|
117
|
+
ValueError: If base_type is not a BaseModel subclass
|
170
118
|
"""
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
119
|
+
# Let parent handle basic Unset population
|
120
|
+
Params._validate(self)
|
121
|
+
|
122
|
+
# Minimal domain validation - only check what matters
|
123
|
+
if not self._is_sentinel(self.base_type):
|
124
|
+
if not (
|
125
|
+
inspect.isclass(self.base_type)
|
126
|
+
and issubclass(self.base_type, BaseModel)
|
127
|
+
):
|
128
|
+
raise ValueError(
|
129
|
+
f"base_type must be BaseModel subclass, got {self.base_type}"
|
130
|
+
)
|
176
131
|
|
177
|
-
|
178
|
-
|
132
|
+
# Process and merge all field sources
|
133
|
+
self._process_fields()
|
179
134
|
|
180
|
-
|
181
|
-
|
135
|
+
def _process_fields(self) -> None:
|
136
|
+
"""Merge all field sources into final configuration.
|
182
137
|
|
183
|
-
|
184
|
-
|
138
|
+
This method processes and combines fields from parameter_fields, base_type,
|
139
|
+
and field_models, handling exclusions and descriptions.
|
185
140
|
"""
|
186
|
-
|
187
|
-
|
188
|
-
@field_validator("field_descriptions", mode="before")
|
189
|
-
def _validate_field_descriptions(cls, value) -> dict[str, str]:
|
190
|
-
"""Validate field descriptions dictionary.
|
191
|
-
|
192
|
-
Args:
|
193
|
-
value: Value to validate.
|
141
|
+
fields = {}
|
142
|
+
validators = {}
|
194
143
|
|
195
|
-
|
196
|
-
|
144
|
+
# Start with explicit parameter_fields
|
145
|
+
if not self._is_sentinel(self.parameter_fields):
|
146
|
+
# Handle empty values - treat them as no fields
|
147
|
+
if not self.parameter_fields:
|
148
|
+
pass # Empty dict/list/None - no fields to add
|
149
|
+
elif isinstance(self.parameter_fields, dict):
|
150
|
+
# Validate parameter_fields contain FieldInfo instances
|
151
|
+
for name, field_info in self.parameter_fields.items():
|
152
|
+
if not isinstance(field_info, FieldInfo):
|
153
|
+
raise ValueError(
|
154
|
+
f"parameter_fields must contain FieldInfo instances, got {type(field_info)} for field '{name}'"
|
155
|
+
)
|
156
|
+
fields.update(copy(self.parameter_fields))
|
157
|
+
else:
|
158
|
+
raise ValueError(
|
159
|
+
f"parameter_fields must be a dictionary, got {type(self.parameter_fields)}"
|
160
|
+
)
|
161
|
+
|
162
|
+
# Add base_type fields (respecting exclusions)
|
163
|
+
if not self._is_sentinel(self.base_type):
|
164
|
+
base_fields = copy(self.base_type.model_fields)
|
165
|
+
if not self._is_sentinel(self.exclude_fields):
|
166
|
+
base_fields = {
|
167
|
+
k: v
|
168
|
+
for k, v in base_fields.items()
|
169
|
+
if k not in self.exclude_fields
|
170
|
+
}
|
171
|
+
fields.update(base_fields)
|
172
|
+
|
173
|
+
# Process field_models
|
174
|
+
if not self._is_sentinel(self.field_models):
|
175
|
+
# Coerce to list if single FieldModel instance
|
176
|
+
field_models_list = (
|
177
|
+
[self.field_models]
|
178
|
+
if isinstance(self.field_models, FieldModel)
|
179
|
+
else self.field_models
|
180
|
+
)
|
181
|
+
|
182
|
+
for fm in field_models_list:
|
183
|
+
if not isinstance(fm, FieldModel):
|
184
|
+
raise ValueError(
|
185
|
+
f"field_models must contain FieldModel instances, got {type(fm)}"
|
186
|
+
)
|
197
187
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
188
|
+
# Apply descriptions first
|
189
|
+
field_models = field_models_list
|
190
|
+
if not self._is_sentinel(self.field_descriptions):
|
191
|
+
field_models = [
|
192
|
+
(
|
193
|
+
fm.with_description(self.field_descriptions[fm.name])
|
194
|
+
if fm.name in self.field_descriptions
|
195
|
+
else fm
|
196
|
+
)
|
197
|
+
for fm in field_models
|
198
|
+
]
|
202
199
|
|
203
|
-
|
204
|
-
|
205
|
-
|
200
|
+
# Extract fields and validators using public interface
|
201
|
+
for fm in field_models:
|
202
|
+
fields[fm.name] = fm.create_field()
|
203
|
+
fields[fm.name].annotation = fm.annotation
|
206
204
|
|
207
|
-
|
208
|
-
|
205
|
+
# Use the public field_validator property
|
206
|
+
if fm.field_validator:
|
207
|
+
validators.update(fm.field_validator)
|
209
208
|
|
210
|
-
|
211
|
-
|
212
|
-
""
|
213
|
-
return validate_boolean_field(cls, value, default=True)
|
209
|
+
# Store computed state
|
210
|
+
object.__setattr__(self, "_final_fields", fields)
|
211
|
+
object.__setattr__(self, "_validators", validators)
|
214
212
|
|
215
|
-
@
|
216
|
-
def
|
217
|
-
"""
|
213
|
+
@property
|
214
|
+
def use_fields(self) -> dict[str, tuple[type, FieldInfo]]:
|
215
|
+
"""Get field definitions to use in new model.
|
218
216
|
|
219
|
-
|
220
|
-
value: Value to validate.
|
217
|
+
Filters and prepares fields based on processed configuration.
|
221
218
|
|
222
219
|
Returns:
|
223
|
-
|
224
|
-
|
225
|
-
Raises:
|
226
|
-
ValueError: If name is invalid.
|
220
|
+
Dictionary mapping field names to (type, FieldInfo) tuples
|
227
221
|
"""
|
228
|
-
|
229
|
-
|
230
|
-
@field_validator("field_models", mode="before")
|
231
|
-
def _validate_field_models(cls, value) -> list[FieldModel]:
|
232
|
-
"""Validate field model definitions.
|
222
|
+
if not hasattr(self, "_final_fields"):
|
223
|
+
return {}
|
233
224
|
|
234
|
-
|
235
|
-
value: Value to validate.
|
225
|
+
return {k: (v.annotation, v) for k, v in self._final_fields.items()}
|
236
226
|
|
237
|
-
|
238
|
-
|
227
|
+
@property
|
228
|
+
def _use_keys(self) -> set[str]:
|
229
|
+
"""Get field keys for backward compatibility.
|
239
230
|
|
240
|
-
|
241
|
-
|
231
|
+
Returns the set of field names that will be used in the generated model.
|
232
|
+
This is derived from _final_fields for consistency.
|
242
233
|
"""
|
234
|
+
if not hasattr(self, "_final_fields"):
|
235
|
+
return set()
|
236
|
+
return set(self._final_fields.keys())
|
243
237
|
|
244
|
-
|
238
|
+
def _get_cache_key(self) -> int:
|
239
|
+
"""Create a hashable cache key from object state.
|
245
240
|
|
246
|
-
|
247
|
-
def validate_param_model(self) -> Self:
|
248
|
-
"""Validate complete model configuration.
|
249
|
-
|
250
|
-
Performs comprehensive validation and setup of the model parameters:
|
251
|
-
1. Updates parameter fields from base type if present
|
252
|
-
2. Merges field models into parameter fields
|
253
|
-
3. Manages field inclusion/exclusion via _use_keys
|
254
|
-
4. Sets up validators from field models
|
255
|
-
5. Applies field descriptions
|
256
|
-
6. Handles model name resolution
|
257
|
-
|
258
|
-
Returns:
|
259
|
-
The validated model instance with all configurations applied.
|
241
|
+
Converts unhashable types to hashable representations for caching.
|
260
242
|
"""
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
for i in self.field_models:
|
279
|
-
if i.field_validator is not None:
|
280
|
-
validators.update(i.field_validator)
|
281
|
-
self._validators = validators
|
282
|
-
|
283
|
-
if self.field_descriptions:
|
284
|
-
# Update field_models with descriptions (create new instances since they're immutable)
|
285
|
-
updated_field_models = []
|
286
|
-
for i in self.field_models:
|
287
|
-
if i.name in self.field_descriptions:
|
288
|
-
# Create new FieldModel with updated description
|
289
|
-
updated_field_model = i.with_description(
|
290
|
-
self.field_descriptions[i.name]
|
291
|
-
)
|
292
|
-
updated_field_models.append(updated_field_model)
|
293
|
-
else:
|
294
|
-
updated_field_models.append(i)
|
295
|
-
self.field_models = updated_field_models
|
296
|
-
|
297
|
-
if not isinstance(self.name, str):
|
298
|
-
if hasattr(self.base_type, "class_name"):
|
299
|
-
if callable(self.base_type.class_name):
|
300
|
-
self.name = self.base_type.class_name()
|
301
|
-
else:
|
302
|
-
self.name = self.base_type.class_name
|
303
|
-
elif inspect.isclass(self.base_type):
|
304
|
-
self.name = self.base_type.__name__
|
305
|
-
|
306
|
-
return self
|
243
|
+
state = self.to_dict()
|
244
|
+
|
245
|
+
def make_hashable(obj):
|
246
|
+
if isinstance(obj, dict):
|
247
|
+
return tuple(
|
248
|
+
sorted((k, make_hashable(v)) for k, v in obj.items())
|
249
|
+
)
|
250
|
+
elif isinstance(obj, list):
|
251
|
+
return tuple(make_hashable(x) for x in obj)
|
252
|
+
elif isinstance(obj, set):
|
253
|
+
return tuple(sorted(make_hashable(x) for x in obj))
|
254
|
+
else:
|
255
|
+
return obj
|
256
|
+
|
257
|
+
hashable_state = make_hashable(state)
|
258
|
+
return hash(hashable_state)
|
307
259
|
|
308
260
|
def create_new_model(self) -> type[BaseModel]:
|
309
261
|
"""Create new Pydantic model with specified configuration.
|
310
262
|
|
311
263
|
This method generates a new Pydantic model class based on the configured
|
312
|
-
parameters
|
264
|
+
parameters. Results are cached for performance when the same configuration
|
265
|
+
is used multiple times.
|
313
266
|
|
314
267
|
Returns:
|
315
|
-
|
268
|
+
Newly created or cached Pydantic model class
|
316
269
|
"""
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
270
|
+
# Create stable cache key from hashable representation
|
271
|
+
cache_key = self._get_cache_key()
|
272
|
+
|
273
|
+
# Check cache first
|
274
|
+
with _model_cache_lock:
|
275
|
+
if cache_key in _model_cache:
|
276
|
+
_model_cache.move_to_end(cache_key)
|
277
|
+
return _model_cache[cache_key]
|
278
|
+
|
279
|
+
# Determine model name
|
280
|
+
model_name = self.name
|
281
|
+
if self._is_sentinel(model_name) and not self._is_sentinel(
|
282
|
+
self.base_type
|
283
|
+
):
|
284
|
+
if hasattr(self.base_type, "class_name"):
|
285
|
+
model_name = self.base_type.class_name
|
286
|
+
if callable(model_name):
|
287
|
+
model_name = model_name()
|
288
|
+
else:
|
289
|
+
model_name = self.base_type.__name__
|
290
|
+
|
291
|
+
if self._is_sentinel(model_name):
|
292
|
+
model_name = "GeneratedModel"
|
293
|
+
|
294
|
+
# Determine base class
|
295
|
+
base_type = None
|
296
|
+
if (
|
297
|
+
not self._is_sentinel(self.inherit_base)
|
298
|
+
and self.inherit_base
|
299
|
+
and not self._is_sentinel(self.base_type)
|
300
|
+
):
|
301
|
+
# Don't inherit if we're excluding base fields
|
302
|
+
if self._is_sentinel(self.exclude_fields) or not any(
|
303
|
+
f in self.exclude_fields for f in self.base_type.model_fields
|
322
304
|
):
|
323
|
-
base_type =
|
305
|
+
base_type = self.base_type
|
324
306
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
__doc__=self.doc,
|
307
|
+
# Create the model
|
308
|
+
model = create_model(
|
309
|
+
model_name,
|
329
310
|
__base__=base_type,
|
330
|
-
|
311
|
+
__config__=(
|
312
|
+
self.config_dict
|
313
|
+
if not self._is_sentinel(self.config_dict)
|
314
|
+
else None
|
315
|
+
),
|
316
|
+
__doc__=self.doc if not self._is_sentinel(self.doc) else None,
|
317
|
+
__validators__=self._validators if self._validators else None,
|
331
318
|
**self.use_fields,
|
332
319
|
)
|
333
|
-
|
334
|
-
|
335
|
-
|
320
|
+
|
321
|
+
# Apply frozen configuration
|
322
|
+
if not self._is_sentinel(self.frozen) and self.frozen:
|
323
|
+
model.model_config["frozen"] = True
|
324
|
+
|
325
|
+
# Cache the result
|
326
|
+
with _model_cache_lock:
|
327
|
+
_model_cache[cache_key] = model
|
328
|
+
|
329
|
+
# LRU eviction
|
330
|
+
while len(_model_cache) > _MODEL_CACHE_SIZE:
|
331
|
+
try:
|
332
|
+
_model_cache.popitem(last=False) # Remove oldest
|
333
|
+
except KeyError:
|
334
|
+
# Handle race condition
|
335
|
+
break
|
336
|
+
|
337
|
+
return model
|
lionagi/models/operable_model.py
CHANGED
@@ -115,13 +115,13 @@ class OperableModel(HashableModel):
|
|
115
115
|
if isinstance(value, dict):
|
116
116
|
for k, v in value.items():
|
117
117
|
if isinstance(v, FieldModel):
|
118
|
-
out[k] = v.
|
118
|
+
out[k] = v.create_field()
|
119
119
|
elif isinstance(v, FieldInfo):
|
120
120
|
out[k] = v
|
121
121
|
return out
|
122
122
|
|
123
123
|
elif isinstance(value, list) and is_same_dtype(value, FieldModel):
|
124
|
-
return {v.name: v.
|
124
|
+
return {v.name: v.create_field() for v in value}
|
125
125
|
|
126
126
|
raise ValueError("Invalid extra_fields value")
|
127
127
|
|
@@ -139,7 +139,7 @@ class OperableModel(HashableModel):
|
|
139
139
|
if isinstance(self.extra_fields, dict):
|
140
140
|
for k, v in self.extra_fields.items():
|
141
141
|
if isinstance(v, FieldModel):
|
142
|
-
extra_fields[k] = v.
|
142
|
+
extra_fields[k] = v.create_field()
|
143
143
|
extra_field_models[k] = v
|
144
144
|
elif isinstance(v, FieldInfo):
|
145
145
|
extra_fields[k] = v
|
@@ -149,7 +149,7 @@ class OperableModel(HashableModel):
|
|
149
149
|
for v in self.extra_fields:
|
150
150
|
# list[FieldModel]
|
151
151
|
if isinstance(v, FieldModel):
|
152
|
-
extra_fields[v.name] = v.
|
152
|
+
extra_fields[v.name] = v.create_field()
|
153
153
|
extra_field_models[v.name] = v
|
154
154
|
|
155
155
|
# Handle list[tuple[str, FieldInfo | FieldModel]]
|
@@ -158,11 +158,11 @@ class OperableModel(HashableModel):
|
|
158
158
|
if isinstance(v[1], FieldInfo):
|
159
159
|
extra_fields[v[0]] = v[1]
|
160
160
|
if isinstance(v[1], FieldModel):
|
161
|
-
extra_fields[v[1].name] = v[1].
|
161
|
+
extra_fields[v[1].name] = v[1].create_field()
|
162
162
|
extra_field_models[v[1].name] = v[1]
|
163
163
|
|
164
|
-
self
|
165
|
-
self
|
164
|
+
object.__setattr__(self, "extra_fields", extra_fields)
|
165
|
+
object.__setattr__(self, "extra_field_models", extra_field_models)
|
166
166
|
return self
|
167
167
|
|
168
168
|
@override
|
@@ -349,7 +349,7 @@ class OperableModel(HashableModel):
|
|
349
349
|
raise ValueError(
|
350
350
|
"Invalid field_model, should be a FieldModel object"
|
351
351
|
)
|
352
|
-
self.extra_fields[field_name] = field_model.
|
352
|
+
self.extra_fields[field_name] = field_model.create_field()
|
353
353
|
self.extra_field_models[field_name] = field_model
|
354
354
|
|
355
355
|
# Handle kwargs
|