lionagi 0.12.8__py3-none-any.whl → 0.13.1__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/_errors.py +4 -0
- lionagi/libs/schema/load_pydantic_model_from_schema.py +0 -9
- lionagi/models/field_model.py +645 -174
- lionagi/models/model_params.py +17 -8
- lionagi/models/operable_model.py +4 -2
- lionagi/protocols/operatives/operative.py +205 -36
- lionagi/protocols/operatives/step.py +2 -1
- lionagi/service/connections/endpoint_config.py +7 -3
- lionagi/service/connections/providers/claude_code_.py +186 -81
- lionagi/service/connections/providers/oai_.py +1 -13
- lionagi/service/imodel.py +9 -4
- lionagi/service/third_party/anthropic_models.py +1 -1
- lionagi/service/token_calculator.py +1 -1
- lionagi/traits/__init__.py +58 -0
- lionagi/traits/base.py +216 -0
- lionagi/traits/composer.py +343 -0
- lionagi/traits/protocols.py +495 -0
- lionagi/traits/registry.py +1071 -0
- lionagi/version.py +1 -1
- {lionagi-0.12.8.dist-info → lionagi-0.13.1.dist-info}/METADATA +8 -4
- {lionagi-0.12.8.dist-info → lionagi-0.13.1.dist-info}/RECORD +23 -18
- {lionagi-0.12.8.dist-info → lionagi-0.13.1.dist-info}/WHEEL +0 -0
- {lionagi-0.12.8.dist-info → lionagi-0.13.1.dist-info}/licenses/LICENSE +0 -0
lionagi/models/model_params.py
CHANGED
@@ -122,13 +122,13 @@ class ModelParams(SchemaModel):
|
|
122
122
|
for k, v in self.parameter_fields.items()
|
123
123
|
if k in self._use_keys
|
124
124
|
}
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
132
|
return {k: (v.annotation, v) for k, v in params.items()}
|
133
133
|
|
134
134
|
@field_validator("parameter_fields", mode="before")
|
@@ -281,9 +281,18 @@ class ModelParams(SchemaModel):
|
|
281
281
|
self._validators = validators
|
282
282
|
|
283
283
|
if self.field_descriptions:
|
284
|
+
# Update field_models with descriptions (create new instances since they're immutable)
|
285
|
+
updated_field_models = []
|
284
286
|
for i in self.field_models:
|
285
287
|
if i.name in self.field_descriptions:
|
286
|
-
|
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
|
287
296
|
|
288
297
|
if not isinstance(self.name, str):
|
289
298
|
if hasattr(self.base_type, "class_name"):
|
lionagi/models/operable_model.py
CHANGED
@@ -202,9 +202,10 @@ class OperableModel(HashableModel):
|
|
202
202
|
|
203
203
|
if (
|
204
204
|
field_name in self.extra_field_models
|
205
|
-
and self.extra_field_models[field_name].
|
205
|
+
and self.extra_field_models[field_name].has_validator()
|
206
206
|
):
|
207
|
-
value
|
207
|
+
# Use the validate method to check value - let validation errors propagate
|
208
|
+
self.extra_field_models[field_name].validate(value, field_name)
|
208
209
|
if field_name in self.extra_fields:
|
209
210
|
object.__setattr__(self, field_name, value)
|
210
211
|
else:
|
@@ -264,6 +265,7 @@ class OperableModel(HashableModel):
|
|
264
265
|
"""
|
265
266
|
a = {**self.model_fields, **self.extra_fields}
|
266
267
|
a.pop("extra_fields", None)
|
268
|
+
a.pop("extra_field_models", None) # Exclude internal field tracking
|
267
269
|
return a
|
268
270
|
|
269
271
|
def add_field(
|
@@ -2,41 +2,139 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
-
from
|
5
|
+
from typing import Any, Optional
|
6
|
+
|
7
|
+
from pydantic import BaseModel
|
6
8
|
from pydantic.fields import FieldInfo
|
7
|
-
from typing_extensions import Self
|
8
9
|
|
9
10
|
from lionagi.libs.validate.fuzzy_match_keys import fuzzy_match_keys
|
10
|
-
from lionagi.models import FieldModel, ModelParams,
|
11
|
+
from lionagi.models import FieldModel, ModelParams, OperableModel
|
11
12
|
from lionagi.utils import UNDEFINED, to_json
|
12
13
|
|
13
14
|
|
14
|
-
class Operative
|
15
|
-
"""Class representing an operative that handles request and response models for operations.
|
15
|
+
class Operative:
|
16
|
+
"""Class representing an operative that handles request and response models for operations.
|
17
|
+
|
18
|
+
This implementation uses OperableModel internally for better performance while
|
19
|
+
maintaining backward compatibility with the existing API.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
name: str | None = None,
|
25
|
+
request_type: type[BaseModel] | None = None,
|
26
|
+
response_type: type[BaseModel] | None = None,
|
27
|
+
response_model: BaseModel | None = None,
|
28
|
+
response_str_dict: dict | str | None = None,
|
29
|
+
auto_retry_parse: bool = True,
|
30
|
+
max_retries: int = 3,
|
31
|
+
parse_kwargs: dict | None = None,
|
32
|
+
request_params: (
|
33
|
+
ModelParams | None
|
34
|
+
) = None, # Deprecated, for backward compatibility
|
35
|
+
**_kwargs, # Ignored for backward compatibility
|
36
|
+
):
|
37
|
+
"""Initialize the Operative.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
name: Name of the operative
|
41
|
+
request_type: Pydantic model type for requests
|
42
|
+
response_type: Pydantic model type for responses
|
43
|
+
response_model: Current response model instance
|
44
|
+
response_str_dict: Raw response string/dict
|
45
|
+
auto_retry_parse: Whether to auto-retry parsing
|
46
|
+
max_retries: Maximum parse retries
|
47
|
+
parse_kwargs: Additional parse arguments
|
48
|
+
request_params: Deprecated - use direct field addition
|
49
|
+
response_params: Deprecated - use direct field addition
|
50
|
+
"""
|
51
|
+
self.name = name
|
52
|
+
self.request_type = request_type
|
53
|
+
self.response_type = response_type
|
54
|
+
self.response_model = response_model
|
55
|
+
self.response_str_dict = response_str_dict
|
56
|
+
self.auto_retry_parse = auto_retry_parse
|
57
|
+
self.max_retries = max_retries
|
58
|
+
self.parse_kwargs = parse_kwargs or {}
|
59
|
+
self._should_retry = None
|
60
|
+
|
61
|
+
# Internal OperableModel instances
|
62
|
+
self._request_operable = OperableModel()
|
63
|
+
self._response_operable = OperableModel()
|
64
|
+
|
65
|
+
# Handle deprecated ModelParams for backward compatibility
|
66
|
+
if request_params:
|
67
|
+
self._init_from_model_params(request_params)
|
68
|
+
|
69
|
+
# Set default name if not provided
|
70
|
+
if not self.name:
|
71
|
+
self.name = (
|
72
|
+
self.request_type.__name__
|
73
|
+
if self.request_type
|
74
|
+
else "Operative"
|
75
|
+
)
|
76
|
+
|
77
|
+
def _init_from_model_params(self, params: ModelParams):
|
78
|
+
"""Initialize from ModelParams for backward compatibility."""
|
79
|
+
# Add field models to the request operable
|
80
|
+
if params.field_models:
|
81
|
+
for field_model in params.field_models:
|
82
|
+
self._request_operable.add_field(
|
83
|
+
field_model.name,
|
84
|
+
field_model=field_model,
|
85
|
+
annotation=field_model.base_type,
|
86
|
+
)
|
87
|
+
|
88
|
+
# Add parameter fields (skip if already added from field_models)
|
89
|
+
if params.parameter_fields:
|
90
|
+
for name, field_info in params.parameter_fields.items():
|
91
|
+
if (
|
92
|
+
name not in (params.exclude_fields or [])
|
93
|
+
and name not in self._request_operable.all_fields
|
94
|
+
):
|
95
|
+
self._request_operable.add_field(
|
96
|
+
name, field_obj=field_info
|
97
|
+
)
|
16
98
|
|
17
|
-
|
99
|
+
# Generate request_type if not provided
|
100
|
+
if not self.request_type:
|
101
|
+
exclude_fields = params.exclude_fields or []
|
102
|
+
use_fields = set(self._request_operable.all_fields.keys()) - set(
|
103
|
+
exclude_fields
|
104
|
+
)
|
105
|
+
self.request_type = self._request_operable.new_model(
|
106
|
+
name=params.name or "RequestModel",
|
107
|
+
use_fields=use_fields,
|
108
|
+
base_type=params.base_type,
|
109
|
+
frozen=params.frozen,
|
110
|
+
config_dict=params.config_dict,
|
111
|
+
doc=params.doc,
|
112
|
+
)
|
18
113
|
|
19
|
-
|
20
|
-
|
114
|
+
# Update name if not set
|
115
|
+
if not self.name and params.name:
|
116
|
+
self.name = params.name
|
21
117
|
|
22
|
-
|
23
|
-
|
24
|
-
response_model: BaseModel | None = Field(default=None)
|
25
|
-
response_str_dict: dict | str | None = Field(default=None)
|
118
|
+
def model_dump(self) -> dict[str, Any]:
|
119
|
+
"""Convert to dictionary for backward compatibility.
|
26
120
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
121
|
+
Note: This returns a Python dict, not JSON-serializable data.
|
122
|
+
For JSON serialization, convert types appropriately.
|
123
|
+
"""
|
124
|
+
return {
|
125
|
+
"name": self.name,
|
126
|
+
"request_type": self.request_type, # Python class object
|
127
|
+
"response_type": self.response_type, # Python class object
|
128
|
+
"response_model": self.response_model,
|
129
|
+
"response_str_dict": self.response_str_dict,
|
130
|
+
"auto_retry_parse": self.auto_retry_parse,
|
131
|
+
"max_retries": self.max_retries,
|
132
|
+
"parse_kwargs": self.parse_kwargs,
|
133
|
+
}
|
31
134
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
if self.request_type is None:
|
36
|
-
self.request_type = self.request_params.create_new_model()
|
37
|
-
if self.name is None:
|
38
|
-
self.name = self.request_params.name or self.request_type.__name__
|
39
|
-
return self
|
135
|
+
def to_dict(self) -> dict[str, Any]:
|
136
|
+
"""Alias for model_dump() - more appropriate name for non-Pydantic class."""
|
137
|
+
return self.model_dump()
|
40
138
|
|
41
139
|
def raise_validate_pydantic(self, text: str) -> None:
|
42
140
|
"""Validates and updates the response model using strict matching.
|
@@ -153,18 +251,89 @@ class Operative(SchemaModel):
|
|
153
251
|
frozen (bool, optional): Whether the model is frozen.
|
154
252
|
validators (dict, optional): Dictionary of validators.
|
155
253
|
"""
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
254
|
+
# Process response_params if provided (for backward compatibility)
|
255
|
+
if response_params:
|
256
|
+
# Extract values from ModelParams
|
257
|
+
field_models = field_models or response_params.field_models
|
258
|
+
parameter_fields = (
|
259
|
+
parameter_fields or response_params.parameter_fields
|
260
|
+
)
|
261
|
+
exclude_fields = exclude_fields or response_params.exclude_fields
|
262
|
+
field_descriptions = (
|
263
|
+
field_descriptions or response_params.field_descriptions
|
264
|
+
)
|
265
|
+
inherit_base = (
|
266
|
+
response_params.inherit_base if inherit_base else False
|
267
|
+
)
|
268
|
+
config_dict = config_dict or response_params.config_dict
|
269
|
+
doc = doc or response_params.doc
|
270
|
+
frozen = frozen or response_params.frozen
|
271
|
+
|
272
|
+
# Clear response operable and rebuild
|
273
|
+
self._response_operable = OperableModel()
|
274
|
+
|
275
|
+
# Copy fields from request operable if inherit_base
|
276
|
+
if inherit_base and self._request_operable:
|
277
|
+
for (
|
278
|
+
field_name,
|
279
|
+
field_model,
|
280
|
+
) in self._request_operable.extra_field_models.items():
|
281
|
+
self._response_operable.add_field(
|
282
|
+
field_name, field_model=field_model
|
283
|
+
)
|
284
|
+
|
285
|
+
# Add field models (skip if already exists from inheritance)
|
286
|
+
if field_models:
|
287
|
+
for field_model in field_models:
|
288
|
+
if field_model.name not in self._response_operable.all_fields:
|
289
|
+
self._response_operable.add_field(
|
290
|
+
field_model.name,
|
291
|
+
field_model=field_model,
|
292
|
+
annotation=field_model.base_type,
|
293
|
+
)
|
294
|
+
|
295
|
+
# Add parameter fields (skip if already added)
|
296
|
+
if parameter_fields:
|
297
|
+
for name, field_info in parameter_fields.items():
|
298
|
+
if (
|
299
|
+
name not in (exclude_fields or [])
|
300
|
+
and name not in self._response_operable.all_fields
|
301
|
+
):
|
302
|
+
self._response_operable.add_field(
|
303
|
+
name, field_obj=field_info
|
304
|
+
)
|
305
|
+
|
306
|
+
# Add validators if provided
|
307
|
+
if validators:
|
308
|
+
for field_name, validator in validators.items():
|
309
|
+
if field_name in self._response_operable.all_fields:
|
310
|
+
field_model = (
|
311
|
+
self._response_operable.extra_field_models.get(
|
312
|
+
field_name
|
313
|
+
)
|
314
|
+
)
|
315
|
+
if field_model:
|
316
|
+
field_model.validator = validator
|
317
|
+
|
318
|
+
# Generate response type
|
319
|
+
exclude_fields = exclude_fields or []
|
320
|
+
use_fields = set(self._response_operable.all_fields.keys()) - set(
|
321
|
+
exclude_fields
|
322
|
+
)
|
323
|
+
|
324
|
+
# Determine base type - use request_type if inheriting and no specific base provided
|
325
|
+
base_type = None
|
326
|
+
if response_params and response_params.base_type:
|
327
|
+
base_type = response_params.base_type
|
328
|
+
elif inherit_base and self.request_type:
|
329
|
+
base_type = self.request_type
|
330
|
+
|
331
|
+
self.response_type = self._response_operable.new_model(
|
332
|
+
name=(response_params.name if response_params else None)
|
333
|
+
or "ResponseModel",
|
334
|
+
use_fields=use_fields,
|
335
|
+
base_type=base_type,
|
336
|
+
frozen=frozen,
|
162
337
|
config_dict=config_dict,
|
163
338
|
doc=doc,
|
164
|
-
frozen=frozen,
|
165
|
-
base_type=self.request_params.base_type,
|
166
339
|
)
|
167
|
-
if validators and isinstance(validators, dict):
|
168
|
-
self.response_params._validators.update(validators)
|
169
|
-
|
170
|
-
self.response_type = self.response_params.create_new_model()
|
@@ -230,7 +230,8 @@ class Step:
|
|
230
230
|
field_models.extend([REASON_FIELD])
|
231
231
|
|
232
232
|
exclude_fields = exclude_fields or []
|
233
|
-
|
233
|
+
# Note: We no longer have access to request_params.exclude_fields
|
234
|
+
# since Operative doesn't store ModelParams anymore
|
234
235
|
|
235
236
|
operative.create_response_type(
|
236
237
|
response_params=response_params,
|
@@ -53,7 +53,6 @@ class EndpointConfig(BaseModel):
|
|
53
53
|
|
54
54
|
@model_validator(mode="after")
|
55
55
|
def _validate_api_key(self):
|
56
|
-
|
57
56
|
if self.api_key is not None:
|
58
57
|
if isinstance(self.api_key, SecretStr):
|
59
58
|
self._api_key = self.api_key.get_secret_value()
|
@@ -61,6 +60,9 @@ class EndpointConfig(BaseModel):
|
|
61
60
|
# Skip settings lookup for ollama special case
|
62
61
|
if self.provider == "ollama" and self.api_key == "ollama_key":
|
63
62
|
self._api_key = "ollama_key"
|
63
|
+
if self.provider == "claude_code":
|
64
|
+
self._api_key = "dummy"
|
65
|
+
|
64
66
|
else:
|
65
67
|
from lionagi.config import settings
|
66
68
|
|
@@ -89,9 +91,11 @@ class EndpointConfig(BaseModel):
|
|
89
91
|
if isinstance(v, BaseModel):
|
90
92
|
return v.__class__
|
91
93
|
if isinstance(v, dict | str):
|
92
|
-
from lionagi.libs.schema import
|
94
|
+
from lionagi.libs.schema.load_pydantic_model_from_schema import (
|
95
|
+
load_pydantic_model_from_schema,
|
96
|
+
)
|
93
97
|
|
94
|
-
return
|
98
|
+
return load_pydantic_model_from_schema(v)
|
95
99
|
except Exception as e:
|
96
100
|
raise ValueError("Invalid request options") from e
|
97
101
|
raise ValueError(
|