lionagi 0.18.0__py3-none-any.whl → 0.18.2__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.
Files changed (93) hide show
  1. lionagi/__init__.py +102 -59
  2. lionagi/_errors.py +0 -5
  3. lionagi/adapters/spec_adapters/__init__.py +9 -0
  4. lionagi/adapters/spec_adapters/_protocol.py +236 -0
  5. lionagi/adapters/spec_adapters/pydantic_field.py +158 -0
  6. lionagi/fields.py +83 -0
  7. lionagi/ln/__init__.py +3 -1
  8. lionagi/ln/_async_call.py +2 -2
  9. lionagi/ln/concurrency/primitives.py +4 -4
  10. lionagi/ln/concurrency/task.py +1 -0
  11. lionagi/ln/fuzzy/_fuzzy_match.py +2 -2
  12. lionagi/ln/types/__init__.py +51 -0
  13. lionagi/ln/types/_sentinel.py +154 -0
  14. lionagi/ln/{types.py → types/base.py} +108 -168
  15. lionagi/ln/types/operable.py +221 -0
  16. lionagi/ln/types/spec.py +441 -0
  17. lionagi/models/field_model.py +69 -7
  18. lionagi/models/hashable_model.py +2 -3
  19. lionagi/models/model_params.py +4 -3
  20. lionagi/operations/ReAct/ReAct.py +1 -1
  21. lionagi/operations/act/act.py +3 -3
  22. lionagi/operations/builder.py +5 -7
  23. lionagi/operations/fields.py +380 -0
  24. lionagi/operations/flow.py +4 -6
  25. lionagi/operations/node.py +4 -4
  26. lionagi/operations/operate/operate.py +123 -89
  27. lionagi/operations/operate/operative.py +198 -0
  28. lionagi/operations/operate/step.py +203 -0
  29. lionagi/operations/select/select.py +1 -1
  30. lionagi/operations/select/utils.py +7 -1
  31. lionagi/operations/types.py +7 -7
  32. lionagi/protocols/action/manager.py +5 -6
  33. lionagi/protocols/contracts.py +2 -2
  34. lionagi/protocols/generic/__init__.py +22 -0
  35. lionagi/protocols/generic/element.py +36 -127
  36. lionagi/protocols/generic/pile.py +9 -10
  37. lionagi/protocols/generic/progression.py +23 -22
  38. lionagi/protocols/graph/edge.py +6 -5
  39. lionagi/protocols/ids.py +6 -49
  40. lionagi/protocols/messages/__init__.py +3 -1
  41. lionagi/protocols/messages/base.py +7 -6
  42. lionagi/protocols/messages/instruction.py +0 -1
  43. lionagi/protocols/messages/message.py +2 -2
  44. lionagi/protocols/types.py +1 -11
  45. lionagi/service/connections/__init__.py +3 -0
  46. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  47. lionagi/service/hooks/_types.py +1 -1
  48. lionagi/service/hooks/_utils.py +1 -1
  49. lionagi/service/hooks/hook_event.py +3 -8
  50. lionagi/service/hooks/hook_registry.py +5 -5
  51. lionagi/service/hooks/hooked_event.py +61 -1
  52. lionagi/service/imodel.py +24 -20
  53. lionagi/service/third_party/claude_code.py +1 -2
  54. lionagi/service/third_party/openai_models.py +24 -22
  55. lionagi/service/token_calculator.py +1 -94
  56. lionagi/session/branch.py +26 -228
  57. lionagi/session/session.py +5 -90
  58. lionagi/version.py +1 -1
  59. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/METADATA +6 -5
  60. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/RECORD +62 -82
  61. lionagi/fields/__init__.py +0 -47
  62. lionagi/fields/action.py +0 -188
  63. lionagi/fields/base.py +0 -153
  64. lionagi/fields/code.py +0 -239
  65. lionagi/fields/file.py +0 -234
  66. lionagi/fields/instruct.py +0 -135
  67. lionagi/fields/reason.py +0 -55
  68. lionagi/fields/research.py +0 -52
  69. lionagi/operations/brainstorm/__init__.py +0 -2
  70. lionagi/operations/brainstorm/brainstorm.py +0 -498
  71. lionagi/operations/brainstorm/prompt.py +0 -11
  72. lionagi/operations/instruct/__init__.py +0 -2
  73. lionagi/operations/instruct/instruct.py +0 -28
  74. lionagi/operations/plan/__init__.py +0 -6
  75. lionagi/operations/plan/plan.py +0 -386
  76. lionagi/operations/plan/prompt.py +0 -25
  77. lionagi/operations/utils.py +0 -45
  78. lionagi/protocols/forms/__init__.py +0 -2
  79. lionagi/protocols/forms/base.py +0 -85
  80. lionagi/protocols/forms/flow.py +0 -79
  81. lionagi/protocols/forms/form.py +0 -86
  82. lionagi/protocols/forms/report.py +0 -48
  83. lionagi/protocols/mail/__init__.py +0 -2
  84. lionagi/protocols/mail/exchange.py +0 -220
  85. lionagi/protocols/mail/mail.py +0 -51
  86. lionagi/protocols/mail/mailbox.py +0 -103
  87. lionagi/protocols/mail/manager.py +0 -218
  88. lionagi/protocols/mail/package.py +0 -101
  89. lionagi/protocols/operatives/__init__.py +0 -2
  90. lionagi/protocols/operatives/operative.py +0 -362
  91. lionagi/protocols/operatives/step.py +0 -227
  92. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/WHEEL +0 -0
  93. {lionagi-0.18.0.dist-info → lionagi-0.18.2.dist-info}/licenses/LICENSE +0 -0
