lionagi 0.0.315__py3-none-any.whl → 0.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- lionagi/core/__init__.py +19 -8
- lionagi/core/agent/__init__.py +0 -3
- lionagi/core/agent/base_agent.py +26 -30
- lionagi/core/branch/__init__.py +0 -4
- lionagi/core/branch/{base_branch.py → base.py} +13 -14
- lionagi/core/branch/branch.py +22 -20
- lionagi/core/branch/executable_branch.py +0 -347
- lionagi/core/branch/{branch_flow_mixin.py → flow_mixin.py} +6 -6
- lionagi/core/branch/util.py +1 -1
- lionagi/core/direct/__init__.py +13 -1
- lionagi/core/direct/cot.py +123 -1
- lionagi/core/direct/plan.py +164 -0
- lionagi/core/direct/predict.py +13 -9
- lionagi/core/direct/react.py +12 -8
- lionagi/core/direct/score.py +4 -4
- lionagi/core/direct/select.py +4 -4
- lionagi/core/direct/utils.py +23 -0
- lionagi/core/direct/vote.py +2 -2
- lionagi/core/execute/base_executor.py +50 -0
- lionagi/core/execute/branch_executor.py +233 -0
- lionagi/core/execute/instruction_map_executor.py +131 -0
- lionagi/core/execute/structure_executor.py +218 -0
- lionagi/core/flow/monoflow/ReAct.py +4 -4
- lionagi/core/flow/monoflow/chat.py +6 -6
- lionagi/core/flow/monoflow/chat_mixin.py +24 -34
- lionagi/core/flow/monoflow/followup.py +4 -4
- lionagi/core/flow/polyflow/__init__.py +1 -1
- lionagi/core/flow/polyflow/chat.py +15 -12
- lionagi/core/{prompt/action_template.py → form/action_form.py} +2 -2
- lionagi/core/{prompt → form}/field_validator.py +40 -31
- lionagi/core/form/form.py +302 -0
- lionagi/core/form/mixin.py +214 -0
- lionagi/core/{prompt/scored_template.py → form/scored_form.py} +2 -2
- lionagi/core/generic/__init__.py +37 -0
- lionagi/core/generic/action.py +26 -0
- lionagi/core/generic/component.py +457 -0
- lionagi/core/generic/condition.py +44 -0
- lionagi/core/generic/data_logger.py +305 -0
- lionagi/core/generic/edge.py +110 -0
- lionagi/core/generic/mail.py +90 -0
- lionagi/core/generic/mailbox.py +36 -0
- lionagi/core/generic/node.py +285 -0
- lionagi/core/generic/relation.py +70 -0
- lionagi/core/generic/signal.py +22 -0
- lionagi/core/generic/structure.py +362 -0
- lionagi/core/generic/transfer.py +20 -0
- lionagi/core/generic/work.py +40 -0
- lionagi/core/graph/graph.py +126 -0
- lionagi/core/graph/tree.py +190 -0
- lionagi/core/mail/__init__.py +0 -8
- lionagi/core/mail/mail_manager.py +12 -10
- lionagi/core/mail/schema.py +9 -2
- lionagi/core/messages/__init__.py +0 -3
- lionagi/core/messages/schema.py +17 -225
- lionagi/core/session/__init__.py +0 -3
- lionagi/core/session/session.py +25 -23
- lionagi/core/tool/__init__.py +3 -1
- lionagi/core/tool/tool.py +28 -0
- lionagi/core/tool/tool_manager.py +75 -75
- lionagi/integrations/chunker/chunk.py +7 -7
- lionagi/integrations/config/oai_configs.py +4 -4
- lionagi/integrations/loader/load.py +6 -6
- lionagi/integrations/loader/load_util.py +8 -8
- lionagi/libs/ln_api.py +3 -3
- lionagi/libs/ln_parse.py +43 -6
- lionagi/libs/ln_validate.py +288 -0
- lionagi/libs/sys_util.py +28 -6
- lionagi/tests/libs/test_async.py +0 -0
- lionagi/tests/libs/test_field_validators.py +353 -0
- lionagi/tests/test_core/test_base_branch.py +0 -1
- lionagi/tests/test_core/test_branch.py +3 -0
- lionagi/tests/test_core/test_session_base_util.py +1 -0
- lionagi/version.py +1 -1
- {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/METADATA +1 -1
- lionagi-0.1.0.dist-info/RECORD +136 -0
- lionagi/core/prompt/prompt_template.py +0 -312
- lionagi/core/schema/__init__.py +0 -22
- lionagi/core/schema/action_node.py +0 -29
- lionagi/core/schema/base_mixin.py +0 -296
- lionagi/core/schema/base_node.py +0 -199
- lionagi/core/schema/condition.py +0 -24
- lionagi/core/schema/data_logger.py +0 -354
- lionagi/core/schema/data_node.py +0 -93
- lionagi/core/schema/prompt_template.py +0 -67
- lionagi/core/schema/structure.py +0 -912
- lionagi/core/tool/manual.py +0 -1
- lionagi-0.0.315.dist-info/RECORD +0 -121
- /lionagi/core/{branch/base → execute}/__init__.py +0 -0
- /lionagi/core/flow/{base/baseflow.py → baseflow.py} +0 -0
- /lionagi/core/flow/{base/__init__.py → mono_chat_mixin.py} +0 -0
- /lionagi/core/{prompt → form}/__init__.py +0 -0
- /lionagi/{tests/test_integrations → core/graph}/__init__.py +0 -0
- /lionagi/tests/{test_libs → integrations}/__init__.py +0 -0
- /lionagi/tests/{test_libs/test_async.py → libs/__init__.py} +0 -0
- /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_func_call.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_nested.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_parse.py +0 -0
- /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
- {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/LICENSE +0 -0
- {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/WHEEL +0 -0
- {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,285 @@
|
|
1
|
+
from typing import Any, Type
|
2
|
+
from pydantic import Field
|
3
|
+
from lionagi.integrations.bridge import LlamaIndexBridge, LangchainBridge
|
4
|
+
|
5
|
+
from lionagi.core.generic.component import BaseNode
|
6
|
+
from lionagi.core.generic.condition import Condition
|
7
|
+
from lionagi.core.generic.edge import Edge
|
8
|
+
from lionagi.core.generic.relation import Relations
|
9
|
+
from lionagi.core.generic.mailbox import MailBox
|
10
|
+
|
11
|
+
|
12
|
+
class Node(BaseNode):
|
13
|
+
"""
|
14
|
+
Represents a node with relations to other nodes.
|
15
|
+
|
16
|
+
Attributes:
|
17
|
+
relations (Relations): The relations of the node, managed through a
|
18
|
+
`Relations` instance.
|
19
|
+
|
20
|
+
Properties:
|
21
|
+
related_nodes: A set of IDs representing nodes related to this node.
|
22
|
+
edges: A dictionary of all edges connected to this node.
|
23
|
+
node_relations: A dictionary categorizing preceding and succeeding
|
24
|
+
relations to this node.
|
25
|
+
precedessors: A list of node IDs that precede this node.
|
26
|
+
successors: A list of node IDs that succeed this node.
|
27
|
+
|
28
|
+
Methods:
|
29
|
+
relate(node, self_as, condition, **kwargs): Relates this node to
|
30
|
+
another node with an edge.
|
31
|
+
unrelate(node, edge): Removes one or all relations between this node
|
32
|
+
and another.
|
33
|
+
to_llama_index(node_type, **kwargs): Serializes this node for
|
34
|
+
LlamaIndex.
|
35
|
+
to_langchain(**kwargs): Serializes this node for Langchain.
|
36
|
+
from_llama_index(llama_node, **kwargs): Deserializes a node from
|
37
|
+
LlamaIndex data.
|
38
|
+
from_langchain(lc_doc): Deserializes a node from Langchain data.
|
39
|
+
__str__(): String representation of the node.
|
40
|
+
|
41
|
+
Raises:
|
42
|
+
ValueError: When invalid parameters are provided to methods.
|
43
|
+
"""
|
44
|
+
|
45
|
+
relations: Relations = Field(
|
46
|
+
default_factory=Relations,
|
47
|
+
description="The relations of the node.",
|
48
|
+
alias="node_relations",
|
49
|
+
)
|
50
|
+
|
51
|
+
mailbox: MailBox = Field(
|
52
|
+
default_factory=MailBox,
|
53
|
+
description="The mailbox for incoming and outgoing mails.",
|
54
|
+
)
|
55
|
+
|
56
|
+
@property
|
57
|
+
def related_nodes(self) -> list[str]:
|
58
|
+
"""Returns a set of node IDs related to this node, excluding itself."""
|
59
|
+
nodes = set(self.relations.all_nodes)
|
60
|
+
nodes.discard(self.id_)
|
61
|
+
return list(nodes)
|
62
|
+
|
63
|
+
@property
|
64
|
+
def edges(self) -> dict[str, Edge]:
|
65
|
+
"""Returns a dictionary of all edges connected to this node."""
|
66
|
+
return self.relations.all_edges
|
67
|
+
|
68
|
+
@property
|
69
|
+
def node_relations(self) -> dict:
|
70
|
+
"""Categorizes preceding and succeeding relations to this node."""
|
71
|
+
|
72
|
+
points_to_nodes = {}
|
73
|
+
for edge in self.relations.points_to.values():
|
74
|
+
for i in self.related_nodes:
|
75
|
+
if edge.tail == i:
|
76
|
+
if i in points_to_nodes:
|
77
|
+
points_to_nodes[i].append(edge)
|
78
|
+
else:
|
79
|
+
points_to_nodes[i] = [edge]
|
80
|
+
|
81
|
+
pointed_by_nodes = {}
|
82
|
+
for edge in self.relations.pointed_by.values():
|
83
|
+
for i in self.related_nodes:
|
84
|
+
if edge.head == i:
|
85
|
+
if i in pointed_by_nodes:
|
86
|
+
pointed_by_nodes[i].append(edge)
|
87
|
+
else:
|
88
|
+
pointed_by_nodes[i] = [edge]
|
89
|
+
|
90
|
+
return {"points_to": points_to_nodes, "pointed_by": pointed_by_nodes}
|
91
|
+
|
92
|
+
@property
|
93
|
+
def precedessors(self) -> list[str]:
|
94
|
+
"""return a list of nodes id that precede this node"""
|
95
|
+
return [k for k, v in self.node_relations["pointed_by"].items() if len(v) > 0]
|
96
|
+
|
97
|
+
@property
|
98
|
+
def successors(self) -> list[str]:
|
99
|
+
"""return a list of nodes id that succeed this node"""
|
100
|
+
return [k for k, v in self.node_relations["points_to"].items() if len(v) > 0]
|
101
|
+
|
102
|
+
def relate(
|
103
|
+
self,
|
104
|
+
node: "Node",
|
105
|
+
node_as: str = "head",
|
106
|
+
condition: Condition | None = None,
|
107
|
+
label: str | None = None,
|
108
|
+
bundle=False,
|
109
|
+
) -> None:
|
110
|
+
"""Relates this node to another node with an edge.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
node (Node): The node to relate to.
|
114
|
+
self_as (str): Specifies whether this node is the 'head' or 'tail'
|
115
|
+
of the relation. Defaults to "head".
|
116
|
+
condition (Condition | None): The condition associated with the
|
117
|
+
edge, if any. Defaults to None.
|
118
|
+
**kwargs: Additional keyword arguments for edge creation.
|
119
|
+
|
120
|
+
Raises:
|
121
|
+
ValueError: If `self_as` is not 'head' or 'tail'.
|
122
|
+
"""
|
123
|
+
if node_as == "head":
|
124
|
+
edge = Edge(
|
125
|
+
head=self, tail=node, condition=condition, bundle=bundle, label=label
|
126
|
+
)
|
127
|
+
self.relations.points_to[edge.id_] = edge
|
128
|
+
node.relations.pointed_by[edge.id_] = edge
|
129
|
+
|
130
|
+
elif node_as == "tail":
|
131
|
+
edge = Edge(
|
132
|
+
head=node, tail=self, condition=condition, label=label, bundle=bundle
|
133
|
+
)
|
134
|
+
self.relations.pointed_by[edge.id_] = edge
|
135
|
+
node.relations.points_to[edge.id_] = edge
|
136
|
+
|
137
|
+
else:
|
138
|
+
raise ValueError(
|
139
|
+
f"Invalid value for self_as: {node_as}, must be 'head' or 'tail'"
|
140
|
+
)
|
141
|
+
|
142
|
+
def remove_edge(self, node: "Node", edge: Edge | str) -> bool:
|
143
|
+
if node.id_ not in self.related_nodes:
|
144
|
+
raise ValueError(f"Node {self.id_} is not related to node {node.id_}.")
|
145
|
+
|
146
|
+
edge_id = edge.id_ if isinstance(edge, Edge) else edge
|
147
|
+
|
148
|
+
if (
|
149
|
+
edge_id not in self.relations.all_edges
|
150
|
+
or edge_id not in node.relations.all_edges
|
151
|
+
):
|
152
|
+
raise ValueError(
|
153
|
+
f"Edge {edge_id} does not exist between nodes {self.id_} and "
|
154
|
+
f"{node.id_}."
|
155
|
+
)
|
156
|
+
|
157
|
+
all_dicts = [
|
158
|
+
self.relations.points_to,
|
159
|
+
self.relations.pointed_by,
|
160
|
+
node.relations.points_to,
|
161
|
+
node.relations.pointed_by,
|
162
|
+
]
|
163
|
+
try:
|
164
|
+
for _dict in all_dicts:
|
165
|
+
edge_id = edge.id_ if isinstance(edge, Edge) else edge
|
166
|
+
_dict.pop(edge_id, None)
|
167
|
+
return True
|
168
|
+
|
169
|
+
except Exception as e:
|
170
|
+
raise ValueError(
|
171
|
+
f"Failed to remove edge between nodes {self.id_} and " f"{node.id_}."
|
172
|
+
) from e
|
173
|
+
|
174
|
+
def unrelate(self, node: "Node", edge: Edge | str = "all") -> bool:
|
175
|
+
"""
|
176
|
+
Removes one or all relations between this node and another.
|
177
|
+
|
178
|
+
Args:
|
179
|
+
node (Node): The node to unrelate from.
|
180
|
+
edge (Edge | str): Specific edge or 'all' to remove all relations.
|
181
|
+
Defaults to "all".
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
bool: True if the operation is successful, False otherwise.
|
185
|
+
|
186
|
+
Raises:
|
187
|
+
ValueError: If the node is not related or the edge does not exist.
|
188
|
+
"""
|
189
|
+
if edge == "all":
|
190
|
+
edge = self.node_relations["points_to"].get(
|
191
|
+
node.id_, []
|
192
|
+
) + self.node_relations["pointed_by"].get(node.id_, [])
|
193
|
+
else:
|
194
|
+
edge = [edge.id_] if isinstance(edge, Edge) else [edge]
|
195
|
+
|
196
|
+
if len(edge) == 0:
|
197
|
+
raise ValueError(f"Node {self.id_} is not related to node {node.id_}.")
|
198
|
+
|
199
|
+
try:
|
200
|
+
for edge_id in edge:
|
201
|
+
self.remove_edge(node, edge_id)
|
202
|
+
return True
|
203
|
+
except Exception as e:
|
204
|
+
raise ValueError(
|
205
|
+
f"Failed to remove edge between nodes {self.id_} and " f"{node.id_}."
|
206
|
+
) from e
|
207
|
+
|
208
|
+
def to_llama_index(self, node_type: Type | str | Any = None, **kwargs) -> Any:
|
209
|
+
"""
|
210
|
+
Serializes this node for LlamaIndex.
|
211
|
+
|
212
|
+
Args:
|
213
|
+
node_type (Type | str | Any): The type of node in LlamaIndex.
|
214
|
+
Defaults to None.
|
215
|
+
**kwargs: Additional keyword arguments for serialization.
|
216
|
+
|
217
|
+
Returns:
|
218
|
+
Any: The serialized node for LlamaIndex.
|
219
|
+
"""
|
220
|
+
return LlamaIndexBridge.to_llama_index_node(self, node_type=node_type, **kwargs)
|
221
|
+
|
222
|
+
def to_langchain(self, **kwargs) -> Any:
|
223
|
+
"""
|
224
|
+
Serializes this node for Langchain.
|
225
|
+
|
226
|
+
Args:
|
227
|
+
**kwargs: Additional keyword arguments for serialization.
|
228
|
+
|
229
|
+
Returns:
|
230
|
+
Any: The serialized node for Langchain.
|
231
|
+
"""
|
232
|
+
return LangchainBridge.to_langchain_document(self, **kwargs)
|
233
|
+
|
234
|
+
@classmethod
|
235
|
+
def from_llama_index(cls, llama_node: Any, **kwargs) -> "Node":
|
236
|
+
"""
|
237
|
+
Deserializes a node from LlamaIndex data.
|
238
|
+
|
239
|
+
Args:
|
240
|
+
llama_node (Any): The LlamaIndex node data.
|
241
|
+
**kwargs: Additional keyword arguments for deserialization.
|
242
|
+
|
243
|
+
Returns:
|
244
|
+
Node: The deserialized node.
|
245
|
+
"""
|
246
|
+
llama_dict = llama_node.to_dict(**kwargs)
|
247
|
+
return cls.from_obj(llama_dict)
|
248
|
+
|
249
|
+
@classmethod
|
250
|
+
def from_langchain(cls, lc_doc: Any) -> "Node":
|
251
|
+
"""Deserializes a node from Langchain data.
|
252
|
+
|
253
|
+
Args:
|
254
|
+
lc_doc (Any): The Langchain document data.
|
255
|
+
|
256
|
+
Returns:
|
257
|
+
Node: The deserialized node.
|
258
|
+
"""
|
259
|
+
langchain_json = lc_doc.to_json()
|
260
|
+
langchain_dict = {"lc_id": langchain_json["id"], **langchain_json["kwargs"]}
|
261
|
+
return cls.from_obj(langchain_dict)
|
262
|
+
|
263
|
+
def __str__(self) -> str:
|
264
|
+
"""
|
265
|
+
Provides a string representation of the node.
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
str: The string representation of the node.
|
269
|
+
"""
|
270
|
+
timestamp = f" ({self.timestamp})" if self.timestamp else ""
|
271
|
+
if self.content:
|
272
|
+
content_preview = (
|
273
|
+
f"{self.content[:50]}..." if len(self.content) > 50 else self.content
|
274
|
+
)
|
275
|
+
else:
|
276
|
+
content_preview = ""
|
277
|
+
meta_preview = (
|
278
|
+
f"{str(self.metadata)[:50]}..."
|
279
|
+
if len(str(self.metadata)) > 50
|
280
|
+
else str(self.metadata)
|
281
|
+
)
|
282
|
+
return (
|
283
|
+
f"{self.class_name()}({self.id_}, {content_preview}, {meta_preview},"
|
284
|
+
f"{timestamp})"
|
285
|
+
)
|
@@ -0,0 +1,70 @@
|
|
1
|
+
"""
|
2
|
+
A module for representing relationships between nodes in a graph structure,
|
3
|
+
encapsulating incoming and outgoing edges.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from pydantic import Field
|
7
|
+
from pydantic.dataclasses import dataclass
|
8
|
+
from lionagi.libs import convert
|
9
|
+
from lionagi.core.generic.edge import Edge
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class Relations:
|
14
|
+
"""
|
15
|
+
Represents the relationships of a node via its incoming and outgoing edges.
|
16
|
+
|
17
|
+
This class stores edges in two dictionaries: `preceding` for outgoing edges
|
18
|
+
and `succeeding` for incoming edges. It provides properties to access all
|
19
|
+
edges together and to get a unique set of all connected node IDs.
|
20
|
+
|
21
|
+
Attributes:
|
22
|
+
preceding (dict[str, Edge]): A dictionary of outgoing edges from the
|
23
|
+
node, with the edge ID as the key and the `Edge` object as the
|
24
|
+
value. Represents edges leading from this node to other nodes.
|
25
|
+
succeeding (dict[str, Edge]): A dictionary of incoming edges to the
|
26
|
+
node, with the edge ID as the key and the `Edge` object as the
|
27
|
+
value. Represents edges from other nodes leading to this node.
|
28
|
+
"""
|
29
|
+
|
30
|
+
points_to: dict[str, Edge] = Field(
|
31
|
+
title="Outgoing edges",
|
32
|
+
default_factory=dict,
|
33
|
+
description="The Outgoing edges of the node, reads self precedes other, \
|
34
|
+
{edge_id: Edge}",
|
35
|
+
)
|
36
|
+
|
37
|
+
pointed_by: dict[str, Edge] = Field(
|
38
|
+
title="Incoming edges",
|
39
|
+
default_factory=dict,
|
40
|
+
description="The Incoming edges of the node, reads self succeeds other, \
|
41
|
+
{edge_id: Edge}",
|
42
|
+
)
|
43
|
+
|
44
|
+
@property
|
45
|
+
def all_edges(self) -> dict[str, Edge]:
|
46
|
+
"""
|
47
|
+
Combines and returns all incoming and outgoing edges of the node.
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
dict[str, Edge]: A dictionary of all edges connected to the node,
|
51
|
+
including both preceding (outgoing) and succeeding (incoming)
|
52
|
+
edges, indexed by edge IDs.
|
53
|
+
"""
|
54
|
+
return {**self.points_to, **self.pointed_by}
|
55
|
+
|
56
|
+
@property
|
57
|
+
def all_nodes(self) -> set[str]:
|
58
|
+
"""
|
59
|
+
Extracts and returns a unique set of all node IDs connected to this
|
60
|
+
node through its edges.
|
61
|
+
|
62
|
+
It processes both heads and tails of each edge in `all_edges`, flattens
|
63
|
+
the list to a one-dimensional list, and then converts it to a set to
|
64
|
+
ensure uniqueness.
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
set[str]: A set of unique node IDs connected to this node, derived
|
68
|
+
from both incoming and outgoing edges.
|
69
|
+
"""
|
70
|
+
return set(convert.to_list([[i.head, i.tail] for i in self.all_edges.values()]))
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from abc import ABC
|
2
|
+
from collections import deque
|
3
|
+
from lionagi.core.generic import BaseNode
|
4
|
+
from lionagi.core.generic.mail import Mail
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
|
8
|
+
class Signal(BaseNode, ABC): ...
|
9
|
+
|
10
|
+
|
11
|
+
class Start(Signal):
|
12
|
+
pending_outs: deque = deque()
|
13
|
+
|
14
|
+
def trigger(self, context: Any, structure_id: str, executable_id: str):
|
15
|
+
start_mail_content = {"context": context, "structure_id": structure_id}
|
16
|
+
start_mail = Mail(
|
17
|
+
sender=self.id_,
|
18
|
+
recipient=executable_id,
|
19
|
+
category="start",
|
20
|
+
package=start_mail_content,
|
21
|
+
)
|
22
|
+
self.pending_outs.append(start_mail)
|