lionagi 0.15.9__py3-none-any.whl → 0.15.13__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/__init__.py +4 -6
- lionagi/adapters/async_postgres_adapter.py +56 -320
- lionagi/libs/file/_utils.py +10 -0
- lionagi/libs/file/process.py +16 -13
- lionagi/libs/unstructured/pdf_to_image.py +2 -2
- lionagi/libs/validate/string_similarity.py +4 -4
- lionagi/ln/__init__.py +28 -0
- lionagi/ln/_async_call.py +1 -0
- lionagi/ln/_extract_json.py +60 -0
- lionagi/ln/_fuzzy_json.py +116 -0
- lionagi/models/field_model.py +8 -6
- lionagi/operations/__init__.py +3 -0
- lionagi/operations/builder.py +10 -0
- lionagi/protocols/generic/element.py +120 -17
- lionagi/protocols/generic/pile.py +56 -1
- lionagi/protocols/generic/progression.py +11 -11
- lionagi/protocols/graph/_utils.py +22 -0
- lionagi/protocols/graph/graph.py +17 -21
- lionagi/protocols/graph/node.py +23 -3
- lionagi/protocols/messages/manager.py +41 -45
- lionagi/protocols/operatives/step.py +2 -19
- lionagi/protocols/types.py +1 -2
- lionagi/tools/file/reader.py +5 -6
- lionagi/utils.py +8 -385
- lionagi/version.py +1 -1
- {lionagi-0.15.9.dist-info → lionagi-0.15.13.dist-info}/METADATA +21 -16
- {lionagi-0.15.9.dist-info → lionagi-0.15.13.dist-info}/RECORD +29 -30
- lionagi/libs/package/__init__.py +0 -3
- lionagi/libs/package/imports.py +0 -21
- lionagi/libs/package/management.py +0 -62
- lionagi/libs/package/params.py +0 -30
- lionagi/libs/package/system.py +0 -22
- {lionagi-0.15.9.dist-info → lionagi-0.15.13.dist-info}/WHEEL +0 -0
- {lionagi-0.15.9.dist-info → lionagi-0.15.13.dist-info}/licenses/LICENSE +0 -0
lionagi/utils.py
CHANGED
@@ -2,18 +2,12 @@
|
|
2
2
|
#
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
4
4
|
|
5
|
-
import asyncio
|
6
5
|
import contextlib
|
7
6
|
import copy as _copy
|
8
7
|
import dataclasses
|
9
|
-
import functools
|
10
8
|
import importlib.util
|
11
9
|
import json
|
12
10
|
import logging
|
13
|
-
import re
|
14
|
-
import shutil
|
15
|
-
import subprocess
|
16
|
-
import sys
|
17
11
|
import types
|
18
12
|
import uuid
|
19
13
|
from collections.abc import (
|
@@ -23,7 +17,6 @@ from collections.abc import (
|
|
23
17
|
Mapping,
|
24
18
|
Sequence,
|
25
19
|
)
|
26
|
-
from concurrent.futures import ThreadPoolExecutor
|
27
20
|
from datetime import datetime, timezone
|
28
21
|
from enum import Enum as _Enum
|
29
22
|
from functools import partial
|
@@ -44,16 +37,10 @@ from pydantic_core import PydanticUndefinedType
|
|
44
37
|
from typing_extensions import deprecated
|
45
38
|
|
46
39
|
from .libs.validate.xml_parser import xml_to_dict
|
47
|
-
from .ln import
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
Params,
|
52
|
-
Undefined,
|
53
|
-
UndefinedType,
|
54
|
-
hash_dict,
|
55
|
-
to_list,
|
56
|
-
)
|
40
|
+
from .ln import DataClass, Enum, KeysDict, Params, Undefined, UndefinedType
|
41
|
+
from .ln import extract_json as to_json
|
42
|
+
from .ln import fuzzy_json as fuzzy_parse_json
|
43
|
+
from .ln import hash_dict, to_list
|
57
44
|
from .ln.concurrency import is_coro_func
|
58
45
|
from .settings import Settings
|
59
46
|
|
@@ -99,6 +86,7 @@ __all__ = (
|
|
99
86
|
"hash_dict",
|
100
87
|
"is_union_type",
|
101
88
|
"union_members",
|
89
|
+
"to_json",
|
102
90
|
)
|
103
91
|
|
104
92
|
|
@@ -313,6 +301,9 @@ def lcall(
|
|
313
301
|
)
|
314
302
|
|
315
303
|
|
304
|
+
@deprecated(
|
305
|
+
"Use `lionagi.ln.alcall` instead, function signature has changed, this will be removed in future versions."
|
306
|
+
)
|
316
307
|
async def alcall(
|
317
308
|
input_: list[Any],
|
318
309
|
func: Callable[..., T],
|
@@ -514,119 +505,6 @@ def create_path(
|
|
514
505
|
return full_path
|
515
506
|
|
516
507
|
|
517
|
-
def fuzzy_parse_json(
|
518
|
-
str_to_parse: str, /
|
519
|
-
) -> dict[str, Any] | list[dict[str, Any]]:
|
520
|
-
"""
|
521
|
-
Attempt to parse a JSON string, trying a few minimal "fuzzy" fixes if needed.
|
522
|
-
|
523
|
-
Steps:
|
524
|
-
1. Parse directly with json.loads.
|
525
|
-
2. Replace single quotes with double quotes, normalize spacing, and try again.
|
526
|
-
3. Attempt to fix unmatched brackets using fix_json_string.
|
527
|
-
4. If all fail, raise ValueError.
|
528
|
-
|
529
|
-
Args:
|
530
|
-
str_to_parse: The JSON string to parse
|
531
|
-
|
532
|
-
Returns:
|
533
|
-
Parsed JSON (dict or list of dicts)
|
534
|
-
|
535
|
-
Raises:
|
536
|
-
ValueError: If the string cannot be parsed as valid JSON
|
537
|
-
TypeError: If the input is not a string
|
538
|
-
"""
|
539
|
-
_check_valid_str(str_to_parse)
|
540
|
-
|
541
|
-
# 1. Direct attempt
|
542
|
-
with contextlib.suppress(Exception):
|
543
|
-
return json.loads(str_to_parse)
|
544
|
-
|
545
|
-
# 2. Try cleaning: replace single quotes with double and normalize
|
546
|
-
cleaned = _clean_json_string(str_to_parse.replace("'", '"'))
|
547
|
-
with contextlib.suppress(Exception):
|
548
|
-
return json.loads(cleaned)
|
549
|
-
|
550
|
-
# 3. Try fixing brackets
|
551
|
-
fixed = fix_json_string(cleaned)
|
552
|
-
with contextlib.suppress(Exception):
|
553
|
-
return json.loads(fixed)
|
554
|
-
|
555
|
-
# If all attempts fail
|
556
|
-
raise ValueError("Invalid JSON string")
|
557
|
-
|
558
|
-
|
559
|
-
def _check_valid_str(str_to_parse: str, /):
|
560
|
-
if not isinstance(str_to_parse, str):
|
561
|
-
raise TypeError("Input must be a string")
|
562
|
-
if not str_to_parse.strip():
|
563
|
-
raise ValueError("Input string is empty")
|
564
|
-
|
565
|
-
|
566
|
-
def _clean_json_string(s: str) -> str:
|
567
|
-
"""Basic normalization: replace unescaped single quotes, trim spaces, ensure keys are quoted."""
|
568
|
-
# Replace unescaped single quotes with double quotes
|
569
|
-
# '(?<!\\)'" means a single quote not preceded by a backslash
|
570
|
-
s = re.sub(r"(?<!\\)'", '"', s)
|
571
|
-
# Collapse multiple whitespaces
|
572
|
-
s = re.sub(r"\s+", " ", s)
|
573
|
-
# Ensure keys are quoted
|
574
|
-
# This attempts to find patterns like { key: value } and turn them into {"key": value}
|
575
|
-
s = re.sub(r'([{,])\s*([^"\s]+)\s*:', r'\1"\2":', s)
|
576
|
-
return s.strip()
|
577
|
-
|
578
|
-
|
579
|
-
def fix_json_string(str_to_parse: str, /) -> str:
|
580
|
-
"""Try to fix JSON string by ensuring brackets are matched properly."""
|
581
|
-
if not str_to_parse:
|
582
|
-
raise ValueError("Input string is empty")
|
583
|
-
|
584
|
-
brackets = {"{": "}", "[": "]"}
|
585
|
-
open_brackets = []
|
586
|
-
pos = 0
|
587
|
-
length = len(str_to_parse)
|
588
|
-
|
589
|
-
while pos < length:
|
590
|
-
char = str_to_parse[pos]
|
591
|
-
|
592
|
-
if char == "\\":
|
593
|
-
pos += 2 # Skip escaped chars
|
594
|
-
continue
|
595
|
-
|
596
|
-
if char == '"':
|
597
|
-
pos += 1
|
598
|
-
# skip string content
|
599
|
-
while pos < length:
|
600
|
-
if str_to_parse[pos] == "\\":
|
601
|
-
pos += 2
|
602
|
-
continue
|
603
|
-
if str_to_parse[pos] == '"':
|
604
|
-
pos += 1
|
605
|
-
break
|
606
|
-
pos += 1
|
607
|
-
continue
|
608
|
-
|
609
|
-
if char in brackets:
|
610
|
-
open_brackets.append(brackets[char])
|
611
|
-
elif char in brackets.values():
|
612
|
-
if not open_brackets:
|
613
|
-
# Extra closing bracket
|
614
|
-
# Better to raise error than guess
|
615
|
-
raise ValueError("Extra closing bracket found.")
|
616
|
-
if open_brackets[-1] != char:
|
617
|
-
# Mismatched bracket
|
618
|
-
raise ValueError("Mismatched brackets.")
|
619
|
-
open_brackets.pop()
|
620
|
-
|
621
|
-
pos += 1
|
622
|
-
|
623
|
-
# Add missing closing brackets if any
|
624
|
-
if open_brackets:
|
625
|
-
str_to_parse += "".join(reversed(open_brackets))
|
626
|
-
|
627
|
-
return str_to_parse
|
628
|
-
|
629
|
-
|
630
508
|
def to_dict(
|
631
509
|
input_: Any,
|
632
510
|
/,
|
@@ -947,63 +825,6 @@ def _to_dict(
|
|
947
825
|
return dict(input_)
|
948
826
|
|
949
827
|
|
950
|
-
# Precompile the regex for extracting JSON code blocks
|
951
|
-
_JSON_BLOCK_PATTERN = re.compile(r"```json\s*(.*?)\s*```", re.DOTALL)
|
952
|
-
|
953
|
-
|
954
|
-
def to_json(
|
955
|
-
input_data: str | list[str], /, *, fuzzy_parse: bool = False
|
956
|
-
) -> dict[str, Any] | list[dict[str, Any]]:
|
957
|
-
"""
|
958
|
-
Extract and parse JSON content from a string or markdown code blocks.
|
959
|
-
|
960
|
-
Attempts direct JSON parsing first. If that fails, looks for JSON content
|
961
|
-
within markdown code blocks denoted by ```json.
|
962
|
-
|
963
|
-
Args:
|
964
|
-
input_data (str | list[str]): The input string or list of strings to parse.
|
965
|
-
fuzzy_parse (bool): If True, attempts fuzzy JSON parsing on failed attempts.
|
966
|
-
|
967
|
-
Returns:
|
968
|
-
dict or list of dicts:
|
969
|
-
- If a single JSON object is found: returns a dict.
|
970
|
-
- If multiple JSON objects are found: returns a list of dicts.
|
971
|
-
- If no valid JSON found: returns an empty list.
|
972
|
-
"""
|
973
|
-
|
974
|
-
# If input_data is a list, join into a single string
|
975
|
-
if isinstance(input_data, list):
|
976
|
-
input_str = "\n".join(input_data)
|
977
|
-
else:
|
978
|
-
input_str = input_data
|
979
|
-
|
980
|
-
# 1. Try direct parsing
|
981
|
-
try:
|
982
|
-
if fuzzy_parse:
|
983
|
-
return fuzzy_parse_json(input_str)
|
984
|
-
return json.loads(input_str)
|
985
|
-
except Exception:
|
986
|
-
pass
|
987
|
-
|
988
|
-
# 2. Attempt extracting JSON blocks from markdown
|
989
|
-
matches = _JSON_BLOCK_PATTERN.findall(input_str)
|
990
|
-
if not matches:
|
991
|
-
return []
|
992
|
-
|
993
|
-
# If only one match, return single dict; if multiple, return list of dicts
|
994
|
-
if len(matches) == 1:
|
995
|
-
data_str = matches[0]
|
996
|
-
return (
|
997
|
-
fuzzy_parse_json(data_str) if fuzzy_parse else json.loads(data_str)
|
998
|
-
)
|
999
|
-
|
1000
|
-
# Multiple matches
|
1001
|
-
if fuzzy_parse:
|
1002
|
-
return [fuzzy_parse_json(m) for m in matches]
|
1003
|
-
else:
|
1004
|
-
return [json.loads(m) for m in matches]
|
1005
|
-
|
1006
|
-
|
1007
828
|
def get_bins(input_: list[str], upper: int) -> list[list[int]]:
|
1008
829
|
"""Organizes indices of strings into bins based on a cumulative upper limit.
|
1009
830
|
|
@@ -1030,79 +851,6 @@ def get_bins(input_: list[str], upper: int) -> list[list[int]]:
|
|
1030
851
|
return bins
|
1031
852
|
|
1032
853
|
|
1033
|
-
def force_async(fn: Callable[..., T]) -> Callable[..., Callable[..., T]]:
|
1034
|
-
"""
|
1035
|
-
Convert a synchronous function to an asynchronous function
|
1036
|
-
using a thread pool.
|
1037
|
-
|
1038
|
-
Args:
|
1039
|
-
fn: The synchronous function to convert.
|
1040
|
-
|
1041
|
-
Returns:
|
1042
|
-
The asynchronous version of the function.
|
1043
|
-
"""
|
1044
|
-
pool = ThreadPoolExecutor()
|
1045
|
-
|
1046
|
-
@functools.wraps(fn)
|
1047
|
-
def wrapper(*args, **kwargs):
|
1048
|
-
future = pool.submit(fn, *args, **kwargs)
|
1049
|
-
return asyncio.wrap_future(future) # Make it awaitable
|
1050
|
-
|
1051
|
-
return wrapper
|
1052
|
-
|
1053
|
-
|
1054
|
-
def throttle(
|
1055
|
-
func: Callable[..., T], period: float
|
1056
|
-
) -> Callable[..., Callable[..., T]]:
|
1057
|
-
"""
|
1058
|
-
Throttle function execution to limit the rate of calls.
|
1059
|
-
|
1060
|
-
Args:
|
1061
|
-
func: The function to throttle.
|
1062
|
-
period: The minimum time interval between consecutive calls.
|
1063
|
-
|
1064
|
-
Returns:
|
1065
|
-
The throttled function.
|
1066
|
-
"""
|
1067
|
-
from .ln.concurrency.throttle import Throttle
|
1068
|
-
|
1069
|
-
if not is_coro_func(func):
|
1070
|
-
func = force_async(func)
|
1071
|
-
throttle_instance = Throttle(period)
|
1072
|
-
|
1073
|
-
@functools.wraps(func)
|
1074
|
-
async def wrapper(*args, **kwargs):
|
1075
|
-
await throttle_instance(func)(*args, **kwargs)
|
1076
|
-
return await func(*args, **kwargs)
|
1077
|
-
|
1078
|
-
return wrapper
|
1079
|
-
|
1080
|
-
|
1081
|
-
def max_concurrent(
|
1082
|
-
func: Callable[..., T], limit: int
|
1083
|
-
) -> Callable[..., Callable[..., T]]:
|
1084
|
-
"""
|
1085
|
-
Limit the concurrency of function execution using a semaphore.
|
1086
|
-
|
1087
|
-
Args:
|
1088
|
-
func: The function to limit concurrency for.
|
1089
|
-
limit: The maximum number of concurrent executions.
|
1090
|
-
|
1091
|
-
Returns:
|
1092
|
-
The function wrapped with concurrency control.
|
1093
|
-
"""
|
1094
|
-
if not is_coro_func(func):
|
1095
|
-
func = force_async(func)
|
1096
|
-
semaphore = asyncio.Semaphore(limit)
|
1097
|
-
|
1098
|
-
@functools.wraps(func)
|
1099
|
-
async def wrapper(*args, **kwargs):
|
1100
|
-
async with semaphore:
|
1101
|
-
return await func(*args, **kwargs)
|
1102
|
-
|
1103
|
-
return wrapper
|
1104
|
-
|
1105
|
-
|
1106
854
|
def breakdown_pydantic_annotation(
|
1107
855
|
model: type[B], max_depth: int | None = None, current_depth: int = 0
|
1108
856
|
) -> dict[str, Any]:
|
@@ -1142,87 +890,6 @@ def _is_pydantic_model(x: Any) -> bool:
|
|
1142
890
|
return False
|
1143
891
|
|
1144
892
|
|
1145
|
-
def run_package_manager_command(
|
1146
|
-
args: Sequence[str],
|
1147
|
-
) -> subprocess.CompletedProcess[bytes]:
|
1148
|
-
"""Run a package manager command, using uv if available, otherwise falling back to pip."""
|
1149
|
-
# Check if uv is available in PATH
|
1150
|
-
uv_path = shutil.which("uv")
|
1151
|
-
|
1152
|
-
if uv_path:
|
1153
|
-
# Use uv if available
|
1154
|
-
try:
|
1155
|
-
return subprocess.run(
|
1156
|
-
[uv_path] + list(args),
|
1157
|
-
check=True,
|
1158
|
-
capture_output=True,
|
1159
|
-
)
|
1160
|
-
except subprocess.CalledProcessError:
|
1161
|
-
# If uv fails, fall back to pip
|
1162
|
-
print("uv command failed, falling back to pip...")
|
1163
|
-
|
1164
|
-
# Fall back to pip
|
1165
|
-
return subprocess.run(
|
1166
|
-
[sys.executable, "-m", "pip"] + list(args),
|
1167
|
-
check=True,
|
1168
|
-
capture_output=True,
|
1169
|
-
)
|
1170
|
-
|
1171
|
-
|
1172
|
-
def check_import(
|
1173
|
-
package_name: str,
|
1174
|
-
module_name: str | None = None,
|
1175
|
-
import_name: str | None = None,
|
1176
|
-
pip_name: str | None = None,
|
1177
|
-
attempt_install: bool = True,
|
1178
|
-
error_message: str = "",
|
1179
|
-
):
|
1180
|
-
"""
|
1181
|
-
Check if a package is installed, attempt to install if not.
|
1182
|
-
|
1183
|
-
Args:
|
1184
|
-
package_name: The name of the package to check.
|
1185
|
-
module_name: The specific module to import (if any).
|
1186
|
-
import_name: The specific name to import from the module (if any).
|
1187
|
-
pip_name: The name to use for pip installation (if different).
|
1188
|
-
attempt_install: Whether to attempt installation if not found.
|
1189
|
-
error_message: Custom error message to use if package not found.
|
1190
|
-
|
1191
|
-
Raises:
|
1192
|
-
ImportError: If the package is not found and not installed.
|
1193
|
-
ValueError: If the import fails after installation attempt.
|
1194
|
-
"""
|
1195
|
-
if not is_import_installed(package_name):
|
1196
|
-
if attempt_install:
|
1197
|
-
logging.info(
|
1198
|
-
f"Package {package_name} not found. Attempting to install.",
|
1199
|
-
)
|
1200
|
-
try:
|
1201
|
-
return install_import(
|
1202
|
-
package_name=package_name,
|
1203
|
-
module_name=module_name,
|
1204
|
-
import_name=import_name,
|
1205
|
-
pip_name=pip_name,
|
1206
|
-
)
|
1207
|
-
except ImportError as e:
|
1208
|
-
raise ValueError(
|
1209
|
-
f"Failed to install {package_name}: {e}"
|
1210
|
-
) from e
|
1211
|
-
else:
|
1212
|
-
logging.info(
|
1213
|
-
f"Package {package_name} not found. {error_message}",
|
1214
|
-
)
|
1215
|
-
raise ImportError(
|
1216
|
-
f"Package {package_name} not found. {error_message}",
|
1217
|
-
)
|
1218
|
-
|
1219
|
-
return import_module(
|
1220
|
-
package_name=package_name,
|
1221
|
-
module_name=module_name,
|
1222
|
-
import_name=import_name,
|
1223
|
-
)
|
1224
|
-
|
1225
|
-
|
1226
893
|
def import_module(
|
1227
894
|
package_name: str,
|
1228
895
|
module_name: str = None,
|
@@ -1267,50 +934,6 @@ def import_module(
|
|
1267
934
|
) from e
|
1268
935
|
|
1269
936
|
|
1270
|
-
def install_import(
|
1271
|
-
package_name: str,
|
1272
|
-
module_name: str | None = None,
|
1273
|
-
import_name: str | None = None,
|
1274
|
-
pip_name: str | None = None,
|
1275
|
-
):
|
1276
|
-
"""
|
1277
|
-
Attempt to import a package, installing it if not found.
|
1278
|
-
|
1279
|
-
Args:
|
1280
|
-
package_name: The name of the package to import.
|
1281
|
-
module_name: The specific module to import (if any).
|
1282
|
-
import_name: The specific name to import from the module (if any).
|
1283
|
-
pip_name: The name to use for pip installation (if different).
|
1284
|
-
|
1285
|
-
Raises:
|
1286
|
-
ImportError: If the package cannot be imported or installed.
|
1287
|
-
subprocess.CalledProcessError: If pip installation fails.
|
1288
|
-
"""
|
1289
|
-
pip_name = pip_name or package_name
|
1290
|
-
|
1291
|
-
try:
|
1292
|
-
return import_module(
|
1293
|
-
package_name=package_name,
|
1294
|
-
module_name=module_name,
|
1295
|
-
import_name=import_name,
|
1296
|
-
)
|
1297
|
-
except ImportError:
|
1298
|
-
logging.info(f"Installing {pip_name}...")
|
1299
|
-
try:
|
1300
|
-
run_package_manager_command(["install", pip_name])
|
1301
|
-
return import_module(
|
1302
|
-
package_name=package_name,
|
1303
|
-
module_name=module_name,
|
1304
|
-
import_name=import_name,
|
1305
|
-
)
|
1306
|
-
except subprocess.CalledProcessError as e:
|
1307
|
-
raise ImportError(f"Failed to install {pip_name}: {e}") from e
|
1308
|
-
except ImportError as e:
|
1309
|
-
raise ImportError(
|
1310
|
-
f"Failed to import {pip_name} after installation: {e}"
|
1311
|
-
) from e
|
1312
|
-
|
1313
|
-
|
1314
937
|
def is_import_installed(package_name: str) -> bool:
|
1315
938
|
"""
|
1316
939
|
Check if a package is installed.
|
lionagi/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.15.
|
1
|
+
__version__ = "0.15.13"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lionagi
|
3
|
-
Version: 0.15.
|
3
|
+
Version: 0.15.13
|
4
4
|
Summary: An Intelligence Operating System.
|
5
5
|
Author-email: HaiyangLi <quantocean.li@gmail.com>
|
6
6
|
License: Apache License
|
@@ -225,10 +225,10 @@ Requires-Dist: anyio>=4.7.0
|
|
225
225
|
Requires-Dist: backoff>=2.0.0
|
226
226
|
Requires-Dist: jinja2>=3.0.0
|
227
227
|
Requires-Dist: json-repair>=0.40.0
|
228
|
-
Requires-Dist: pillow>=
|
228
|
+
Requires-Dist: pillow>=10.0.0
|
229
229
|
Requires-Dist: psutil>=6.0.0
|
230
230
|
Requires-Dist: pydantic-settings>=2.8.0
|
231
|
-
Requires-Dist: pydapter[pandas]>=1.0.
|
231
|
+
Requires-Dist: pydapter[pandas]>=1.0.4
|
232
232
|
Requires-Dist: python-dotenv>=1.1.0
|
233
233
|
Requires-Dist: tiktoken>=0.9.0
|
234
234
|
Requires-Dist: toml>=0.8.0
|
@@ -274,13 +274,13 @@ Description-Content-Type: text/markdown
|
|
274
274
|

|
275
275
|

|
276
276
|
|
277
|
-
[Documentation](https://
|
277
|
+
[Documentation](https://khive-ai.github.io/lionagi/) |
|
278
278
|
[Discord](https://discord.gg/JDj9ENhUE8) |
|
279
279
|
[PyPI](https://pypi.org/project/lionagi/)
|
280
280
|
|
281
281
|
# LION - Language InterOperable Network
|
282
282
|
|
283
|
-
## An
|
283
|
+
## An AGentic Intelligence SDK
|
284
284
|
|
285
285
|
LionAGI is a robust framework for orchestrating multi-step AI operations with
|
286
286
|
precise control. Bring together multiple models, advanced ReAct reasoning, tool
|
@@ -288,13 +288,13 @@ integrations, and custom validations in a single coherent pipeline.
|
|
288
288
|
|
289
289
|
## Why LionAGI?
|
290
290
|
|
291
|
-
- **Structured**:
|
291
|
+
- **Structured**: Validate and type all LLM interactions with Pydantic.
|
292
292
|
- **Expandable**: Integrate multiple providers (OpenAI, Anthropic, Perplexity,
|
293
293
|
custom) with minimal friction.
|
294
|
-
- **Controlled**:
|
295
|
-
multi-step flows
|
296
|
-
- **Transparent**:
|
297
|
-
|
294
|
+
- **Controlled**: Use built-in safety checks, concurrency strategies, and advanced
|
295
|
+
multi-step flows like ReAct.
|
296
|
+
- **Transparent**: Debug easily with real-time logging, message introspection, and
|
297
|
+
tool usage tracking.
|
298
298
|
|
299
299
|
## Installation
|
300
300
|
|
@@ -310,12 +310,12 @@ pip install lionagi # or install directly
|
|
310
310
|
from lionagi import Branch, iModel
|
311
311
|
|
312
312
|
# Pick a model
|
313
|
-
|
313
|
+
gpt4o = iModel(provider="openai", model="gpt-4o-mini")
|
314
314
|
|
315
315
|
# Create a Branch (conversation context)
|
316
316
|
hunter = Branch(
|
317
317
|
system="you are a hilarious dragon hunter who responds in 10 words rhymes.",
|
318
|
-
chat_model=
|
318
|
+
chat_model=gpt4o,
|
319
319
|
)
|
320
320
|
|
321
321
|
# Communicate asynchronously
|
@@ -341,8 +341,8 @@ res = await hunter.communicate(
|
|
341
341
|
"Tell me a short dragon joke",
|
342
342
|
response_format=Joke
|
343
343
|
)
|
344
|
-
print(type(
|
345
|
-
print(
|
344
|
+
print(type(res))
|
345
|
+
print(res.joke)
|
346
346
|
```
|
347
347
|
|
348
348
|
```
|
@@ -362,7 +362,10 @@ pip install "lionagi[reader]"
|
|
362
362
|
```python
|
363
363
|
from lionagi.tools.types import ReaderTool
|
364
364
|
|
365
|
-
|
365
|
+
# Define model first
|
366
|
+
gpt4o = iModel(provider="openai", model="gpt-4o-mini")
|
367
|
+
|
368
|
+
branch = Branch(chat_model=gpt4o, tools=[ReaderTool])
|
366
369
|
result = await branch.ReAct(
|
367
370
|
instruct={
|
368
371
|
"instruction": "Summarize my PDF and compare with relevant papers.",
|
@@ -396,13 +399,15 @@ print(df.tail())
|
|
396
399
|
```python
|
397
400
|
from lionagi import Branch, iModel
|
398
401
|
|
402
|
+
# Define models for multi-model orchestration
|
403
|
+
gpt4o = iModel(provider="openai", model="gpt-4o-mini")
|
399
404
|
sonnet = iModel(
|
400
405
|
provider="anthropic",
|
401
406
|
model="claude-3-5-sonnet-20241022",
|
402
407
|
max_tokens=1000, # max_tokens is required for anthropic models
|
403
408
|
)
|
404
409
|
|
405
|
-
branch = Branch(chat_model=
|
410
|
+
branch = Branch(chat_model=gpt4o)
|
406
411
|
analysis = await branch.communicate("Analyze these stats", chat_model=sonnet) # Switch mid-flow
|
407
412
|
```
|
408
413
|
|