@@ -3,152 +3,26 @@ from __future__ import annotations
3
3
  from collections.abc import Sequence
4
4
  from dataclasses import dataclass, field
5
5
  from enum import Enum as _Enum
6
- from typing import Any, ClassVar, Final, Literal, TypeVar, Union
6
+ from typing import Any, ClassVar
7
7
 
8
8
  from typing_extensions import TypedDict, override
9
9
 
10
+ from ._sentinel import Undefined, Unset, is_sentinel
11
+
10
12
  __all__ = (
11
- "Undefined",
12
- "Unset",
13
- "MaybeUndefined",
14
- "MaybeUnset",
15
- "MaybeSentinel",
16
- "SingletonType",
17
- "UndefinedType",
18
- "UnsetType",
19
- "KeysDict",
20
- "T",
21
13
  "Enum",
22
- "is_sentinel",
23
- "not_sentinel",
14
+ "ModelConfig",
24
15
  "Params",
25
16
  "DataClass",
26
- "KeysLike",
27
17
  "Meta",
18
+ "KeysDict",
19
+ "KeysLike",
28
20
  )
29
21
 
30
- T = TypeVar("T")
31
-
32
-
33
- class _SingletonMeta(type):
34
- """Metaclass that guarantees exactly one instance per subclass.
35
-
36
- This ensures that sentinel values maintain identity across the entire application,
37
- allowing safe identity checks with 'is' operator.
38
- """
39
-
40
- _cache: dict[type, SingletonType] = {}
41
-
42
- def __call__(cls, *a, **kw):
43
- if cls not in cls._cache:
44
- cls._cache[cls] = super().__call__(*a, **kw)
45
- return cls._cache[cls]
46
-
47
-
48
- class SingletonType(metaclass=_SingletonMeta):
49
- """Base class for singleton sentinel types.
50
-
51
- Provides consistent interface for sentinel values with:
52
- - Identity preservation across deepcopy
53
- - Falsy boolean evaluation
54
- - Clear string representation
55
- """
56
-
57
- __slots__: tuple[str, ...] = ()
58
-
59
- def __deepcopy__(self, memo): # copy & deepcopy both noop
60
- return self
61
-
62
- def __copy__(self):
63
- return self
64
-
65
- # concrete classes *must* override the two methods below
66
- def __bool__(self) -> bool: ...
67
- def __repr__(self) -> str: ...
68
-
69
-
70
- class UndefinedType(SingletonType):
71
- """Sentinel for a key or field entirely missing from a namespace.
72
-
73
- Use this when:
74
- - A field has never been set
75
- - A key doesn't exist in a mapping
76
- - A value is conceptually undefined (not just unset)
77
-
78
- Example:
79
- >>> d = {"a": 1}
80
- >>> d.get("b", Undefined) is Undefined
81
- True
82
- """
83
-
84
- __slots__ = ()
85
-
86
- def __bool__(self) -> Literal[False]:
87
- return False
88
-
89
- def __repr__(self) -> Literal["Undefined"]:
90
- return "Undefined"
91
-
92
- def __str__(self) -> Literal["Undefined"]:
93
- return "Undefined"
94
-
95
- def __reduce__(self):
96
- """Ensure pickle preservation of singleton identity."""
97
- return "Undefined"
98
-
99
-
100
- class UnsetType(SingletonType):
101
- """Sentinel for a key present but value not yet provided.
102
-
103
- Use this when:
104
- - A parameter exists but hasn't been given a value
105
- - Distinguishing between None and "not provided"
106
- - API parameters that are optional but need explicit handling
107
-
108
- Example:
109
- >>> def func(param=Unset):
110
- ... if param is not Unset:
111
- ... # param was explicitly provided
112
- ... process(param)
113
- """
114
-
115
- __slots__ = ()
116
-
117
- def __bool__(self) -> Literal[False]:
118
- return False
119
-
120
- def __repr__(self) -> Literal["Unset"]:
121
- return "Unset"
122
-
123
- def __str__(self) -> Literal["Unset"]:
124
- return "Unset"
125
-
126
- def __reduce__(self):
127
- """Ensure pickle preservation of singleton identity."""
128
- return "Unset"
129
-
130
-
131
- Undefined: Final = UndefinedType()
132
- """A key or field entirely missing from a namespace"""
133
- Unset: Final = UnsetType()
134
- """A key present but value not yet provided."""
135
-
136
- MaybeUndefined = Union[T, UndefinedType]
137
- MaybeUnset = Union[T, UnsetType]
138
- MaybeSentinel = Union[T, UndefinedType, UnsetType]
139
-
140
-
141
- def is_sentinel(value: Any) -> bool:
142
- """Check if a value is any sentinel (Undefined or Unset)."""
143
- return value is Undefined or value is Unset
144
-
145
-
146
- def not_sentinel(value: Any) -> bool:
147
- """Check if a value is NOT a sentinel. Useful for filtering operations."""
148
- return value is not Undefined and value is not Unset
149
-
150
22
 
