lionagi 0.16.2__py3-none-any.whl → 0.17.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.
Files changed (63) hide show
  1. lionagi/adapters/_utils.py +10 -23
  2. lionagi/adapters/async_postgres_adapter.py +83 -79
  3. lionagi/ln/__init__.py +4 -4
  4. lionagi/ln/_json_dump.py +0 -6
  5. lionagi/ln/fuzzy/__init__.py +4 -1
  6. lionagi/ln/fuzzy/_fuzzy_validate.py +109 -0
  7. lionagi/ln/fuzzy/_to_dict.py +388 -0
  8. lionagi/models/__init__.py +0 -2
  9. lionagi/operations/__init__.py +0 -6
  10. lionagi/operations/_visualize_graph.py +285 -0
  11. lionagi/operations/brainstorm/brainstorm.py +14 -12
  12. lionagi/operations/builder.py +23 -302
  13. lionagi/operations/communicate/communicate.py +1 -1
  14. lionagi/operations/flow.py +14 -11
  15. lionagi/operations/node.py +14 -3
  16. lionagi/operations/operate/operate.py +5 -11
  17. lionagi/operations/parse/parse.py +2 -3
  18. lionagi/operations/types.py +0 -2
  19. lionagi/operations/utils.py +11 -5
  20. lionagi/protocols/generic/pile.py +3 -7
  21. lionagi/protocols/graph/graph.py +23 -6
  22. lionagi/protocols/graph/node.py +0 -2
  23. lionagi/protocols/messages/message.py +0 -1
  24. lionagi/protocols/operatives/operative.py +2 -2
  25. lionagi/protocols/types.py +0 -15
  26. lionagi/service/connections/endpoint.py +11 -5
  27. lionagi/service/connections/match_endpoint.py +2 -10
  28. lionagi/service/connections/providers/types.py +1 -3
  29. lionagi/service/hooks/hook_event.py +1 -1
  30. lionagi/service/hooks/hook_registry.py +1 -1
  31. lionagi/service/rate_limited_processor.py +1 -1
  32. lionagi/session/branch.py +24 -18
  33. lionagi/session/session.py +2 -18
  34. lionagi/utils.py +3 -335
  35. lionagi/version.py +1 -1
  36. {lionagi-0.16.2.dist-info → lionagi-0.17.0.dist-info}/METADATA +4 -13
  37. {lionagi-0.16.2.dist-info → lionagi-0.17.0.dist-info}/RECORD +39 -61
  38. lionagi/adapters/postgres_model_adapter.py +0 -131
  39. lionagi/libs/concurrency.py +0 -1
  40. lionagi/libs/nested/__init__.py +0 -3
  41. lionagi/libs/nested/flatten.py +0 -172
  42. lionagi/libs/nested/nfilter.py +0 -59
  43. lionagi/libs/nested/nget.py +0 -45
  44. lionagi/libs/nested/ninsert.py +0 -104
  45. lionagi/libs/nested/nmerge.py +0 -158
  46. lionagi/libs/nested/npop.py +0 -69
  47. lionagi/libs/nested/nset.py +0 -94
  48. lionagi/libs/nested/unflatten.py +0 -83
  49. lionagi/libs/nested/utils.py +0 -189
  50. lionagi/libs/parse.py +0 -31
  51. lionagi/libs/schema/json_schema.py +0 -231
  52. lionagi/libs/unstructured/__init__.py +0 -0
  53. lionagi/libs/unstructured/pdf_to_image.py +0 -45
  54. lionagi/libs/unstructured/read_image_to_base64.py +0 -33
  55. lionagi/libs/validate/fuzzy_match_keys.py +0 -7
  56. lionagi/libs/validate/fuzzy_validate_mapping.py +0 -144
  57. lionagi/libs/validate/string_similarity.py +0 -7
  58. lionagi/libs/validate/xml_parser.py +0 -203
  59. lionagi/models/note.py +0 -387
  60. lionagi/protocols/graph/_utils.py +0 -22
  61. lionagi/service/connections/providers/claude_code_.py +0 -299
  62. {lionagi-0.16.2.dist-info → lionagi-0.17.0.dist-info}/WHEEL +0 -0
  63. {lionagi-0.16.2.dist-info → lionagi-0.17.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,26 +1,13 @@
1
1
  def check_async_postgres_available():
2
- try:
3
- import sqlalchemy as sa
4
- from pydapter.extras.async_postgres_ import AsyncPostgresAdapter
5
- from sqlalchemy.ext.asyncio import create_async_engine
2
+ from lionagi.utils import is_import_installed
6
3
 
4
+ all_import_present = 0
5
+ for pkg in ("sqlalchemy", "asyncpg"):
6
+ if is_import_installed(pkg):
7
+ all_import_present += 1
8
+ if all_import_present == 2:
7
9
  return True
8
- except Exception:
9
- return ImportError(
10
- "This adapter requires postgres option to be installed. "
11
- 'Please install them using `uv pip install "lionagi[postgres]"`.'
12
- )
13
-
14
-
15
- def check_postgres_available():
16
- try:
17
- from pydapter.model_adapters.postgres_model import PostgresModelAdapter
18
- from sqlalchemy import String
19
- from sqlalchemy.orm import DeclarativeBase
20
-
21
- return True
22
- except Exception:
23
- return ImportError(
24
- "This adapter requires postgres option to be installed. "
25
- 'Please install them using `uv pip install "lionagi[postgres]"`.'
26
- )
10
+ return ImportError(
11
+ "This adapter requires postgres option to be installed. "
12
+ 'Please install them using `uv pip install "lionagi[postgres]"`.'
13
+ )
@@ -13,86 +13,90 @@ from __future__ import annotations
13
13
 
14
14
  from typing import ClassVar, TypeVar
15
15
 
16
- import sqlalchemy as sa
17
- from pydapter.extras.async_postgres_ import AsyncPostgresAdapter
18
- from sqlalchemy.ext.asyncio import create_async_engine
19
-
20
- from ._utils import check_async_postgres_available
21
-
22
- _ASYNC_POSTGRES_AVAILABLE = check_async_postgres_available()
23
-
24
- if isinstance(_ASYNC_POSTGRES_AVAILABLE, ImportError):
25
- raise _ASYNC_POSTGRES_AVAILABLE
16
+ from pydapter import AsyncAdapter
26
17
 
27
18
  T = TypeVar("T")
28
19
 
29
20
 
30
- class LionAGIAsyncPostgresAdapter(AsyncPostgresAdapter[T]):
31
- """
32
- Streamlined async adapter for lionagi Nodes.
33
-
34
- Features:
35
- - Auto-creates tables with lionagi schema
36
- - Inherits all pydapter v1.0.4+ improvements
37
- - No workarounds needed for SQLite or raw SQL
38
- """
39
-
40
- obj_key: ClassVar[str] = "lionagi_async_pg"
41
-
42
- @classmethod
43
- async def to_obj(
44
- cls,
45
- subj,
46
- /,
47
- *,
48
- many: bool = True,
49
- adapt_meth: str = None,
50
- **kw,
51
- ):
52
- """Write lionagi Node(s) to database with auto-table creation."""
53
- # Auto-create table if needed
54
- if table := kw.get("table"):
55
- if engine_url := (kw.get("dsn") or kw.get("engine_url")):
56
- await cls._ensure_table(engine_url, table)
57
- elif engine := kw.get("engine"):
58
- await cls._ensure_table(engine, table)
59
-
60
- return await super().to_obj(
61
- subj, many=many, adapt_meth=adapt_meth, **kw
62
- )
63
-
64
- @classmethod
65
- async def _ensure_table(cls, engine_or_url, table_name: str):
66
- """Create table with lionagi schema if it doesn't exist."""
67
- should_dispose = False
68
- if isinstance(engine_or_url, str):
69
- engine = create_async_engine(engine_or_url, future=True)
70
- should_dispose = True
71
- else:
72
- engine = engine_or_url
73
-
74
- try:
75
- async with engine.begin() as conn:
76
- # Determine JSON type based on database
77
- engine_url = str(engine.url)
78
- json_type = (
79
- sa.dialects.postgresql.JSONB
80
- if "postgresql" in engine_url
81
- else sa.JSON
82
- )
83
-
84
- # Create table with lionagi schema
85
- await conn.run_sync(
86
- lambda sync_conn: sa.Table(
87
- table_name,
88
- sa.MetaData(),
89
- sa.Column("id", sa.String, primary_key=True),
90
- sa.Column("content", json_type),
91
- sa.Column("node_metadata", json_type),
92
- sa.Column("created_at", sa.Float),
93
- sa.Column("embedding", json_type, nullable=True),
94
- ).create(sync_conn, checkfirst=True)
95
- )
96
- finally:
97
- if should_dispose:
98
- await engine.dispose()
21
+ def create_lionagi_async_postgres_adapter() -> type[AsyncAdapter]:
22
+ from pydapter.extras.async_postgres_ import AsyncPostgresAdapter
23
+
24
+ class LionAGIAsyncPostgresAdapter(AsyncPostgresAdapter[T]):
25
+ """
26
+ Streamlined async adapter for lionagi Nodes.
27
+
28
+ Features:
29
+ - Auto-creates tables with lionagi schema
30
+ - Inherits all pydapter v1.0.4+ improvements
31
+ - No workarounds needed for SQLite or raw SQL
32
+ """
33
+
34
+ obj_key: ClassVar[str] = "lionagi_async_pg"
35
+
36
+ @classmethod
37
+ async def to_obj(
38
+ cls,
39
+ subj,
40
+ /,
41
+ *,
42
+ many: bool = True,
43
+ adapt_meth: str = None,
44
+ **kw,
45
+ ):
46
+ """Write lionagi Node(s) to database with auto-table creation."""
47
+ # Auto-create table if needed
48
+ if table := kw.get("table"):
49
+ if engine_url := (kw.get("dsn") or kw.get("engine_url")):
50
+ await cls._ensure_table(engine_url, table)
51
+ elif engine := kw.get("engine"):
52
+ await cls._ensure_table(engine, table)
53
+
54
+ return await super().to_obj(
55
+ subj, many=many, adapt_meth=adapt_meth, **kw
56
+ )
57
+
58
+ @classmethod
59
+ async def _ensure_table(cls, engine_or_url, table_name: str):
60
+ """Create table with lionagi schema if it doesn't exist."""
61
+ import sqlalchemy as sa
62
+ from sqlalchemy.ext.asyncio import create_async_engine
63
+
64
+ should_dispose = False
65
+ if isinstance(engine_or_url, str):
66
+ engine = create_async_engine(engine_or_url, future=True)
67
+ should_dispose = True
68
+ else:
69
+ engine = engine_or_url
70
+
71
+ try:
72
+ async with engine.begin() as conn:
73
+ # Determine JSON type based on database
74
+ engine_url = str(engine.url)
75
+ json_type = (
76
+ sa.dialects.postgresql.JSONB
77
+ if "postgresql" in engine_url
78
+ else sa.JSON
79
+ )
80
+
81
+ # Create table with lionagi schema
82
+ await conn.run_sync(
83
+ lambda sync_conn: sa.Table(
84
+ table_name,
85
+ sa.MetaData(),
86
+ sa.Column("id", sa.String, primary_key=True),
87
+ sa.Column("content", json_type),
88
+ sa.Column("node_metadata", json_type),
89
+ sa.Column("created_at", sa.Float),
90
+ sa.Column("embedding", json_type, nullable=True),
91
+ ).create(sync_conn, checkfirst=True)
92
+ )
93
+ finally:
94
+ if should_dispose:
95
+ await engine.dispose()
96
+
97
+ return LionAGIAsyncPostgresAdapter
98
+
99
+
100
+ LionAGIAsyncPostgresAdapter = create_lionagi_async_postgres_adapter()
101
+
102
+ __all__ = ("LionAGIAsyncPostgresAdapter",)
lionagi/ln/__init__.py CHANGED
@@ -1,8 +1,6 @@
1
1
  from ._async_call import alcall, bcall
2
2
  from ._hash import hash_dict
3
3
  from ._json_dump import (
4
- DEFAULT_SERIALIZER,
5
- DEFAULT_SERIALIZER_OPTION,
6
4
  get_orjson_default,
7
5
  json_dumpb,
8
6
  json_dumps,
@@ -37,8 +35,10 @@ from .fuzzy import (
37
35
  extract_json,
38
36
  fuzzy_json,
39
37
  fuzzy_match_keys,
38
+ fuzzy_validate_mapping,
40
39
  fuzzy_validate_pydantic,
41
40
  string_similarity,
41
+ to_dict,
42
42
  )
43
43
  from .types import is_sentinel, not_sentinel
44
44
 
@@ -46,8 +46,6 @@ __all__ = (
46
46
  "alcall",
47
47
  "bcall",
48
48
  "hash_dict",
49
- "DEFAULT_SERIALIZER",
50
- "DEFAULT_SERIALIZER_OPTION",
51
49
  "get_orjson_default",
52
50
  "json_dumps",
53
51
  "make_options",
@@ -80,4 +78,6 @@ __all__ = (
80
78
  "string_similarity",
81
79
  "is_sentinel",
82
80
  "not_sentinel",
81
+ "to_dict",
82
+ "fuzzy_validate_mapping",
83
83
  )
lionagi/ln/_json_dump.py CHANGED
@@ -15,8 +15,6 @@ import orjson
15
15
 
16
16
  __all__ = [
17
17
  "get_orjson_default",
18
- "DEFAULT_SERIALIZER",
19
- "DEFAULT_SERIALIZER_OPTION",
20
18
  "make_options",
21
19
  "json_dumpb",
22
20
  "json_dumps",
@@ -193,10 +191,6 @@ def _cached_default(
193
191
 
194
192
  # --------- defaults & options -------------------------------------------------
195
193
 
196
- # Compact, no newline, no sorting: neutral default for most use-cases.
197
- DEFAULT_SERIALIZER_OPTION = 0
198
- DEFAULT_SERIALIZER = get_orjson_default()
199
-
200
194
 
201
195
  def make_options(
202
196
  *,
@@ -1,10 +1,12 @@
1
1
  from ._extract_json import extract_json
2
2
  from ._fuzzy_json import fuzzy_json
3
3
  from ._fuzzy_match import FuzzyMatchKeysParams, fuzzy_match_keys
4
- from ._fuzzy_validate import fuzzy_validate_pydantic
4
+ from ._fuzzy_validate import fuzzy_validate_mapping, fuzzy_validate_pydantic
5
5
  from ._string_similarity import SIMILARITY_TYPE, string_similarity
6
+ from ._to_dict import to_dict
6
7
 
7
8
  __all__ = (
9
+ "to_dict",
8
10
  "fuzzy_json",
9
11
  "fuzzy_match_keys",
10
12
  "extract_json",
@@ -12,4 +14,5 @@ __all__ = (
12
14
  "SIMILARITY_TYPE",
13
15
  "fuzzy_validate_pydantic",
14
16
  "FuzzyMatchKeysParams",
17
+ "fuzzy_validate_mapping",
15
18
  )
@@ -1,9 +1,15 @@
1
+ from collections.abc import Callable, Sequence
2
+ from typing import Any, Literal
3
+
1
4
  from pydantic import BaseModel
2
5
 
3
6
  from lionagi._errors import ValidationError
4
7
 
8
+ from ..types import KeysDict
5
9
  from ._extract_json import extract_json
6
10
  from ._fuzzy_match import FuzzyMatchKeysParams, fuzzy_match_keys
11
+ from ._string_similarity import SIMILARITY_TYPE
12
+ from ._to_dict import to_dict
7
13
 
8
14
  __all__ = ("fuzzy_validate_pydantic",)
9
15
 
@@ -44,3 +50,106 @@ def fuzzy_validate_pydantic(
44
50
  return model_type.model_validate(model_data)
45
51
  except Exception as e:
46
52
  raise ValidationError(f"Validation failed: {e}") from e
53
+
54
+
55
+ def fuzzy_validate_mapping(
56
+ d: Any,
57
+ keys: Sequence[str] | KeysDict,
58
+ /,
59
+ *,
60
+ similarity_algo: (
61
+ SIMILARITY_TYPE | Callable[[str, str], float]
62
+ ) = "jaro_winkler",
63
+ similarity_threshold: float = 0.85,
64
+ fuzzy_match: bool = True,
65
+ handle_unmatched: Literal[
66
+ "ignore", "raise", "remove", "fill", "force"
67
+ ] = "ignore",
68
+ fill_value: Any = None,
69
+ fill_mapping: dict[str, Any] | None = None,
70
+ strict: bool = False,
71
+ suppress_conversion_errors: bool = False,
72
+ ) -> dict[str, Any]:
73
+ """
74
+ Validate and correct any input into a dictionary with expected keys.
75
+
76
+ Args:
77
+ d: Input to validate. Can be:
78
+ - Dictionary
79
+ - JSON string or markdown code block
80
+ - XML string
81
+ - Object with to_dict/model_dump method
82
+ - Any type convertible to dictionary
83
+ keys: List of expected keys or dictionary mapping keys to types.
84
+ similarity_algo: String similarity algorithm or custom function.
85
+ similarity_threshold: Minimum similarity score for fuzzy matching.
86
+ fuzzy_match: If True, use fuzzy matching for key correction.
87
+ handle_unmatched: How to handle unmatched keys:
88
+ - "ignore": Keep unmatched keys
89
+ - "raise": Raise error for unmatched keys
90
+ - "remove": Remove unmatched keys
91
+ - "fill": Fill missing keys with default values
92
+ - "force": Combine "fill" and "remove" behaviors
93
+ fill_value: Default value for filling unmatched keys.
94
+ fill_mapping: Dictionary mapping keys to default values.
95
+ strict: Raise error if any expected key is missing.
96
+ suppress_conversion_errors: Return empty dict on conversion errors.
97
+
98
+ Returns:
99
+ Validated and corrected dictionary.
100
+
101
+ Raises:
102
+ ValueError: If input cannot be converted or validation fails.
103
+ TypeError: If input types are invalid.
104
+ """
105
+ if d is None:
106
+ raise TypeError("Input cannot be None")
107
+
108
+ # Try converting to dictionary
109
+ try:
110
+ if isinstance(d, str):
111
+ # First try to_json for JSON strings and code blocks
112
+ try:
113
+ json_result = extract_json(
114
+ d, fuzzy_parse=True, return_one_if_single=True
115
+ )
116
+ dict_input = (
117
+ json_result[0]
118
+ if isinstance(json_result, list)
119
+ else json_result
120
+ )
121
+ except Exception:
122
+ dict_input = to_dict(
123
+ d, str_type="json", fuzzy_parse=True, suppress=True
124
+ )
125
+ else:
126
+ dict_input = to_dict(
127
+ d, use_model_dump=True, fuzzy_parse=True, suppress=True
128
+ )
129
+
130
+ if not isinstance(dict_input, dict):
131
+ if suppress_conversion_errors:
132
+ dict_input = {}
133
+ else:
134
+ raise ValueError(
135
+ f"Failed to convert input to dictionary: {type(dict_input)}"
136
+ )
137
+
138
+ except Exception as e:
139
+ if suppress_conversion_errors:
140
+ dict_input = {}
141
+ else:
142
+ raise ValueError(f"Failed to convert input to dictionary: {e}")
143
+
144
+ # Validate the dictionary
145
+ return fuzzy_match_keys(
146
+ dict_input,
147
+ keys,
148
+ similarity_algo=similarity_algo,
149
+ similarity_threshold=similarity_threshold,
150
+ fuzzy_match=fuzzy_match,
151
+ handle_unmatched=handle_unmatched,
152
+ fill_value=fill_value,
153
+ fill_mapping=fill_mapping,
154
+ strict=strict,
155
+ )