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.
- lionagi/__init__.py +1 -2
- lionagi/_class_registry.py +1 -2
- lionagi/_errors.py +1 -2
- lionagi/adapters/async_postgres_adapter.py +2 -10
- lionagi/config.py +1 -2
- lionagi/fields/action.py +1 -2
- lionagi/fields/base.py +3 -0
- lionagi/fields/code.py +3 -0
- lionagi/fields/file.py +3 -0
- lionagi/fields/instruct.py +1 -2
- lionagi/fields/reason.py +1 -2
- lionagi/fields/research.py +3 -0
- lionagi/libs/__init__.py +1 -2
- lionagi/libs/file/__init__.py +1 -2
- lionagi/libs/file/chunk.py +1 -2
- lionagi/libs/file/process.py +1 -2
- lionagi/libs/schema/__init__.py +1 -2
- lionagi/libs/schema/as_readable.py +1 -2
- lionagi/libs/schema/extract_code_block.py +1 -2
- lionagi/libs/schema/extract_docstring.py +1 -2
- lionagi/libs/schema/function_to_schema.py +1 -2
- lionagi/libs/schema/load_pydantic_model_from_schema.py +1 -2
- lionagi/libs/schema/minimal_yaml.py +98 -0
- lionagi/libs/validate/__init__.py +1 -2
- lionagi/libs/validate/common_field_validators.py +1 -2
- lionagi/libs/validate/validate_boolean.py +1 -2
- lionagi/ln/fuzzy/_string_similarity.py +1 -2
- lionagi/ln/types.py +32 -5
- lionagi/models/__init__.py +1 -2
- lionagi/models/field_model.py +9 -1
- lionagi/models/hashable_model.py +4 -2
- lionagi/models/model_params.py +1 -2
- lionagi/models/operable_model.py +1 -2
- lionagi/models/schema_model.py +1 -2
- lionagi/operations/ReAct/ReAct.py +475 -239
- lionagi/operations/ReAct/__init__.py +1 -2
- lionagi/operations/ReAct/utils.py +4 -2
- lionagi/operations/__init__.py +1 -2
- lionagi/operations/act/__init__.py +2 -0
- lionagi/operations/act/act.py +206 -0
- lionagi/operations/brainstorm/__init__.py +1 -2
- lionagi/operations/brainstorm/brainstorm.py +1 -2
- lionagi/operations/brainstorm/prompt.py +1 -2
- lionagi/operations/builder.py +1 -2
- lionagi/operations/chat/__init__.py +1 -2
- lionagi/operations/chat/chat.py +131 -116
- lionagi/operations/communicate/communicate.py +102 -44
- lionagi/operations/flow.py +5 -6
- lionagi/operations/instruct/__init__.py +1 -2
- lionagi/operations/instruct/instruct.py +1 -2
- lionagi/operations/interpret/__init__.py +1 -2
- lionagi/operations/interpret/interpret.py +66 -22
- lionagi/operations/operate/__init__.py +1 -2
- lionagi/operations/operate/operate.py +213 -108
- lionagi/operations/parse/__init__.py +1 -2
- lionagi/operations/parse/parse.py +171 -144
- lionagi/operations/plan/__init__.py +1 -2
- lionagi/operations/plan/plan.py +1 -2
- lionagi/operations/plan/prompt.py +1 -2
- lionagi/operations/select/__init__.py +1 -2
- lionagi/operations/select/select.py +79 -19
- lionagi/operations/select/utils.py +2 -3
- lionagi/operations/types.py +120 -25
- lionagi/operations/utils.py +1 -2
- lionagi/protocols/__init__.py +1 -2
- lionagi/protocols/_concepts.py +1 -2
- lionagi/protocols/action/__init__.py +1 -2
- lionagi/protocols/action/function_calling.py +3 -20
- lionagi/protocols/action/manager.py +34 -4
- lionagi/protocols/action/tool.py +1 -2
- lionagi/protocols/contracts.py +1 -2
- lionagi/protocols/forms/__init__.py +1 -2
- lionagi/protocols/forms/base.py +1 -2
- lionagi/protocols/forms/flow.py +1 -2
- lionagi/protocols/forms/form.py +1 -2
- lionagi/protocols/forms/report.py +1 -2
- lionagi/protocols/generic/__init__.py +1 -2
- lionagi/protocols/generic/element.py +17 -65
- lionagi/protocols/generic/event.py +1 -2
- lionagi/protocols/generic/log.py +17 -14
- lionagi/protocols/generic/pile.py +3 -4
- lionagi/protocols/generic/processor.py +1 -2
- lionagi/protocols/generic/progression.py +1 -2
- lionagi/protocols/graph/__init__.py +1 -2
- lionagi/protocols/graph/edge.py +1 -2
- lionagi/protocols/graph/graph.py +1 -2
- lionagi/protocols/graph/node.py +1 -2
- lionagi/protocols/ids.py +1 -2
- lionagi/protocols/mail/__init__.py +1 -2
- lionagi/protocols/mail/exchange.py +1 -2
- lionagi/protocols/mail/mail.py +1 -2
- lionagi/protocols/mail/mailbox.py +1 -2
- lionagi/protocols/mail/manager.py +1 -2
- lionagi/protocols/mail/package.py +1 -2
- lionagi/protocols/messages/__init__.py +28 -2
- lionagi/protocols/messages/action_request.py +87 -186
- lionagi/protocols/messages/action_response.py +74 -133
- lionagi/protocols/messages/assistant_response.py +131 -161
- lionagi/protocols/messages/base.py +27 -20
- lionagi/protocols/messages/instruction.py +281 -626
- lionagi/protocols/messages/manager.py +113 -64
- lionagi/protocols/messages/message.py +88 -199
- lionagi/protocols/messages/system.py +53 -125
- lionagi/protocols/operatives/__init__.py +1 -2
- lionagi/protocols/operatives/operative.py +1 -2
- lionagi/protocols/operatives/step.py +1 -2
- lionagi/protocols/types.py +1 -4
- lionagi/service/connections/__init__.py +1 -2
- lionagi/service/connections/api_calling.py +1 -2
- lionagi/service/connections/endpoint.py +1 -10
- lionagi/service/connections/endpoint_config.py +1 -2
- lionagi/service/connections/header_factory.py +1 -2
- lionagi/service/connections/match_endpoint.py +1 -2
- lionagi/service/connections/mcp/__init__.py +1 -2
- lionagi/service/connections/mcp/wrapper.py +1 -2
- lionagi/service/connections/providers/__init__.py +1 -2
- lionagi/service/connections/providers/anthropic_.py +1 -2
- lionagi/service/connections/providers/claude_code_cli.py +1 -2
- lionagi/service/connections/providers/exa_.py +1 -2
- lionagi/service/connections/providers/nvidia_nim_.py +2 -27
- lionagi/service/connections/providers/oai_.py +30 -96
- lionagi/service/connections/providers/ollama_.py +4 -4
- lionagi/service/connections/providers/perplexity_.py +1 -2
- lionagi/service/hooks/__init__.py +1 -1
- lionagi/service/hooks/_types.py +1 -1
- lionagi/service/hooks/_utils.py +1 -1
- lionagi/service/hooks/hook_event.py +1 -1
- lionagi/service/hooks/hook_registry.py +1 -1
- lionagi/service/hooks/hooked_event.py +3 -4
- lionagi/service/imodel.py +1 -2
- lionagi/service/manager.py +1 -2
- lionagi/service/rate_limited_processor.py +1 -2
- lionagi/service/resilience.py +1 -2
- lionagi/service/third_party/anthropic_models.py +1 -2
- lionagi/service/third_party/claude_code.py +4 -4
- lionagi/service/third_party/openai_models.py +433 -0
- lionagi/service/token_calculator.py +1 -2
- lionagi/session/__init__.py +1 -2
- lionagi/session/branch.py +171 -180
- lionagi/session/session.py +4 -11
- lionagi/tools/__init__.py +1 -2
- lionagi/tools/base.py +1 -2
- lionagi/tools/file/__init__.py +1 -2
- lionagi/tools/file/reader.py +3 -4
- lionagi/tools/types.py +1 -2
- lionagi/utils.py +1 -2
- lionagi/version.py +1 -1
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
- lionagi-0.18.0.dist-info/RECORD +191 -0
- lionagi/operations/_act/__init__.py +0 -3
- lionagi/operations/_act/act.py +0 -87
- lionagi/protocols/messages/templates/README.md +0 -28
- lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
- lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
- lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
- lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
- lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
- lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
- lionagi/service/connections/providers/types.py +0 -28
- lionagi/service/third_party/openai_model_names.py +0 -198
- lionagi/service/types.py +0 -59
- lionagi-0.17.10.dist-info/RECORD +0 -199
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
lionagi/operations/types.py
CHANGED
@@ -1,27 +1,122 @@
|
|
1
|
-
# Copyright (c) 2023
|
2
|
-
#
|
1
|
+
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
3
2
|
# SPDX-License-Identifier: Apache-2.0
|
4
3
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
|
9
|
-
from
|
10
|
-
|
11
|
-
from .
|
12
|
-
from .
|
13
|
-
from .
|
14
|
-
from .
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
"
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from enum import Enum
|
6
|
+
from typing import ClassVar, Literal
|
7
|
+
|
8
|
+
from pydantic import BaseModel, JsonValue
|
9
|
+
|
10
|
+
from lionagi.ln._async_call import AlcallParams
|
11
|
+
from lionagi.ln.fuzzy import FuzzyMatchKeysParams
|
12
|
+
from lionagi.ln.types import Params
|
13
|
+
from lionagi.protocols.action.tool import ToolRef
|
14
|
+
from lionagi.protocols.types import ID, SenderRecipient
|
15
|
+
from lionagi.service.imodel import iModel
|
16
|
+
|
17
|
+
HandleValidation = Literal["raise", "return_value", "return_none"]
|
18
|
+
|
19
|
+
|
20
|
+
class ContextPolicy(str, Enum):
|
21
|
+
"""Policy for merging prompt context across morphism invocations.
|
22
|
+
|
23
|
+
Attributes:
|
24
|
+
REPLACE: New context completely replaces existing context
|
25
|
+
EXTEND: New context is appended to existing context
|
26
|
+
DEDUP: New context is appended but duplicates are removed
|
27
|
+
"""
|
28
|
+
|
29
|
+
REPLACE = "replace"
|
30
|
+
EXTEND = "extend"
|
31
|
+
DEDUP = "dedup"
|
32
|
+
|
33
|
+
|
34
|
+
@dataclass(slots=True, frozen=True, init=False)
|
35
|
+
class MorphParam(Params):
|
36
|
+
"""Base class for morphism parameters (invariants).
|
37
|
+
|
38
|
+
MorphParams represent the invariant properties that define a morphism
|
39
|
+
in LionAGI's categorical framework. They are frozen (immutable) and
|
40
|
+
hashable, enabling reproducible operations and efficient caching.
|
41
|
+
|
42
|
+
Morphisms are the fundamental abstraction in LionAGI - they represent
|
43
|
+
transformations between message states with well-defined parameters.
|
44
|
+
"""
|
45
|
+
|
46
|
+
_none_as_sentinel: ClassVar[bool] = True
|
47
|
+
|
48
|
+
|
49
|
+
@dataclass(slots=True, frozen=True, init=False)
|
50
|
+
class ChatParam(MorphParam):
|
51
|
+
"""Parameters for chat/communicate morphism.
|
52
|
+
|
53
|
+
Defines the invariant properties of a chat operation, including
|
54
|
+
guidance, context, response format, and LLM-visible content.
|
55
|
+
|
56
|
+
Note: 'context' field contains prompt context (LLM-visible facts).
|
57
|
+
This gets mapped to InstructionContent.prompt_context during message creation.
|
58
|
+
"""
|
59
|
+
|
60
|
+
_none_as_sentinel: ClassVar[bool] = True
|
61
|
+
guidance: JsonValue = None
|
62
|
+
context: JsonValue = None
|
63
|
+
sender: SenderRecipient = None
|
64
|
+
recipient: SenderRecipient = None
|
65
|
+
response_format: type[BaseModel] | dict = None
|
66
|
+
progression: ID.RefSeq = None
|
67
|
+
tool_schemas: list[dict] = None
|
68
|
+
images: list = None
|
69
|
+
image_detail: Literal["low", "high", "auto"] = None
|
70
|
+
plain_content: str = None
|
71
|
+
include_token_usage_to_model: bool = False
|
72
|
+
imodel: iModel = None
|
73
|
+
imodel_kw: dict = None
|
74
|
+
|
75
|
+
|
76
|
+
@dataclass(slots=True, frozen=True, init=False)
|
77
|
+
class InterpretParam(MorphParam):
|
78
|
+
"""Parameters for interpret morphism.
|
79
|
+
|
80
|
+
Defines interpretation style, domain, and sample writing for
|
81
|
+
transforming content according to specified guidelines.
|
82
|
+
"""
|
83
|
+
|
84
|
+
_none_as_sentinel: ClassVar[bool] = True
|
85
|
+
domain: str = None
|
86
|
+
style: str = None
|
87
|
+
sample_writing: str = None
|
88
|
+
imodel: iModel = None
|
89
|
+
imodel_kw: dict = None
|
90
|
+
|
91
|
+
|
92
|
+
@dataclass(slots=True, frozen=True, init=False)
|
93
|
+
class ParseParam(MorphParam):
|
94
|
+
"""Parameters for parse morphism.
|
95
|
+
|
96
|
+
Defines parsing behavior including response format validation,
|
97
|
+
fuzzy matching, and error handling strategies.
|
98
|
+
"""
|
99
|
+
|
100
|
+
_none_as_sentinel: ClassVar[bool] = True
|
101
|
+
response_format: type[BaseModel] | dict = None
|
102
|
+
fuzzy_match_params: FuzzyMatchKeysParams | dict = None
|
103
|
+
handle_validation: HandleValidation = "raise"
|
104
|
+
alcall_params: AlcallParams | dict = None
|
105
|
+
imodel: iModel = None
|
106
|
+
imodel_kw: dict = None
|
107
|
+
|
108
|
+
|
109
|
+
@dataclass(slots=True, frozen=True, init=False)
|
110
|
+
class ActionParam(MorphParam):
|
111
|
+
"""Parameters for action/tool execution morphism.
|
112
|
+
|
113
|
+
Defines tool execution strategy, error handling, and verbosity
|
114
|
+
for action-based operations.
|
115
|
+
"""
|
116
|
+
|
117
|
+
_none_as_sentinel: ClassVar[bool] = True
|
118
|
+
action_call_params: AlcallParams = None
|
119
|
+
tools: ToolRef = None
|
120
|
+
strategy: Literal["concurrent", "sequential"] = "concurrent"
|
121
|
+
suppress_errors: bool = True
|
122
|
+
verbose_action: bool = False
|
lionagi/operations/utils.py
CHANGED
lionagi/protocols/__init__.py
CHANGED
lionagi/protocols/_concepts.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
# Copyright (c) 2023
|
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 asyncio
|
@@ -116,29 +115,13 @@ class FunctionCalling(Event):
|
|
116
115
|
self.execution.status = EventStatus.FAILED
|
117
116
|
self.execution.error = str(e)
|
118
117
|
|
119
|
-
def
|
120
|
-
"""Returns a string representation of the function call.
|
121
|
-
|
122
|
-
Returns:
|
123
|
-
A string in the format "function_name(arguments)".
|
124
|
-
"""
|
125
|
-
return f"{self.func_tool.function}({self.arguments})"
|
126
|
-
|
127
|
-
def __repr__(self) -> str:
|
128
|
-
"""Returns a detailed string representation of the function call.
|
129
|
-
|
130
|
-
Returns:
|
131
|
-
A string containing the class name and key attributes.
|
132
|
-
"""
|
133
|
-
return f"FunctionCalling(function={self.func_tool.function}, arguments={self.arguments})"
|
134
|
-
|
135
|
-
def to_dict(self) -> dict[str, Any]:
|
118
|
+
def to_dict(self, *args, **kw) -> dict[str, Any]:
|
136
119
|
"""Convert instance to dictionary.
|
137
120
|
|
138
121
|
Returns:
|
139
122
|
dict[str, Any]: Dictionary representation of the instance.
|
140
123
|
"""
|
141
|
-
dict_ = super().to_dict()
|
124
|
+
dict_ = super().to_dict(*args, **kw)
|
142
125
|
dict_["function"] = self.function
|
143
126
|
dict_["arguments"] = self.arguments
|
144
127
|
return dict_
|
@@ -1,7 +1,7 @@
|
|
1
|
-
# Copyright (c) 2023
|
2
|
-
#
|
1
|
+
# Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
|
3
2
|
# SPDX-License-Identifier: Apache-2.0
|
4
3
|
|
4
|
+
import logging
|
5
5
|
from typing import Any
|
6
6
|
|
7
7
|
from lionagi.fields.action import ActionRequestModel
|
@@ -348,16 +348,46 @@ class ActionManager(Manager):
|
|
348
348
|
if request_options and tool.name in request_options:
|
349
349
|
tool_request_options = request_options[tool.name]
|
350
350
|
|
351
|
+
# Extract schema from FastMCP tool and convert to lionagi format
|
352
|
+
tool_schema = None
|
351
353
|
try:
|
352
|
-
|
354
|
+
if (
|
355
|
+
hasattr(tool, "inputSchema")
|
356
|
+
and tool.inputSchema is not None
|
357
|
+
and isinstance(tool.inputSchema, dict)
|
358
|
+
):
|
359
|
+
tool_schema = {
|
360
|
+
"type": "function",
|
361
|
+
"function": {
|
362
|
+
"name": tool.name,
|
363
|
+
"description": (
|
364
|
+
tool.description
|
365
|
+
if hasattr(tool, "description")
|
366
|
+
else None
|
367
|
+
),
|
368
|
+
"parameters": tool.inputSchema,
|
369
|
+
},
|
370
|
+
}
|
371
|
+
except Exception as schema_error:
|
372
|
+
# If schema extraction fails, let Tool auto-generate from function signature
|
373
|
+
logging.warning(
|
374
|
+
f"Could not extract schema for {tool.name}: {schema_error}"
|
375
|
+
)
|
376
|
+
tool_schema = None
|
377
|
+
|
378
|
+
try:
|
379
|
+
# Create tool with auto-populated schema from MCP discovery
|
353
380
|
tool_obj = Tool(
|
354
381
|
mcp_config=mcp_config,
|
355
382
|
request_options=tool_request_options,
|
383
|
+
tool_schema=tool_schema,
|
356
384
|
)
|
357
385
|
self.register_tool(tool_obj, update=update)
|
358
386
|
registered_tools.append(tool.name)
|
359
387
|
except Exception as e:
|
360
|
-
|
388
|
+
logging.warning(
|
389
|
+
f"Failed to register tool {tool.name}: {e}"
|
390
|
+
)
|
361
391
|
|
362
392
|
return registered_tools
|
363
393
|
|
lionagi/protocols/action/tool.py
CHANGED
lionagi/protocols/contracts.py
CHANGED
lionagi/protocols/forms/base.py
CHANGED
lionagi/protocols/forms/flow.py
CHANGED
lionagi/protocols/forms/form.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
# Copyright (c) 2023
|
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
|
@@ -48,22 +47,13 @@ class IDType:
|
|
48
47
|
__slots__ = ("_id",)
|
49
48
|
|
50
49
|
def __init__(self, id: UUID) -> None:
|
51
|
-
"""Initializes an IDType instance.
|
52
|
-
|
53
|
-
Args:
|
54
|
-
id (UUID): A UUID object (version 4 preferred).
|
55
|
-
"""
|
50
|
+
"""Initializes an IDType instance."""
|
56
51
|
self._id = id
|
57
52
|
|
58
53
|
@classmethod
|
59
54
|
def validate(cls, value: str | UUID | IDType) -> IDType:
|
60
55
|
"""Validates and converts a value into an IDType.
|
61
56
|
|
62
|
-
Args:
|
63
|
-
value (str | UUID | IDType):
|
64
|
-
A string representing a UUID, a UUID instance, or another
|
65
|
-
IDType instance.
|
66
|
-
|
67
57
|
Returns:
|
68
58
|
IDType: The validated IDType object.
|
69
59
|
|
@@ -105,9 +95,6 @@ class IDType:
|
|
105
95
|
def __eq__(self, other: Any) -> bool:
|
106
96
|
"""Checks equality with another IDType based on UUID value.
|
107
97
|
|
108
|
-
Args:
|
109
|
-
other (Any): Another object for equality comparison.
|
110
|
-
|
111
98
|
Returns:
|
112
99
|
bool: True if both have the same underlying UUID; False otherwise.
|
113
100
|
"""
|
@@ -190,17 +177,7 @@ class Element(BaseModel, Observable):
|
|
190
177
|
def _coerce_created_at(
|
191
178
|
cls, val: float | dt.datetime | str | None
|
192
179
|
) -> float:
|
193
|
-
"""Coerces `created_at` to a float-based timestamp.
|
194
|
-
|
195
|
-
Args:
|
196
|
-
val (float | datetime | str | None): The initial creation time value.
|
197
|
-
|
198
|
-
Returns:
|
199
|
-
float: A float representing Unix epoch time in seconds.
|
200
|
-
|
201
|
-
Raises:
|
202
|
-
ValueError: If `val` cannot be converted to a float timestamp.
|
203
|
-
"""
|
180
|
+
"""Coerces `created_at` to a float-based timestamp."""
|
204
181
|
if val is None:
|
205
182
|
return ln.now_utc().timestamp()
|
206
183
|
if isinstance(val, float):
|
@@ -235,36 +212,17 @@ class Element(BaseModel, Observable):
|
|
235
212
|
|
236
213
|
@field_validator("id", mode="before")
|
237
214
|
def _ensure_idtype(cls, val: IDType | UUID | str) -> IDType:
|
238
|
-
"""Ensures `id` is validated as an IDType.
|
239
|
-
|
240
|
-
Args:
|
241
|
-
val (IDType | UUID | str):
|
242
|
-
The incoming value for the `id` field.
|
243
|
-
|
244
|
-
Returns:
|
245
|
-
IDType: A validated IDType object.
|
246
|
-
"""
|
215
|
+
"""Ensures `id` is validated as an IDType."""
|
247
216
|
return IDType.validate(val)
|
248
217
|
|
249
218
|
@field_serializer("id")
|
250
219
|
def _serialize_id_type(self, val: IDType) -> str:
|
251
|
-
"""Serializes the `id` field to a string.
|
252
|
-
|
253
|
-
Args:
|
254
|
-
val (IDType): The IDType object to be serialized.
|
255
|
-
|
256
|
-
Returns:
|
257
|
-
str: The string representation of the UUID.
|
258
|
-
"""
|
220
|
+
"""Serializes the `id` field to a string."""
|
259
221
|
return str(val)
|
260
222
|
|
261
223
|
@property
|
262
224
|
def created_datetime(self) -> dt.datetime:
|
263
|
-
"""Returns the creation time as a datetime object.
|
264
|
-
|
265
|
-
Returns:
|
266
|
-
datetime: The creation time in UTC.
|
267
|
-
"""
|
225
|
+
"""Returns the creation time as a datetime object."""
|
268
226
|
return dt.datetime.fromtimestamp(self.created_at, tz=dt.timezone.utc)
|
269
227
|
|
270
228
|
def __eq__(self, other: Any) -> bool:
|
@@ -294,23 +252,25 @@ class Element(BaseModel, Observable):
|
|
294
252
|
return str(cls).split("'")[1]
|
295
253
|
return cls.__name__
|
296
254
|
|
297
|
-
def _to_dict(self) -> dict:
|
298
|
-
|
255
|
+
def _to_dict(self, **kw) -> dict:
|
256
|
+
"""kw for model_dump."""
|
257
|
+
dict_ = self.model_dump(**kw)
|
299
258
|
dict_["metadata"].update({"lion_class": self.class_name(full=True)})
|
300
259
|
return {k: v for k, v in dict_.items() if ln.not_sentinel(v)}
|
301
260
|
|
302
261
|
def to_dict(
|
303
|
-
self, mode: Literal["python", "json", "db"] = "python"
|
262
|
+
self, mode: Literal["python", "json", "db"] = "python", **kw
|
304
263
|
) -> dict:
|
305
264
|
"""Converts this Element to a dictionary."""
|
306
265
|
if mode == "python":
|
307
|
-
return self._to_dict()
|
266
|
+
return self._to_dict(**kw)
|
308
267
|
if mode == "json":
|
309
|
-
return orjson.loads(self.to_json(decode=False))
|
268
|
+
return orjson.loads(self.to_json(decode=False, **kw))
|
310
269
|
if mode == "db":
|
311
|
-
dict_ = orjson.loads(self.to_json(decode=False))
|
270
|
+
dict_ = orjson.loads(self.to_json(decode=False, **kw))
|
312
271
|
dict_["node_metadata"] = dict_.pop("metadata", {})
|
313
272
|
return dict_
|
273
|
+
raise ValueError(f"Unsupported mode: {mode}")
|
314
274
|
|
315
275
|
@classmethod
|
316
276
|
def from_dict(cls, data: dict) -> Element:
|
@@ -353,9 +313,10 @@ class Element(BaseModel, Observable):
|
|
353
313
|
data["metadata"] = metadata
|
354
314
|
return cls.model_validate(data)
|
355
315
|
|
356
|
-
def to_json(self, decode: bool = True) -> str:
|
316
|
+
def to_json(self, decode: bool = True, **kw) -> str:
|
357
317
|
"""Converts this Element to a JSON string."""
|
358
|
-
|
318
|
+
kw.pop("mode", None)
|
319
|
+
dict_ = self._to_dict(**kw)
|
359
320
|
return ln.json_dumps(
|
360
321
|
dict_, default=DEFAULT_ELEMENT_SERIALIZER, decode=decode
|
361
322
|
)
|
@@ -383,9 +344,6 @@ def validate_order(order: Any) -> list[IDType]:
|
|
383
344
|
(e.g., a single Element, a list of Elements, a dictionary with ID keys,
|
384
345
|
or a nested structure) and returns a flat list of IDType objects.
|
385
346
|
|
386
|
-
Args:
|
387
|
-
order (Any): A potentially nested structure of items to be ordered.
|
388
|
-
|
389
347
|
Returns:
|
390
348
|
list[IDType]: A flat list of validated IDType objects.
|
391
349
|
|
@@ -458,9 +416,6 @@ class ID(Generic[E]):
|
|
458
416
|
- UUID: Validates and wraps it.
|
459
417
|
- str: Interpreted as a UUID if possible.
|
460
418
|
|
461
|
-
Args:
|
462
|
-
item (E): The item to convert to an ID.
|
463
|
-
|
464
419
|
Returns:
|
465
420
|
IDType: The validated ID.
|
466
421
|
|
@@ -477,9 +432,6 @@ class ID(Generic[E]):
|
|
477
432
|
def is_id(item: Any) -> bool:
|
478
433
|
"""Checks if an item can be validated as an IDType.
|
479
434
|
|
480
|
-
Args:
|
481
|
-
item (Any): The object to check.
|
482
|
-
|
483
435
|
Returns:
|
484
436
|
bool: True if `item` is or can be validated as an IDType;
|
485
437
|
otherwise, False.
|
lionagi/protocols/generic/log.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
# Copyright (c) 2023
|
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
|
@@ -11,6 +10,7 @@ from typing import Any
|
|
11
10
|
|
12
11
|
from pydantic import BaseModel, Field, PrivateAttr, field_validator
|
13
12
|
|
13
|
+
from lionagi.models.hashable_model import HashableModel
|
14
14
|
from lionagi.utils import create_path, to_dict
|
15
15
|
|
16
16
|
from .element import Element
|
@@ -24,6 +24,8 @@ __all__ = (
|
|
24
24
|
"LogManager",
|
25
25
|
)
|
26
26
|
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
27
29
|
|
28
30
|
class DataLoggerConfig(BaseModel):
|
29
31
|
persist_dir: str | Path = "./data/logs"
|
@@ -88,13 +90,13 @@ class Log(Element):
|
|
88
90
|
Create a new Log from an Element, storing a dict snapshot
|
89
91
|
of the element's data.
|
90
92
|
"""
|
91
|
-
if
|
92
|
-
content = content.to_dict()
|
93
|
+
if isinstance(content, Element | HashableModel):
|
94
|
+
content = content.to_dict(mode="json")
|
93
95
|
else:
|
94
96
|
content = to_dict(content, recursive=True, suppress=True)
|
95
97
|
|
96
98
|
if content is {}:
|
97
|
-
|
99
|
+
logger.warning(
|
98
100
|
"No content to log, or original data was of invalid type. Making an empty log..."
|
99
101
|
)
|
100
102
|
return cls(content={"error": "No content to log."})
|
@@ -141,18 +143,19 @@ class DataLogger:
|
|
141
143
|
if self._config.auto_save_on_exit:
|
142
144
|
atexit.register(self.save_at_exit)
|
143
145
|
|
144
|
-
def log(self, log_:
|
146
|
+
def log(self, log_: Any) -> None:
|
145
147
|
"""
|
146
148
|
Add a log synchronously. If capacity is reached, auto-dump to file.
|
147
149
|
"""
|
150
|
+
log_ = Log.create(log_) if not isinstance(log_, Log) else log_
|
148
151
|
if self._config.capacity and len(self.logs) >= self._config.capacity:
|
149
152
|
try:
|
150
153
|
self.dump(clear=self._config.clear_after_dump)
|
151
154
|
except Exception as e:
|
152
|
-
|
155
|
+
logger.error(f"Failed to auto-dump logs: {e}")
|
153
156
|
self.logs.include(log_)
|
154
157
|
|
155
|
-
async def alog(self, log_:
|
158
|
+
async def alog(self, log_: Any) -> None:
|
156
159
|
"""
|
157
160
|
Add a log asynchronously. If capacity is reached, auto-dump to file.
|
158
161
|
"""
|
@@ -169,7 +172,7 @@ class DataLogger:
|
|
169
172
|
unsupported, raise ValueError. Optionally clear logs after.
|
170
173
|
"""
|
171
174
|
if not self.logs:
|
172
|
-
|
175
|
+
logger.debug("No logs to dump.")
|
173
176
|
return
|
174
177
|
|
175
178
|
fp = persist_path or self._create_path()
|
@@ -182,7 +185,7 @@ class DataLogger:
|
|
182
185
|
else:
|
183
186
|
raise ValueError(f"Unsupported file extension: {suffix}")
|
184
187
|
|
185
|
-
|
188
|
+
logger.info(f"Dumped logs to {fp}")
|
186
189
|
do_clear = (
|
187
190
|
self._config.clear_after_dump if clear is None else clear
|
188
191
|
)
|
@@ -191,12 +194,12 @@ class DataLogger:
|
|
191
194
|
except Exception as e:
|
192
195
|
# Check if it's a JSON serialization error with complex objects
|
193
196
|
if "JSON serializable" in str(e):
|
194
|
-
|
197
|
+
logger.debug(f"Could not serialize logs to JSON: {e}")
|
195
198
|
# Don't raise for JSON serialization issues during dumps
|
196
199
|
if clear is not False:
|
197
200
|
self.logs.clear() # Still clear if requested
|
198
201
|
else:
|
199
|
-
|
202
|
+
logger.error(f"Failed to dump logs: {e}")
|
200
203
|
raise
|
201
204
|
|
202
205
|
async def adump(
|
@@ -233,9 +236,9 @@ class DataLogger:
|
|
233
236
|
# Only log debug level for JSON serialization errors during exit
|
234
237
|
# These are non-critical and often occur with complex objects
|
235
238
|
if "JSON serializable" in str(e):
|
236
|
-
|
239
|
+
logger.debug(f"Could not serialize logs to JSON: {e}")
|
237
240
|
else:
|
238
|
-
|
241
|
+
logger.error(f"Failed to save logs on exit: {e}")
|
239
242
|
|
240
243
|
@classmethod
|
241
244
|
def from_config(
|
@@ -1,5 +1,4 @@
|
|
1
|
-
# Copyright (c) 2023
|
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
|
@@ -222,13 +221,13 @@ class Pile(Element, Collective[T], Generic[T], Adaptable, AsyncAdaptable):
|
|
222
221
|
"strict_type",
|
223
222
|
}
|
224
223
|
|
225
|
-
def __pydantic_extra__(self) -> dict[str,
|
224
|
+
def __pydantic_extra__(self) -> dict[str, FieldInfo]:
|
226
225
|
return {
|
227
226
|
"_lock": Field(default_factory=threading.Lock),
|
228
227
|
"_async": Field(default_factory=ConcurrencyLock),
|
229
228
|
}
|
230
229
|
|
231
|
-
def __pydantic_private__(self) -> dict[str,
|
230
|
+
def __pydantic_private__(self) -> dict[str, FieldInfo]:
|
232
231
|
return self.__pydantic_extra__()
|
233
232
|
|
234
233
|
@classmethod
|