151
23
  class Enum(_Enum):
24
+ """Enhanced Enum with allowed() classmethod."""
25
+
152
26
  @classmethod
153
27
  def allowed(cls) -> tuple[str, ...]:
154
28
  return tuple(e.value for e in cls)
@@ -160,18 +34,46 @@ class KeysDict(TypedDict, total=False):
160
34
  key: Any # Represents any key-type pair
161
35
 
162
36
 
37
+ @dataclass(slots=True, frozen=True)
38
+ class ModelConfig:
39
+ """Configuration for Params and DataClass behavior.
40
+
41
+ Attributes:
42
+ none_as_sentinel: If True, None is treated as a sentinel value (excluded from to_dict).
43
+ empty_as_sentinel: If True, empty collections are treated as sentinels (excluded from to_dict).
44
+ strict: If True, no sentinels allowed (all fields must have values).
45
+ prefill_unset: If True, unset fields are prefilled with Unset.
46
+ use_enum_values: If True, use enum values instead of enum instances in to_dict().
47
+ """
48
+
49
+ # Sentinel handling (controls what gets excluded from to_dict)
50
+ none_as_sentinel: bool = False
51
+ empty_as_sentinel: bool = False
52
+
53
+ # Validation
54
+ strict: bool = False
55
+ prefill_unset: bool = True
56
+
57
+ # Serialization
58
+ use_enum_values: bool = False
59
+
60
+
163
61
  @dataclass(slots=True, frozen=True, init=False)
164
62
  class Params:
165
- """Base class for parameters used in various functions."""
63
+ """Base class for parameters used in various functions.
166
64
 
167
- _none_as_sentinel: ClassVar[bool] = False
168
- """If True, None is treated as a sentinel value."""
65
+ Use the ModelConfig class attribute to customize behavior:
169
66
 
170
- _strict: ClassVar[bool] = False
171
- """No sentinels allowed if strict is True."""
67
+ Example:
68
+ @dataclass(slots=True, frozen=True, init=False)
69
+ class MyParams(Params):
70
+ _config: ClassVar[ModelConfig] = ModelConfig(strict=True)
71
+ param1: str
72
+ param2: int
73
+ """
172
74
 
173
- _prefill_unset: ClassVar[bool] = True
174
- """If True, unset fields are prefilled with Unset."""
75
+ _config: ClassVar[ModelConfig] = ModelConfig()
76
+ """Configuration for this Params class."""
175
77
 
