lionagi 0.13.2__py3-none-any.whl → 0.13.3__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/fields/action.py +0 -1
- lionagi/fields/reason.py +0 -1
- lionagi/libs/file/save.py +1 -1
- lionagi/libs/schema/as_readable.py +142 -196
- lionagi/libs/schema/extract_docstring.py +1 -2
- lionagi/libs/token_transform/synthlang_/base.py +0 -2
- lionagi/libs/validate/string_similarity.py +1 -2
- lionagi/models/hashable_model.py +0 -1
- lionagi/models/schema_model.py +0 -1
- lionagi/operations/ReAct/utils.py +0 -1
- lionagi/operations/_act/act.py +0 -1
- lionagi/operations/interpret/interpret.py +1 -4
- lionagi/operations/manager.py +0 -1
- lionagi/operations/plan/plan.py +0 -1
- lionagi/operations/select/utils.py +0 -2
- lionagi/protocols/forms/flow.py +3 -1
- lionagi/protocols/generic/pile.py +1 -2
- lionagi/protocols/generic/processor.py +0 -1
- lionagi/protocols/graph/graph.py +1 -3
- lionagi/protocols/mail/package.py +0 -1
- lionagi/protocols/messages/assistant_response.py +0 -2
- lionagi/protocols/messages/message.py +0 -1
- lionagi/service/connections/endpoint_config.py +6 -0
- lionagi/service/connections/match_endpoint.py +26 -8
- lionagi/service/connections/providers/claude_code_.py +8 -9
- lionagi/service/connections/providers/claude_code_cli.py +414 -0
- lionagi/service/connections/providers/oai_.py +1 -1
- lionagi/service/manager.py +0 -1
- lionagi/service/rate_limited_processor.py +0 -2
- lionagi/service/token_calculator.py +0 -3
- lionagi/session/branch.py +0 -2
- lionagi/session/session.py +0 -1
- lionagi/settings.py +0 -1
- lionagi/utils.py +6 -9
- lionagi/version.py +1 -1
- {lionagi-0.13.2.dist-info → lionagi-0.13.3.dist-info}/METADATA +5 -3
- {lionagi-0.13.2.dist-info → lionagi-0.13.3.dist-info}/RECORD +39 -43
- lionagi/traits/__init__.py +0 -58
- lionagi/traits/base.py +0 -216
- lionagi/traits/composer.py +0 -343
- lionagi/traits/protocols.py +0 -495
- lionagi/traits/registry.py +0 -1071
- {lionagi-0.13.2.dist-info → lionagi-0.13.3.dist-info}/WHEEL +0 -0
- {lionagi-0.13.2.dist-info → lionagi-0.13.3.dist-info}/licenses/LICENSE +0 -0
lionagi/traits/composer.py
DELETED
@@ -1,343 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
TraitComposer - Algebraic trait composition with LRU caching.
|
3
|
-
|
4
|
-
This module provides the TraitComposer class that enables algebraic composition
|
5
|
-
of traits with + and & operators, backed by LRU caching to achieve the
|
6
|
-
research target of <10μs model generation.
|
7
|
-
|
8
|
-
Performance characteristics:
|
9
|
-
- LRU cache with configurable size (default: 512 entries)
|
10
|
-
- <10μs model generation target (research validated)
|
11
|
-
- Algebraic composition operations (union, intersection)
|
12
|
-
- Thread-safe composition with proper dependency resolution
|
13
|
-
"""
|
14
|
-
|
15
|
-
from __future__ import annotations
|
16
|
-
|
17
|
-
import functools
|
18
|
-
import threading
|
19
|
-
import time
|
20
|
-
from dataclasses import dataclass, field
|
21
|
-
from typing import Any, ClassVar
|
22
|
-
|
23
|
-
from .base import Trait
|
24
|
-
from .registry import get_global_registry
|
25
|
-
|
26
|
-
__all__ = ["CompositionError", "TraitComposer", "TraitComposition"]
|
27
|
-
|
28
|
-
|
29
|
-
class CompositionError(Exception):
|
30
|
-
"""Raised when trait composition fails due to conflicts or missing dependencies."""
|
31
|
-
|
32
|
-
pass
|
33
|
-
|
34
|
-
|
35
|
-
@dataclass(frozen=True, slots=True)
|
36
|
-
class TraitComposition:
|
37
|
-
"""
|
38
|
-
Immutable representation of a trait composition.
|
39
|
-
|
40
|
-
Supports algebraic operations and caching for fast model generation.
|
41
|
-
"""
|
42
|
-
|
43
|
-
traits: frozenset[Trait]
|
44
|
-
dependencies: frozenset[Trait] = field(default_factory=frozenset)
|
45
|
-
composition_id: str = field(default="")
|
46
|
-
|
47
|
-
def __post_init__(self) -> None:
|
48
|
-
"""Calculate composition ID and dependencies."""
|
49
|
-
if not self.composition_id:
|
50
|
-
# Generate deterministic ID from sorted trait names
|
51
|
-
sorted_names = sorted(trait.name for trait in self.traits)
|
52
|
-
object.__setattr__(self, "composition_id", "+".join(sorted_names))
|
53
|
-
|
54
|
-
if not self.dependencies:
|
55
|
-
# Calculate all dependencies
|
56
|
-
registry = get_global_registry()
|
57
|
-
all_deps: set[Trait] = set()
|
58
|
-
|
59
|
-
for trait in self.traits:
|
60
|
-
trait_def = registry.get_trait_definition(trait)
|
61
|
-
if trait_def:
|
62
|
-
all_deps.update(trait_def.dependencies)
|
63
|
-
|
64
|
-
object.__setattr__(self, "dependencies", frozenset(all_deps))
|
65
|
-
|
66
|
-
def __add__(self, other: TraitComposition | Trait) -> TraitComposition:
|
67
|
-
"""Union composition (A + B contains traits from both A and B)."""
|
68
|
-
if isinstance(other, Trait):
|
69
|
-
other = TraitComposition(traits=frozenset([other]))
|
70
|
-
|
71
|
-
return TraitComposition(traits=self.traits | other.traits)
|
72
|
-
|
73
|
-
def __and__(self, other: TraitComposition | Trait) -> TraitComposition:
|
74
|
-
"""Intersection composition (A & B contains only common traits)."""
|
75
|
-
if isinstance(other, Trait):
|
76
|
-
other = TraitComposition(traits=frozenset([other]))
|
77
|
-
|
78
|
-
return TraitComposition(traits=self.traits & other.traits)
|
79
|
-
|
80
|
-
def __or__(self, other: TraitComposition | Trait) -> TraitComposition:
|
81
|
-
"""Alias for union (same as +)."""
|
82
|
-
return self.__add__(other)
|
83
|
-
|
84
|
-
def __hash__(self) -> int: # type: ignore[explicit-override]
|
85
|
-
"""Hash based on trait set for caching."""
|
86
|
-
return hash(self.traits)
|
87
|
-
|
88
|
-
def __repr__(self) -> str: # type: ignore[explicit-override]
|
89
|
-
"""String representation for debugging."""
|
90
|
-
trait_names = sorted(trait.name for trait in self.traits)
|
91
|
-
return f"TraitComposition({'+'.join(trait_names)})"
|
92
|
-
|
93
|
-
def is_valid(self) -> bool:
|
94
|
-
"""Check if all dependencies are satisfied."""
|
95
|
-
missing = self.dependencies - self.traits
|
96
|
-
return len(missing) == 0
|
97
|
-
|
98
|
-
def get_missing_dependencies(self) -> frozenset[Trait]:
|
99
|
-
"""Get traits that are required but missing."""
|
100
|
-
return self.dependencies - self.traits
|
101
|
-
|
102
|
-
def with_dependencies(self) -> TraitComposition:
|
103
|
-
"""Return a new composition that includes all dependencies."""
|
104
|
-
return TraitComposition(traits=self.traits | self.dependencies)
|
105
|
-
|
106
|
-
|
107
|
-
class TraitComposer:
|
108
|
-
"""
|
109
|
-
High-performance trait composer with LRU caching.
|
110
|
-
|
111
|
-
Provides algebraic trait composition with caching to achieve <10μs
|
112
|
-
model generation as validated in research.
|
113
|
-
"""
|
114
|
-
|
115
|
-
_instance: ClassVar[TraitComposer | None] = None
|
116
|
-
_lock: ClassVar[threading.RLock] = threading.RLock()
|
117
|
-
_compose_lock: ClassVar[threading.Lock] = (
|
118
|
-
threading.Lock()
|
119
|
-
) # Guard for LRU cache miss path
|
120
|
-
|
121
|
-
# Initialize class variables
|
122
|
-
_protocol_cache: ClassVar[dict[Trait, type[Any]]] = {}
|
123
|
-
_validated_compositions: ClassVar[set[frozenset[Trait]]] = set()
|
124
|
-
|
125
|
-
def __init__(self, cache_size: int = 512) -> None:
|
126
|
-
"""Initialize composer with LRU cache."""
|
127
|
-
self._cache_size = cache_size
|
128
|
-
self._composition_cache: dict[frozenset[Trait], type[Any]] = {}
|
129
|
-
self._cache_lock = threading.RLock()
|
130
|
-
self._cache_hits = 0
|
131
|
-
self._cache_misses = 0
|
132
|
-
self._generation_count = 0
|
133
|
-
|
134
|
-
@classmethod
|
135
|
-
def get_instance(cls, cache_size: int = 512) -> TraitComposer:
|
136
|
-
"""Get singleton composer instance."""
|
137
|
-
if cls._instance is None:
|
138
|
-
with cls._lock:
|
139
|
-
if cls._instance is None:
|
140
|
-
cls._instance = cls(cache_size)
|
141
|
-
return cls._instance
|
142
|
-
|
143
|
-
@classmethod
|
144
|
-
def reset(cls) -> None:
|
145
|
-
"""Reset singleton for testing."""
|
146
|
-
with cls._lock:
|
147
|
-
if cls._instance is not None:
|
148
|
-
# Clear the LRU cache before resetting instance
|
149
|
-
cls._instance.clear_cache()
|
150
|
-
cls._protocol_cache.clear()
|
151
|
-
cls._validated_compositions.clear()
|
152
|
-
cls._instance = None
|
153
|
-
|
154
|
-
def compose(self, *traits: Trait | TraitComposition) -> TraitComposition:
|
155
|
-
"""Compose multiple traits into a single composition."""
|
156
|
-
all_traits = set()
|
157
|
-
|
158
|
-
for trait in traits:
|
159
|
-
if isinstance(trait, Trait):
|
160
|
-
all_traits.add(trait)
|
161
|
-
elif isinstance(trait, TraitComposition):
|
162
|
-
all_traits.update(trait.traits)
|
163
|
-
else:
|
164
|
-
raise CompositionError(f"Invalid trait type: {type(trait)}")
|
165
|
-
|
166
|
-
return TraitComposition(traits=frozenset(all_traits))
|
167
|
-
|
168
|
-
# Pre-built empty mixin for performance
|
169
|
-
_EMPTY_MIXIN = type("EmptyMixin", (), {"__slots__": ()})
|
170
|
-
|
171
|
-
@functools.lru_cache(maxsize=512) # noqa: B019
|
172
|
-
def _generate_model_cached(
|
173
|
-
self, trait_tuple: tuple[Trait, ...]
|
174
|
-
) -> type[Any]:
|
175
|
-
"""Generate model class with LRU caching (internal method)."""
|
176
|
-
# Only lock for the actual composition logic, not the entire method
|
177
|
-
with TraitComposer._compose_lock:
|
178
|
-
traits = frozenset(trait_tuple)
|
179
|
-
|
180
|
-
# Performance optimization 1: Skip validation if previously validated
|
181
|
-
composition = TraitComposition(traits=traits)
|
182
|
-
if traits not in TraitComposer._validated_compositions:
|
183
|
-
if not composition.is_valid():
|
184
|
-
missing = composition.get_missing_dependencies()
|
185
|
-
raise CompositionError(
|
186
|
-
f"Missing dependencies: {[t.name for t in missing]}"
|
187
|
-
)
|
188
|
-
TraitComposer._validated_compositions.add(traits)
|
189
|
-
|
190
|
-
# Generate unique class name with hash suffix to avoid collisions
|
191
|
-
sorted_names = sorted(trait.name for trait in traits)
|
192
|
-
# Use hash of the frozenset to ensure uniqueness even with different trait orders
|
193
|
-
trait_hash = abs(hash(traits)) % 10000 # 4-digit hash suffix
|
194
|
-
class_name = (
|
195
|
-
f"Generated{''.join(sorted_names)}Model_{trait_hash:04d}"
|
196
|
-
)
|
197
|
-
|
198
|
-
# Performance optimization 2: Cache protocol lookups
|
199
|
-
registry = get_global_registry()
|
200
|
-
protocol_types = []
|
201
|
-
|
202
|
-
for trait in traits:
|
203
|
-
# Check cached protocols first
|
204
|
-
if trait not in TraitComposer._protocol_cache:
|
205
|
-
trait_def = registry.get_trait_definition(trait)
|
206
|
-
if trait_def and trait_def.protocol_type:
|
207
|
-
TraitComposer._protocol_cache[trait] = (
|
208
|
-
trait_def.protocol_type
|
209
|
-
)
|
210
|
-
|
211
|
-
protocol_type = TraitComposer._protocol_cache.get(trait)
|
212
|
-
if protocol_type:
|
213
|
-
protocol_types.append(protocol_type)
|
214
|
-
|
215
|
-
if not protocol_types:
|
216
|
-
raise CompositionError(
|
217
|
-
f"No protocol types found for traits: {sorted_names}"
|
218
|
-
)
|
219
|
-
|
220
|
-
# Performance optimization 3: Use pre-built empty mixin if only one protocol
|
221
|
-
bases: tuple[type[Any], ...]
|
222
|
-
if len(protocol_types) == 1:
|
223
|
-
# Single inheritance is faster
|
224
|
-
bases = (protocol_types[0], self._EMPTY_MIXIN)
|
225
|
-
else:
|
226
|
-
bases = tuple(protocol_types)
|
227
|
-
|
228
|
-
# Create class with optimized attributes
|
229
|
-
model_class = type(
|
230
|
-
class_name,
|
231
|
-
bases,
|
232
|
-
{
|
233
|
-
"__traits__": traits,
|
234
|
-
"__composition__": composition,
|
235
|
-
"__module__": "lionagi.traits.generated",
|
236
|
-
"__slots__": (), # Prevent __dict__ creation for memory efficiency
|
237
|
-
},
|
238
|
-
)
|
239
|
-
|
240
|
-
# Performance optimization 4: Batch register traits
|
241
|
-
for trait in traits:
|
242
|
-
# Skip validation since we already validated composition
|
243
|
-
registry._trait_implementations.setdefault(
|
244
|
-
model_class, set()
|
245
|
-
).add(trait)
|
246
|
-
|
247
|
-
return model_class
|
248
|
-
|
249
|
-
def generate_model(self, composition: TraitComposition) -> type[Any]:
|
250
|
-
"""
|
251
|
-
Generate a model class from trait composition.
|
252
|
-
|
253
|
-
Performance characteristics:
|
254
|
-
- First generation (cold): ~25μs (relaxed from 10μs research target)
|
255
|
-
- Cached generation (warm): <1μs (meets research target)
|
256
|
-
|
257
|
-
The 25μs cold generation is acceptable for production use as:
|
258
|
-
1. It only occurs once per unique trait combination
|
259
|
-
2. Subsequent calls use LRU cache (<1μs)
|
260
|
-
3. Most applications have limited trait combinations
|
261
|
-
"""
|
262
|
-
start_time = time.perf_counter()
|
263
|
-
|
264
|
-
# Convert to sorted tuple for caching
|
265
|
-
trait_tuple = tuple(sorted(composition.traits, key=lambda t: t.name))
|
266
|
-
|
267
|
-
try:
|
268
|
-
# Use LRU cache for fast generation
|
269
|
-
model_class = self._generate_model_cached(trait_tuple)
|
270
|
-
|
271
|
-
# Track performance
|
272
|
-
generation_time = (
|
273
|
-
time.perf_counter() - start_time
|
274
|
-
) * 1_000_000 # μs
|
275
|
-
self._generation_count += 1
|
276
|
-
|
277
|
-
# Updated performance target to realistic value
|
278
|
-
PERFORMANCE_TARGET_US = (
|
279
|
-
25.0 # Relaxed from 10μs for cold generation
|
280
|
-
)
|
281
|
-
if generation_time > PERFORMANCE_TARGET_US and __debug__:
|
282
|
-
import warnings
|
283
|
-
|
284
|
-
warnings.warn(
|
285
|
-
f"Model generation took {generation_time:.1f}μs, "
|
286
|
-
f"exceeding {PERFORMANCE_TARGET_US}μs target",
|
287
|
-
PerformanceWarning,
|
288
|
-
stacklevel=2,
|
289
|
-
)
|
290
|
-
|
291
|
-
return model_class
|
292
|
-
|
293
|
-
except Exception as e:
|
294
|
-
raise CompositionError(f"Failed to generate model: {e}") from e
|
295
|
-
|
296
|
-
def get_cache_stats(self) -> dict[str, Any]:
|
297
|
-
"""Get performance statistics."""
|
298
|
-
info = self._generate_model_cached.cache_info()
|
299
|
-
return {
|
300
|
-
"cache_hits": info.hits,
|
301
|
-
"cache_misses": info.misses,
|
302
|
-
"cache_size": info.currsize,
|
303
|
-
"max_cache_size": info.maxsize,
|
304
|
-
"hit_ratio": (
|
305
|
-
info.hits / (info.hits + info.misses)
|
306
|
-
if (info.hits + info.misses) > 0
|
307
|
-
else 0.0
|
308
|
-
),
|
309
|
-
"generations": self._generation_count,
|
310
|
-
}
|
311
|
-
|
312
|
-
def clear_cache(self) -> None:
|
313
|
-
"""Clear the composition cache."""
|
314
|
-
self._generate_model_cached.cache_clear()
|
315
|
-
self._generation_count = 0
|
316
|
-
|
317
|
-
|
318
|
-
# Initialize class-level caches
|
319
|
-
TraitComposer._protocol_cache = {}
|
320
|
-
TraitComposer._validated_compositions = set()
|
321
|
-
|
322
|
-
|
323
|
-
# Convenience functions
|
324
|
-
def compose(*traits: Trait | TraitComposition) -> TraitComposition:
|
325
|
-
"""Compose traits using the global composer."""
|
326
|
-
return TraitComposer.get_instance().compose(*traits)
|
327
|
-
|
328
|
-
|
329
|
-
def generate_model(composition: TraitComposition) -> type[Any]:
|
330
|
-
"""Generate a model class from composition using the global composer."""
|
331
|
-
return TraitComposer.get_instance().generate_model(composition)
|
332
|
-
|
333
|
-
|
334
|
-
def create_trait_composition(*traits: Trait) -> TraitComposition:
|
335
|
-
"""Create a trait composition from individual traits."""
|
336
|
-
return TraitComposition(traits=frozenset(traits))
|
337
|
-
|
338
|
-
|
339
|
-
# Performance warning class
|
340
|
-
class PerformanceWarning(UserWarning):
|
341
|
-
"""Warning for performance issues in trait composition."""
|
342
|
-
|
343
|
-
pass
|