lionagi 0.17.10__py3-none-any.whl → 0.18.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 (164) hide show
  1. lionagi/__init__.py +1 -2
  2. lionagi/_class_registry.py +1 -2
  3. lionagi/_errors.py +1 -2
  4. lionagi/adapters/async_postgres_adapter.py +2 -10
  5. lionagi/config.py +1 -2
  6. lionagi/fields/action.py +1 -2
  7. lionagi/fields/base.py +3 -0
  8. lionagi/fields/code.py +3 -0
  9. lionagi/fields/file.py +3 -0
  10. lionagi/fields/instruct.py +1 -2
  11. lionagi/fields/reason.py +1 -2
  12. lionagi/fields/research.py +3 -0
  13. lionagi/libs/__init__.py +1 -2
  14. lionagi/libs/file/__init__.py +1 -2
  15. lionagi/libs/file/chunk.py +1 -2
  16. lionagi/libs/file/process.py +1 -2
  17. lionagi/libs/schema/__init__.py +1 -2
  18. lionagi/libs/schema/as_readable.py +1 -2
  19. lionagi/libs/schema/extract_code_block.py +1 -2
  20. lionagi/libs/schema/extract_docstring.py +1 -2
  21. lionagi/libs/schema/function_to_schema.py +1 -2
  22. lionagi/libs/schema/load_pydantic_model_from_schema.py +1 -2
  23. lionagi/libs/schema/minimal_yaml.py +98 -0
  24. lionagi/libs/validate/__init__.py +1 -2
  25. lionagi/libs/validate/common_field_validators.py +1 -2
  26. lionagi/libs/validate/validate_boolean.py +1 -2
  27. lionagi/ln/fuzzy/_string_similarity.py +1 -2
  28. lionagi/ln/types.py +32 -5
  29. lionagi/models/__init__.py +1 -2
  30. lionagi/models/field_model.py +9 -1
  31. lionagi/models/hashable_model.py +4 -2
  32. lionagi/models/model_params.py +1 -2
  33. lionagi/models/operable_model.py +1 -2
  34. lionagi/models/schema_model.py +1 -2
  35. lionagi/operations/ReAct/ReAct.py +475 -239
  36. lionagi/operations/ReAct/__init__.py +1 -2
  37. lionagi/operations/ReAct/utils.py +4 -2
  38. lionagi/operations/__init__.py +1 -2
  39. lionagi/operations/act/__init__.py +2 -0
  40. lionagi/operations/act/act.py +206 -0
  41. lionagi/operations/brainstorm/__init__.py +1 -2
  42. lionagi/operations/brainstorm/brainstorm.py +1 -2
  43. lionagi/operations/brainstorm/prompt.py +1 -2
  44. lionagi/operations/builder.py +1 -2
  45. lionagi/operations/chat/__init__.py +1 -2
  46. lionagi/operations/chat/chat.py +131 -116
  47. lionagi/operations/communicate/communicate.py +102 -44
  48. lionagi/operations/flow.py +5 -6
  49. lionagi/operations/instruct/__init__.py +1 -2
  50. lionagi/operations/instruct/instruct.py +1 -2
  51. lionagi/operations/interpret/__init__.py +1 -2
  52. lionagi/operations/interpret/interpret.py +66 -22
  53. lionagi/operations/operate/__init__.py +1 -2
  54. lionagi/operations/operate/operate.py +213 -108
  55. lionagi/operations/parse/__init__.py +1 -2
  56. lionagi/operations/parse/parse.py +171 -144
  57. lionagi/operations/plan/__init__.py +1 -2
  58. lionagi/operations/plan/plan.py +1 -2
  59. lionagi/operations/plan/prompt.py +1 -2
  60. lionagi/operations/select/__init__.py +1 -2
  61. lionagi/operations/select/select.py +79 -19
  62. lionagi/operations/select/utils.py +2 -3
  63. lionagi/operations/types.py +120 -25
  64. lionagi/operations/utils.py +1 -2
  65. lionagi/protocols/__init__.py +1 -2
  66. lionagi/protocols/_concepts.py +1 -2
  67. lionagi/protocols/action/__init__.py +1 -2
  68. lionagi/protocols/action/function_calling.py +3 -20
  69. lionagi/protocols/action/manager.py +34 -4
  70. lionagi/protocols/action/tool.py +1 -2
  71. lionagi/protocols/contracts.py +1 -2
  72. lionagi/protocols/forms/__init__.py +1 -2
  73. lionagi/protocols/forms/base.py +1 -2
  74. lionagi/protocols/forms/flow.py +1 -2
  75. lionagi/protocols/forms/form.py +1 -2
  76. lionagi/protocols/forms/report.py +1 -2
  77. lionagi/protocols/generic/__init__.py +1 -2
  78. lionagi/protocols/generic/element.py +17 -65
  79. lionagi/protocols/generic/event.py +1 -2
  80. lionagi/protocols/generic/log.py +17 -14
  81. lionagi/protocols/generic/pile.py +3 -4
  82. lionagi/protocols/generic/processor.py +1 -2
  83. lionagi/protocols/generic/progression.py +1 -2
  84. lionagi/protocols/graph/__init__.py +1 -2
  85. lionagi/protocols/graph/edge.py +1 -2
  86. lionagi/protocols/graph/graph.py +1 -2
  87. lionagi/protocols/graph/node.py +1 -2
  88. lionagi/protocols/ids.py +1 -2
  89. lionagi/protocols/mail/__init__.py +1 -2
  90. lionagi/protocols/mail/exchange.py +1 -2
  91. lionagi/protocols/mail/mail.py +1 -2
  92. lionagi/protocols/mail/mailbox.py +1 -2
  93. lionagi/protocols/mail/manager.py +1 -2
  94. lionagi/protocols/mail/package.py +1 -2
  95. lionagi/protocols/messages/__init__.py +28 -2
  96. lionagi/protocols/messages/action_request.py +87 -186
  97. lionagi/protocols/messages/action_response.py +74 -133
  98. lionagi/protocols/messages/assistant_response.py +131 -161
  99. lionagi/protocols/messages/base.py +27 -20
  100. lionagi/protocols/messages/instruction.py +281 -626
  101. lionagi/protocols/messages/manager.py +113 -64
  102. lionagi/protocols/messages/message.py +88 -199
  103. lionagi/protocols/messages/system.py +53 -125
  104. lionagi/protocols/operatives/__init__.py +1 -2
  105. lionagi/protocols/operatives/operative.py +1 -2
  106. lionagi/protocols/operatives/step.py +1 -2
  107. lionagi/protocols/types.py +1 -4
  108. lionagi/service/connections/__init__.py +1 -2
  109. lionagi/service/connections/api_calling.py +1 -2
  110. lionagi/service/connections/endpoint.py +1 -10
  111. lionagi/service/connections/endpoint_config.py +1 -2
  112. lionagi/service/connections/header_factory.py +1 -2
  113. lionagi/service/connections/match_endpoint.py +1 -2
  114. lionagi/service/connections/mcp/__init__.py +1 -2
  115. lionagi/service/connections/mcp/wrapper.py +1 -2
  116. lionagi/service/connections/providers/__init__.py +1 -2
  117. lionagi/service/connections/providers/anthropic_.py +1 -2
  118. lionagi/service/connections/providers/claude_code_cli.py +1 -2
  119. lionagi/service/connections/providers/exa_.py +1 -2
  120. lionagi/service/connections/providers/nvidia_nim_.py +2 -27
  121. lionagi/service/connections/providers/oai_.py +30 -96
  122. lionagi/service/connections/providers/ollama_.py +4 -4
  123. lionagi/service/connections/providers/perplexity_.py +1 -2
  124. lionagi/service/hooks/__init__.py +1 -1
  125. lionagi/service/hooks/_types.py +1 -1
  126. lionagi/service/hooks/_utils.py +1 -1
  127. lionagi/service/hooks/hook_event.py +1 -1
  128. lionagi/service/hooks/hook_registry.py +1 -1
  129. lionagi/service/hooks/hooked_event.py +3 -4
  130. lionagi/service/imodel.py +1 -2
  131. lionagi/service/manager.py +1 -2
  132. lionagi/service/rate_limited_processor.py +1 -2
  133. lionagi/service/resilience.py +1 -2
  134. lionagi/service/third_party/anthropic_models.py +1 -2
  135. lionagi/service/third_party/claude_code.py +4 -4
  136. lionagi/service/third_party/openai_models.py +433 -0
  137. lionagi/service/token_calculator.py +1 -2
  138. lionagi/session/__init__.py +1 -2
  139. lionagi/session/branch.py +171 -180
  140. lionagi/session/session.py +4 -11
  141. lionagi/tools/__init__.py +1 -2
  142. lionagi/tools/base.py +1 -2
  143. lionagi/tools/file/__init__.py +1 -2
  144. lionagi/tools/file/reader.py +3 -4
  145. lionagi/tools/types.py +1 -2
  146. lionagi/utils.py +1 -2
  147. lionagi/version.py +1 -1
  148. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
  149. lionagi-0.18.0.dist-info/RECORD +191 -0
  150. lionagi/operations/_act/__init__.py +0 -3
  151. lionagi/operations/_act/act.py +0 -87
  152. lionagi/protocols/messages/templates/README.md +0 -28
  153. lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
  154. lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
  155. lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
  156. lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
  157. lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
  158. lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
  159. lionagi/service/connections/providers/types.py +0 -28
  160. lionagi/service/third_party/openai_model_names.py +0 -198
  161. lionagi/service/types.py +0 -59
  162. lionagi-0.17.10.dist-info/RECORD +0 -199
  163. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
  164. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
