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.
@@ -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
- params.update(
126
- {
127
- f.name: f.field_info
128
- for f in self.field_models
129
- if f.name in self._use_keys
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
- i.description = self.field_descriptions[i.name]
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"):
@@ -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].validator is not UNDEFINED
205
+ and self.extra_field_models[field_name].has_validator()
206
206
  ):
207
- value = self.extra_field_models[field_name].validator(None, 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 pydantic import BaseModel, Field, PrivateAttr, model_validator
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, SchemaModel
11
+ from lionagi.models import FieldModel, ModelParams, OperableModel
11
12
  from lionagi.utils import UNDEFINED, to_json
12
13
 
13
14
 
14
- class Operative(SchemaModel):
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
- name: str | None = None
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
- request_params: ModelParams | None = Field(default=None)
20
- request_type: type[BaseModel] | None = Field(default=None)
114
+ # Update name if not set
115
+ if not self.name and params.name:
116
+ self.name = params.name
21
117
 
22
- response_params: ModelParams | None = Field(default=None)
23
- response_type: type[BaseModel] | None = Field(default=None)
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
- auto_retry_parse: bool = True
28
- max_retries: int = 3
29
- parse_kwargs: dict | None = None
30
- _should_retry: bool = PrivateAttr(default=None)
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
- @model_validator(mode="after")
33
- def _validate(self) -> Self:
34
- """Validates the operative instance after initialization."""
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
- self.response_params = response_params or ModelParams(
157
- parameter_fields=parameter_fields,
158
- field_models=field_models,
159
- exclude_fields=exclude_fields,
160
- field_descriptions=field_descriptions,
161
- inherit_base=inherit_base,
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
- exclude_fields.extend(operative.request_params.exclude_fields)
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 SchemaUtil
94
+ from lionagi.libs.schema.load_pydantic_model_from_schema import (
95
+ load_pydantic_model_from_schema,
96
+ )
93
97
 
94
- return SchemaUtil.load_pydantic_model_from_schema(v)
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(