176
78
  _allowed_keys: ClassVar[set[str]] = field(
177
79
  default=set(), init=False, repr=False
@@ -193,9 +95,23 @@ class Params:
193
95
  @classmethod
194
96
  def _is_sentinel(cls, value: Any) -> bool:
195
97
  """Check if a value is a sentinel (Undefined or Unset)."""
196
- if value is None and cls._none_as_sentinel:
197
- return True
198
- return is_sentinel(value)
98
+ return is_sentinel(
99
+ value,
100
+ none_as_sentinel=cls._config.none_as_sentinel,
101
+ empty_as_sentinel=cls._config.empty_as_sentinel,
102
+ )
103
+
104
+ @classmethod
105
+ def _normalize_value(cls, value: Any) -> Any:
106
+ """Normalize a value for serialization.
107
+
108
+ Handles:
109
+ - Enum values if use_enum_values is True
110
+ - Can be extended for other transformations
111
+ """
112
+ if cls._config.use_enum_values and isinstance(value, _Enum):
113
+ return value.value
114
+ return value
199
115
 
200
116
  @classmethod
201
117
  def allowed(cls) -> set[str]:
@@ -210,10 +126,12 @@ class Params:
210
126
  @override
211
127
  def _validate(self) -> None:
212
128
  def _validate_strict(k):
213
- if self._strict and self._is_sentinel(getattr(self, k, Unset)):
129
+ if self._config.strict and self._is_sentinel(
130
+ getattr(self, k, Unset)
131
+ ):
214
132
  raise ValueError(f"Missing required parameter: {k}")
215
133
  if (
216
- self._prefill_unset
134
+ self._config.prefill_unset
217
135
  and getattr(self, k, Undefined) is Undefined
218
136
  ):
219
137
  object.__setattr__(self, k, Unset)
@@ -236,14 +154,14 @@ class Params:
236
154
  data = {}
237
155
  exclude = exclude or set()
238
156
  for k in self.allowed():
239
- if k not in exclude and not self._is_sentinel(
240
- v := getattr(self, k, Undefined)
241
- ):
242
- data[k] = v
157
+ if k not in exclude:
158
+ v = getattr(self, k, Undefined)
159
+ if not self._is_sentinel(v):
160
+ data[k] = self._normalize_value(v)
243
161
  return data
244
162
 
245
163
  def __hash__(self) -> int:
246
- from ._hash import hash_dict
164
+ from .._hash import hash_dict
247
165
 
248
166
  return hash_dict(self.to_dict())
249
167
 
@@ -261,16 +179,20 @@ class Params:
261
179
 
262
180
  @dataclass(slots=True)
263
181
  class DataClass:
264
- """A base class for data classes with strict parameter handling."""
182
+ """A base class for data classes with strict parameter handling.
265
183
 
266
- _none_as_sentinel: ClassVar[bool] = False
267
- """If True, None is treated as a sentinel value."""
184
+ Use the ModelConfig class attribute to customize behavior:
268
185
 
269
- _strict: ClassVar[bool] = False
270
- """No sentinels allowed if strict is True."""
186
+ Example:
187
+ @dataclass(slots=True)
188
+ class MyDataClass(DataClass):
189
+ _config: ClassVar[ModelConfig] = ModelConfig(strict=True, prefill_unset=False)
190
+ field1: str
191
+ field2: int
192
+ """
271
193
 
272
- _prefill_unset: ClassVar[bool] = True
273
- """If True, unset fields are prefilled with Unset."""
194
+ _config: ClassVar[ModelConfig] = ModelConfig()
195
+ """Configuration for this DataClass."""
274
196
 
275
197
  _allowed_keys: ClassVar[set[str]] = field(
276
198
  default=set(), init=False, repr=False
@@ -294,10 +216,12 @@ class DataClass:
294
216
  @override
295
217
  def _validate(self) -> None:
296
218
  def _validate_strict(k):
297
- if self._strict and self._is_sentinel(getattr(self, k, Unset)):
219
+ if self._config.strict and self._is_sentinel(
220
+ getattr(self, k, Unset)
221
+ ):
298
222
  raise ValueError(f"Missing required parameter: {k}")
299
223
  if (
300
- self._prefill_unset
224
+ self._config.prefill_unset
301
225
  and getattr(self, k, Undefined) is Undefined
302
226
  ):
303
227
  self.__setattr__(k, Unset)
@@ -309,18 +233,34 @@ class DataClass:
309
233
  data = {}
310
234
  exclude = exclude or set()
311
235
  for k in type(self).allowed():
312
- if k not in exclude and not self._is_sentinel(
313
- v := getattr(self, k)
314
- ):
315
- data[k] = v
236
+ if k not in exclude:
237
+ v = getattr(self, k)
238
+ if not self._is_sentinel(v):
239
+ data[k] = self._normalize_value(v)
316
240
  return data
317
241
 
318
242
  @classmethod
319
243
  def _is_sentinel(cls, value: Any) -> bool:
320
244
  """Check if a value is a sentinel (Undefined or Unset)."""
321
- if value is None and cls._none_as_sentinel:
322
- return True
323
- return is_sentinel(value)
245
+ return is_sentinel(
246
+ value,
247
+ none_as_sentinel=cls._config.none_as_sentinel,
248
+ empty_as_sentinel=cls._config.empty_as_sentinel,
249
+ )
250
+
251
+ @classmethod
252
+ def _normalize_value(cls, value: Any) -> Any:
253
+ """Normalize a value for serialization.
254
+
255
+ Handles:
256
+ - Enum values if use_enum_values is True
257
+ - Can be extended for other transformations
258
+ """
259
+ from enum import Enum as _Enum
260
+
261
+ if cls._config.use_enum_values and isinstance(value, _Enum):
262
+ return value.value
263
+ return value
324
264
 
325
265
  def with_updates(self, **kwargs: Any) -> DataClass:
326
266
  """Return a new instance with updated fields."""
@@ -329,7 +269,7 @@ class DataClass:
329
269
  return type(self)(**dict_)
330
270
 
331
271
  def __hash__(self) -> int:
332
- from ._hash import hash_dict
272
+ from .._hash import hash_dict
333
273
 
334
274
  return hash_dict(self.to_dict())
335
275
 
@@ -0,0 +1,221 @@
1
+ """Operable - Container for Spec collections with model generation.
2
+
3
+ This module provides the Operable class for managing collections of Spec objects
4
+ and generating framework-specific models via adapters.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import TYPE_CHECKING, Literal
11
+
12
+ from ._sentinel import MaybeUnset, Unset
13
+
14
+ if TYPE_CHECKING:
15
+ from .spec import Spec
16
+
17
+ __all__ = ("Operable",)
18
+
19
+
20
+ @dataclass(frozen=True, slots=True, init=False)
21
+ class Operable:
22
+ """Collection of Spec objects with model generation capabilities.
23
+
24
+ Operable manages an ordered collection of Spec objects and provides
25
+ methods to generate framework-specific models via adapters.
26
+
27
+ Attributes:
28
+ __op_fields__: Ordered tuple of Spec objects
29
+ name: Optional name for this operable
30
+
31
+ Example:
32
+ >>> from lionagi.ln.types import Spec, Operable
33
+ >>> specs = (
34
+ ... Spec(str, name="username"),
35
+ ... Spec(int, name="age"),
36
+ ... )
37
+ >>> operable = Operable(specs, name="User")
38
+ >>> UserModel = operable.create_model(adapter="pydantic")
39
+ """
40
+
41
+ __op_fields__: tuple[Spec, ...]
42
+ name: str | None
43
+
44
+ def __init__(
45
+ self,
46
+ specs: tuple[Spec, ...] | list[Spec] = (),
47
+ *,
48
+ name: str | None = None,
49
+ ):
50
+ """Initialize Operable with specs.
51
+
52
+ Args:
53
+ specs: Tuple or list of Spec objects
54
+ name: Optional name for this operable
55
+
56
+ Raises:
57
+ TypeError: If specs contains non-Spec objects
58
+ ValueError: If specs contains duplicate field names
59
+ """
60
+ # Import here to avoid circular import
61
+ from .spec import Spec
62
+
63
+ # Convert to tuple if list
64
+ if isinstance(specs, list):
65
+ specs = tuple(specs)
66
+
67
+ # Validate all items are Spec objects
68
+ for i, item in enumerate(specs):
69
+ if not isinstance(item, Spec):
70
+ raise TypeError(
71
+ f"All specs must be Spec objects, got {type(item).__name__} "
72
+ f"at index {i}"
73
+ )
74
+
75
+ # Check for duplicate names
76
+ names = [s.name for s in specs if s.name is not None]
77
+ if len(names) != len(set(names)):
78
+ from collections import Counter
79
+
80
+ duplicates = [
81
+ name for name, count in Counter(names).items() if count > 1
82
+ ]
83
+ raise ValueError(
84
+ f"Duplicate field names found: {duplicates}. "
85
+ "Each spec must have a unique name."
86
+ )
87
+
88
+ object.__setattr__(self, "__op_fields__", specs)
89
+ object.__setattr__(self, "name", name)
90
+
91
+ def allowed(self) -> set[str]:
92
+ """Get set of allowed field names.
93
+
94
+ Returns:
95
+ Set of field names from specs
96
+ """
97
+ return {i.name for i in self.__op_fields__}
98
+
99
+ def check_allowed(self, *args, as_boolean: bool = False):
100
+ """Check if field names are allowed.
101
+
102
+ Args:
103
+ *args: Field names to check
104
+ as_boolean: If True, return bool instead of raising
105
+
106
+ Returns:
107
+ True if all allowed, False if as_boolean=True and not all allowed
108
+
109
+ Raises:
110
+ ValueError: If as_boolean=False and not all allowed
111
+ """
112
+ if not set(args).issubset(self.allowed()):
113
+ if as_boolean:
114
+ return False
115
+ raise ValueError(
116
+ "Some specified fields are not allowed: "
117
+ f"{set(args).difference(self.allowed())}"
118
+ )
119
+ return True
120
+
121
+ def get(self, key: str, /, default=Unset) -> MaybeUnset[Spec]:
122
+ """Get Spec by field name.
123
+
124
+ Args:
125
+ key: Field name
126
+ default: Default value if not found
127
+
128
+ Returns:
129
+ Spec object or default
130
+ """
131
+ if not self.check_allowed(key, as_boolean=True):
132
+ return default
133
+ for i in self.__op_fields__:
134
+ if i.name == key:
135
+ return i
136
+ return default
137
+
138
+ def get_specs(
139
+ self,
140
+ *,
141
+ include: set[str] | None = None,
142
+ exclude: set[str] | None = None,
143
+ ) -> tuple[Spec, ...]:
144
+ """Get filtered tuple of Specs.
145
+
146
+ Args:
147
+ include: Only include these field names
148
+ exclude: Exclude these field names
149
+
150
+ Returns:
151
+ Filtered tuple of Specs
152
+
153
+ Raises:
154
+ ValueError: If both include and exclude specified, or if invalid names
155
+ """
156
+ if include is not None and exclude is not None:
157
+ raise ValueError("Cannot specify both include and exclude")
158
+
159
+ if include:
160
+ if self.check_allowed(*include, as_boolean=True) is False:
161
+ raise ValueError(
162
+ "Some specified fields are not allowed: "
163
+ f"{set(include).difference(self.allowed())}"
164
+ )
165
+ return tuple(
166
+ self.get(i) for i in include if self.get(i) is not Unset
167
+ )
168
+
169
+ if exclude:
170
+ _discards = {
171
+ self.get(i) for i in exclude if self.get(i) is not Unset
172
+ }
173
+ return tuple(s for s in self.__op_fields__ if s not in _discards)
174
+
175
+ return self.__op_fields__
176
+
177
+ def create_model(
178
+ self,
179
+ adapter: Literal["pydantic"] = "pydantic",
180
+ model_name: str | None = None,
181
+ include: set[str] | None = None,
182
+ exclude: set[str] | None = None,
183
+ **kw,
184
+ ):
185
+ """Create framework-specific model from specs.
186
+
187
+ Args:
188
+ adapter: Adapter type (currently only "pydantic")
189
+ model_name: Name for generated model
190
+ include: Only include these fields
191
+ exclude: Exclude these fields
192
+ **kw: Additional adapter-specific kwargs
193
+
194
+ Returns:
195
+ Generated model class
196
+
197
+ Raises:
198
+ ImportError: If adapter not installed
199
+ ValueError: If adapter not supported
200
+ """
201
+ match adapter:
202
+ case "pydantic":
203
+ try:
204
+ from lionagi.adapters.spec_adapters import (
205
+ PydanticSpecAdapter,
206
+ )
207
+ except ImportError as e:
208
+ raise ImportError(
209
+ "PydanticSpecAdapter requires Pydantic. "
210
+ "Install with: pip install pydantic"
211
+ ) from e
212
+
213
+ kws = {
214
+ "model_name": model_name or self.name or "DynamicModel",
215
+ "include": include,
216
+ "exclude": exclude,
217
+ **kw,
218
+ }
219
+ return PydanticSpecAdapter.create_model(self, **kws)
220
+ case _:
221
+ raise ValueError(f"Unsupported adapter: {adapter}")