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,384 @@
|
|
1
|
+
from collections import deque
|
2
|
+
import json
|
3
|
+
from typing import Callable
|
4
|
+
|
5
|
+
from lionagi.core.executor.base_executor import BaseExecutor
|
6
|
+
from lionagi.integrations.storage.neo4j import Neo4j
|
7
|
+
from lionagi.integrations.storage.storage_util import ParseNode
|
8
|
+
from lionagi.core.agent.base_agent import BaseAgent
|
9
|
+
from lionagi.core.engine.instruction_map_engine import InstructionMapEngine
|
10
|
+
|
11
|
+
from lionagi.core.mail import Mail
|
12
|
+
from lionagi.core.action import Tool, DirectiveSelection, ActionNode
|
13
|
+
from lionagi.core.generic.edge import Edge
|
14
|
+
from lionagi.core.collections.progression import progression
|
15
|
+
|
16
|
+
from lionagi.libs import AsyncUtil
|
17
|
+
|
18
|
+
|
19
|
+
class Neo4jExecutor(BaseExecutor):
|
20
|
+
"""
|
21
|
+
Executes tasks within a Neo4j graph database, handling dynamic instruction flows and conditional logic across various nodes and agents.
|
22
|
+
|
23
|
+
Attributes:
|
24
|
+
driver (Neo4j | None): Connection driver to the Neo4j database.
|
25
|
+
structure_id (str | None): Identifier for the structure being executed within the graph.
|
26
|
+
structure_name (str | None): Name of the structure being executed.
|
27
|
+
middle_agents (list | None): List of agents operating within the structure.
|
28
|
+
default_agent_executable (BaseExecutor): Default executor for running tasks not handled by specific agents.
|
29
|
+
condition_check_result (bool | None): Result of the last condition check performed during execution.
|
30
|
+
"""
|
31
|
+
|
32
|
+
driver: Neo4j | None
|
33
|
+
structure_id: str = None
|
34
|
+
structure_name: str = None
|
35
|
+
middle_agents: list | None = None
|
36
|
+
default_agent_executable: BaseExecutor = InstructionMapEngine()
|
37
|
+
condition_check_result: bool | None = None
|
38
|
+
|
39
|
+
class Config:
|
40
|
+
arbitrary_types_allowed = True
|
41
|
+
|
42
|
+
async def check_edge_condition(
|
43
|
+
self, condition, executable_id, request_source, head, tail
|
44
|
+
):
|
45
|
+
"""
|
46
|
+
Evaluates the condition associated with an edge in the graph, determining if execution should proceed along that edge.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
condition: The condition object or logic to be evaluated.
|
50
|
+
executable_id (str): ID of the executor responsible for this condition check.
|
51
|
+
request_source (str): Origin of the request prompting this check.
|
52
|
+
head (str): ID of the head node in the edge.
|
53
|
+
tail (str): ID of the tail node in the edge.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
bool: Result of the condition check.
|
57
|
+
"""
|
58
|
+
if condition.source == "structure":
|
59
|
+
return condition.applies(self)
|
60
|
+
elif condition.source == "executable":
|
61
|
+
return await self._check_executable_condition(
|
62
|
+
condition, executable_id, head, tail, request_source
|
63
|
+
)
|
64
|
+
|
65
|
+
def _process_edge_condition(self, edge_id):
|
66
|
+
"""
|
67
|
+
Process the condition of a edge.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
edge_id (str): The ID of the edge.
|
71
|
+
"""
|
72
|
+
for key in list(self.mailbox.pending_ins.keys()):
|
73
|
+
skipped_requests = progression()
|
74
|
+
while self.mailbox.pending_ins[key].size() > 0:
|
75
|
+
mail_id = self.mailbox.pending_ins[key].popleft()
|
76
|
+
mail = self.mailbox.pile[mail_id]
|
77
|
+
if (
|
78
|
+
mail.category == "condition"
|
79
|
+
and mail.package.package["edge_id"] == edge_id
|
80
|
+
):
|
81
|
+
self.mailbox.pile.pop(mail_id)
|
82
|
+
self.condition_check_result = mail.package.package["check_result"]
|
83
|
+
else:
|
84
|
+
skipped_requests.append(mail)
|
85
|
+
self.mailbox.pending_ins[key] = skipped_requests
|
86
|
+
|
87
|
+
async def _check_executable_condition(
|
88
|
+
self, condition, executable_id, head, tail, request_source
|
89
|
+
):
|
90
|
+
"""
|
91
|
+
Sends a condition to be checked by an external executable and awaits the result.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
condition: The condition object to be evaluated.
|
95
|
+
executable_id (str): ID of the executable that will evaluate the condition.
|
96
|
+
head (str): Starting node of the edge.
|
97
|
+
tail (str): Ending node of the edge.
|
98
|
+
request_source (str): Source of the request for condition evaluation.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
bool: The result of the condition check.
|
102
|
+
"""
|
103
|
+
edge = Edge(head=head, tail=tail, condition=condition)
|
104
|
+
self.send(
|
105
|
+
recipient=executable_id,
|
106
|
+
category="condition",
|
107
|
+
package=edge,
|
108
|
+
request_source=request_source,
|
109
|
+
)
|
110
|
+
while self.condition_check_result is None:
|
111
|
+
await AsyncUtil.sleep(0.1)
|
112
|
+
self._process_edge_condition(edge.ln_id)
|
113
|
+
continue
|
114
|
+
check_result = self.condition_check_result
|
115
|
+
self.condition_check_result = None
|
116
|
+
return check_result
|
117
|
+
|
118
|
+
@staticmethod
|
119
|
+
def parse_bundled_to_action(instruction, bundle_list):
|
120
|
+
"""
|
121
|
+
Parses bundled actions and tools from a list of nodes, creating a composite action node from them.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
instruction: The initial instruction leading to this bundle.
|
125
|
+
bundle_list (list): List of nodes bundled together.
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
ActionNode: A node representing a composite action constructed from the bundled nodes.
|
129
|
+
"""
|
130
|
+
bundled_nodes = deque()
|
131
|
+
for node_labels, node_properties in bundle_list:
|
132
|
+
try:
|
133
|
+
if "DirectiveSelection" in node_labels:
|
134
|
+
node = ParseNode.parse_directiveSelection(node_properties)
|
135
|
+
bundled_nodes.append(node)
|
136
|
+
elif "Tool" in node_labels:
|
137
|
+
node = ParseNode.parse_tool(node_properties)
|
138
|
+
bundled_nodes.append(node)
|
139
|
+
else:
|
140
|
+
raise ValueError(
|
141
|
+
f"Invalid bundle node {node_properties.ln_id}. Valid nodes are ActionSelection or Tool"
|
142
|
+
)
|
143
|
+
except Exception as e:
|
144
|
+
raise ValueError(
|
145
|
+
f"Failed to parse ActionSelection or Tool node {node_properties.ln_id}. Error: {e}"
|
146
|
+
)
|
147
|
+
|
148
|
+
action_node = ActionNode(instruction=instruction)
|
149
|
+
while bundled_nodes:
|
150
|
+
node = bundled_nodes.popleft()
|
151
|
+
if isinstance(node, DirectiveSelection):
|
152
|
+
action_node.directive = node.directive
|
153
|
+
action_node.directive_kwargs = node.directive_kwargs
|
154
|
+
elif isinstance(node, Tool):
|
155
|
+
action_node.tools.append(node)
|
156
|
+
return action_node
|
157
|
+
|
158
|
+
def parse_agent(self, node_properties):
|
159
|
+
"""
|
160
|
+
Parses agent properties and creates an agent executor.
|
161
|
+
|
162
|
+
Args:
|
163
|
+
node_properties (dict): Properties defining the agent.
|
164
|
+
|
165
|
+
Returns:
|
166
|
+
BaseAgent: An agent executor configured with the given properties.
|
167
|
+
"""
|
168
|
+
output_parser = (
|
169
|
+
ParseNode.convert_to_def(node_properties["outputParser"])
|
170
|
+
if "outputParser" in node_properties
|
171
|
+
else None
|
172
|
+
)
|
173
|
+
|
174
|
+
structure = Neo4jExecutor(
|
175
|
+
driver=self.driver, structure_id=node_properties["structureId"]
|
176
|
+
)
|
177
|
+
agent = BaseAgent(
|
178
|
+
structure=structure,
|
179
|
+
executable=self.default_agent_executable,
|
180
|
+
output_parser=output_parser,
|
181
|
+
ln_id=node_properties["ln_id"],
|
182
|
+
timestamp=node_properties["timestamp"],
|
183
|
+
)
|
184
|
+
return agent
|
185
|
+
|
186
|
+
async def _next_node(
|
187
|
+
self, query_list, node_id=None, executable_id=None, request_source=None
|
188
|
+
):
|
189
|
+
"""
|
190
|
+
Processes the next set of nodes based on the results of a query list, applying conditions and preparing nodes
|
191
|
+
for further execution.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
query_list (list): List of nodes and their properties.
|
195
|
+
node_id (str | None): Current node ID, if applicable.
|
196
|
+
executable_id (str | None): ID of the executor handling these nodes.
|
197
|
+
request_source (str | None): Source of the node processing request.
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
list: Next nodes ready for processing.
|
201
|
+
"""
|
202
|
+
next_nodes = []
|
203
|
+
for edge_properties, node_labels, node_properties in query_list:
|
204
|
+
if "condition" in edge_properties.keys():
|
205
|
+
try:
|
206
|
+
condition = json.loads(edge_properties["condition"])
|
207
|
+
condition_cls = await self.driver.get_condition_cls_code(
|
208
|
+
condition["class"]
|
209
|
+
)
|
210
|
+
condition_obj = ParseNode.parse_condition(condition, condition_cls)
|
211
|
+
|
212
|
+
head = node_id
|
213
|
+
tail = node_properties["ln_id"]
|
214
|
+
check = await self.check_edge_condition(
|
215
|
+
condition_obj, executable_id, request_source, head, tail
|
216
|
+
)
|
217
|
+
if not check:
|
218
|
+
continue
|
219
|
+
except Exception as e:
|
220
|
+
raise ValueError(
|
221
|
+
f"Failed to use condition {edge_properties['condition']} from {node_id} to {node_properties['ln_id']}, Error: {e}"
|
222
|
+
)
|
223
|
+
|
224
|
+
try:
|
225
|
+
if "System" in node_labels:
|
226
|
+
node = ParseNode.parse_system(node_properties)
|
227
|
+
elif "Instruction" in node_labels:
|
228
|
+
node = ParseNode.parse_instruction(node_properties)
|
229
|
+
elif "Agent" in node_labels:
|
230
|
+
node = self.parse_agent(node_properties)
|
231
|
+
|
232
|
+
else:
|
233
|
+
raise ValueError(
|
234
|
+
f"Invalid start node {node_properties.ln_id}. Valid nodes are System or Instruction"
|
235
|
+
)
|
236
|
+
except Exception as e:
|
237
|
+
raise ValueError(
|
238
|
+
f"Failed to parse System or Instruction node {node_properties.ln_id}. Error: {e}"
|
239
|
+
)
|
240
|
+
|
241
|
+
bundle_list = await self.driver.get_bundle(node.ln_id)
|
242
|
+
|
243
|
+
if bundle_list and "System" in node_labels:
|
244
|
+
raise ValueError("System node does not support bundle edge")
|
245
|
+
if bundle_list:
|
246
|
+
node = self.parse_bundled_to_action(node, bundle_list)
|
247
|
+
next_nodes.append(node)
|
248
|
+
return next_nodes
|
249
|
+
|
250
|
+
async def _handle_start(self):
|
251
|
+
"""
|
252
|
+
Handles the start of execution, fetching and processing head nodes from the structure.
|
253
|
+
|
254
|
+
Raises:
|
255
|
+
ValueError: If there is an issue with finding or starting the structure.
|
256
|
+
"""
|
257
|
+
try:
|
258
|
+
id_, head_list = await self.driver.get_heads(
|
259
|
+
self.structure_name, self.structure_id
|
260
|
+
)
|
261
|
+
self.structure_id = id_
|
262
|
+
return await self._next_node(head_list)
|
263
|
+
except Exception as e:
|
264
|
+
raise ValueError(f"Error in searching for structure in Neo4j. Error: {e}")
|
265
|
+
|
266
|
+
async def _handle_node_id(self, node_id, executable_id, request_source):
|
267
|
+
"""
|
268
|
+
Handles the processing of a specific node ID, fetching its forward connections and conditions.
|
269
|
+
|
270
|
+
Args:
|
271
|
+
node_id (str): The node ID to process.
|
272
|
+
executable_id (str): ID of the executor handling this node.
|
273
|
+
request_source (str): Source of the node processing request.
|
274
|
+
|
275
|
+
Returns:
|
276
|
+
list: Next nodes derived from the given node ID.
|
277
|
+
"""
|
278
|
+
check = await self.driver.node_exist(node_id)
|
279
|
+
if not check:
|
280
|
+
raise ValueError(f"Node {node_id} if not found in the database")
|
281
|
+
node_list = await self.driver.get_forwards(node_id)
|
282
|
+
return await self._next_node(node_list, node_id, executable_id, request_source)
|
283
|
+
|
284
|
+
async def _handle_mail(self, mail: Mail):
|
285
|
+
"""
|
286
|
+
Processes incoming mail, determining the next action based on the mail's category and content.
|
287
|
+
|
288
|
+
Args:
|
289
|
+
mail (Mail): The incoming mail to be processed.
|
290
|
+
|
291
|
+
Raises:
|
292
|
+
ValueError: If there is an error processing the mail.
|
293
|
+
"""
|
294
|
+
if mail.category == "start":
|
295
|
+
try:
|
296
|
+
return await self._handle_start()
|
297
|
+
except Exception as e:
|
298
|
+
raise ValueError(f"Error in start. Error: {e}")
|
299
|
+
|
300
|
+
elif mail.category == "end":
|
301
|
+
self.execute_stop = True
|
302
|
+
return None
|
303
|
+
|
304
|
+
elif mail.category == "node_id":
|
305
|
+
try:
|
306
|
+
node_id = mail.package.package
|
307
|
+
executable_id = mail.sender
|
308
|
+
request_source = mail.package.request_source
|
309
|
+
return await self._handle_node_id(
|
310
|
+
node_id, executable_id, request_source
|
311
|
+
)
|
312
|
+
except Exception as e:
|
313
|
+
raise ValueError(f"Error in handling node_id: {e}")
|
314
|
+
elif mail.category == "node":
|
315
|
+
try:
|
316
|
+
node_id = mail.package.package.ln_id
|
317
|
+
executable_id = mail.sender
|
318
|
+
request_source = mail.package.request_source
|
319
|
+
return await self._handle_node_id(
|
320
|
+
node_id, executable_id, request_source
|
321
|
+
)
|
322
|
+
except Exception as e:
|
323
|
+
raise ValueError(f"Error in handling node: {e}")
|
324
|
+
else:
|
325
|
+
raise ValueError(f"Invalid mail type for structure")
|
326
|
+
|
327
|
+
def _send_mail(self, next_nodes: list | None, mail: Mail):
|
328
|
+
"""
|
329
|
+
Sends out mail to the next nodes or marks the execution as ended if there are no next nodes.
|
330
|
+
|
331
|
+
Args:
|
332
|
+
next_nodes (list | None): List of next nodes to which mail should be sent.
|
333
|
+
mail (Mail): The current mail being processed.
|
334
|
+
"""
|
335
|
+
if not next_nodes: # tail
|
336
|
+
self.send(
|
337
|
+
recipient=mail.sender,
|
338
|
+
category="end",
|
339
|
+
package="end",
|
340
|
+
request_source=mail.package.request_source,
|
341
|
+
)
|
342
|
+
else:
|
343
|
+
if len(next_nodes) == 1:
|
344
|
+
self.send(
|
345
|
+
recipient=mail.sender,
|
346
|
+
category="node",
|
347
|
+
package=next_nodes[0],
|
348
|
+
request_source=mail.package.request_source,
|
349
|
+
)
|
350
|
+
else:
|
351
|
+
self.send(
|
352
|
+
recipient=mail.sender,
|
353
|
+
category="node_list",
|
354
|
+
package=next_nodes,
|
355
|
+
request_source=mail.package.request_source,
|
356
|
+
)
|
357
|
+
|
358
|
+
async def forward(self) -> None:
|
359
|
+
"""
|
360
|
+
Forwards execution by processing all pending mails and advancing to next nodes or actions.
|
361
|
+
"""
|
362
|
+
for key in list(self.mailbox.pending_ins.keys()):
|
363
|
+
while self.mailbox.pending_ins[key].size() > 0:
|
364
|
+
mail_id = self.mailbox.pending_ins[key].popleft()
|
365
|
+
mail = self.mailbox.pile.pop(mail_id)
|
366
|
+
try:
|
367
|
+
if mail == "end":
|
368
|
+
self.execute_stop = True
|
369
|
+
return
|
370
|
+
next_nodes = await self._handle_mail(mail)
|
371
|
+
self._send_mail(next_nodes, mail)
|
372
|
+
except Exception as e:
|
373
|
+
raise ValueError(f"Error handling mail: {e}") from e
|
374
|
+
|
375
|
+
async def execute(self, refresh_time=1):
|
376
|
+
"""
|
377
|
+
Continuously executes the forward process at specified intervals until instructed to stop.
|
378
|
+
|
379
|
+
Args:
|
380
|
+
refresh_time (int): The time in seconds between execution cycles.
|
381
|
+
"""
|
382
|
+
while not self.execute_stop:
|
383
|
+
await self.forward()
|
384
|
+
await AsyncUtil.sleep(refresh_time)
|
@@ -0,0 +1,112 @@
|
|
1
|
+
from pydantic import Field, field_validator
|
2
|
+
from typing import Any
|
3
|
+
from lionagi.core.collections.abc import Component, get_lion_id, LionIDable, Condition
|
4
|
+
from lionagi.core.generic.edge_condition import EdgeCondition
|
5
|
+
|
6
|
+
|
7
|
+
class Edge(Component):
|
8
|
+
"""Represents a directed edge between two nodes in a graph."""
|
9
|
+
|
10
|
+
head: str = Field(
|
11
|
+
...,
|
12
|
+
title="Head",
|
13
|
+
description="The identifier of the head node of the edge.",
|
14
|
+
)
|
15
|
+
|
16
|
+
tail: str = Field(
|
17
|
+
...,
|
18
|
+
title="Out",
|
19
|
+
description="The identifier of the tail node of the edge.",
|
20
|
+
)
|
21
|
+
|
22
|
+
condition: Condition | EdgeCondition | None = Field(
|
23
|
+
default=None,
|
24
|
+
description="Optional condition that must be met for the edge "
|
25
|
+
"to be considered active.",
|
26
|
+
)
|
27
|
+
|
28
|
+
label: str | None = Field(
|
29
|
+
default=None,
|
30
|
+
description="An optional label for the edge.",
|
31
|
+
)
|
32
|
+
|
33
|
+
bundle: bool = Field(
|
34
|
+
default=False,
|
35
|
+
description="A flag indicating if the edge is bundled.",
|
36
|
+
)
|
37
|
+
|
38
|
+
async def check_condition(self, obj: Any) -> bool:
|
39
|
+
"""Check if the edge condition is met for the given object."""
|
40
|
+
if not self.condition:
|
41
|
+
raise ValueError("The condition for the edge is not set.")
|
42
|
+
check = await self.condition.applies(obj)
|
43
|
+
return check
|
44
|
+
|
45
|
+
@field_validator("head", "tail", mode="before")
|
46
|
+
def _validate_head_tail(cls, value):
|
47
|
+
"""Validate the head and tail fields."""
|
48
|
+
return get_lion_id(value)
|
49
|
+
|
50
|
+
def string_condition(self):
|
51
|
+
"""
|
52
|
+
Retrieves the condition class source code as a string.
|
53
|
+
|
54
|
+
This method is useful for serialization and debugging, allowing
|
55
|
+
the condition logic to be inspected or stored in a human-readable
|
56
|
+
format. It employs advanced introspection techniques to locate and
|
57
|
+
extract the exact class definition, handling edge cases like
|
58
|
+
dynamically defined classes or classes defined interactively.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
str | None: The condition class source code if available.
|
62
|
+
If the condition is None or the source code cannot be
|
63
|
+
located, this method returns None.
|
64
|
+
|
65
|
+
Raises:
|
66
|
+
TypeError: If the condition class source code cannot be found
|
67
|
+
due to the class being defined in a non-standard manner or
|
68
|
+
in the interactive interpreter (__main__ context).
|
69
|
+
"""
|
70
|
+
if self.condition is None:
|
71
|
+
return
|
72
|
+
|
73
|
+
import inspect, sys
|
74
|
+
|
75
|
+
def new_getfile(object, _old_getfile=inspect.getfile):
|
76
|
+
if not inspect.isclass(object):
|
77
|
+
return _old_getfile(object)
|
78
|
+
|
79
|
+
# Lookup by parent module (as in current inspect)
|
80
|
+
if hasattr(object, "__module__"):
|
81
|
+
object_ = sys.modules.get(object.__module__)
|
82
|
+
if hasattr(object_, "__file__"):
|
83
|
+
return object_.__file__
|
84
|
+
|
85
|
+
# If parent module is __main__, lookup by methods (NEW)
|
86
|
+
for name, member in inspect.getmembers(object):
|
87
|
+
if (
|
88
|
+
inspect.isfunction(member)
|
89
|
+
and object.__qualname__ + "." + member.__name__
|
90
|
+
== member.__qualname__
|
91
|
+
):
|
92
|
+
return inspect.getfile(member)
|
93
|
+
else:
|
94
|
+
raise TypeError("Source for {!r} not found".format(object))
|
95
|
+
|
96
|
+
inspect.getfile = new_getfile
|
97
|
+
|
98
|
+
import inspect
|
99
|
+
from IPython.core.magics.code import extract_symbols
|
100
|
+
|
101
|
+
obj = self.condition.__class__
|
102
|
+
cell_code = "".join(inspect.linecache.getlines(new_getfile(obj)))
|
103
|
+
class_code = extract_symbols(cell_code, obj.__name__)[0][0]
|
104
|
+
return class_code
|
105
|
+
|
106
|
+
def __len__(self):
|
107
|
+
"""Return the length of the edge (always 1)."""
|
108
|
+
return 1
|
109
|
+
|
110
|
+
def __contains__(self, item: LionIDable) -> bool:
|
111
|
+
"""Check if the given item is the head or tail of the edge."""
|
112
|
+
return get_lion_id(item) in (self.head, self.tail)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from typing import Any
|
2
|
+
from pydantic import Field
|
3
|
+
from lionagi.core.collections.abc import Condition
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
|
7
|
+
class EdgeCondition(Condition, BaseModel):
|
8
|
+
source: Any = Field(
|
9
|
+
title="Source",
|
10
|
+
description="The source for condition check",
|
11
|
+
)
|
12
|
+
|
13
|
+
class Config:
|
14
|
+
"""Model configuration settings."""
|
15
|
+
|
16
|
+
extra = "allow"
|