lionagi 0.14.5__py3-none-any.whl → 0.14.7__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/instruct.py +3 -17
- lionagi/libs/concurrency/cancel.py +1 -1
- lionagi/libs/hash/__init__.py +3 -0
- lionagi/libs/hash/hash_dict.py +108 -0
- lionagi/libs/hash/manager.py +26 -0
- lionagi/models/hashable_model.py +2 -1
- lionagi/operations/builder.py +9 -0
- lionagi/operations/flow.py +163 -60
- lionagi/protocols/generic/pile.py +34 -15
- lionagi/protocols/messages/message.py +3 -1
- lionagi/service/connections/providers/_claude_code/__init__.py +3 -0
- lionagi/service/connections/providers/_claude_code/models.py +234 -0
- lionagi/service/connections/providers/_claude_code/stream_cli.py +359 -0
- lionagi/service/connections/providers/claude_code_.py +13 -223
- lionagi/service/connections/providers/claude_code_cli.py +38 -343
- lionagi/service/types.py +2 -1
- lionagi/session/branch.py +6 -46
- lionagi/session/session.py +26 -8
- lionagi/version.py +1 -1
- {lionagi-0.14.5.dist-info → lionagi-0.14.7.dist-info}/METADATA +9 -19
- {lionagi-0.14.5.dist-info → lionagi-0.14.7.dist-info}/RECORD +23 -17
- {lionagi-0.14.5.dist-info → lionagi-0.14.7.dist-info}/WHEEL +0 -0
- {lionagi-0.14.5.dist-info → lionagi-0.14.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,352 +1,23 @@
|
|
1
|
+
# Copyright (c) 2025, HaiyangLi <quantocean.li at gmail dot com>
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
1
5
|
from __future__ import annotations
|
2
6
|
|
3
|
-
import
|
4
|
-
import codecs
|
5
|
-
import contextlib
|
6
|
-
import dataclasses
|
7
|
-
import json
|
8
|
-
import logging
|
9
|
-
import shutil
|
10
|
-
from collections.abc import AsyncIterator, Callable
|
11
|
-
from datetime import datetime
|
12
|
-
from functools import partial
|
13
|
-
from textwrap import shorten
|
14
|
-
from typing import Any
|
7
|
+
from collections.abc import AsyncIterator
|
15
8
|
|
16
|
-
from json_repair import repair_json
|
17
9
|
from pydantic import BaseModel
|
18
10
|
|
19
|
-
from lionagi.libs.schema.as_readable import as_readable
|
20
11
|
from lionagi.service.connections.endpoint import Endpoint, EndpointConfig
|
21
12
|
from lionagi.utils import to_dict
|
22
13
|
|
23
|
-
from .
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
logging.basicConfig(level=logging.INFO)
|
31
|
-
log = logging.getLogger("claude-cli")
|
32
|
-
|
33
|
-
|
34
|
-
@dataclasses.dataclass
|
35
|
-
class ClaudeChunk:
|
36
|
-
"""Low-level wrapper around every NDJSON object coming from the CLI."""
|
37
|
-
|
38
|
-
raw: dict[str, Any]
|
39
|
-
type: str
|
40
|
-
# convenience views
|
41
|
-
thinking: str | None = None
|
42
|
-
text: str | None = None
|
43
|
-
tool_use: dict[str, Any] | None = None
|
44
|
-
tool_result: dict[str, Any] | None = None
|
45
|
-
|
46
|
-
|
47
|
-
@dataclasses.dataclass
|
48
|
-
class ClaudeSession:
|
49
|
-
"""Aggregated view of a whole CLI conversation."""
|
50
|
-
|
51
|
-
session_id: str | None = None
|
52
|
-
model: str | None = None
|
53
|
-
|
54
|
-
# chronological log
|
55
|
-
chunks: list[ClaudeChunk] = dataclasses.field(default_factory=list)
|
56
|
-
|
57
|
-
# materialised views
|
58
|
-
thinking_log: list[str] = dataclasses.field(default_factory=list)
|
59
|
-
messages: list[dict[str, Any]] = dataclasses.field(default_factory=list)
|
60
|
-
tool_uses: list[dict[str, Any]] = dataclasses.field(default_factory=list)
|
61
|
-
tool_results: list[dict[str, Any]] = dataclasses.field(
|
62
|
-
default_factory=list
|
63
|
-
)
|
64
|
-
|
65
|
-
# final summary
|
66
|
-
result: str = ""
|
67
|
-
usage: dict[str, Any] = dataclasses.field(default_factory=dict)
|
68
|
-
total_cost_usd: float | None = None
|
69
|
-
num_turns: int | None = None
|
70
|
-
duration_ms: int | None = None
|
71
|
-
duration_api_ms: int | None = None
|
72
|
-
is_error: bool = False
|
73
|
-
|
74
|
-
|
75
|
-
# --------------------------------------------------------------------------- helpers
|
76
|
-
|
77
|
-
|
78
|
-
async def ndjson_from_cli(request: ClaudeCodeRequest):
|
79
|
-
"""
|
80
|
-
Yields each JSON object emitted by the *claude-code* CLI.
|
81
|
-
|
82
|
-
• Robust against UTF‑8 splits across chunks (incremental decoder).
|
83
|
-
• Robust against braces inside strings (uses json.JSONDecoder.raw_decode)
|
84
|
-
• Falls back to `json_repair.repair_json` when necessary.
|
85
|
-
"""
|
86
|
-
workspace = request.cwd()
|
87
|
-
workspace.mkdir(parents=True, exist_ok=True)
|
88
|
-
|
89
|
-
proc = await asyncio.create_subprocess_exec(
|
90
|
-
CLAUDE,
|
91
|
-
*request.as_cmd_args(),
|
92
|
-
cwd=str(workspace),
|
93
|
-
stdout=asyncio.subprocess.PIPE,
|
94
|
-
stderr=asyncio.subprocess.PIPE,
|
95
|
-
)
|
96
|
-
|
97
|
-
decoder = codecs.getincrementaldecoder("utf-8")()
|
98
|
-
json_decoder = json.JSONDecoder()
|
99
|
-
buffer: str = "" # text buffer that may hold >1 JSON objects
|
100
|
-
|
101
|
-
try:
|
102
|
-
while True:
|
103
|
-
chunk = await proc.stdout.read(4096)
|
104
|
-
if not chunk:
|
105
|
-
break
|
106
|
-
|
107
|
-
# 1) decode *incrementally* so we never split multibyte chars
|
108
|
-
buffer += decoder.decode(chunk)
|
109
|
-
|
110
|
-
# 2) try to peel off as many complete JSON objs as possible
|
111
|
-
while buffer:
|
112
|
-
buffer = buffer.lstrip() # remove leading spaces/newlines
|
113
|
-
if not buffer:
|
114
|
-
break
|
115
|
-
try:
|
116
|
-
obj, idx = json_decoder.raw_decode(buffer)
|
117
|
-
yield obj
|
118
|
-
buffer = buffer[idx:] # keep remainder for next round
|
119
|
-
except json.JSONDecodeError:
|
120
|
-
# incomplete → need more bytes
|
121
|
-
break
|
122
|
-
|
123
|
-
# 3) flush any tail bytes in the incremental decoder
|
124
|
-
buffer += decoder.decode(b"", final=True)
|
125
|
-
buffer = buffer.strip()
|
126
|
-
if buffer:
|
127
|
-
try:
|
128
|
-
obj, idx = json_decoder.raw_decode(buffer)
|
129
|
-
yield obj
|
130
|
-
except json.JSONDecodeError:
|
131
|
-
try:
|
132
|
-
fixed = repair_json(buffer)
|
133
|
-
yield json.loads(fixed)
|
134
|
-
log.warning(
|
135
|
-
"Repaired malformed JSON fragment at stream end"
|
136
|
-
)
|
137
|
-
except Exception:
|
138
|
-
log.error(
|
139
|
-
"Skipped unrecoverable JSON tail: %.120s…", buffer
|
140
|
-
)
|
141
|
-
|
142
|
-
# 4) propagate non‑zero exit code
|
143
|
-
if await proc.wait() != 0:
|
144
|
-
err = (await proc.stderr.read()).decode().strip()
|
145
|
-
raise RuntimeError(err or "CLI exited non‑zero")
|
146
|
-
|
147
|
-
finally:
|
148
|
-
with contextlib.suppress(ProcessLookupError):
|
149
|
-
proc.terminate()
|
150
|
-
await proc.wait()
|
151
|
-
|
152
|
-
|
153
|
-
# --------------------------------------------------------------------------- SSE route
|
154
|
-
async def stream_events(request: ClaudeCodeRequest):
|
155
|
-
async for obj in ndjson_from_cli(request):
|
156
|
-
yield obj
|
157
|
-
yield {"type": "done"}
|
158
|
-
|
159
|
-
|
160
|
-
print_readable = partial(as_readable, md=True, display_str=True)
|
161
|
-
|
162
|
-
|
163
|
-
def _pp_system(sys_obj: dict[str, Any], theme) -> None:
|
164
|
-
txt = (
|
165
|
-
f"◼️ **Claude Code Session** \n"
|
166
|
-
f"- id: `{sys_obj.get('session_id', '?')}` \n"
|
167
|
-
f"- model: `{sys_obj.get('model', '?')}` \n"
|
168
|
-
f"- tools: {', '.join(sys_obj.get('tools', [])[:8])}"
|
169
|
-
+ ("…" if len(sys_obj.get("tools", [])) > 8 else "")
|
170
|
-
)
|
171
|
-
print_readable(txt, border=False, theme=theme)
|
172
|
-
|
173
|
-
|
174
|
-
def _pp_thinking(thought: str, theme) -> None:
|
175
|
-
text = f"""
|
176
|
-
🧠 Thinking:
|
177
|
-
{thought}
|
178
|
-
"""
|
179
|
-
print_readable(text, border=True, theme=theme)
|
180
|
-
|
181
|
-
|
182
|
-
def _pp_assistant_text(text: str, theme) -> None:
|
183
|
-
txt = f"""
|
184
|
-
> 🗣️ Claude:
|
185
|
-
{text}
|
186
|
-
"""
|
187
|
-
print_readable(txt, theme=theme)
|
188
|
-
|
189
|
-
|
190
|
-
def _pp_tool_use(tu: dict[str, Any], theme) -> None:
|
191
|
-
preview = shorten(str(tu["input"]).replace("\n", " "), 130)
|
192
|
-
body = f"- 🔧 Tool Use — {tu['name']}({tu['id']}) - input: {preview}"
|
193
|
-
print_readable(body, border=False, panel=False, theme=theme)
|
194
|
-
|
195
|
-
|
196
|
-
def _pp_tool_result(tr: dict[str, Any], theme) -> None:
|
197
|
-
body_preview = shorten(str(tr["content"]).replace("\n", " "), 130)
|
198
|
-
status = "ERR" if tr.get("is_error") else "OK"
|
199
|
-
body = f"- 📄 Tool Result({tr['tool_use_id']}) - {status}\n\n\tcontent: {body_preview}"
|
200
|
-
print_readable(body, border=False, panel=False, theme=theme)
|
201
|
-
|
202
|
-
|
203
|
-
def _pp_final(sess: ClaudeSession, theme) -> None:
|
204
|
-
usage = sess.usage or {}
|
205
|
-
txt = (
|
206
|
-
f"### ✅ Session complete - {datetime.utcnow().isoformat(timespec='seconds')} UTC\n"
|
207
|
-
f"**Result:**\n\n{sess.result or ''}\n\n"
|
208
|
-
f"- cost: **${sess.total_cost_usd:.4f}** \n"
|
209
|
-
f"- turns: **{sess.num_turns}** \n"
|
210
|
-
f"- duration: **{sess.duration_ms} ms** (API {sess.duration_api_ms} ms) \n"
|
211
|
-
f"- tokens in/out: {usage.get('input_tokens', 0)}/{usage.get('output_tokens', 0)}"
|
212
|
-
)
|
213
|
-
print_readable(txt, theme=theme)
|
214
|
-
|
215
|
-
|
216
|
-
# --------------------------------------------------------------------------- internal utils
|
217
|
-
|
218
|
-
|
219
|
-
async def _maybe_await(func, *args, **kw):
|
220
|
-
"""Call func which may be sync or async."""
|
221
|
-
res = func(*args, **kw) if func else None
|
222
|
-
if asyncio.iscoroutine(res):
|
223
|
-
await res
|
224
|
-
|
225
|
-
|
226
|
-
# --------------------------------------------------------------------------- main parser
|
227
|
-
|
228
|
-
|
229
|
-
async def stream_claude_code_cli( # noqa: C901 (complexity from branching is fine here)
|
230
|
-
request: ClaudeCodeRequest,
|
231
|
-
session: ClaudeSession = ClaudeSession(),
|
232
|
-
*,
|
233
|
-
on_system: Callable[[dict[str, Any]], None] | None = None,
|
234
|
-
on_thinking: Callable[[str], None] | None = None,
|
235
|
-
on_text: Callable[[str], None] | None = None,
|
236
|
-
on_tool_use: Callable[[dict[str, Any]], None] | None = None,
|
237
|
-
on_tool_result: Callable[[dict[str, Any]], None] | None = None,
|
238
|
-
on_final: Callable[[ClaudeSession], None] | None = None,
|
239
|
-
) -> AsyncIterator[ClaudeChunk | dict | ClaudeSession]:
|
240
|
-
"""
|
241
|
-
Consume the ND‑JSON stream produced by ndjson_from_cli()
|
242
|
-
and return a fully‑populated ClaudeSession.
|
243
|
-
|
244
|
-
If callbacks are omitted a default pretty‑print is emitted.
|
245
|
-
"""
|
246
|
-
stream = ndjson_from_cli(request)
|
247
|
-
theme = request.cli_display_theme or "light"
|
248
|
-
|
249
|
-
async for obj in stream:
|
250
|
-
typ = obj.get("type", "unknown")
|
251
|
-
chunk = ClaudeChunk(raw=obj, type=typ)
|
252
|
-
session.chunks.append(chunk)
|
253
|
-
|
254
|
-
# ------------------------ SYSTEM -----------------------------------
|
255
|
-
if typ == "system":
|
256
|
-
data = obj
|
257
|
-
session.session_id = data.get("session_id", session.session_id)
|
258
|
-
session.model = data.get("model", session.model)
|
259
|
-
await _maybe_await(on_system, data)
|
260
|
-
if request.verbose_output and on_system is None:
|
261
|
-
_pp_system(data, theme)
|
262
|
-
yield data
|
263
|
-
|
264
|
-
# ------------------------ ASSISTANT --------------------------------
|
265
|
-
elif typ == "assistant":
|
266
|
-
msg = obj["message"]
|
267
|
-
session.messages.append(msg)
|
268
|
-
|
269
|
-
for blk in msg.get("content", []):
|
270
|
-
btype = blk.get("type")
|
271
|
-
if btype == "thinking":
|
272
|
-
thought = blk.get("thinking", "").strip()
|
273
|
-
chunk.thinking = thought
|
274
|
-
session.thinking_log.append(thought)
|
275
|
-
await _maybe_await(on_thinking, thought)
|
276
|
-
if request.verbose_output and on_thinking is None:
|
277
|
-
_pp_thinking(thought, theme)
|
278
|
-
|
279
|
-
elif btype == "text":
|
280
|
-
text = blk.get("text", "")
|
281
|
-
chunk.text = text
|
282
|
-
await _maybe_await(on_text, text)
|
283
|
-
if request.verbose_output and on_text is None:
|
284
|
-
_pp_assistant_text(text, theme)
|
285
|
-
|
286
|
-
elif btype == "tool_use":
|
287
|
-
tu = {
|
288
|
-
"id": blk["id"],
|
289
|
-
"name": blk["name"],
|
290
|
-
"input": blk["input"],
|
291
|
-
}
|
292
|
-
chunk.tool_use = tu
|
293
|
-
session.tool_uses.append(tu)
|
294
|
-
await _maybe_await(on_tool_use, tu)
|
295
|
-
if request.verbose_output and on_tool_use is None:
|
296
|
-
_pp_tool_use(tu, theme)
|
297
|
-
|
298
|
-
elif btype == "tool_result":
|
299
|
-
tr = {
|
300
|
-
"tool_use_id": blk["tool_use_id"],
|
301
|
-
"content": blk["content"],
|
302
|
-
"is_error": blk.get("is_error", False),
|
303
|
-
}
|
304
|
-
chunk.tool_result = tr
|
305
|
-
session.tool_results.append(tr)
|
306
|
-
await _maybe_await(on_tool_result, tr)
|
307
|
-
if request.verbose_output and on_tool_result is None:
|
308
|
-
_pp_tool_result(tr, theme)
|
309
|
-
yield chunk
|
310
|
-
|
311
|
-
# ------------------------ USER (tool_result containers) ------------
|
312
|
-
elif typ == "user":
|
313
|
-
msg = obj["message"]
|
314
|
-
session.messages.append(msg)
|
315
|
-
for blk in msg.get("content", []):
|
316
|
-
if blk.get("type") == "tool_result":
|
317
|
-
tr = {
|
318
|
-
"tool_use_id": blk["tool_use_id"],
|
319
|
-
"content": blk["content"],
|
320
|
-
"is_error": blk.get("is_error", False),
|
321
|
-
}
|
322
|
-
chunk.tool_result = tr
|
323
|
-
session.tool_results.append(tr)
|
324
|
-
await _maybe_await(on_tool_result, tr)
|
325
|
-
if request.verbose_output and on_tool_result is None:
|
326
|
-
_pp_tool_result(tr, theme)
|
327
|
-
yield chunk
|
328
|
-
|
329
|
-
# ------------------------ RESULT -----------------------------------
|
330
|
-
elif typ == "result":
|
331
|
-
session.result = obj.get("result", "").strip()
|
332
|
-
session.usage = obj.get("usage", {})
|
333
|
-
session.total_cost_usd = obj.get("total_cost_usd")
|
334
|
-
session.num_turns = obj.get("num_turns")
|
335
|
-
session.duration_ms = obj.get("duration_ms")
|
336
|
-
session.duration_api_ms = obj.get("duration_api_ms")
|
337
|
-
session.is_error = obj.get("is_error", False)
|
338
|
-
|
339
|
-
# ------------------------ DONE -------------------------------------
|
340
|
-
elif typ == "done":
|
341
|
-
break
|
342
|
-
|
343
|
-
# final pretty print
|
344
|
-
await _maybe_await(on_final, session)
|
345
|
-
if request.verbose_output and on_final is None:
|
346
|
-
_pp_final(session, theme)
|
347
|
-
|
348
|
-
yield session
|
349
|
-
|
14
|
+
from ._claude_code.models import ClaudeCodeRequest
|
15
|
+
from ._claude_code.stream_cli import (
|
16
|
+
ClaudeChunk,
|
17
|
+
ClaudeSession,
|
18
|
+
log,
|
19
|
+
stream_claude_code_cli,
|
20
|
+
)
|
350
21
|
|
351
22
|
ENDPOINT_CONFIG = EndpointConfig(
|
352
23
|
name="claude_code_cli",
|
@@ -363,13 +34,27 @@ class ClaudeCodeCLIEndpoint(Endpoint):
|
|
363
34
|
def __init__(self, config: EndpointConfig = ENDPOINT_CONFIG, **kwargs):
|
364
35
|
super().__init__(config=config, **kwargs)
|
365
36
|
|
37
|
+
@property
|
38
|
+
def claude_handlers(self):
|
39
|
+
handlers = {
|
40
|
+
"on_thinking": None,
|
41
|
+
"on_text": None,
|
42
|
+
"on_tool_use": None,
|
43
|
+
"on_tool_result": None,
|
44
|
+
"on_system": None,
|
45
|
+
"on_final": None,
|
46
|
+
}
|
47
|
+
return self.config.kwargs.get("claude_handlers", handlers)
|
48
|
+
|
366
49
|
def create_payload(self, request: dict | BaseModel, **kwargs):
|
367
50
|
req_dict = {**self.config.kwargs, **to_dict(request), **kwargs}
|
368
51
|
messages = req_dict.pop("messages")
|
369
52
|
req_obj = ClaudeCodeRequest.create(messages=messages, **req_dict)
|
370
53
|
return {"request": req_obj}, {}
|
371
54
|
|
372
|
-
async def stream(
|
55
|
+
async def stream(
|
56
|
+
self, request: dict | BaseModel, **kwargs
|
57
|
+
) -> AsyncIterator[ClaudeChunk | dict | ClaudeSession]:
|
373
58
|
payload, _ = self.create_payload(request, **kwargs)["request"]
|
374
59
|
async for chunk in stream_claude_code_cli(payload):
|
375
60
|
yield chunk
|
@@ -386,7 +71,9 @@ class ClaudeCodeCLIEndpoint(Endpoint):
|
|
386
71
|
system: dict = None
|
387
72
|
|
388
73
|
# 1. stream the Claude Code response
|
389
|
-
async for chunk in stream_claude_code_cli(
|
74
|
+
async for chunk in stream_claude_code_cli(
|
75
|
+
request, session, **self.claude_handlers, **kwargs
|
76
|
+
):
|
390
77
|
if isinstance(chunk, dict):
|
391
78
|
system = chunk
|
392
79
|
responses.append(chunk)
|
@@ -395,6 +82,7 @@ class ClaudeCodeCLIEndpoint(Endpoint):
|
|
395
82
|
responses[-1], ClaudeSession
|
396
83
|
):
|
397
84
|
req2 = request.model_copy(deep=True)
|
85
|
+
req2.prompt = "Please provide a the final result message only"
|
398
86
|
req2.max_turns = 1
|
399
87
|
req2.continue_conversation = True
|
400
88
|
if system:
|
@@ -407,4 +95,11 @@ class ClaudeCodeCLIEndpoint(Endpoint):
|
|
407
95
|
log.info(
|
408
96
|
f"Session {session.session_id} finished with {len(responses)} chunks"
|
409
97
|
)
|
98
|
+
texts = []
|
99
|
+
for i in session.chunks:
|
100
|
+
if i.text is not None:
|
101
|
+
texts.append(i.text)
|
102
|
+
|
103
|
+
texts.append(session.result)
|
104
|
+
session.result = "\n".join(texts)
|
410
105
|
return to_dict(session, recursive=True)
|
lionagi/service/types.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
5
|
from .connections.api_calling import APICalling
|
6
|
-
from .connections.endpoint import Endpoint
|
6
|
+
from .connections.endpoint import Endpoint, EndpointConfig
|
7
7
|
from .imodel import iModel
|
8
8
|
from .manager import iModelManager
|
9
9
|
from .rate_limited_processor import RateLimitedAPIExecutor
|
@@ -12,6 +12,7 @@ from .token_calculator import TokenCalculator
|
|
12
12
|
__all__ = (
|
13
13
|
"APICalling",
|
14
14
|
"Endpoint",
|
15
|
+
"EndpointConfig",
|
15
16
|
"RateLimitedAPIExecutor",
|
16
17
|
"TokenCalculator",
|
17
18
|
"iModel",
|
lionagi/session/branch.py
CHANGED
@@ -917,9 +917,7 @@ class Branch(Element, Communicatable, Relational):
|
|
917
917
|
actions: bool = False,
|
918
918
|
reason: bool = False,
|
919
919
|
action_kwargs: dict = None,
|
920
|
-
action_strategy: Literal[
|
921
|
-
"sequential", "concurrent", "batch"
|
922
|
-
] = "concurrent",
|
920
|
+
action_strategy: Literal["sequential", "concurrent"] = "concurrent",
|
923
921
|
verbose_action: bool = False,
|
924
922
|
field_models: list[FieldModel] = None,
|
925
923
|
exclude_fields: list | dict | None = None,
|
@@ -987,7 +985,7 @@ class Branch(Element, Communicatable, Relational):
|
|
987
985
|
If `True`, signals that the LLM should provide chain-of-thought or reasoning (where applicable).
|
988
986
|
action_kwargs (dict | None, optional):
|
989
987
|
Additional parameters for the `branch.act()` call if tools are invoked.
|
990
|
-
action_strategy (Literal["sequential","concurrent"
|
988
|
+
action_strategy (Literal["sequential","concurrent"], optional):
|
991
989
|
The strategy for invoking tools (default: "concurrent").
|
992
990
|
verbose_action (bool, optional):
|
993
991
|
If `True`, logs detailed information about tool invocation.
|
@@ -1174,9 +1172,8 @@ class Branch(Element, Communicatable, Relational):
|
|
1174
1172
|
self,
|
1175
1173
|
action_request: list | ActionRequest | BaseModel | dict,
|
1176
1174
|
*,
|
1177
|
-
strategy: Literal["concurrent", "sequential"
|
1175
|
+
strategy: Literal["concurrent", "sequential"] = "concurrent",
|
1178
1176
|
verbose_action: bool = False,
|
1179
|
-
batch_size: int = None,
|
1180
1177
|
suppress_errors: bool = True,
|
1181
1178
|
sanitize_input: bool = False,
|
1182
1179
|
unique_input: bool = False,
|
@@ -1223,7 +1220,7 @@ class Branch(Element, Communicatable, Relational):
|
|
1223
1220
|
retry_timeout (float|None):
|
1224
1221
|
Overall timeout for all attempts (None = no limit).
|
1225
1222
|
max_concurrent (int|None):
|
1226
|
-
Maximum concurrent tasks
|
1223
|
+
Maximum concurrent tasks.
|
1227
1224
|
throttle_period (float|None):
|
1228
1225
|
Minimum spacing (in seconds) between requests.
|
1229
1226
|
flatten (bool):
|
@@ -1239,11 +1236,6 @@ class Branch(Element, Communicatable, Relational):
|
|
1239
1236
|
Any:
|
1240
1237
|
The result or results from the invoked tool(s).
|
1241
1238
|
"""
|
1242
|
-
if batch_size and not strategy == "batch":
|
1243
|
-
raise ValueError(
|
1244
|
-
"Batch size is only applicable for 'batch' strategy."
|
1245
|
-
)
|
1246
|
-
|
1247
1239
|
match strategy:
|
1248
1240
|
case "concurrent":
|
1249
1241
|
return await self._concurrent_act(
|
@@ -1271,27 +1263,8 @@ class Branch(Element, Communicatable, Relational):
|
|
1271
1263
|
verbose_action=verbose_action,
|
1272
1264
|
suppress_errors=suppress_errors,
|
1273
1265
|
)
|
1274
|
-
case
|
1275
|
-
|
1276
|
-
action_request,
|
1277
|
-
verbose_action=verbose_action,
|
1278
|
-
batch_size=batch_size or 1,
|
1279
|
-
max_concurrent=max_concurrent,
|
1280
|
-
suppress_errors=suppress_errors,
|
1281
|
-
sanitize_input=sanitize_input,
|
1282
|
-
unique_input=unique_input,
|
1283
|
-
num_retries=num_retries,
|
1284
|
-
initial_delay=initial_delay,
|
1285
|
-
retry_delay=retry_delay,
|
1286
|
-
backoff_factor=backoff_factor,
|
1287
|
-
retry_default=retry_default,
|
1288
|
-
retry_timeout=retry_timeout,
|
1289
|
-
throttle_period=throttle_period,
|
1290
|
-
flatten=flatten,
|
1291
|
-
dropna=dropna,
|
1292
|
-
unique_output=unique_output,
|
1293
|
-
flatten_tuple_set=flatten_tuple_set,
|
1294
|
-
)
|
1266
|
+
case _:
|
1267
|
+
raise
|
1295
1268
|
|
1296
1269
|
async def _concurrent_act(
|
1297
1270
|
self,
|
@@ -1322,19 +1295,6 @@ class Branch(Element, Communicatable, Relational):
|
|
1322
1295
|
)
|
1323
1296
|
return results
|
1324
1297
|
|
1325
|
-
async def _batch_act(
|
1326
|
-
self,
|
1327
|
-
action_request: list[ActionRequest | BaseModel | dict],
|
1328
|
-
batch_size: int = None,
|
1329
|
-
**kwargs,
|
1330
|
-
) -> list:
|
1331
|
-
result = []
|
1332
|
-
async for i in bcall(
|
1333
|
-
action_request, self._act, batch_size=batch_size, **kwargs
|
1334
|
-
):
|
1335
|
-
result.extend(i)
|
1336
|
-
return result
|
1337
|
-
|
1338
1298
|
async def translate(
|
1339
1299
|
self,
|
1340
1300
|
text: str,
|
lionagi/session/session.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
+
import contextlib
|
5
6
|
from collections.abc import Callable
|
6
7
|
from typing import Any
|
7
8
|
|
@@ -46,7 +47,7 @@ class Session(Node, Communicatable, Relational):
|
|
46
47
|
mail_manager (MailManager | None): Manages mail operations.
|
47
48
|
"""
|
48
49
|
|
49
|
-
branches: Pile[
|
50
|
+
branches: Pile[Branch] = Field(
|
50
51
|
default_factory=lambda: Pile(item_type={Branch}, strict_type=False)
|
51
52
|
)
|
52
53
|
default_branch: Any = Field(default=None, exclude=True)
|
@@ -57,21 +58,38 @@ class Session(Node, Communicatable, Relational):
|
|
57
58
|
name: str = Field(default="Session")
|
58
59
|
|
59
60
|
@model_validator(mode="after")
|
60
|
-
def
|
61
|
+
def _add_mail_sources(self) -> Self:
|
61
62
|
if self.default_branch is None:
|
62
|
-
from .branch import Branch
|
63
|
-
|
64
63
|
self.default_branch = Branch()
|
65
|
-
return self
|
66
|
-
|
67
|
-
@model_validator(mode="after")
|
68
|
-
def _add_mail_sources(self) -> Self:
|
69
64
|
if self.default_branch not in self.branches:
|
70
65
|
self.branches.include(self.default_branch)
|
71
66
|
if self.branches:
|
72
67
|
self.mail_manager.add_sources(self.branches)
|
73
68
|
return self
|
74
69
|
|
70
|
+
def _lookup_branch_by_name(self, name: str) -> Branch | None:
|
71
|
+
for branch in self.branches:
|
72
|
+
if branch.name == name:
|
73
|
+
return branch
|
74
|
+
return None
|
75
|
+
|
76
|
+
def get_branch(
|
77
|
+
self, branch: ID.Ref | str, default: Any = ..., /
|
78
|
+
) -> Branch:
|
79
|
+
"""Get a branch by its ID or name."""
|
80
|
+
|
81
|
+
with contextlib.suppress(ItemNotFoundError, ValueError):
|
82
|
+
id = ID.get_id(branch)
|
83
|
+
return self.branches[id]
|
84
|
+
|
85
|
+
if isinstance(branch, str):
|
86
|
+
if b := self._lookup_branch_by_name(branch):
|
87
|
+
return b
|
88
|
+
|
89
|
+
if default is ...:
|
90
|
+
raise ItemNotFoundError(f"Branch '{branch}' not found.")
|
91
|
+
return default
|
92
|
+
|
75
93
|
def new_branch(
|
76
94
|
self,
|
77
95
|
system: System | JsonValue = None,
|
lionagi/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.14.
|
1
|
+
__version__ = "0.14.7"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lionagi
|
3
|
-
Version: 0.14.
|
3
|
+
Version: 0.14.7
|
4
4
|
Summary: An Intelligence Operating System.
|
5
5
|
Author-email: HaiyangLi <quantocean.li@gmail.com>, Liangbingyan Luo <llby_luo@outlook.com>
|
6
6
|
License: Apache License
|
@@ -234,9 +234,9 @@ Requires-Dist: tiktoken>=0.8.0
|
|
234
234
|
Requires-Dist: toml>=0.9.0
|
235
235
|
Provides-Extra: all
|
236
236
|
Requires-Dist: aiosqlite>=0.21.0; extra == 'all'
|
237
|
-
Requires-Dist: claude-code-sdk>=0.0.
|
237
|
+
Requires-Dist: claude-code-sdk>=0.0.15; extra == 'all'
|
238
238
|
Requires-Dist: datamodel-code-generator>=0.31.2; extra == 'all'
|
239
|
-
Requires-Dist: docling>=2.15.
|
239
|
+
Requires-Dist: docling>=2.15.0; extra == 'all'
|
240
240
|
Requires-Dist: fastmcp>=2.10.5; extra == 'all'
|
241
241
|
Requires-Dist: matplotlib>=3.7.0; extra == 'all'
|
242
242
|
Requires-Dist: networkx>=3.0.0; extra == 'all'
|
@@ -244,36 +244,26 @@ Requires-Dist: ollama>=0.4.0; extra == 'all'
|
|
244
244
|
Requires-Dist: pydapter[postgres]; extra == 'all'
|
245
245
|
Requires-Dist: rich>=13.0.0; extra == 'all'
|
246
246
|
Provides-Extra: claude-code
|
247
|
-
Requires-Dist: claude-code-sdk>=0.0.
|
248
|
-
Provides-Extra: docs
|
249
|
-
Requires-Dist: furo>=2024.8.6; extra == 'docs'
|
250
|
-
Requires-Dist: sphinx-autobuild>=2024.10.3; extra == 'docs'
|
251
|
-
Requires-Dist: sphinx>=8.1.3; extra == 'docs'
|
247
|
+
Requires-Dist: claude-code-sdk>=0.0.15; extra == 'claude-code'
|
252
248
|
Provides-Extra: graph
|
253
249
|
Requires-Dist: matplotlib>=3.7.0; extra == 'graph'
|
254
250
|
Requires-Dist: networkx>=3.0.0; extra == 'graph'
|
255
|
-
Provides-Extra: lint
|
256
|
-
Requires-Dist: black[jupyter]>=24.10.0; extra == 'lint'
|
257
|
-
Requires-Dist: isort>=5.13.2; extra == 'lint'
|
258
|
-
Requires-Dist: pre-commit>=4.0.1; extra == 'lint'
|
259
251
|
Provides-Extra: mcp
|
260
252
|
Requires-Dist: fastmcp>=2.10.5; extra == 'mcp'
|
261
253
|
Provides-Extra: ollama
|
262
254
|
Requires-Dist: ollama>=0.4.0; extra == 'ollama'
|
263
255
|
Provides-Extra: postgres
|
264
|
-
Requires-Dist: aiosqlite>=0.21.0; extra == 'postgres'
|
265
256
|
Requires-Dist: pydapter[postgres]; extra == 'postgres'
|
266
257
|
Provides-Extra: reader
|
267
|
-
Requires-Dist: docling>=2.15.
|
258
|
+
Requires-Dist: docling>=2.15.0; extra == 'reader'
|
268
259
|
Provides-Extra: rich
|
269
260
|
Requires-Dist: rich>=13.0.0; extra == 'rich'
|
270
261
|
Provides-Extra: schema
|
271
262
|
Requires-Dist: datamodel-code-generator>=0.31.2; extra == 'schema'
|
272
|
-
Provides-Extra:
|
273
|
-
Requires-Dist:
|
274
|
-
Requires-Dist: pytest>=8.3.4; extra == 'test'
|
263
|
+
Provides-Extra: sqlite
|
264
|
+
Requires-Dist: aiosqlite>=0.21.0; extra == 'sqlite'
|
275
265
|
Provides-Extra: tools
|
276
|
-
Requires-Dist: docling>=2.15.
|
266
|
+
Requires-Dist: docling>=2.15.0; extra == 'tools'
|
277
267
|
Description-Content-Type: text/markdown
|
278
268
|
|
279
269
|

|
@@ -416,7 +406,7 @@ Seamlessly route to different models in the same workflow.
|
|
416
406
|
|
417
407
|
### Claude Code Integration
|
418
408
|
|
419
|
-
LionAGI now supports Anthropic's [Claude Code SDK](https://github.com/anthropics/claude-code-sdk), enabling autonomous coding capabilities with persistent session management:
|
409
|
+
LionAGI now supports Anthropic's [Claude Code Python SDK](https://github.com/anthropics/claude-code-sdk-python), enabling autonomous coding capabilities with persistent session management:
|
420
410
|
|
421
411
|
```python
|
422
412
|
from lionagi import iModel, Branch
|