lionagi/__init__.py CHANGED
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import logging
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import ast
lionagi/_errors.py CHANGED
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from __future__ import annotations
@@ -1,13 +1,5 @@
1
- """
2
- Simplified LionAGI async PostgreSQL adapter for pydapter v1.0.4+
3
-
4
- This adapter leverages pydapter's improved raw SQL handling.
5
- No workarounds needed - pydapter now properly handles:
6
- - Raw SQL without table parameter
7
- - No table inspection for raw SQL
8
- - ORDER BY operations
9
- - Both SQLite and PostgreSQL connections
10
- """
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ # SPDX-License-Identifier: Apache-2.0
11
3
 
12
4
  from __future__ import annotations
13
5
 
lionagi/config.py CHANGED
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from typing import Any, ClassVar
lionagi/fields/action.py CHANGED
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import re
lionagi/fields/base.py CHANGED
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
1
4
  from enum import Enum
2
5
 
3
6
  from pydantic import Field, HttpUrl
lionagi/fields/code.py CHANGED
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
1
4
  from enum import Enum
2
5
 
3
6
  from pydantic import Field
lionagi/fields/file.py CHANGED
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
1
4
  from abc import abstractmethod
2
5
  from pathlib import Path
3
6
 
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from typing import Any, ClassVar, Literal
lionagi/fields/reason.py CHANGED
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from pydantic import Field, field_validator
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
1
4
  from pydantic import Field
