lionagi 0.15.13__py3-none-any.whl → 0.16.0__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/config.py +1 -0
- lionagi/libs/validate/fuzzy_match_keys.py +5 -182
- lionagi/libs/validate/string_similarity.py +6 -331
- lionagi/ln/__init__.py +56 -66
- lionagi/ln/_async_call.py +13 -10
- lionagi/ln/_hash.py +33 -8
- lionagi/ln/_list_call.py +2 -35
- lionagi/ln/_to_list.py +51 -28
- lionagi/ln/_utils.py +156 -0
- lionagi/ln/concurrency/__init__.py +39 -31
- lionagi/ln/concurrency/_compat.py +65 -0
- lionagi/ln/concurrency/cancel.py +92 -109
- lionagi/ln/concurrency/errors.py +17 -17
- lionagi/ln/concurrency/patterns.py +249 -206
- lionagi/ln/concurrency/primitives.py +257 -216
- lionagi/ln/concurrency/resource_tracker.py +42 -155
- lionagi/ln/concurrency/task.py +55 -73
- lionagi/ln/concurrency/throttle.py +3 -0
- lionagi/ln/concurrency/utils.py +1 -0
- lionagi/ln/fuzzy/__init__.py +15 -0
- lionagi/ln/{_extract_json.py → fuzzy/_extract_json.py} +22 -9
- lionagi/ln/{_fuzzy_json.py → fuzzy/_fuzzy_json.py} +14 -8
- lionagi/ln/fuzzy/_fuzzy_match.py +172 -0
- lionagi/ln/fuzzy/_fuzzy_validate.py +46 -0
- lionagi/ln/fuzzy/_string_similarity.py +332 -0
- lionagi/ln/{_models.py → types.py} +153 -4
- lionagi/operations/flow.py +2 -1
- lionagi/operations/operate/operate.py +26 -16
- lionagi/protocols/contracts.py +46 -0
- lionagi/protocols/generic/event.py +6 -6
- lionagi/protocols/generic/processor.py +9 -5
- lionagi/protocols/ids.py +82 -0
- lionagi/protocols/types.py +10 -12
- lionagi/service/connections/match_endpoint.py +9 -0
- lionagi/service/connections/providers/nvidia_nim_.py +100 -0
- lionagi/utils.py +34 -64
- lionagi/version.py +1 -1
- {lionagi-0.15.13.dist-info → lionagi-0.16.0.dist-info}/METADATA +4 -2
- {lionagi-0.15.13.dist-info → lionagi-0.16.0.dist-info}/RECORD +41 -33
- lionagi/ln/_types.py +0 -146
- {lionagi-0.15.13.dist-info → lionagi-0.16.0.dist-info}/WHEEL +0 -0
- {lionagi-0.15.13.dist-info → lionagi-0.16.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,290 +1,331 @@
|
|
1
|
-
"""
|
1
|
+
"""Core async primitives (thin wrappers over anyio)"""
|
2
2
|
|
3
|
-
|
4
|
-
"""
|
3
|
+
from __future__ import annotations
|
5
4
|
|
6
|
-
import
|
7
|
-
from
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from typing import Any, Generic, TypeVar
|
8
7
|
|
9
8
|
import anyio
|
9
|
+
import anyio.abc
|
10
10
|
|
11
|
-
|
11
|
+
T = TypeVar("T")
|
12
|
+
|
13
|
+
|
14
|
+
__all__ = (
|
15
|
+
"Lock",
|
16
|
+
"Semaphore",
|
17
|
+
"CapacityLimiter",
|
18
|
+
"Queue",
|
19
|
+
"Event",
|
20
|
+
"Condition",
|
21
|
+
)
|
12
22
|
|
13
23
|
|
14
24
|
class Lock:
|
15
|
-
"""
|
25
|
+
"""Async mutex lock (anyio.Lock wrapper).
|
26
|
+
|
27
|
+
Provides mutual exclusion for async code. Use with async context manager
|
28
|
+
for automatic acquisition/release.
|
29
|
+
"""
|
16
30
|
|
17
|
-
|
18
|
-
|
31
|
+
__slots__ = ("_lock",)
|
32
|
+
|
33
|
+
def __init__(self) -> None:
|
19
34
|
self._lock = anyio.Lock()
|
20
|
-
self._acquired = False
|
21
|
-
track_resource(self, f"Lock-{id(self)}", "Lock")
|
22
|
-
|
23
|
-
def __del__(self):
|
24
|
-
"""Clean up resource tracking when lock is destroyed."""
|
25
|
-
try:
|
26
|
-
untrack_resource(self)
|
27
|
-
except Exception:
|
28
|
-
pass
|
29
|
-
|
30
|
-
async def __aenter__(self) -> None:
|
31
|
-
"""Acquire the lock."""
|
32
|
-
await self._lock.acquire()
|
33
|
-
self._acquired = True
|
34
|
-
|
35
|
-
async def __aexit__(
|
36
|
-
self,
|
37
|
-
exc_type: type[BaseException] | None,
|
38
|
-
exc_val: BaseException | None,
|
39
|
-
exc_tb: TracebackType | None,
|
40
|
-
) -> None:
|
41
|
-
"""Release the lock."""
|
42
|
-
self._lock.release()
|
43
|
-
self._acquired = False
|
44
35
|
|
45
36
|
async def acquire(self) -> None:
|
46
|
-
"""Acquire the lock
|
37
|
+
"""Acquire the lock, blocking if necessary."""
|
47
38
|
await self._lock.acquire()
|
48
|
-
self._acquired = True
|
49
39
|
|
50
40
|
def release(self) -> None:
|
51
|
-
"""Release the lock
|
52
|
-
if not self._acquired:
|
53
|
-
raise RuntimeError(
|
54
|
-
"Attempted to release lock that was not acquired by this task"
|
55
|
-
)
|
41
|
+
"""Release the lock."""
|
56
42
|
self._lock.release()
|
57
|
-
|
43
|
+
|
44
|
+
async def __aenter__(self) -> Lock:
|
45
|
+
await self.acquire()
|
46
|
+
return self
|
47
|
+
|
48
|
+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
49
|
+
self.release()
|
58
50
|
|
59
51
|
|
60
52
|
class Semaphore:
|
61
|
-
"""
|
53
|
+
"""Async semaphore (anyio.Semaphore wrapper).
|
54
|
+
|
55
|
+
Limits concurrent access to a resource. Initialized with a count,
|
56
|
+
decremented on acquire, incremented on release.
|
57
|
+
"""
|
58
|
+
|
59
|
+
__slots__ = ("_sem",)
|
62
60
|
|
63
|
-
def __init__(self, initial_value: int):
|
64
|
-
"""Initialize a new semaphore."""
|
61
|
+
def __init__(self, initial_value: int) -> None:
|
65
62
|
if initial_value < 0:
|
66
|
-
raise ValueError("
|
67
|
-
self.
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
async def __aenter__(self) -> None:
|
80
|
-
"""Acquire the semaphore."""
|
63
|
+
raise ValueError("initial_value must be >= 0")
|
64
|
+
self._sem = anyio.Semaphore(initial_value)
|
65
|
+
|
66
|
+
async def acquire(self) -> None:
|
67
|
+
"""Acquire a semaphore slot, blocking if none available."""
|
68
|
+
await self._sem.acquire()
|
69
|
+
|
70
|
+
def release(self) -> None:
|
71
|
+
"""Release a semaphore slot."""
|
72
|
+
self._sem.release()
|
73
|
+
|
74
|
+
async def __aenter__(self) -> Semaphore:
|
81
75
|
await self.acquire()
|
76
|
+
return self
|
82
77
|
|
83
|
-
async def __aexit__(
|
84
|
-
self,
|
85
|
-
exc_type: type[BaseException] | None,
|
86
|
-
exc_val: BaseException | None,
|
87
|
-
exc_tb: TracebackType | None,
|
88
|
-
) -> None:
|
89
|
-
"""Release the semaphore."""
|
78
|
+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
90
79
|
self.release()
|
91
80
|
|
81
|
+
|
82
|
+
class CapacityLimiter:
|
83
|
+
"""Capacity limiter for controlling resource usage (anyio.CapacityLimiter wrapper).
|
84
|
+
|
85
|
+
Controls concurrent access to limited resources like threads or connections.
|
86
|
+
Key advantages over Semaphore:
|
87
|
+
- Supports fractional tokens for fine-grained control
|
88
|
+
- Allows dynamic capacity adjustment at runtime
|
89
|
+
- Provides delegation methods for resource pooling
|
90
|
+
"""
|
91
|
+
|
92
|
+
__slots__ = ("_lim",)
|
93
|
+
|
94
|
+
def __init__(self, total_tokens: float) -> None:
|
95
|
+
"""Initialize with given capacity.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
total_tokens: Maximum capacity (must be > 0).
|
99
|
+
Can be fractional for fine-grained control.
|
100
|
+
"""
|
101
|
+
if total_tokens <= 0:
|
102
|
+
raise ValueError("total_tokens must be > 0")
|
103
|
+
self._lim = anyio.CapacityLimiter(total_tokens)
|
104
|
+
|
92
105
|
async def acquire(self) -> None:
|
93
|
-
"""Acquire
|
94
|
-
await self.
|
95
|
-
self._current_acquisitions += 1
|
106
|
+
"""Acquire capacity, blocking if none available."""
|
107
|
+
await self._lim.acquire()
|
96
108
|
|
97
109
|
def release(self) -> None:
|
98
|
-
"""Release
|
99
|
-
|
100
|
-
raise RuntimeError(
|
101
|
-
"Cannot release semaphore: no outstanding acquisitions"
|
102
|
-
)
|
103
|
-
self._semaphore.release()
|
104
|
-
self._current_acquisitions -= 1
|
110
|
+
"""Release capacity."""
|
111
|
+
self._lim.release()
|
105
112
|
|
106
113
|
@property
|
107
|
-
def
|
108
|
-
"""
|
109
|
-
return self.
|
114
|
+
def remaining_tokens(self) -> float:
|
115
|
+
"""Current available capacity (deprecated, use available_tokens)."""
|
116
|
+
return self._lim.available_tokens
|
110
117
|
|
111
118
|
@property
|
112
|
-
def
|
113
|
-
"""Get the
|
114
|
-
return self.
|
119
|
+
def total_tokens(self) -> float:
|
120
|
+
"""Get the current capacity limit."""
|
121
|
+
return self._lim.total_tokens
|
115
122
|
|
123
|
+
@total_tokens.setter
|
124
|
+
def total_tokens(self, value: float) -> None:
|
125
|
+
"""Dynamically adjust the capacity limit.
|
116
126
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
self.
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
"""
|
127
|
+
Args:
|
128
|
+
value: New capacity (must be > 0).
|
129
|
+
Can be adjusted at runtime to adapt to load.
|
130
|
+
"""
|
131
|
+
if value <= 0:
|
132
|
+
raise ValueError("total_tokens must be > 0")
|
133
|
+
self._lim.total_tokens = value
|
134
|
+
|
135
|
+
@property
|
136
|
+
def borrowed_tokens(self) -> float:
|
137
|
+
"""Get the number of currently borrowed tokens."""
|
138
|
+
return self._lim.borrowed_tokens
|
139
|
+
|
140
|
+
@property
|
141
|
+
def available_tokens(self) -> float:
|
142
|
+
"""Get the number of currently available tokens."""
|
143
|
+
return self._lim.available_tokens
|
144
|
+
|
145
|
+
def acquire_on_behalf_of(self, borrower: object) -> None:
|
146
|
+
"""Synchronously acquire capacity on behalf of another object.
|
147
|
+
|
148
|
+
For resource pooling where the acquirer differs from the releaser.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
borrower: Object that will be responsible for releasing.
|
152
|
+
"""
|
153
|
+
self._lim.acquire_on_behalf_of(borrower)
|
154
|
+
|
155
|
+
def release_on_behalf_of(self, borrower: object) -> None:
|
156
|
+
"""Release capacity that was acquired on behalf of an object.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
borrower: Object that previously acquired the capacity.
|
160
|
+
"""
|
161
|
+
self._lim.release_on_behalf_of(borrower)
|
162
|
+
|
163
|
+
# Support idiomatic AnyIO usage: `async with limiter: ...`
|
164
|
+
async def __aenter__(self) -> CapacityLimiter:
|
147
165
|
await self.acquire()
|
166
|
+
return self
|
148
167
|
|
149
|
-
async def __aexit__(
|
150
|
-
self,
|
151
|
-
exc_type: type[BaseException] | None,
|
152
|
-
exc_val: BaseException | None,
|
153
|
-
exc_tb: TracebackType | None,
|
154
|
-
) -> None:
|
155
|
-
"""Release the token."""
|
168
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
156
169
|
self.release()
|
157
170
|
|
158
|
-
async def acquire(self) -> None:
|
159
|
-
"""Acquire a token."""
|
160
|
-
# Create a unique borrower identity for each acquisition
|
161
|
-
self._borrower_counter += 1
|
162
|
-
borrower = f"borrower-{self._borrower_counter}"
|
163
|
-
await self._limiter.acquire_on_behalf_of(borrower)
|
164
|
-
self._active_borrowers[borrower] = True
|
165
171
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
if not self._active_borrowers:
|
170
|
-
raise RuntimeError("No tokens to release")
|
172
|
+
@dataclass(slots=True)
|
173
|
+
class Queue(Generic[T]):
|
174
|
+
"""Async queue using anyio memory object streams.
|
171
175
|
|
172
|
-
|
173
|
-
|
174
|
-
del self._active_borrowers[borrower]
|
176
|
+
Provides FIFO queue semantics with optional maxsize for backpressure.
|
177
|
+
Must call close() or use async context manager for proper cleanup.
|
175
178
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
179
|
+
Usage:
|
180
|
+
queue = Queue.with_maxsize(100)
|
181
|
+
await queue.put(item)
|
182
|
+
item = await queue.get()
|
183
|
+
await queue.close()
|
184
|
+
"""
|
180
185
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
186
|
+
_send: anyio.abc.ObjectSendStream[T]
|
187
|
+
_recv: anyio.abc.ObjectReceiveStream[T]
|
188
|
+
|
189
|
+
@classmethod
|
190
|
+
def with_maxsize(cls, maxsize: int) -> Queue[T]:
|
191
|
+
"""Create queue with maximum buffer size."""
|
192
|
+
send, recv = anyio.create_memory_object_stream(maxsize)
|
193
|
+
return cls(send, recv)
|
194
|
+
|
195
|
+
async def put(self, item: T) -> None:
|
196
|
+
"""Put item into queue. May block if queue is full."""
|
197
|
+
await self._send.send(item)
|
198
|
+
|
199
|
+
def put_nowait(self, item: T) -> None:
|
200
|
+
"""Put item into queue without blocking.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
item: Item to put in the queue.
|
204
|
+
|
205
|
+
Raises:
|
206
|
+
anyio.WouldBlock: If the queue is full.
|
207
|
+
"""
|
208
|
+
self._send.send_nowait(item)
|
209
|
+
|
210
|
+
async def get(self) -> T:
|
211
|
+
"""Get item from queue. Blocks until item available."""
|
212
|
+
return await self._recv.receive()
|
213
|
+
|
214
|
+
def get_nowait(self) -> T:
|
215
|
+
"""Get item from queue without blocking.
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
Next item from the queue.
|
219
|
+
|
220
|
+
Raises:
|
221
|
+
anyio.WouldBlock: If the queue is empty.
|
222
|
+
"""
|
223
|
+
return self._recv.receive_nowait()
|
224
|
+
|
225
|
+
async def close(self) -> None:
|
226
|
+
"""Close both send and receive streams. Call this for cleanup."""
|
227
|
+
await self._send.aclose()
|
228
|
+
await self._recv.aclose()
|
229
|
+
|
230
|
+
async def __aenter__(self) -> Queue[T]:
|
231
|
+
return self
|
232
|
+
|
233
|
+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
234
|
+
await self.close()
|
202
235
|
|
203
236
|
@property
|
204
|
-
def
|
205
|
-
"""
|
206
|
-
return self.
|
237
|
+
def sender(self) -> anyio.abc.ObjectSendStream[T]:
|
238
|
+
"""Direct access to send stream for advanced usage."""
|
239
|
+
return self._send
|
207
240
|
|
208
241
|
@property
|
209
|
-
def
|
210
|
-
"""
|
211
|
-
return self.
|
242
|
+
def receiver(self) -> anyio.abc.ObjectReceiveStream[T]:
|
243
|
+
"""Direct access to receive stream for advanced usage."""
|
244
|
+
return self._recv
|
212
245
|
|
213
246
|
|
214
247
|
class Event:
|
215
|
-
"""
|
248
|
+
"""Async event for signaling between tasks (anyio.Event wrapper).
|
216
249
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
250
|
+
An event object manages an internal flag that can be set to true
|
251
|
+
with set() and reset to false with clear(). The wait() method blocks
|
252
|
+
until the flag is true.
|
253
|
+
"""
|
221
254
|
|
222
|
-
|
223
|
-
"""Clean up resource tracking when event is destroyed."""
|
224
|
-
try:
|
225
|
-
untrack_resource(self)
|
226
|
-
except Exception:
|
227
|
-
pass
|
255
|
+
__slots__ = ("_event",)
|
228
256
|
|
229
|
-
def
|
230
|
-
|
231
|
-
return self._event.is_set()
|
257
|
+
def __init__(self) -> None:
|
258
|
+
self._event = anyio.Event()
|
232
259
|
|
233
260
|
def set(self) -> None:
|
234
|
-
"""Set the
|
261
|
+
"""Set the internal flag to true, waking up all waiting tasks."""
|
235
262
|
self._event.set()
|
236
263
|
|
264
|
+
def is_set(self) -> bool:
|
265
|
+
"""Return True if the internal flag is set."""
|
266
|
+
return self._event.is_set()
|
267
|
+
|
237
268
|
async def wait(self) -> None:
|
238
|
-
"""
|
269
|
+
"""Block until the internal flag becomes true."""
|
239
270
|
await self._event.wait()
|
240
271
|
|
272
|
+
def statistics(self) -> anyio.EventStatistics:
|
273
|
+
"""Return statistics about waiting tasks."""
|
274
|
+
return self._event.statistics()
|
275
|
+
|
241
276
|
|
242
277
|
class Condition:
|
243
|
-
"""
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
def
|
252
|
-
"""
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
278
|
+
"""Async condition variable (anyio.Condition wrapper).
|
279
|
+
|
280
|
+
A condition variable allows one or more coroutines to wait until
|
281
|
+
they are notified by another coroutine. Must be used with a Lock.
|
282
|
+
"""
|
283
|
+
|
284
|
+
__slots__ = ("_condition",)
|
285
|
+
|
286
|
+
def __init__(self, lock: Lock | None = None) -> None:
|
287
|
+
"""Initialize with an optional lock.
|
288
|
+
|
289
|
+
Args:
|
290
|
+
lock: Lock to use. If None, creates a new Lock.
|
291
|
+
"""
|
292
|
+
_lock = lock._lock if lock else None
|
293
|
+
self._condition = anyio.Condition(_lock)
|
294
|
+
|
295
|
+
async def acquire(self) -> None:
|
259
296
|
"""Acquire the underlying lock."""
|
260
|
-
await self.
|
261
|
-
return self
|
297
|
+
await self._condition.acquire()
|
262
298
|
|
263
|
-
|
264
|
-
self,
|
265
|
-
exc_type: type[BaseException] | None,
|
266
|
-
exc_val: BaseException | None,
|
267
|
-
exc_tb: TracebackType | None,
|
268
|
-
) -> None:
|
299
|
+
def release(self) -> None:
|
269
300
|
"""Release the underlying lock."""
|
270
|
-
|
301
|
+
self._condition.release()
|
302
|
+
|
303
|
+
async def __aenter__(self) -> Condition:
|
304
|
+
await self.acquire()
|
305
|
+
return self
|
306
|
+
|
307
|
+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
308
|
+
self.release()
|
271
309
|
|
272
310
|
async def wait(self) -> None:
|
273
|
-
"""Wait
|
311
|
+
"""Wait until notified.
|
274
312
|
|
275
|
-
|
276
|
-
reacquires the lock.
|
313
|
+
Releases the lock, blocks until notified, then re-acquires the lock.
|
277
314
|
"""
|
278
315
|
await self._condition.wait()
|
279
316
|
|
280
|
-
|
281
|
-
"""
|
317
|
+
def notify(self, n: int = 1) -> None:
|
318
|
+
"""Wake up at most n tasks waiting on this condition.
|
282
319
|
|
283
320
|
Args:
|
284
|
-
n:
|
321
|
+
n: Maximum number of tasks to wake (default: 1)
|
285
322
|
"""
|
286
|
-
|
323
|
+
self._condition.notify(n)
|
324
|
+
|
325
|
+
def notify_all(self) -> None:
|
326
|
+
"""Wake up all tasks waiting on this condition."""
|
327
|
+
self._condition.notify_all()
|
287
328
|
|
288
|
-
|
289
|
-
"""
|
290
|
-
|
329
|
+
def statistics(self) -> anyio.abc.ConditionStatistics:
|
330
|
+
"""Return statistics about waiting tasks."""
|
331
|
+
return self._condition.statistics()
|