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.
Files changed (103) hide show
  1. lionagi/core/__init__.py +19 -8
  2. lionagi/core/agent/__init__.py +0 -3
  3. lionagi/core/agent/base_agent.py +26 -30
  4. lionagi/core/branch/__init__.py +0 -4
  5. lionagi/core/branch/{base_branch.py → base.py} +13 -14
  6. lionagi/core/branch/branch.py +22 -20
  7. lionagi/core/branch/executable_branch.py +0 -347
  8. lionagi/core/branch/{branch_flow_mixin.py → flow_mixin.py} +6 -6
  9. lionagi/core/branch/util.py +1 -1
  10. lionagi/core/direct/__init__.py +13 -1
  11. lionagi/core/direct/cot.py +123 -1
  12. lionagi/core/direct/plan.py +164 -0
  13. lionagi/core/direct/predict.py +13 -9
  14. lionagi/core/direct/react.py +12 -8
  15. lionagi/core/direct/score.py +4 -4
  16. lionagi/core/direct/select.py +4 -4
  17. lionagi/core/direct/utils.py +23 -0
  18. lionagi/core/direct/vote.py +2 -2
  19. lionagi/core/execute/base_executor.py +50 -0
  20. lionagi/core/execute/branch_executor.py +233 -0
  21. lionagi/core/execute/instruction_map_executor.py +131 -0
  22. lionagi/core/execute/structure_executor.py +218 -0
  23. lionagi/core/flow/monoflow/ReAct.py +4 -4
  24. lionagi/core/flow/monoflow/chat.py +6 -6
  25. lionagi/core/flow/monoflow/chat_mixin.py +24 -34
  26. lionagi/core/flow/monoflow/followup.py +4 -4
  27. lionagi/core/flow/polyflow/__init__.py +1 -1
  28. lionagi/core/flow/polyflow/chat.py +15 -12
  29. lionagi/core/{prompt/action_template.py → form/action_form.py} +2 -2
  30. lionagi/core/{prompt → form}/field_validator.py +40 -31
  31. lionagi/core/form/form.py +302 -0
  32. lionagi/core/form/mixin.py +214 -0
  33. lionagi/core/{prompt/scored_template.py → form/scored_form.py} +2 -2
  34. lionagi/core/generic/__init__.py +37 -0
  35. lionagi/core/generic/action.py +26 -0
  36. lionagi/core/generic/component.py +457 -0
  37. lionagi/core/generic/condition.py +44 -0
  38. lionagi/core/generic/data_logger.py +305 -0
  39. lionagi/core/generic/edge.py +110 -0
  40. lionagi/core/generic/mail.py +90 -0
  41. lionagi/core/generic/mailbox.py +36 -0
  42. lionagi/core/generic/node.py +285 -0
  43. lionagi/core/generic/relation.py +70 -0
  44. lionagi/core/generic/signal.py +22 -0
  45. lionagi/core/generic/structure.py +362 -0
  46. lionagi/core/generic/transfer.py +20 -0
  47. lionagi/core/generic/work.py +40 -0
  48. lionagi/core/graph/graph.py +126 -0
  49. lionagi/core/graph/tree.py +190 -0
  50. lionagi/core/mail/__init__.py +0 -8
  51. lionagi/core/mail/mail_manager.py +12 -10
  52. lionagi/core/mail/schema.py +9 -2
  53. lionagi/core/messages/__init__.py +0 -3
  54. lionagi/core/messages/schema.py +17 -225
  55. lionagi/core/session/__init__.py +0 -3
  56. lionagi/core/session/session.py +25 -23
  57. lionagi/core/tool/__init__.py +3 -1
  58. lionagi/core/tool/tool.py +28 -0
  59. lionagi/core/tool/tool_manager.py +75 -75
  60. lionagi/integrations/chunker/chunk.py +7 -7
  61. lionagi/integrations/config/oai_configs.py +4 -4
  62. lionagi/integrations/loader/load.py +6 -6
  63. lionagi/integrations/loader/load_util.py +8 -8
  64. lionagi/libs/ln_api.py +3 -3
  65. lionagi/libs/ln_parse.py +43 -6
  66. lionagi/libs/ln_validate.py +288 -0
  67. lionagi/libs/sys_util.py +28 -6
  68. lionagi/tests/libs/test_async.py +0 -0
  69. lionagi/tests/libs/test_field_validators.py +353 -0
  70. lionagi/tests/test_core/test_base_branch.py +0 -1
  71. lionagi/tests/test_core/test_branch.py +3 -0
  72. lionagi/tests/test_core/test_session_base_util.py +1 -0
  73. lionagi/version.py +1 -1
  74. {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/METADATA +1 -1
  75. lionagi-0.1.0.dist-info/RECORD +136 -0
  76. lionagi/core/prompt/prompt_template.py +0 -312
  77. lionagi/core/schema/__init__.py +0 -22
  78. lionagi/core/schema/action_node.py +0 -29
  79. lionagi/core/schema/base_mixin.py +0 -296
  80. lionagi/core/schema/base_node.py +0 -199
  81. lionagi/core/schema/condition.py +0 -24
  82. lionagi/core/schema/data_logger.py +0 -354
  83. lionagi/core/schema/data_node.py +0 -93
  84. lionagi/core/schema/prompt_template.py +0 -67
  85. lionagi/core/schema/structure.py +0 -912
  86. lionagi/core/tool/manual.py +0 -1
  87. lionagi-0.0.315.dist-info/RECORD +0 -121
  88. /lionagi/core/{branch/base → execute}/__init__.py +0 -0
  89. /lionagi/core/flow/{base/baseflow.py → baseflow.py} +0 -0
  90. /lionagi/core/flow/{base/__init__.py → mono_chat_mixin.py} +0 -0
  91. /lionagi/core/{prompt → form}/__init__.py +0 -0
  92. /lionagi/{tests/test_integrations → core/graph}/__init__.py +0 -0
  93. /lionagi/tests/{test_libs → integrations}/__init__.py +0 -0
  94. /lionagi/tests/{test_libs/test_async.py → libs/__init__.py} +0 -0
  95. /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
  96. /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
  97. /lionagi/tests/{test_libs → libs}/test_func_call.py +0 -0
  98. /lionagi/tests/{test_libs → libs}/test_nested.py +0 -0
  99. /lionagi/tests/{test_libs → libs}/test_parse.py +0 -0
  100. /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
  101. {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/LICENSE +0 -0
  102. {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/WHEEL +0 -0
  103. {lionagi-0.0.315.dist-info → lionagi-0.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,233 @@
1
+ import contextlib
2
+ from lionagi.libs import convert, AsyncUtil, ParseUtil
3
+ from lionagi.core.generic import ActionNode, Edge
4
+ from lionagi.core.mail.schema import BaseMail
5
+ from lionagi.core.messages.schema import System, Instruction
6
+
7
+ from lionagi.core.branch.branch import Branch
8
+ from lionagi.core.execute.base_executor import BaseExecutor
9
+
10
+
11
+ class BranchExecutor(Branch, BaseExecutor):
12
+
13
+ async def forward(self) -> None:
14
+ for key in list(self.pending_ins.keys()):
15
+ while self.pending_ins[key]:
16
+ mail = self.pending_ins[key].popleft()
17
+ if mail.category == "start":
18
+ self._process_start(mail)
19
+ elif mail.category == "node":
20
+ await self._process_node(mail)
21
+ elif mail.category == "node_list":
22
+ self._process_node_list(mail)
23
+ elif mail.category == "condition":
24
+ self._process_condition(mail)
25
+ elif mail.category == "end":
26
+ self._process_end(mail)
27
+
28
+ async def execute(self, refresh_time=1) -> None:
29
+ while not self.execute_stop:
30
+ await self.forward()
31
+ await AsyncUtil.sleep(refresh_time)
32
+
33
+ async def _process_node(self, mail: BaseMail):
34
+ if isinstance(mail.package["package"], System):
35
+ self._system_process(mail.package["package"], verbose=self.verbose)
36
+ self.send(
37
+ mail.sender_id,
38
+ "node_id",
39
+ {"request_source": self.id_, "package": mail.package["package"].id_},
40
+ )
41
+
42
+ elif isinstance(mail.package["package"], Instruction):
43
+ await self._instruction_process(
44
+ mail.package["package"], verbose=self.verbose
45
+ )
46
+ self.send(
47
+ mail.sender_id,
48
+ "node_id",
49
+ {"request_source": self.id_, "package": mail.package["package"].id_},
50
+ )
51
+
52
+ elif isinstance(mail.package["package"], ActionNode):
53
+ await self._action_process(mail.package["package"], verbose=self.verbose)
54
+ self.send(
55
+ mail.sender_id,
56
+ "node_id",
57
+ {
58
+ "request_source": self.id_,
59
+ "package": mail.package["package"].instruction.id_,
60
+ },
61
+ )
62
+ else:
63
+ try:
64
+ await self._agent_process(mail.package["package"], verbose=self.verbose)
65
+ self.send(
66
+ mail.sender_id,
67
+ "node_id",
68
+ {
69
+ "request_source": self.id_,
70
+ "package": mail.package["package"].id_,
71
+ },
72
+ )
73
+ except:
74
+ raise ValueError(f"Invalid mail to process. Mail:{mail}")
75
+
76
+ def _process_node_list(self, mail: BaseMail):
77
+ self.send(mail.sender_id, "end", {"request_source": self.id_, "package": "end"})
78
+ self.execute_stop = True
79
+ raise ValueError("Multiple path selection is currently not supported")
80
+
81
+ def _process_condition(self, mail: BaseMail):
82
+ relationship: Edge = mail.package["package"]
83
+ check_result = relationship.condition(self)
84
+ back_mail = {
85
+ "from": self.id_,
86
+ "edge_id": mail.package["package"].id_,
87
+ "check_result": check_result,
88
+ }
89
+ self.send(
90
+ mail.sender_id,
91
+ "condition",
92
+ {"request_source": self.id_, "package": back_mail},
93
+ )
94
+
95
+ def _system_process(self, system: System, verbose=True, context_verbose=False):
96
+ from lionagi.libs import SysUtil
97
+
98
+ SysUtil.check_import("IPython")
99
+ from IPython.display import Markdown, display
100
+
101
+ if verbose:
102
+ print(f"------------------Welcome: {system.sender}--------------------")
103
+ with contextlib.suppress(Exception):
104
+ system.content = ParseUtil.fuzzy_parse_json(system.content)
105
+ display(Markdown(f"system: {convert.to_str(system.system_info)}"))
106
+ if self.context and context_verbose:
107
+ display(Markdown(f"context: {convert.to_str(self.context)}"))
108
+
109
+ self.add_message(system=system)
110
+
111
+ async def _instruction_process(
112
+ self, instruction: Instruction, verbose=True, **kwargs
113
+ ):
114
+ from lionagi.libs import SysUtil
115
+
116
+ SysUtil.check_import("IPython")
117
+ from IPython.display import Markdown, display
118
+
119
+ if verbose:
120
+ with contextlib.suppress(Exception):
121
+ instruction.content = ParseUtil.fuzzy_parse_json(instruction.content)
122
+ display(
123
+ Markdown(
124
+ f"{instruction.sender}: {convert.to_str(instruction.instruct)}"
125
+ )
126
+ )
127
+
128
+ if self.context:
129
+ instruction.content.update({"context": self.context})
130
+ self.context_log.append(self.context)
131
+ self.context = None
132
+
133
+ result = await self.chat(instruction, **kwargs)
134
+ with contextlib.suppress(Exception):
135
+ result = ParseUtil.fuzzy_parse_json(result)
136
+ if "response" in result.keys():
137
+ result = result["response"]
138
+ if verbose and len(self.assistant_responses) != 0:
139
+ display(
140
+ Markdown(
141
+ f"{self.last_assistant_response.sender}: {convert.to_str(result)}"
142
+ )
143
+ )
144
+ print("-----------------------------------------------------")
145
+
146
+ self.execution_responses.append(result)
147
+
148
+ async def _action_process(self, action: ActionNode, verbose=True):
149
+ from lionagi.libs import SysUtil
150
+
151
+ SysUtil.check_import("IPython")
152
+ from IPython.display import Markdown, display
153
+
154
+ try:
155
+ func = getattr(self, action.action)
156
+ except:
157
+ raise ValueError(f"{action.action} is not a valid action")
158
+
159
+ if verbose:
160
+ display(
161
+ Markdown(
162
+ f"{action.instruction.sender}: {convert.to_str(action.instruction.instruct)}"
163
+ )
164
+ )
165
+
166
+ if action.tools:
167
+ self.register_tools(action.tools)
168
+ if self.context:
169
+ result = await func(
170
+ action.instruction.content["instruction"],
171
+ context=self.context,
172
+ tools=action.tools,
173
+ **action.action_kwargs,
174
+ )
175
+ self.context = None
176
+ else:
177
+ result = await func(
178
+ action.instruction.content, tools=action.tools, **action.action_kwargs
179
+ )
180
+
181
+ if verbose and len(self.assistant_responses) != 0:
182
+ display(
183
+ Markdown(
184
+ f"{self.last_assistant_response.sender}: {convert.to_str(result)}"
185
+ )
186
+ )
187
+ print("-----------------------------------------------------")
188
+
189
+ self.execution_responses.append(result)
190
+
191
+ async def _agent_process(self, agent, verbose=True):
192
+ """
193
+ Processes an agent.
194
+
195
+ Args:
196
+ agent: The agent to process.
197
+ verbose (bool): A flag indicating whether to provide verbose output (default: True).
198
+ """
199
+ context = self.responses
200
+ if verbose:
201
+ print("*****************************************************")
202
+ result = await agent.execute(context)
203
+
204
+ if verbose:
205
+ print("*****************************************************")
206
+
207
+ self.context = result
208
+ self.responses.append(result)
209
+
210
+ def _process_start(self, mail):
211
+ """
212
+ Processes a start mail.
213
+
214
+ Args:
215
+ mail (BaseMail): The start mail to process.
216
+ """
217
+ start_mail_content = mail.package
218
+ self.context = start_mail_content["context"]
219
+ self.send(
220
+ start_mail_content["structure_id"],
221
+ "start",
222
+ {"request_source": self.id_, "package": "start"},
223
+ )
224
+
225
+ def _process_end(self, mail: BaseMail):
226
+ """
227
+ Processes an end mail.
228
+
229
+ Args:
230
+ mail (BaseMail): The end mail to process.
231
+ """
232
+ self.execute_stop = True
233
+ self.send(mail.sender_id, "end", {"request_source": self.id_, "package": "end"})
@@ -0,0 +1,131 @@
1
+ import asyncio
2
+ from pydantic import Field
3
+
4
+ from lionagi.libs import AsyncUtil
5
+
6
+ from lionagi.core.mail.schema import BaseMail, MailTransfer
7
+ from lionagi.core.mail.mail_manager import MailManager
8
+ from lionagi.core.execute.base_executor import BaseExecutor
9
+ from lionagi.core.execute.branch_executor import BranchExecutor
10
+
11
+
12
+ class InstructionMapExecutor(BaseExecutor):
13
+ branches: dict[str, BranchExecutor] = Field(
14
+ default_factory=dict, description="The branches of the instruction mapping."
15
+ )
16
+ structure_id: str = Field("", description="The ID of the executable structure.")
17
+ mail_transfer: MailTransfer = Field(
18
+ default_factory=MailTransfer, description="The mail transfer."
19
+ )
20
+ branch_kwargs: dict = Field(
21
+ default_factory=dict,
22
+ description="The keyword arguments for the initializing the branches.",
23
+ )
24
+ num_end_branches: int = Field(0, description="The number of end branches.")
25
+ mail_manager: MailManager = Field(
26
+ default_factory=MailManager, description="The mail manager."
27
+ )
28
+
29
+ def __init__(self, **kwargs):
30
+ super().__init__(**kwargs)
31
+ self.mail_manager = MailManager([self.mail_transfer])
32
+
33
+ def transfer_ins(self):
34
+ for key in list(self.pending_ins.keys()):
35
+ while self.pending_ins[key]:
36
+ mail: BaseMail = self.pending_ins[key].popleft()
37
+ if mail.category == "start":
38
+ self._process_start(mail)
39
+ elif mail.category == "node_list":
40
+ self._process_node_list(mail)
41
+ elif (
42
+ (mail.category == "node")
43
+ or (mail.category == "condition")
44
+ or (mail.category == "end")
45
+ ):
46
+ mail.sender_id = self.mail_transfer.id_
47
+ mail.recipient_id = mail.package["request_source"]
48
+ self.mail_transfer.pending_outs.append(mail)
49
+
50
+ def transfer_outs(self):
51
+ for key in list(self.mail_transfer.pending_ins.keys()):
52
+ while self.mail_transfer.pending_ins[key]:
53
+ mail: BaseMail = self.mail_transfer.pending_ins[key].popleft()
54
+ if mail.category == "end":
55
+ self.num_end_branches += 1
56
+ if self.num_end_branches == len(
57
+ self.branches
58
+ ): # tell when structure should stop
59
+ mail.sender_id = self.id_
60
+ mail.recipient_id = self.structure_id
61
+ self.pending_outs.append(mail)
62
+ self.execute_stop = True
63
+ else:
64
+ mail.sender_id = self.id_
65
+ mail.recipient_id = self.structure_id
66
+ self.pending_outs.append(mail)
67
+
68
+ def _process_start(self, start_mail: BaseMail):
69
+ branch = BranchExecutor(verbose=self.verbose, **self.branch_kwargs)
70
+ branch.context = start_mail.package["context"]
71
+ self.branches[branch.id_] = branch
72
+ self.mail_manager.add_sources([branch])
73
+ self.structure_id = start_mail.package["structure_id"]
74
+ mail = BaseMail(
75
+ sender_id=self.id_,
76
+ recipient_id=self.structure_id,
77
+ category="start",
78
+ package={"request_source": branch.id_, "package": "start"},
79
+ )
80
+ self.pending_outs.append(mail)
81
+
82
+ def _process_node_list(self, nl_mail: BaseMail):
83
+ source_branch_id = nl_mail.package["request_source"]
84
+ node_list = nl_mail.package["package"]
85
+ shared_context = self.branches[source_branch_id].context
86
+ shared_context_log = self.branches[source_branch_id].context_log
87
+ base_branch = self.branches[source_branch_id]
88
+
89
+ first_node_mail = BaseMail(
90
+ sender_id=self.mail_transfer.id_,
91
+ recipient_id=source_branch_id,
92
+ category="node",
93
+ package={"request_source": source_branch_id, "package": node_list[0]},
94
+ )
95
+ self.mail_transfer.pending_outs.append(first_node_mail)
96
+
97
+ for i in range(1, len(node_list)):
98
+ branch = BranchExecutor(
99
+ verbose=self.verbose,
100
+ messages=base_branch.messages.copy(),
101
+ service=base_branch.service,
102
+ llmconfig=base_branch.llmconfig,
103
+ datalogger=base_branch.datalogger,
104
+ )
105
+ branch.context = shared_context
106
+ branch.context_log = shared_context_log
107
+ self.branches[branch.id_] = branch
108
+ self.mail_manager.add_sources([branch])
109
+ node_mail = BaseMail(
110
+ sender_id=self.mail_transfer.id_,
111
+ recipient_id=branch.id_,
112
+ category="node",
113
+ package={"request_source": source_branch_id, "package": node_list[i]},
114
+ )
115
+ self.mail_transfer.pending_outs.append(node_mail)
116
+
117
+ async def forward(self):
118
+ self.transfer_ins()
119
+ self.transfer_outs()
120
+ self.mail_manager.collect_all()
121
+ self.mail_manager.send_all()
122
+ tasks = [
123
+ branch.forward() for branch in self.branches.values() if branch.pending_ins
124
+ ]
125
+ await asyncio.gather(*tasks)
126
+ return
127
+
128
+ async def execute(self, refresh_time=1):
129
+ while not self.execute_stop:
130
+ await self.forward()
131
+ await asyncio.sleep(refresh_time)
@@ -0,0 +1,218 @@
1
+ from typing import overload
2
+
3
+ from abc import ABC
4
+ from collections import deque
5
+
6
+ from lionagi.libs import AsyncUtil, convert
7
+
8
+ from lionagi.core.generic import BaseNode, ActionNode, ActionSelection, Edge
9
+ from lionagi.core.tool import Tool
10
+ from lionagi.core.mail.schema import BaseMail
11
+ from lionagi.core.execute.base_executor import BaseExecutor
12
+
13
+ from lionagi.libs import AsyncUtil
14
+ from lionagi.core.generic import Node, ActionSelection, Edge
15
+ from lionagi.core.tool import Tool
16
+
17
+ from lionagi.core.mail.schema import BaseMail
18
+ from lionagi.core.graph.graph import Graph
19
+
20
+
21
+ class StructureExecutor(BaseExecutor, Graph):
22
+
23
+ condition_check_result: bool | None = None
24
+
25
+ async def check_edge_condition(self, edge: Edge, executable_id, request_source):
26
+ if edge.condition.source_type == "structure":
27
+ return edge.condition(self)
28
+
29
+ elif edge.condition.source_type == "executable":
30
+ return await self._check_executable_condition(
31
+ edge, executable_id, request_source
32
+ )
33
+
34
+ else:
35
+ raise ValueError("Invalid source_type.")
36
+
37
+ def _process_edge_condition(self, edge_id):
38
+ """
39
+ Process the condition of a edge.
40
+
41
+ Args:
42
+ edge_id (str): The ID of the edge.
43
+ """
44
+ for key in list(self.pending_ins.keys()):
45
+ skipped_requests = deque()
46
+ while self.pending_ins[key]:
47
+ mail: BaseMail = self.pending_ins[key].popleft()
48
+ if (
49
+ mail.category == "condition"
50
+ and mail.package["package"]["edge_id"] == edge_id
51
+ ):
52
+ self.condition_check_result = mail.package["package"][
53
+ "check_result"
54
+ ]
55
+ else:
56
+ skipped_requests.append(mail)
57
+ self.pending_ins[key] = skipped_requests
58
+
59
+ async def _check_executable_condition(
60
+ self, edge: Edge, executable_id, request_source
61
+ ):
62
+ self.send(
63
+ recipient_id=executable_id,
64
+ category="condition",
65
+ package={"request_source": request_source, "package": edge},
66
+ )
67
+ while self.condition_check_result is None:
68
+ await AsyncUtil.sleep(0.1)
69
+ self._process_edge_condition(edge.id_)
70
+ continue
71
+ check_result = self.condition_check_result
72
+ self.condition_check_result = None
73
+ return check_result
74
+
75
+ async def _handle_node_id(self, mail: BaseMail):
76
+ if mail.package["package"] not in self.internal_nodes:
77
+ raise ValueError(
78
+ f"Node {mail.package} does not exist in the structure {self.id_}"
79
+ )
80
+ return await self._next_node(
81
+ self.internal_nodes[mail.package["package"]],
82
+ mail.sender_id,
83
+ mail.package["request_source"],
84
+ )
85
+
86
+ async def _handle_node(self, mail: BaseMail):
87
+ if not self.node_exist(mail.package["package"]):
88
+ raise ValueError(
89
+ f"Node {mail.package} does not exist in the structure {self.id_}"
90
+ )
91
+ return await self._next_node(
92
+ mail.package["package"], mail.sender_id, mail.package["request_source"]
93
+ )
94
+
95
+ async def _handle_mail(self, mail: BaseMail):
96
+
97
+ if mail.category == "start":
98
+ return self.get_heads()
99
+
100
+ elif mail.category == "end":
101
+ self.execute_stop = True
102
+ return None
103
+
104
+ elif mail.category == "node_id":
105
+ try:
106
+ return await self._handle_node_id(mail)
107
+ except Exception as e:
108
+ raise ValueError(f"Error handling node id: {e}") from e
109
+
110
+ elif mail.category == "node" and isinstance(mail.package["package"], BaseNode):
111
+ try:
112
+ return await self._handle_node(mail)
113
+ except Exception as e:
114
+ raise ValueError(f"Error handling node: {e}") from e
115
+
116
+ else:
117
+ raise ValueError(f"Invalid mail type for structure")
118
+
119
+ async def _next_node(self, current_node: BaseNode, executable_id, request_source):
120
+ """
121
+ Get the next step nodes based on the current node.
122
+
123
+ Args:
124
+ current_node (Node): The current node.
125
+ executable_id (str): The ID of the executable.
126
+
127
+ Returns:
128
+ list[Node]: The next step nodes.
129
+ """
130
+ next_nodes = []
131
+ next_edges: dict[Edge] = self.get_node_edges(current_node, node_as="out")
132
+ for edge in convert.to_list(list(next_edges.values())):
133
+ if edge.bundle:
134
+ continue
135
+ if edge.condition:
136
+ check = await self.check_edge_condition(
137
+ edge, executable_id, request_source
138
+ )
139
+ if not check:
140
+ continue
141
+ node = self.internal_nodes[edge.tail]
142
+ further_edges: dict[Edge] = self.get_node_edges(node, node_as="out")
143
+ bundled_nodes = deque()
144
+ for f_edge in convert.to_list(list(further_edges.values())):
145
+ if f_edge.bundle:
146
+ bundled_nodes.append(self.internal_nodes[f_edge.tail])
147
+ if bundled_nodes:
148
+ node = self.parse_bundled_to_action(node, bundled_nodes)
149
+ next_nodes.append(node)
150
+ return next_nodes
151
+
152
+ def _send_mail(self, next_nodes: list | None, mail: BaseMail):
153
+ if not next_nodes: # tail
154
+ self.send(
155
+ recipient_id=mail.sender_id,
156
+ category="end",
157
+ package={
158
+ "request_source": mail.package["request_source"],
159
+ "package": "end",
160
+ },
161
+ )
162
+ else:
163
+ if len(next_nodes) == 1:
164
+ self.send(
165
+ recipient_id=mail.sender_id,
166
+ category="node",
167
+ package={
168
+ "request_source": mail.package["request_source"],
169
+ "package": next_nodes[0],
170
+ },
171
+ )
172
+ else:
173
+ self.send(
174
+ recipient_id=mail.sender_id,
175
+ category="node_list",
176
+ package={
177
+ "request_source": mail.package["request_source"],
178
+ "package": next_nodes,
179
+ },
180
+ )
181
+
182
+ @staticmethod
183
+ def parse_bundled_to_action(instruction: Node, bundled_nodes: deque):
184
+ action_node = ActionNode(instruction=instruction)
185
+ while bundled_nodes:
186
+ node = bundled_nodes.popleft()
187
+ if isinstance(node, ActionSelection):
188
+ action_node.action = node.action
189
+ action_node.action_kwargs = node.action_kwargs
190
+ elif isinstance(node, Tool):
191
+ action_node.tools.append(node)
192
+ else:
193
+ raise ValueError("Invalid bundles nodes")
194
+ return action_node
195
+
196
+ async def forward(self) -> None:
197
+ """
198
+ Process the pending incoming mails and perform the corresponding actions.
199
+ """
200
+ for key in list(self.pending_ins.keys()):
201
+ while self.pending_ins[key]:
202
+ mail: BaseMail = self.pending_ins[key].popleft()
203
+ try:
204
+ if mail == "end":
205
+ self.execute_stop = True
206
+ return
207
+ next_nodes = await self._handle_mail(mail)
208
+ self._send_mail(next_nodes, mail)
209
+ except Exception as e:
210
+ raise ValueError(f"Error handling mail: {e}") from e
211
+
212
+ async def execute(self, refresh_time=1):
213
+ if not self.acyclic:
214
+ raise ValueError("Structure is not acyclic")
215
+
216
+ while not self.execute_stop:
217
+ await self.forward()
218
+ await AsyncUtil.sleep(refresh_time)
@@ -6,9 +6,9 @@ process instructions, system messages, and invoke tools during the conversation.
6
6
  """
7
7
 
8
8
  from typing import Callable
9
- from .chat import MonoChat
10
- from lionagi.core.schema import Tool
11
- from lionagi.core.messages import Instruction
9
+ from lionagi.core.flow.monoflow.chat import MonoChat
10
+ from lionagi.core.tool import Tool
11
+ from lionagi.core.messages.schema import Instruction
12
12
 
13
13
 
14
14
  class MonoReAct(MonoChat):
@@ -235,4 +235,4 @@ class MonoReAct(MonoChat):
235
235
  instruction=instruction,
236
236
  )
237
237
  _out = await self.chat(_prompt, sender=sender, **kwargs)
238
- return _out if out else None
238
+ return _out if out else None
@@ -7,7 +7,7 @@ during the chat conversation. It extends the BaseMonoFlow and MonoChatMixin clas
7
7
 
8
8
  from typing import Any
9
9
 
10
- from lionagi.core.flow.base.baseflow import BaseMonoFlow
10
+ from lionagi.core.flow.baseflow import BaseMonoFlow
11
11
  from lionagi.core.flow.monoflow.chat_mixin import MonoChatMixin
12
12
 
13
13
 
@@ -24,7 +24,7 @@ class MonoChat(BaseMonoFlow, MonoChatMixin):
24
24
  Initializes the MonoChat instance.
25
25
 
26
26
  async chat(self, instruction=None, context=None, sender=None, system=None, tools=False,
27
- out=True, invoke=True, output_fields=None, prompt_template=None, **kwargs) -> Any:
27
+ out=True, invoke=True, output_fields=None, form=None, **kwargs) -> Any:
28
28
  Performs a chat conversation with an LLM, processing instructions and system messages,
29
29
  and optionally invoking tools.
30
30
  """
@@ -48,7 +48,7 @@ class MonoChat(BaseMonoFlow, MonoChatMixin):
48
48
  out: bool = True,
49
49
  invoke: bool = True,
50
50
  output_fields=None,
51
- prompt_template=None,
51
+ form=None,
52
52
  **kwargs,
53
53
  ) -> Any:
54
54
  """
@@ -64,7 +64,7 @@ class MonoChat(BaseMonoFlow, MonoChatMixin):
64
64
  out (bool): If True, outputs the chat response.
65
65
  invoke (bool): If True, invokes tools as part of the chat.
66
66
  output_fields (Optional[Any]): The output fields for the chat.
67
- prompt_template (Optional[Any]): The prompt template for the chat.
67
+ form (Optional[Any]): The prompt template for the chat.
68
68
  **kwargs: Arbitrary keyword arguments for chat completion.
69
69
 
70
70
  Returns:
@@ -79,7 +79,7 @@ class MonoChat(BaseMonoFlow, MonoChatMixin):
79
79
  context=context,
80
80
  sender=sender,
81
81
  system=system,
82
- prompt_template=prompt_template,
82
+ form=form,
83
83
  tools=tools,
84
84
  output_fields=output_fields,
85
85
  **kwargs,
@@ -91,5 +91,5 @@ class MonoChat(BaseMonoFlow, MonoChatMixin):
91
91
  invoke=invoke,
92
92
  out=out,
93
93
  output_fields=output_fields,
94
- prompt_template=prompt_template,
94
+ form=form,
95
95
  )