2
5
 
3
6
  from lionagi.models import HashableModel
lionagi/libs/__init__.py CHANGED
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import math
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import logging
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import json
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import re
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import inspect
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import inspect
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import importlib.util
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import orjson
6
+ import yaml
7
+
8
+ # --- YAML Dumper with minimal, readable settings --------------------------------
9
+
10
+
11
+ class MinimalDumper(yaml.SafeDumper):
12
+ # Disable anchors/aliases (&id001, *id001) for repeated objects.
13
+ def ignore_aliases(self, data: Any) -> bool: # type: ignore[override]
14
+ return True
15
+
16
+
17
+ def _represent_str(dumper: yaml.SafeDumper, data: str):
18
+ # Use block scalars for multiline text; plain style otherwise.
19
+ if "\n" in data:
20
+ return dumper.represent_scalar(
21
+ "tag:yaml.org,2002:str", data, style="|"
22
+ )
23
+ return dumper.represent_scalar("tag:yaml.org,2002:str", data)
24
+
25
+
26
+ MinimalDumper.add_representer(str, _represent_str)
27
+
28
+ # --- Optional pruning of empty values -------------------------------------------
29
+
30
+
31
+ def _is_empty(x: Any) -> bool:
32
+ """
33
+ Define 'empty' for pruning. Keeps 0 and False.
34
+ - None or '' (after strip) are empty
35
+ - Empty containers are empty
36
+ """
37
+ if x is None:
38
+ return True
39
+ if isinstance(x, str):
40
+ return x.strip() == ""
41
+ if isinstance(x, dict):
42
+ return len(x) == 0
43
+ if isinstance(x, (list, tuple, set)):
44
+ return len(x) == 0
45
+ # Keep numbers (including 0) and booleans (including False)
46
+ return False
47
+
48
+
49
+ def _prune(x: Any) -> Any:
50
+ """Recursively remove empty leaves and empty containers produced thereby."""
51
+ if isinstance(x, dict):
52
+ pruned = {k: _prune(v) for k, v in x.items() if not _is_empty(v)}
53
+ # Remove keys that became empty after recursion
54
+ return {k: v for k, v in pruned.items() if not _is_empty(v)}
55
+ if isinstance(x, list):
56
+ pruned_list = [_prune(v) for v in x if not _is_empty(v)]
57
+ return [v for v in pruned_list if not _is_empty(v)]
58
+ if isinstance(x, tuple):
59
+ pruned_list = [_prune(v) for v in x if not _is_empty(v)]
60
+ return tuple(v for v in pruned_list if not _is_empty(v))
61
+ if isinstance(x, set):
62
+ pruned_set = {_prune(v) for v in x if not _is_empty(v)}
63
+ return {v for v in pruned_set if not _is_empty(v)}
64
+ return x
65
+
66
+
67
+ # --- Public API ------------------------------------------------------------------
68
+
69
+
70
+ def minimal_yaml(
71
+ value: Any,
72
+ *,
73
+ drop_empties: bool = True,
74
+ indent: int = 2,
75
+ line_width: int = 2**31 - 1, # avoid PyYAML inserting line-wraps
76
+ sort_keys: bool = False,
77
+ ) -> str:
78
+ """
79
+ Convert any Python value (dict/list/scalars) to a minimal, readable YAML string.
80
+ - Lists -> YAML sequences with '- '
81
+ - Dicts -> 'key: value' mappings
82
+ - Multiline strings -> block scalars (|)
83
+ - Optional pruning of empty values (keeps 0 and False)
84
+ - No aliases/anchors
85
+ """
86
+ if isinstance(value, str):
87
+ value = orjson.loads(value)
88
+
89
+ data = _prune(value) if drop_empties else value
90
+ return yaml.dump(
91
+ data,
92
+ Dumper=MinimalDumper,
93
+ default_flow_style=False, # block style
94
+ sort_keys=sort_keys, # preserve insertion order
95
+ allow_unicode=True,
96
+ indent=indent,
97
+ width=line_width,
98
+ )
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from pydantic import BaseModel, JsonValue
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from numbers import Complex
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from collections.abc import Callable
lionagi/ln/types.py CHANGED
@@ -232,10 +232,13 @@ class Params:
232
232
  dict_.update(kw_)
