lionagi 0.0.312__py3-none-any.whl → 0.2.1__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 +61 -3
- lionagi/core/__init__.py +0 -14
- lionagi/core/_setting/_setting.py +59 -0
- lionagi/core/action/__init__.py +14 -0
- lionagi/core/action/function_calling.py +136 -0
- lionagi/core/action/manual.py +1 -0
- lionagi/core/action/node.py +109 -0
- lionagi/core/action/tool.py +114 -0
- lionagi/core/action/tool_manager.py +356 -0
- lionagi/core/agent/__init__.py +0 -3
- lionagi/core/agent/base_agent.py +45 -36
- lionagi/core/agent/eval/evaluator.py +1 -0
- lionagi/core/agent/eval/vote.py +40 -0
- lionagi/core/agent/learn/learner.py +59 -0
- lionagi/core/agent/plan/unit_template.py +1 -0
- lionagi/core/collections/__init__.py +17 -0
- lionagi/core/collections/_logger.py +319 -0
- lionagi/core/collections/abc/__init__.py +53 -0
- lionagi/core/collections/abc/component.py +615 -0
- lionagi/core/collections/abc/concepts.py +297 -0
- lionagi/core/collections/abc/exceptions.py +150 -0
- lionagi/core/collections/abc/util.py +45 -0
- lionagi/core/collections/exchange.py +161 -0
- lionagi/core/collections/flow.py +426 -0
- lionagi/core/collections/model.py +419 -0
- lionagi/core/collections/pile.py +913 -0
- lionagi/core/collections/progression.py +236 -0
- lionagi/core/collections/util.py +64 -0
- lionagi/core/director/direct.py +314 -0
- lionagi/core/director/director.py +2 -0
- lionagi/core/engine/branch_engine.py +333 -0
- lionagi/core/engine/instruction_map_engine.py +204 -0
- lionagi/core/engine/sandbox_.py +14 -0
- lionagi/core/engine/script_engine.py +99 -0
- lionagi/core/executor/base_executor.py +90 -0
- lionagi/core/executor/graph_executor.py +330 -0
- lionagi/core/executor/neo4j_executor.py +384 -0
- lionagi/core/generic/__init__.py +7 -0
- lionagi/core/generic/edge.py +112 -0
- lionagi/core/generic/edge_condition.py +16 -0
- lionagi/core/generic/graph.py +236 -0
- lionagi/core/generic/hyperedge.py +1 -0
- lionagi/core/generic/node.py +220 -0
- lionagi/core/generic/tree.py +48 -0
- lionagi/core/generic/tree_node.py +79 -0
- lionagi/core/mail/__init__.py +7 -3
- lionagi/core/mail/mail.py +25 -0
- lionagi/core/mail/mail_manager.py +142 -58
- lionagi/core/mail/package.py +45 -0
- lionagi/core/mail/start_mail.py +36 -0
- lionagi/core/message/__init__.py +19 -0
- lionagi/core/message/action_request.py +133 -0
- lionagi/core/message/action_response.py +135 -0
- lionagi/core/message/assistant_response.py +95 -0
- lionagi/core/message/instruction.py +234 -0
- lionagi/core/message/message.py +101 -0
- lionagi/core/message/system.py +86 -0
- lionagi/core/message/util.py +283 -0
- lionagi/core/report/__init__.py +4 -0
- lionagi/core/report/base.py +217 -0
- lionagi/core/report/form.py +231 -0
- lionagi/core/report/report.py +166 -0
- lionagi/core/report/util.py +28 -0
- lionagi/core/rule/__init__.py +0 -0
- lionagi/core/rule/_default.py +16 -0
- lionagi/core/rule/action.py +99 -0
- lionagi/core/rule/base.py +238 -0
- lionagi/core/rule/boolean.py +56 -0
- lionagi/core/rule/choice.py +47 -0
- lionagi/core/rule/mapping.py +96 -0
- lionagi/core/rule/number.py +71 -0
- lionagi/core/rule/rulebook.py +109 -0
- lionagi/core/rule/string.py +52 -0
- lionagi/core/rule/util.py +35 -0
- lionagi/core/session/__init__.py +0 -3
- lionagi/core/session/branch.py +431 -0
- lionagi/core/session/directive_mixin.py +287 -0
- lionagi/core/session/session.py +230 -902
- lionagi/core/structure/__init__.py +1 -0
- lionagi/core/structure/chain.py +1 -0
- lionagi/core/structure/forest.py +1 -0
- lionagi/core/structure/graph.py +1 -0
- lionagi/core/structure/tree.py +1 -0
- lionagi/core/unit/__init__.py +5 -0
- lionagi/core/unit/parallel_unit.py +245 -0
- lionagi/core/unit/template/__init__.py +0 -0
- lionagi/core/unit/template/action.py +81 -0
- lionagi/core/unit/template/base.py +51 -0
- lionagi/core/unit/template/plan.py +84 -0
- lionagi/core/unit/template/predict.py +109 -0
- lionagi/core/unit/template/score.py +124 -0
- lionagi/core/unit/template/select.py +104 -0
- lionagi/core/unit/unit.py +362 -0
- lionagi/core/unit/unit_form.py +305 -0
- lionagi/core/unit/unit_mixin.py +1168 -0
- lionagi/core/unit/util.py +71 -0
- lionagi/core/validator/__init__.py +0 -0
- lionagi/core/validator/validator.py +364 -0
- lionagi/core/work/__init__.py +0 -0
- lionagi/core/work/work.py +76 -0
- lionagi/core/work/work_function.py +101 -0
- lionagi/core/work/work_queue.py +103 -0
- lionagi/core/work/worker.py +258 -0
- lionagi/core/work/worklog.py +120 -0
- lionagi/experimental/__init__.py +0 -0
- lionagi/experimental/compressor/__init__.py +0 -0
- lionagi/experimental/compressor/base.py +46 -0
- lionagi/experimental/compressor/llm_compressor.py +247 -0
- lionagi/experimental/compressor/llm_summarizer.py +61 -0
- lionagi/experimental/compressor/util.py +70 -0
- lionagi/experimental/directive/__init__.py +19 -0
- lionagi/experimental/directive/parser/__init__.py +0 -0
- lionagi/experimental/directive/parser/base_parser.py +282 -0
- lionagi/experimental/directive/template/__init__.py +0 -0
- lionagi/experimental/directive/template/base_template.py +79 -0
- lionagi/experimental/directive/template/schema.py +36 -0
- lionagi/experimental/directive/tokenizer.py +73 -0
- lionagi/experimental/evaluator/__init__.py +0 -0
- lionagi/experimental/evaluator/ast_evaluator.py +131 -0
- lionagi/experimental/evaluator/base_evaluator.py +218 -0
- lionagi/experimental/knowledge/__init__.py +0 -0
- lionagi/experimental/knowledge/base.py +10 -0
- lionagi/experimental/knowledge/graph.py +0 -0
- lionagi/experimental/memory/__init__.py +0 -0
- lionagi/experimental/strategies/__init__.py +0 -0
- lionagi/experimental/strategies/base.py +1 -0
- lionagi/integrations/bridge/autogen_/__init__.py +0 -0
- lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
- lionagi/integrations/bridge/langchain_/documents.py +4 -0
- lionagi/integrations/bridge/llamaindex_/index.py +30 -0
- lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
- lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
- lionagi/integrations/bridge/llamaindex_/node_parser.py +6 -9
- lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +1 -0
- lionagi/integrations/bridge/transformers_/__init__.py +0 -0
- lionagi/integrations/bridge/transformers_/install_.py +36 -0
- lionagi/integrations/chunker/__init__.py +0 -0
- lionagi/integrations/chunker/chunk.py +312 -0
- lionagi/integrations/config/oai_configs.py +38 -7
- lionagi/integrations/config/ollama_configs.py +1 -1
- lionagi/integrations/config/openrouter_configs.py +14 -2
- lionagi/integrations/loader/__init__.py +0 -0
- lionagi/integrations/loader/load.py +253 -0
- lionagi/integrations/loader/load_util.py +195 -0
- lionagi/integrations/provider/_mapping.py +46 -0
- lionagi/integrations/provider/litellm.py +2 -1
- lionagi/integrations/provider/mlx_service.py +16 -9
- lionagi/integrations/provider/oai.py +91 -4
- lionagi/integrations/provider/ollama.py +7 -6
- lionagi/integrations/provider/openrouter.py +115 -8
- lionagi/integrations/provider/services.py +2 -2
- lionagi/integrations/provider/transformers.py +18 -22
- lionagi/integrations/storage/__init__.py +3 -0
- lionagi/integrations/storage/neo4j.py +665 -0
- lionagi/integrations/storage/storage_util.py +287 -0
- lionagi/integrations/storage/structure_excel.py +285 -0
- lionagi/integrations/storage/to_csv.py +63 -0
- lionagi/integrations/storage/to_excel.py +83 -0
- lionagi/libs/__init__.py +26 -1
- lionagi/libs/ln_api.py +78 -23
- lionagi/libs/ln_context.py +37 -0
- lionagi/libs/ln_convert.py +21 -9
- lionagi/libs/ln_func_call.py +69 -28
- lionagi/libs/ln_image.py +107 -0
- lionagi/libs/ln_knowledge_graph.py +405 -0
- lionagi/libs/ln_nested.py +26 -11
- lionagi/libs/ln_parse.py +110 -14
- lionagi/libs/ln_queue.py +117 -0
- lionagi/libs/ln_tokenize.py +164 -0
- lionagi/{core/prompt/field_validator.py → libs/ln_validate.py} +79 -14
- lionagi/libs/special_tokens.py +172 -0
- lionagi/libs/sys_util.py +107 -2
- lionagi/lions/__init__.py +0 -0
- lionagi/lions/coder/__init__.py +0 -0
- lionagi/lions/coder/add_feature.py +20 -0
- lionagi/lions/coder/base_prompts.py +22 -0
- lionagi/lions/coder/code_form.py +13 -0
- lionagi/lions/coder/coder.py +168 -0
- lionagi/lions/coder/util.py +96 -0
- lionagi/lions/researcher/__init__.py +0 -0
- lionagi/lions/researcher/data_source/__init__.py +0 -0
- lionagi/lions/researcher/data_source/finhub_.py +191 -0
- lionagi/lions/researcher/data_source/google_.py +199 -0
- lionagi/lions/researcher/data_source/wiki_.py +96 -0
- lionagi/lions/researcher/data_source/yfinance_.py +21 -0
- lionagi/tests/integrations/__init__.py +0 -0
- lionagi/tests/libs/__init__.py +0 -0
- lionagi/tests/libs/test_field_validators.py +353 -0
- lionagi/tests/{test_libs → libs}/test_func_call.py +23 -21
- lionagi/tests/{test_libs → libs}/test_nested.py +36 -21
- lionagi/tests/{test_libs → libs}/test_parse.py +1 -1
- lionagi/tests/libs/test_queue.py +67 -0
- lionagi/tests/test_core/collections/__init__.py +0 -0
- lionagi/tests/test_core/collections/test_component.py +206 -0
- lionagi/tests/test_core/collections/test_exchange.py +138 -0
- lionagi/tests/test_core/collections/test_flow.py +145 -0
- lionagi/tests/test_core/collections/test_pile.py +171 -0
- lionagi/tests/test_core/collections/test_progression.py +129 -0
- lionagi/tests/test_core/generic/__init__.py +0 -0
- lionagi/tests/test_core/generic/test_edge.py +67 -0
- lionagi/tests/test_core/generic/test_graph.py +96 -0
- lionagi/tests/test_core/generic/test_node.py +106 -0
- lionagi/tests/test_core/generic/test_tree_node.py +73 -0
- lionagi/tests/test_core/test_branch.py +115 -292
- lionagi/tests/test_core/test_form.py +46 -0
- lionagi/tests/test_core/test_report.py +105 -0
- lionagi/tests/test_core/test_validator.py +111 -0
- lionagi/version.py +1 -1
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/LICENSE +12 -11
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/METADATA +19 -118
- lionagi-0.2.1.dist-info/RECORD +240 -0
- lionagi/core/branch/__init__.py +0 -4
- lionagi/core/branch/base_branch.py +0 -654
- lionagi/core/branch/branch.py +0 -471
- lionagi/core/branch/branch_flow_mixin.py +0 -96
- lionagi/core/branch/executable_branch.py +0 -347
- lionagi/core/branch/util.py +0 -323
- lionagi/core/direct/__init__.py +0 -6
- lionagi/core/direct/predict.py +0 -161
- lionagi/core/direct/score.py +0 -278
- lionagi/core/direct/select.py +0 -169
- lionagi/core/direct/utils.py +0 -87
- lionagi/core/direct/vote.py +0 -64
- lionagi/core/flow/base/baseflow.py +0 -23
- lionagi/core/flow/monoflow/ReAct.py +0 -238
- lionagi/core/flow/monoflow/__init__.py +0 -9
- lionagi/core/flow/monoflow/chat.py +0 -95
- lionagi/core/flow/monoflow/chat_mixin.py +0 -263
- lionagi/core/flow/monoflow/followup.py +0 -214
- lionagi/core/flow/polyflow/__init__.py +0 -1
- lionagi/core/flow/polyflow/chat.py +0 -248
- lionagi/core/mail/schema.py +0 -56
- lionagi/core/messages/__init__.py +0 -3
- lionagi/core/messages/schema.py +0 -533
- lionagi/core/prompt/prompt_template.py +0 -316
- 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 -910
- lionagi/core/tool/__init__.py +0 -3
- lionagi/core/tool/tool_manager.py +0 -280
- lionagi/integrations/bridge/pydantic_/base_model.py +0 -7
- lionagi/tests/test_core/test_base_branch.py +0 -427
- lionagi/tests/test_core/test_chat_flow.py +0 -63
- lionagi/tests/test_core/test_mail_manager.py +0 -75
- lionagi/tests/test_core/test_prompts.py +0 -51
- lionagi/tests/test_core/test_session.py +0 -254
- lionagi/tests/test_core/test_session_base_util.py +0 -312
- lionagi/tests/test_core/test_tool_manager.py +0 -95
- lionagi-0.0.312.dist-info/RECORD +0 -111
- /lionagi/core/{branch/base → _setting}/__init__.py +0 -0
- /lionagi/core/{flow → agent/eval}/__init__.py +0 -0
- /lionagi/core/{flow/base → agent/learn}/__init__.py +0 -0
- /lionagi/core/{prompt → agent/plan}/__init__.py +0 -0
- /lionagi/core/{tool/manual.py → agent/plan/plan.py} +0 -0
- /lionagi/{tests/test_integrations → core/director}/__init__.py +0 -0
- /lionagi/{tests/test_libs → core/engine}/__init__.py +0 -0
- /lionagi/{tests/test_libs/test_async.py → core/executor/__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_sys_util.py +0 -0
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
- {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,236 @@
|
|
1
|
+
import contextlib
|
2
|
+
from collections import deque
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from lionagi.libs.ln_convert import to_list
|
6
|
+
from lionagi.core.collections.abc import (
|
7
|
+
Condition,
|
8
|
+
Actionable,
|
9
|
+
LionTypeError,
|
10
|
+
ItemNotFoundError,
|
11
|
+
LionIDable,
|
12
|
+
)
|
13
|
+
from lionagi.core.collections import pile, Pile
|
14
|
+
|
15
|
+
from lionagi.core.generic.edge import Edge
|
16
|
+
from lionagi.core.generic.node import Node
|
17
|
+
|
18
|
+
|
19
|
+
class Graph(Node):
|
20
|
+
"""Represents a graph structure with nodes and edges."""
|
21
|
+
|
22
|
+
internal_nodes: Pile = pile()
|
23
|
+
|
24
|
+
@property
|
25
|
+
def internal_edges(self) -> Pile[Edge]:
|
26
|
+
"""Return a pile of all edges in the graph."""
|
27
|
+
return pile(
|
28
|
+
{edge.ln_id: edge for node in self.internal_nodes for edge in node.edges},
|
29
|
+
Edge,
|
30
|
+
)
|
31
|
+
|
32
|
+
def is_empty(self) -> bool:
|
33
|
+
"""Check if the graph is empty (has no nodes)."""
|
34
|
+
return self.internal_nodes.is_empty()
|
35
|
+
|
36
|
+
def clear(self):
|
37
|
+
"""Clear all nodes and edges from the graph."""
|
38
|
+
self.internal_nodes.clear()
|
39
|
+
|
40
|
+
def add_edge(
|
41
|
+
self,
|
42
|
+
head: Node,
|
43
|
+
tail: Node,
|
44
|
+
condition: Condition | None = None,
|
45
|
+
bundle=False,
|
46
|
+
label=None,
|
47
|
+
**kwargs,
|
48
|
+
):
|
49
|
+
"""Add an edge between two nodes in the graph."""
|
50
|
+
if isinstance(head, Actionable):
|
51
|
+
raise LionTypeError("Actionable nodes cannot be related as head.")
|
52
|
+
if isinstance(tail, Actionable):
|
53
|
+
bundle = True
|
54
|
+
|
55
|
+
self.internal_nodes.include(head)
|
56
|
+
self.internal_nodes.include(tail)
|
57
|
+
|
58
|
+
head.relate(
|
59
|
+
tail,
|
60
|
+
direction="out",
|
61
|
+
condition=condition,
|
62
|
+
label=label,
|
63
|
+
bundle=bundle,
|
64
|
+
**kwargs,
|
65
|
+
)
|
66
|
+
|
67
|
+
def remove_edge(self, edge: Any) -> bool:
|
68
|
+
"""Remove an edge from the graph."""
|
69
|
+
edge = edge if isinstance(edge, list) else [edge]
|
70
|
+
for i in edge:
|
71
|
+
if i not in self.internal_edges:
|
72
|
+
raise ItemNotFoundError(f"Edge {i} does not exist in structure.")
|
73
|
+
with contextlib.suppress(ItemNotFoundError):
|
74
|
+
self._remove_edge(i)
|
75
|
+
|
76
|
+
def add_node(self, node: Any) -> None:
|
77
|
+
"""Add a node to the graph."""
|
78
|
+
self.internal_nodes.update(node)
|
79
|
+
|
80
|
+
def get_node(self, item: LionIDable, default=...):
|
81
|
+
"""Get a node from the graph by its identifier."""
|
82
|
+
return self.internal_nodes.get(item, default)
|
83
|
+
|
84
|
+
def get_node_edges(
|
85
|
+
self,
|
86
|
+
node: Node | str,
|
87
|
+
direction: str = "both",
|
88
|
+
label: list | str = None,
|
89
|
+
) -> Pile[Edge] | None:
|
90
|
+
"""Get the edges of a node in the specified direction and with the given label."""
|
91
|
+
node = self.internal_nodes[node]
|
92
|
+
edges = None
|
93
|
+
match direction:
|
94
|
+
case "both":
|
95
|
+
edges = node.edges
|
96
|
+
case "head" | "predecessor" | "outgoing" | "out" | "predecessors":
|
97
|
+
edges = node.relations["out"]
|
98
|
+
case "tail" | "successor" | "incoming" | "in" | "successors":
|
99
|
+
edges = node.relations["in"]
|
100
|
+
|
101
|
+
if label:
|
102
|
+
return (
|
103
|
+
pile(
|
104
|
+
[
|
105
|
+
edge
|
106
|
+
for edge in edges
|
107
|
+
if edge.label in to_list(label, dropna=True, flatten=True)
|
108
|
+
]
|
109
|
+
)
|
110
|
+
if edges
|
111
|
+
else None
|
112
|
+
)
|
113
|
+
return pile(edges) if edges else None
|
114
|
+
|
115
|
+
def pop_node(self, item, default=...):
|
116
|
+
"""Remove and return a node from the graph by its identifier."""
|
117
|
+
return self.internal_nodes.pop(item, default)
|
118
|
+
|
119
|
+
def remove_node(self, item):
|
120
|
+
"""Remove a node from the graph by its identifier."""
|
121
|
+
return self.internal_nodes.remove(item)
|
122
|
+
|
123
|
+
def _remove_edge(self, edge: Edge | str) -> bool:
|
124
|
+
"""Remove a specific edge from the graph."""
|
125
|
+
if edge not in self.internal_edges:
|
126
|
+
raise ItemNotFoundError(f"Edge {edge} does not exist in structure.")
|
127
|
+
|
128
|
+
edge = self.internal_edges[edge]
|
129
|
+
head: Node = self.internal_nodes[edge.head]
|
130
|
+
tail: Node = self.internal_nodes[edge.tail]
|
131
|
+
|
132
|
+
head.unrelate(tail, edge=edge)
|
133
|
+
return True
|
134
|
+
|
135
|
+
def get_heads(self) -> Pile:
|
136
|
+
"""Get all head nodes in the graph."""
|
137
|
+
return pile(
|
138
|
+
[
|
139
|
+
node
|
140
|
+
for node in self.internal_nodes
|
141
|
+
if node.relations["in"].is_empty() and not isinstance(node, Actionable)
|
142
|
+
]
|
143
|
+
)
|
144
|
+
|
145
|
+
def is_acyclic(self) -> bool:
|
146
|
+
"""Check if the graph is acyclic (contains no cycles)."""
|
147
|
+
node_ids = list(self.internal_nodes.keys())
|
148
|
+
check_deque = deque(node_ids)
|
149
|
+
check_dict = {key: 0 for key in node_ids} # 0: not visited, 1: temp, 2: perm
|
150
|
+
|
151
|
+
def visit(key):
|
152
|
+
if check_dict[key] == 2:
|
153
|
+
return True
|
154
|
+
elif check_dict[key] == 1:
|
155
|
+
return False
|
156
|
+
|
157
|
+
check_dict[key] = 1
|
158
|
+
|
159
|
+
for edge in self.internal_nodes[key].relations["out"]:
|
160
|
+
check = visit(edge.tail)
|
161
|
+
if not check:
|
162
|
+
return False
|
163
|
+
|
164
|
+
check_dict[key] = 2
|
165
|
+
return True
|
166
|
+
|
167
|
+
while check_deque:
|
168
|
+
key = check_deque.pop()
|
169
|
+
check = visit(key)
|
170
|
+
if not check:
|
171
|
+
return False
|
172
|
+
return True
|
173
|
+
|
174
|
+
def to_networkx(self, **kwargs) -> Any:
|
175
|
+
"""Convert the graph to a NetworkX graph object."""
|
176
|
+
from lionagi.libs import SysUtil
|
177
|
+
|
178
|
+
SysUtil.check_import("networkx")
|
179
|
+
|
180
|
+
from networkx import DiGraph
|
181
|
+
|
182
|
+
g = DiGraph(**kwargs)
|
183
|
+
for node in self.internal_nodes:
|
184
|
+
node_info = node.to_dict()
|
185
|
+
node_info.pop("ln_id")
|
186
|
+
node_info.update({"class_name": node.class_name})
|
187
|
+
g.add_node(node.ln_id, **node_info)
|
188
|
+
|
189
|
+
for _edge in self.internal_edges:
|
190
|
+
edge_info = _edge.to_dict()
|
191
|
+
edge_info.pop("ln_id")
|
192
|
+
edge_info.update({"class_name": _edge.class_name})
|
193
|
+
source_node_id = edge_info.pop("head")
|
194
|
+
target_node_id = edge_info.pop("tail")
|
195
|
+
g.add_edge(source_node_id, target_node_id, **edge_info)
|
196
|
+
|
197
|
+
return g
|
198
|
+
|
199
|
+
def display(self, **kwargs):
|
200
|
+
"""Display the graph using NetworkX and Matplotlib."""
|
201
|
+
from lionagi.libs import SysUtil
|
202
|
+
|
203
|
+
SysUtil.check_import("networkx")
|
204
|
+
SysUtil.check_import("matplotlib", "pyplot")
|
205
|
+
|
206
|
+
import networkx as nx
|
207
|
+
import matplotlib.pyplot as plt
|
208
|
+
|
209
|
+
g = self.to_networkx(**kwargs)
|
210
|
+
pos = nx.spring_layout(g)
|
211
|
+
nx.draw(
|
212
|
+
g,
|
213
|
+
pos,
|
214
|
+
edge_color="black",
|
215
|
+
width=1,
|
216
|
+
linewidths=1,
|
217
|
+
node_size=500,
|
218
|
+
node_color="orange",
|
219
|
+
alpha=0.9,
|
220
|
+
labels=nx.get_node_attributes(g, "class_name"),
|
221
|
+
)
|
222
|
+
|
223
|
+
labels = nx.get_edge_attributes(g, "label")
|
224
|
+
labels = {k: v for k, v in labels.items() if v}
|
225
|
+
|
226
|
+
if labels:
|
227
|
+
nx.draw_networkx_edge_labels(
|
228
|
+
g, pos, edge_labels=labels, font_color="purple"
|
229
|
+
)
|
230
|
+
|
231
|
+
plt.axis("off")
|
232
|
+
plt.show()
|
233
|
+
|
234
|
+
def size(self) -> int:
|
235
|
+
"""Return the number of nodes in the graph."""
|
236
|
+
return len(self.internal_nodes)
|
@@ -0,0 +1 @@
|
|
1
|
+
# TODO
|
@@ -0,0 +1,220 @@
|
|
1
|
+
"""
|
2
|
+
This module defines the Node class, representing a node in a graph-like
|
3
|
+
structure within LionAGI. Nodes can form relationships with other nodes
|
4
|
+
through directed edges, enabling construction and manipulation of complex
|
5
|
+
relational networks.
|
6
|
+
|
7
|
+
Includes functionality for managing relationships, such as adding,
|
8
|
+
modifying, and removing edges, and querying related nodes and connections.
|
9
|
+
"""
|
10
|
+
|
11
|
+
from pydantic import Field
|
12
|
+
from pandas import Series
|
13
|
+
|
14
|
+
from lionagi.libs.ln_convert import to_list
|
15
|
+
|
16
|
+
from lionagi.core.collections.abc import (
|
17
|
+
Component,
|
18
|
+
Condition,
|
19
|
+
Relatable,
|
20
|
+
RelationError,
|
21
|
+
get_lion_id,
|
22
|
+
)
|
23
|
+
from lionagi.core.collections import pile, Pile
|
24
|
+
from lionagi.core.generic.edge import Edge
|
25
|
+
|
26
|
+
|
27
|
+
class Node(Component, Relatable):
|
28
|
+
"""
|
29
|
+
Node in a graph structure, can connect to other nodes via edges.
|
30
|
+
|
31
|
+
Extends `Component` by incorporating relational capabilities, allowing
|
32
|
+
nodes to connect through 'in' and 'out' directed edges, representing
|
33
|
+
incoming and outgoing relationships.
|
34
|
+
|
35
|
+
Attributes:
|
36
|
+
relations (dict[str, Pile]): Dictionary holding 'Pile' instances
|
37
|
+
for incoming ('in') and outgoing ('out') edges.
|
38
|
+
"""
|
39
|
+
|
40
|
+
relations: dict[str, Pile] = Field(
|
41
|
+
default_factory=lambda: {"in": pile(), "out": pile()},
|
42
|
+
description="The relations of the node.",
|
43
|
+
)
|
44
|
+
|
45
|
+
@property
|
46
|
+
def edges(self) -> Pile[Edge]:
|
47
|
+
"""
|
48
|
+
Get unified view of all incoming and outgoing edges.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Combined pile of all edges connected to this node.
|
52
|
+
"""
|
53
|
+
return self.relations["in"] + self.relations["out"]
|
54
|
+
|
55
|
+
@property
|
56
|
+
def related_nodes(self) -> list[str]:
|
57
|
+
"""
|
58
|
+
Get list of all unique node IDs directly related to this node.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
List of node IDs related to this node.
|
62
|
+
"""
|
63
|
+
all_nodes = set(
|
64
|
+
to_list([[i.head, i.tail] for i in self.edges], flatten=True, dropna=True)
|
65
|
+
)
|
66
|
+
all_nodes.discard(self.ln_id)
|
67
|
+
return list(all_nodes)
|
68
|
+
|
69
|
+
@property
|
70
|
+
def node_relations(self) -> dict:
|
71
|
+
"""
|
72
|
+
Get categorized view of direct relationships into groups.
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
Dict with keys 'in' and 'out', each containing a mapping of
|
76
|
+
related node IDs to lists of edges representing relationships.
|
77
|
+
"""
|
78
|
+
out_node_edges = {}
|
79
|
+
if not self.relations["out"].is_empty():
|
80
|
+
for edge in self.relations["out"]:
|
81
|
+
for node_id in self.related_nodes:
|
82
|
+
if edge.tail == node_id:
|
83
|
+
out_node_edges.setdefault(node_id, []).append(edge)
|
84
|
+
|
85
|
+
in_node_edges = {}
|
86
|
+
if not self.relations["in"].is_empty():
|
87
|
+
for edge in self.relations["in"]:
|
88
|
+
for node_id in self.related_nodes:
|
89
|
+
if edge.head == node_id:
|
90
|
+
in_node_edges.setdefault(node_id, []).append(edge)
|
91
|
+
|
92
|
+
return {"out": out_node_edges, "in": in_node_edges}
|
93
|
+
|
94
|
+
@property
|
95
|
+
def predecessors(self) -> list[str]:
|
96
|
+
"""
|
97
|
+
Get list of IDs of nodes with direct incoming relation to this.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
List of node IDs that precede this node.
|
101
|
+
"""
|
102
|
+
return [
|
103
|
+
node_id for node_id, edges in self.node_relations["in"].items() if edges
|
104
|
+
]
|
105
|
+
|
106
|
+
@property
|
107
|
+
def successors(self) -> list[str]:
|
108
|
+
"""
|
109
|
+
Get list of IDs of nodes with direct outgoing relation from this.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
List of node IDs that succeed this node.
|
113
|
+
"""
|
114
|
+
return [
|
115
|
+
node_id for node_id, edges in self.node_relations["out"].items() if edges
|
116
|
+
]
|
117
|
+
|
118
|
+
def relate(
|
119
|
+
self,
|
120
|
+
node: "Node",
|
121
|
+
direction: str = "out",
|
122
|
+
condition: Condition | None = None,
|
123
|
+
label: str | None = None,
|
124
|
+
bundle: bool = False,
|
125
|
+
) -> None:
|
126
|
+
"""
|
127
|
+
Establish directed relationship from this node to another.
|
128
|
+
|
129
|
+
Args:
|
130
|
+
node: Target node to relate to.
|
131
|
+
direction: Direction of edge ('in' or 'out'). Default 'out'.
|
132
|
+
condition: Optional condition to associate with edge.
|
133
|
+
label: Optional label for edge.
|
134
|
+
bundle: Whether to bundle edge with others. Default False.
|
135
|
+
|
136
|
+
Raises:
|
137
|
+
ValueError: If direction is neither 'in' nor 'out'.
|
138
|
+
"""
|
139
|
+
if direction not in ["in", "out"]:
|
140
|
+
raise ValueError(
|
141
|
+
f"Invalid value for direction: {direction}, " "must be 'in' or 'out'"
|
142
|
+
)
|
143
|
+
|
144
|
+
edge = Edge(
|
145
|
+
head=self if direction == "out" else node,
|
146
|
+
tail=node if direction == "out" else self,
|
147
|
+
condition=condition,
|
148
|
+
bundle=bundle,
|
149
|
+
label=label,
|
150
|
+
)
|
151
|
+
|
152
|
+
self.relations[direction].include(edge)
|
153
|
+
node.relations["in" if direction == "out" else "out"].include(edge)
|
154
|
+
|
155
|
+
def remove_edge(self, node: "Node", edge: Edge | str) -> bool:
|
156
|
+
"""
|
157
|
+
Remove specified edge or all edges between this and another node.
|
158
|
+
|
159
|
+
Args:
|
160
|
+
node: Other node involved in edge.
|
161
|
+
edge: Specific edge to remove or 'all' to remove all edges.
|
162
|
+
|
163
|
+
Returns:
|
164
|
+
True if edge(s) successfully removed, False otherwise.
|
165
|
+
|
166
|
+
Raises:
|
167
|
+
RelationError: If removal fails or edge does not exist.
|
168
|
+
"""
|
169
|
+
edge_piles = [
|
170
|
+
self.relations["in"],
|
171
|
+
self.relations["out"],
|
172
|
+
node.relations["in"],
|
173
|
+
node.relations["out"],
|
174
|
+
]
|
175
|
+
|
176
|
+
if not all(pile.exclude(edge) for pile in edge_piles):
|
177
|
+
raise RelationError(f"Failed to remove edge between nodes.")
|
178
|
+
return True
|
179
|
+
|
180
|
+
def unrelate(self, node: "Node", edge: Edge | str = "all") -> bool:
|
181
|
+
"""
|
182
|
+
Remove all or specific relationships between this and another node.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
node: Other node to unrelate from.
|
186
|
+
edge: Specific edge to remove or 'all' for all. Default 'all'.
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
True if relationships successfully removed, False otherwise.
|
190
|
+
|
191
|
+
Raises:
|
192
|
+
RelationError: If operation fails to unrelate nodes.
|
193
|
+
"""
|
194
|
+
if edge == "all":
|
195
|
+
edges = self.node_relations["out"].get(
|
196
|
+
node.ln_id, []
|
197
|
+
) + self.node_relations["in"].get(node.ln_id, [])
|
198
|
+
else:
|
199
|
+
edges = [get_lion_id(edge)]
|
200
|
+
|
201
|
+
if not edges:
|
202
|
+
raise RelationError(f"Node is not related to {node.ln_id}.")
|
203
|
+
|
204
|
+
try:
|
205
|
+
for edge_id in edges:
|
206
|
+
self.remove_edge(node, edge_id)
|
207
|
+
return True
|
208
|
+
except RelationError as e:
|
209
|
+
raise e
|
210
|
+
|
211
|
+
def __str__(self):
|
212
|
+
_dict = self.to_dict()
|
213
|
+
_dict["relations"] = [
|
214
|
+
len(self.relations["in"]),
|
215
|
+
len(self.relations["out"]),
|
216
|
+
]
|
217
|
+
return Series(_dict).__str__()
|
218
|
+
|
219
|
+
def __repr__(self):
|
220
|
+
return self.__str__()
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"""This module provides tree structure."""
|
2
|
+
|
3
|
+
from pydantic import Field
|
4
|
+
from lionagi.core.collections.abc import Condition
|
5
|
+
from lionagi.core.collections.util import to_list_type
|
6
|
+
from lionagi.core.generic.tree_node import TreeNode
|
7
|
+
from lionagi.core.generic.graph import Graph
|
8
|
+
|
9
|
+
|
10
|
+
class Tree(Graph):
|
11
|
+
"""
|
12
|
+
Represents a tree structure, extending the graph with tree-specific functionalities.
|
13
|
+
|
14
|
+
Manages parent-child relationships within the tree.
|
15
|
+
|
16
|
+
Attributes:
|
17
|
+
root (TreeNode | None): The root node of the tree. Defaults to None.
|
18
|
+
"""
|
19
|
+
|
20
|
+
root: TreeNode | None = Field(
|
21
|
+
default=None, description="The root node of the tree graph."
|
22
|
+
)
|
23
|
+
|
24
|
+
def relate_parent_child(
|
25
|
+
self,
|
26
|
+
parent: TreeNode,
|
27
|
+
children,
|
28
|
+
condition: Condition | None = None,
|
29
|
+
bundle: bool = False,
|
30
|
+
) -> None:
|
31
|
+
"""
|
32
|
+
Establishes parent-child relationships between the given parent and child node(s).
|
33
|
+
|
34
|
+
Args:
|
35
|
+
parent (TreeNode): The parent node.
|
36
|
+
children (list[TreeNode]): A list of child nodes.
|
37
|
+
condition (Condition | None): The condition associated with the relationships, if any.
|
38
|
+
bundle (bool): Indicates whether to bundle the relations into a single
|
39
|
+
transaction. Defaults to False.
|
40
|
+
"""
|
41
|
+
|
42
|
+
for i in to_list_type(children):
|
43
|
+
i.relate_parent(parent, condition=condition, bundle=bundle)
|
44
|
+
|
45
|
+
if self.root is None:
|
46
|
+
self.root = parent
|
47
|
+
|
48
|
+
self.add_node([parent, *children])
|
@@ -0,0 +1,79 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from pydantic import Field
|
3
|
+
from lionagi.core.collections.abc import Condition
|
4
|
+
from lionagi.core.collections.util import to_list_type
|
5
|
+
from lionagi.core.generic.node import Node
|
6
|
+
|
7
|
+
|
8
|
+
class TreeLabel(str, Enum):
|
9
|
+
"""Enumeration representing tree relationships."""
|
10
|
+
|
11
|
+
PARENT = "parent"
|
12
|
+
CHILD = "child"
|
13
|
+
|
14
|
+
|
15
|
+
class TreeNode(Node):
|
16
|
+
"""Represents a node in a tree structure."""
|
17
|
+
|
18
|
+
parent: Node | None = Field(
|
19
|
+
default=None,
|
20
|
+
description="The parent node, as an instance of Node.",
|
21
|
+
)
|
22
|
+
|
23
|
+
@property
|
24
|
+
def children(self) -> list[str]:
|
25
|
+
"""Return a list of child node ids."""
|
26
|
+
if not self.parent:
|
27
|
+
return list(self.related_nodes)
|
28
|
+
else:
|
29
|
+
return [node for node in self.related_nodes if node != self.parent.ln_id]
|
30
|
+
|
31
|
+
def relate_child(
|
32
|
+
self,
|
33
|
+
node: Node | list[Node],
|
34
|
+
condition: Condition | None = None,
|
35
|
+
bundle: bool = False,
|
36
|
+
) -> None:
|
37
|
+
"""Establish a parent-child relationship with the given node(s)."""
|
38
|
+
children = to_list_type(node)
|
39
|
+
for _child in children:
|
40
|
+
self.relate(
|
41
|
+
_child,
|
42
|
+
direction="out",
|
43
|
+
# label=TreeLabel.PARENT,
|
44
|
+
condition=condition,
|
45
|
+
bundle=bundle,
|
46
|
+
)
|
47
|
+
if isinstance(_child, TreeNode):
|
48
|
+
_child.parent = self
|
49
|
+
|
50
|
+
def relate_parent(
|
51
|
+
self,
|
52
|
+
node: Node,
|
53
|
+
condition: Condition | None = None,
|
54
|
+
bundle: bool = False,
|
55
|
+
) -> None:
|
56
|
+
"""Establish a parent-child relationship with the given parent node."""
|
57
|
+
if self.parent:
|
58
|
+
self.unrelate(self.parent)
|
59
|
+
self.relate(
|
60
|
+
node,
|
61
|
+
direction="in",
|
62
|
+
# label=TreeLabel.PARENT,
|
63
|
+
condition=condition,
|
64
|
+
bundle=bundle,
|
65
|
+
)
|
66
|
+
self.parent = node
|
67
|
+
|
68
|
+
def unrelate_parent(self):
|
69
|
+
"""Remove the parent-child relationship with the parent node."""
|
70
|
+
self.unrelate(self.parent)
|
71
|
+
self.parent = None
|
72
|
+
|
73
|
+
def unrelate_child(self, child: Node | list[Node]):
|
74
|
+
"""Remove the parent-child relationship with the given child node(s)."""
|
75
|
+
children: list[Node] = [child] if isinstance(child, Node) else child
|
76
|
+
for _child in children:
|
77
|
+
self.unrelate(_child)
|
78
|
+
if isinstance(_child, TreeNode):
|
79
|
+
_child.parent = None
|
lionagi/core/mail/__init__.py
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
-
from .
|
1
|
+
from .mail import Mail
|
2
2
|
from .mail_manager import MailManager
|
3
|
+
from .package import Package
|
4
|
+
from .start_mail import StartMail
|
5
|
+
|
3
6
|
|
4
7
|
__all__ = [
|
5
|
-
"
|
6
|
-
"StartMail",
|
8
|
+
"Mail",
|
7
9
|
"MailManager",
|
10
|
+
"Package",
|
11
|
+
"StartMail",
|
8
12
|
]
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from lionagi.core.collections.abc import Element, Field, Sendable
|
2
|
+
from .package import PackageCategory, Package
|
3
|
+
|
4
|
+
|
5
|
+
class Mail(Element, Sendable):
|
6
|
+
"""Represents a mail component with sender and recipient information."""
|
7
|
+
|
8
|
+
package: Package | None = Field(
|
9
|
+
None,
|
10
|
+
title="Package",
|
11
|
+
description="The package to be delivered.",
|
12
|
+
)
|
13
|
+
|
14
|
+
@property
|
15
|
+
def category(self) -> PackageCategory:
|
16
|
+
"""Return the category of the package."""
|
17
|
+
return self.package.category
|
18
|
+
|
19
|
+
def to_dict(self):
|
20
|
+
return {
|
21
|
+
"ln_id": self.ln_id,
|
22
|
+
"created": self.timestamp,
|
23
|
+
"package_category": self.package.category,
|
24
|
+
"package_id": self.package.ln_id,
|
25
|
+
}
|