233
233
  return dict_
234
234
 
235
- def to_dict(self) -> dict[str, str]:
235
+ def to_dict(self, exclude: set[str] = None) -> dict[str, str]:
236
236
  data = {}
237
+ exclude = exclude or set()
237
238
  for k in self.allowed():
238
- if not self._is_sentinel(v := getattr(self, k, Undefined)):
239
+ if k not in exclude and not self._is_sentinel(
240
+ v := getattr(self, k, Undefined)
241
+ ):
239
242
  data[k] = v
240
243
  return data
241
244
 
@@ -249,6 +252,12 @@ class Params:
249
252
  return False
250
253
  return hash(self) == hash(other)
251
254
 
255
+ def with_updates(self, **kwargs: Any) -> DataClass:
256
+ """Return a new instance with updated fields."""
257
+ dict_ = self.to_dict()
258
+ dict_.update(kwargs)
259
+ return type(self)(**dict_)
260
+
252
261
 
253
262
  @dataclass(slots=True)
254
263
  class DataClass:
@@ -296,11 +305,13 @@ class DataClass:
296
305
  for k in self.allowed():
297
306
  _validate_strict(k)
298
307
 
299
- def to_dict(self) -> dict[str, str]:
308
+ def to_dict(self, exclude: set[str] = None) -> dict[str, str]:
300
309
  data = {}
301
- print(self.allowed())
310
+ exclude = exclude or set()
302
311
  for k in type(self).allowed():
303
- if not self._is_sentinel(v := getattr(self, k)):
312
+ if k not in exclude and not self._is_sentinel(
313
+ v := getattr(self, k)
314
+ ):
304
315
  data[k] = v
305
316
  return data
306
317
 
@@ -311,6 +322,22 @@ class DataClass:
311
322
  return True
312
323
  return is_sentinel(value)
313
324
 
325
+ def with_updates(self, **kwargs: Any) -> DataClass:
326
+ """Return a new instance with updated fields."""
327
+ dict_ = self.to_dict()
328
+ dict_.update(kwargs)
329
+ return type(self)(**dict_)
330
+
331
+ def __hash__(self) -> int:
332
+ from ._hash import hash_dict
333
+
334
+ return hash_dict(self.to_dict())
335
+
336
+ def __eq__(self, other: Any) -> bool:
337
+ if not isinstance(other, DataClass):
338
+ return False
339
+ return hash(self) == hash(other)
340
+
314
341
 
315
342
  KeysLike = Sequence[str] | KeysDict
316
343
 
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from .field_model import FieldModel
@@ -11,7 +11,6 @@ import threading
11
11
  from collections import OrderedDict
12
12
  from collections.abc import Callable
13
13
  from dataclasses import dataclass
14
- from dataclasses import field as dc_field
15
14
  from typing import Annotated, Any, ClassVar
16
15
 
17
16
  from typing_extensions import Self, override
@@ -514,6 +513,15 @@ class FieldModel(Params):
514
513
  # These are FieldTemplate markers, don't pass to FieldInfo
515
514
  pass
516
515
  else:
516
+ # Filter out unserializable objects from json_schema_extra
517
+ # to avoid Pydantic serialization errors when generating JSON schema
518
+ from pydantic import BaseModel
519
+
520
+ # Skip model classes and other unserializable types
521
+ if isinstance(meta.value, type):
522
+ # Skip type objects (including model classes) - they can't be serialized
523
+ continue
524
+
517
525
  # Any other metadata goes in json_schema_extra
518
526
  if "json_schema_extra" not in field_kwargs:
519
527
  field_kwargs["json_schema_extra"] = {}
@@ -56,7 +56,9 @@ class HashableModel(BaseModel):
56
56
  case "python":
57
57
  return cls.model_validate(data, **kw)
58
58
  case "json":
59
- return cls.model_validate(orjson.loads(data), **kw)
59
+ if isinstance(data, str):
60
+ data = orjson.loads(data)
61
+ return cls.model_validate(data, **kw)
60
62
  case "db":
61
63
  if "node_metadata" in data:
62
64
  data["metadata"] = data.pop("node_metadata")
@@ -83,7 +85,7 @@ class HashableModel(BaseModel):
83
85
  cls, data: bytes | str, mode: ConversionMode = "json", **kwargs
84
86
  ) -> Self:
85
87
  """Creates an instance of this class from a JSON string."""
86
- return cls.from_dict(orjson.loads(data), mode=mode, **kwargs)
88
+ return cls.from_dict(data, mode=mode, **kwargs)
87
89
 
88
90
  def __hash__(self):
89
91
  return ln.hash_dict(self.to_dict())
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  """ModelParams implementation using Params base class with aggressive caching.
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from typing import Any, TypeVar
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from pydantic import ConfigDict