lionagi 0.0.305__py3-none-any.whl → 0.0.307__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. lionagi/__init__.py +2 -5
  2. lionagi/core/__init__.py +7 -4
  3. lionagi/core/agent/__init__.py +3 -0
  4. lionagi/core/agent/base_agent.py +46 -0
  5. lionagi/core/branch/__init__.py +4 -0
  6. lionagi/core/branch/base/__init__.py +0 -0
  7. lionagi/core/branch/base_branch.py +100 -78
  8. lionagi/core/branch/branch.py +22 -34
  9. lionagi/core/branch/branch_flow_mixin.py +3 -7
  10. lionagi/core/branch/executable_branch.py +192 -0
  11. lionagi/core/branch/util.py +77 -162
  12. lionagi/core/direct/__init__.py +13 -0
  13. lionagi/core/direct/parallel_predict.py +127 -0
  14. lionagi/core/direct/parallel_react.py +0 -0
  15. lionagi/core/direct/parallel_score.py +0 -0
  16. lionagi/core/direct/parallel_select.py +0 -0
  17. lionagi/core/direct/parallel_sentiment.py +0 -0
  18. lionagi/core/direct/predict.py +174 -0
  19. lionagi/core/direct/react.py +33 -0
  20. lionagi/core/direct/score.py +163 -0
  21. lionagi/core/direct/select.py +144 -0
  22. lionagi/core/direct/sentiment.py +51 -0
  23. lionagi/core/direct/utils.py +83 -0
  24. lionagi/core/flow/__init__.py +0 -3
  25. lionagi/core/flow/monoflow/{mono_react.py → ReAct.py} +52 -9
  26. lionagi/core/flow/monoflow/__init__.py +9 -0
  27. lionagi/core/flow/monoflow/{mono_chat.py → chat.py} +11 -11
  28. lionagi/core/flow/monoflow/{mono_chat_mixin.py → chat_mixin.py} +33 -27
  29. lionagi/core/flow/monoflow/{mono_followup.py → followup.py} +7 -6
  30. lionagi/core/flow/polyflow/__init__.py +1 -0
  31. lionagi/core/flow/polyflow/{polychat.py → chat.py} +15 -3
  32. lionagi/core/mail/__init__.py +8 -0
  33. lionagi/core/mail/mail_manager.py +88 -40
  34. lionagi/core/mail/schema.py +32 -6
  35. lionagi/core/messages/__init__.py +3 -0
  36. lionagi/core/messages/schema.py +56 -25
  37. lionagi/core/prompt/__init__.py +0 -0
  38. lionagi/core/prompt/prompt_template.py +0 -0
  39. lionagi/core/schema/__init__.py +7 -5
  40. lionagi/core/schema/action_node.py +29 -0
  41. lionagi/core/schema/base_mixin.py +56 -59
  42. lionagi/core/schema/base_node.py +35 -38
  43. lionagi/core/schema/condition.py +24 -0
  44. lionagi/core/schema/data_logger.py +98 -98
  45. lionagi/core/schema/data_node.py +19 -19
  46. lionagi/core/schema/prompt_template.py +0 -0
  47. lionagi/core/schema/structure.py +293 -190
  48. lionagi/core/session/__init__.py +1 -3
  49. lionagi/core/session/session.py +196 -214
  50. lionagi/core/tool/tool_manager.py +95 -103
  51. lionagi/integrations/__init__.py +1 -3
  52. lionagi/integrations/bridge/langchain_/documents.py +17 -18
  53. lionagi/integrations/bridge/langchain_/langchain_bridge.py +14 -14
  54. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +22 -22
  55. lionagi/integrations/bridge/llamaindex_/node_parser.py +12 -12
  56. lionagi/integrations/bridge/llamaindex_/reader.py +11 -11
  57. lionagi/integrations/bridge/llamaindex_/textnode.py +7 -7
  58. lionagi/integrations/config/openrouter_configs.py +0 -1
  59. lionagi/integrations/provider/oai.py +26 -26
  60. lionagi/integrations/provider/services.py +38 -38
  61. lionagi/libs/__init__.py +34 -1
  62. lionagi/libs/ln_api.py +211 -221
  63. lionagi/libs/ln_async.py +53 -60
  64. lionagi/libs/ln_convert.py +118 -120
  65. lionagi/libs/ln_dataframe.py +32 -33
  66. lionagi/libs/ln_func_call.py +334 -342
  67. lionagi/libs/ln_nested.py +99 -107
  68. lionagi/libs/ln_parse.py +175 -158
  69. lionagi/libs/sys_util.py +52 -52
  70. lionagi/tests/test_core/test_base_branch.py +427 -427
  71. lionagi/tests/test_core/test_branch.py +292 -292
  72. lionagi/tests/test_core/test_mail_manager.py +57 -57
  73. lionagi/tests/test_core/test_session.py +254 -266
  74. lionagi/tests/test_core/test_session_base_util.py +299 -300
  75. lionagi/tests/test_core/test_tool_manager.py +70 -74
  76. lionagi/tests/test_libs/test_nested.py +2 -7
  77. lionagi/tests/test_libs/test_parse.py +2 -2
  78. lionagi/version.py +1 -1
  79. {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/METADATA +4 -2
  80. lionagi-0.0.307.dist-info/RECORD +115 -0
  81. lionagi-0.0.305.dist-info/RECORD +0 -94
  82. {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/LICENSE +0 -0
  83. {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/WHEEL +0 -0
  84. {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,83 @@
1
+ import contextlib
2
+ from lionagi.libs import ParseUtil, StringMatch, convert, func_call
3
+
4
+
5
+ def _parse_out(out_):
6
+ if isinstance(out_, str):
7
+ try:
8
+ out_ = ParseUtil.md_to_json(out_)
9
+ except Exception:
10
+ with contextlib.suppress(Exception):
11
+ out_ = ParseUtil.fuzzy_parse_json(out_.strip("```json").strip("```"))
12
+ return out_
13
+
14
+
15
+ def _handle_single_out(
16
+ out_,
17
+ default_key,
18
+ choices=None,
19
+ to_type="dict",
20
+ to_type_kwargs=None,
21
+ to_default=True,
22
+ ):
23
+
24
+ if to_type_kwargs is None:
25
+ to_type_kwargs = {}
26
+ out_ = _parse_out(out_)
27
+
28
+ if default_key not in out_:
29
+ raise ValueError(f"Key {default_key} not found in output")
30
+
31
+ answer = out_[default_key]
32
+
33
+ if (
34
+ choices is not None
35
+ and answer not in choices
36
+ and convert.strip_lower(out_) in ["", "none", "null", "na", "n/a"]
37
+ ):
38
+ raise ValueError(f"Answer {answer} not in choices {choices}")
39
+
40
+ if to_type == "str":
41
+ out_[default_key] = convert.to_str(answer, **to_type_kwargs)
42
+
43
+ elif to_type == "num":
44
+ out_[default_key] = convert.to_num(answer, **to_type_kwargs)
45
+
46
+ return out_[default_key] if to_default and len(out_.keys()) == 1 else out_
47
+
48
+
49
+ def _handle_multi_out(
50
+ out_,
51
+ default_key,
52
+ choices=None,
53
+ to_type="dict",
54
+ to_type_kwargs=None,
55
+ to_default=True,
56
+ include_mapping=False,
57
+ ):
58
+ if to_type_kwargs is None:
59
+ to_type_kwargs = {}
60
+ if include_mapping:
61
+ for i in out_:
62
+ i[default_key] = _handle_single_out(
63
+ i[default_key],
64
+ choices=choices,
65
+ default_key=default_key,
66
+ to_type=to_type,
67
+ to_type_kwargs=to_type_kwargs,
68
+ to_default=to_default,
69
+ )
70
+ else:
71
+ _out = []
72
+ for i in out_:
73
+ i = _handle_single_out(
74
+ i,
75
+ choices=choices,
76
+ default_key=default_key,
77
+ to_type=to_type,
78
+ to_type_kwargs=to_type_kwargs,
79
+ to_default=to_default,
80
+ )
81
+ _out.append(i)
82
+
83
+ return out_ if len(out_) > 1 else out_[0]
@@ -1,3 +0,0 @@
1
- from .monoflow.mono_chat import MonoChat
2
-
3
- __all__ = ["MonoChat"]
@@ -1,4 +1,7 @@
1
- from .mono_chat import MonoChat
1
+ from lionagi.core.tool.tool_manager import func_to_tool
2
+ from typing import Callable
3
+ import lionagi.libs.ln_convert as convert
4
+ from .chat import MonoChat
2
5
  from lionagi.core.schema.base_node import Tool
3
6
  from lionagi.core.messages.schema import Instruction
4
7
 
@@ -39,23 +42,25 @@ class MonoReAct(MonoChat):
39
42
  try:
40
43
  try:
41
44
  return default.format(num_steps=num_steps)
42
- except:
45
+ except Exception:
43
46
  return default.format(instruction=instruction)
44
- except:
47
+ except Exception:
45
48
  return default
46
49
 
47
- def _create_followup_config(self, tools, **kwargs):
50
+ def _create_followup_config(self, tools, auto=True, **kwargs):
48
51
 
49
- if tools is not None:
50
- if isinstance(tools, list) and isinstance(tools[0], Tool):
51
- self.branch.tool_manager.register_tools(tools)
52
+ if tools is not None and (
53
+ isinstance(tools, list) and isinstance(tools[0], Tool)
54
+ ):
55
+ self.branch.tool_manager.register_tools(tools)
52
56
 
53
57
  if not self.branch.tool_manager.has_tools:
54
58
  raise ValueError("No tools found, You need to register tools")
55
59
 
56
60
  config = self.branch.tool_manager.parse_tool(tools=True, **kwargs)
57
61
  config["tool_parsed"] = True
58
- config["tool_choice"] = "auto"
62
+ if auto:
63
+ config["tool_choice"] = "auto"
59
64
  return config
60
65
 
61
66
  async def _handle_auto(
@@ -85,7 +90,7 @@ class MonoReAct(MonoChat):
85
90
  prompt_ = self._get_prompt(
86
91
  output_prompt, _output_prompt, instruction=instruction
87
92
  )
88
- return await self.chat(prompt_, sender=sender, **config)
93
+ return await self.chat(prompt_, sender=sender, out=out, **config)
89
94
 
90
95
  async def _ReAct(
91
96
  self,
@@ -152,6 +157,44 @@ class MonoReAct(MonoChat):
152
157
  if a:
153
158
  return a
154
159
 
160
+ async def _react(
161
+ self,
162
+ instruction=None,
163
+ context=None,
164
+ output_fields=None,
165
+ tools=None,
166
+ reason_prompt=None,
167
+ action_prompt=None,
168
+ **kwargs,
169
+ ):
170
+
171
+ config = self._create_followup_config(tools=tools, auto=False, **kwargs)
172
+
173
+ instruct = {
174
+ "requirement": "think step by step, perform reasoning and prepare action plan according to available tools only",
175
+ "task": convert.to_str(instruction),
176
+ }
177
+
178
+ extra_fields = output_fields or {}
179
+ output_fields = {
180
+ "reason": reason_prompt or "reasoning",
181
+ "action": action_prompt
182
+ or "the action(s) to take, in function call format. If no actions are needed return none",
183
+ }
184
+ output_fields = {**output_fields, **extra_fields}
185
+
186
+ out_ = await self.chat(
187
+ instruct, context=context, output_fields=output_fields, **config
188
+ )
189
+ print(out_)
190
+
191
+ if convert.strip_lower(out_["action"]) not in ["none", "no", "na", "nan"]:
192
+ res = await self._invoke_tools(content_={"action_request": out_["action"]})
193
+ for idx, item in enumerate(res):
194
+ out_[f"action_response_{idx}"] = item
195
+
196
+ return out_
197
+
155
198
  # TODO: auto_ReAct
156
199
 
157
200
 
@@ -0,0 +1,9 @@
1
+ from .chat import MonoChat
2
+ from .followup import MonoFollowup
3
+ from .ReAct import MonoReAct
4
+
5
+ __all__ = [
6
+ "MonoChat",
7
+ "MonoFollowup",
8
+ "MonoReAct",
9
+ ]
@@ -1,7 +1,7 @@
1
1
  from typing import Any
2
2
 
3
3
  from lionagi.core.flow.base.baseflow import BaseMonoFlow
4
- from lionagi.core.flow.monoflow.mono_chat_mixin import MonoChatMixin
4
+ from lionagi.core.flow.monoflow.chat_mixin import MonoChatMixin
5
5
 
6
6
 
7
7
  class MonoChat(BaseMonoFlow, MonoChatMixin):
@@ -25,18 +25,18 @@ class MonoChat(BaseMonoFlow, MonoChatMixin):
25
25
  a chat conversation with LLM, processing instructions and system messages, optionally invoking tools.
26
26
 
27
27
  Args:
28
- branch: The Branch instance to perform chat operations.
29
- instruction (Union[Instruction, str]): The instruction for the chat.
30
- context (Optional[Any]): Additional context for the chat.
31
- sender (Optional[str]): The sender of the chat message.
32
- system (Optional[Union[System, str, Dict[str, Any]]]): System message to be processed.
33
- tools (Union[bool, Tool, List[Tool], str, List[str]]): Specifies tools to be invoked.
34
- out (bool): If True, outputs the chat response.
35
- invoke (bool): If True, invokes tools as part of the chat.
36
- **kwargs: Arbitrary keyword arguments for chat completion.
28
+ branch: The Branch instance to perform chat operations.
29
+ instruction (Union[Instruction, str]): The instruction for the chat.
30
+ context (Optional[Any]): Additional context for the chat.
31
+ sender (Optional[str]): The sender of the chat message.
32
+ system (Optional[Union[System, str, Dict[str, Any]]]): System message to be processed.
33
+ tools (Union[bool, Tool, List[Tool], str, List[str]]): Specifies tools to be invoked.
34
+ out (bool): If True, outputs the chat response.
35
+ invoke (bool): If True, invokes tools as part of the chat.
36
+ **kwargs: Arbitrary keyword arguments for chat completion.
37
37
 
38
38
  Examples:
39
- >>> await ChatFlow.chat(branch, "Ask about user preferences")
39
+ >>> await ChatFlow.chat(branch, "Ask about user preferences")
40
40
  """
41
41
 
42
42
  config = self._create_chat_config(
@@ -36,32 +36,33 @@ class MonoChatConfigMixin(ABC):
36
36
  if "tool_parsed" in kwargs:
37
37
  kwargs.pop("tool_parsed")
38
38
  tool_kwarg = {"tools": tools}
39
- kwargs = {**tool_kwarg, **kwargs}
40
- else:
41
- if tools and self.branch.has_tools:
42
- kwargs = self.branch.tool_manager.parse_tool(tools=tools, **kwargs)
39
+ kwargs = tool_kwarg | kwargs
40
+ elif tools and self.branch.has_tools:
41
+ kwargs = self.branch.tool_manager.parse_tool(tools=tools, **kwargs)
43
42
 
44
43
  config = {**self.branch.llmconfig, **kwargs}
45
44
  if sender is not None:
46
- config.update({"sender": sender})
45
+ config["sender"] = sender
47
46
 
48
47
  return config
49
48
 
50
49
 
51
50
  class MonoChatInvokeMixin(ABC):
52
- async def _output(self, invoke, out, output_fields):
51
+ async def _output(self, invoke, out, output_fields, func_calls_=None):
52
+ # sourcery skip: use-contextlib-suppress
53
53
  content_ = self.branch.last_message_content
54
54
 
55
55
  if invoke:
56
56
  try:
57
- await self._invoke_tools(content_)
58
- except:
57
+ await self._invoke_tools(content_, func_calls_=func_calls_)
58
+ except Exception:
59
59
  pass
60
60
  if out:
61
61
  return self._return_response(content_, output_fields)
62
62
 
63
63
  @staticmethod
64
64
  def _return_response(content_, output_fields):
65
+ # sourcery skip: assign-if-exp, use-contextlib-suppress
65
66
  out_ = ""
66
67
 
67
68
  if len(content_.items()) == 1 and len(nested.get_flattened_keys(content_)) == 1:
@@ -75,29 +76,34 @@ class MonoChatInvokeMixin(ABC):
75
76
  else:
76
77
  out_ = ParseUtil.md_to_json(out_)
77
78
  out_ = StringMatch.correct_keys(output_fields=output_fields, out_=out_)
78
- except:
79
+ except Exception:
79
80
  pass
80
81
 
81
82
  return out_
82
83
 
83
- async def _invoke_tools(self, content_):
84
- tool_uses = content_
85
- func_calls = func_call.lcall(
86
- [convert.to_dict(i) for i in tool_uses["action_request"]],
87
- self.branch.tool_manager.get_function_call,
88
- )
84
+ async def _invoke_tools(self, content_=None, func_calls_=None):
85
+
86
+ if func_calls_ is None and content_ is not None:
87
+ tool_uses = content_
88
+ func_calls_ = func_call.lcall(
89
+ [convert.to_dict(i) for i in tool_uses["action_request"]],
90
+ self.branch.tool_manager.get_function_call,
91
+ )
89
92
 
90
- outs = await func_call.alcall(func_calls, self.branch.tool_manager.invoke)
93
+ outs = await func_call.alcall(func_calls_, self.branch.tool_manager.invoke)
91
94
  outs = convert.to_list(outs, flatten=True)
92
95
 
93
- for out_, f in zip(outs, func_calls):
94
- self.branch.add_message(
95
- response={
96
- "function": f[0],
97
- "arguments": f[1],
98
- "output": out_,
99
- }
100
- )
96
+ a = []
97
+ for out_, f in zip(outs, func_calls_):
98
+ res = {
99
+ "function": f[0],
100
+ "arguments": f[1],
101
+ "output": out_,
102
+ }
103
+ self.branch.add_message(response=res)
104
+ a.append(res)
105
+
106
+ return a
101
107
 
102
108
  def _process_chatcompletion(self, payload, completion, sender):
103
109
  if "choices" in completion:
@@ -113,9 +119,9 @@ class MonoChatInvokeMixin(ABC):
113
119
 
114
120
  async def _call_chatcompletion(self, sender=None, with_sender=False, **kwargs):
115
121
  messages = (
116
- self.branch.chat_messages
117
- if not with_sender
118
- else self.branch.chat_messages_with_sender
122
+ self.branch.chat_messages_with_sender
123
+ if with_sender
124
+ else self.branch.chat_messages
119
125
  )
120
126
  payload, completion = await self.branch.service.serve_chat(
121
127
  messages=messages, **kwargs
@@ -1,6 +1,6 @@
1
1
  from lionagi.core.messages.schema import Instruction
2
2
  from lionagi.core.schema.base_node import Tool
3
- from .mono_chat import MonoChat
3
+ from .chat import MonoChat
4
4
 
5
5
 
6
6
  class MonoFollowup(MonoChat):
@@ -41,16 +41,17 @@ class MonoFollowup(MonoChat):
41
41
  try:
42
42
  try:
43
43
  return default.format(num_followup=num_followup)
44
- except:
44
+ except Exception:
45
45
  return default.format(instruction=instruction)
46
- except:
46
+ except Exception:
47
47
  return default
48
48
 
49
49
  def _create_followup_config(self, tools, **kwargs):
50
50
 
51
- if tools is not None:
52
- if isinstance(tools, list) and isinstance(tools[0], Tool):
53
- self.branch.tool_manager.register_tools(tools)
51
+ if tools is not None and (
52
+ isinstance(tools, list) and isinstance(tools[0], Tool)
53
+ ):
54
+ self.branch.tool_manager.register_tools(tools)
54
55
 
55
56
  if not self.branch.tool_manager.has_tools:
56
57
  raise ValueError("No tools found, You need to register tools")
@@ -0,0 +1 @@
1
+ from .chat import PolyChat
@@ -6,7 +6,6 @@ from lionagi.libs.ln_async import AsyncUtil
6
6
  from lionagi.core.messages.schema import Instruction
7
7
  from lionagi.core.branch.branch import Branch
8
8
 
9
-
10
9
  from lionagi.core.flow.base.baseflow import BasePolyFlow
11
10
 
12
11
 
@@ -28,7 +27,7 @@ class PolyChat(BasePolyFlow):
28
27
  invoke: bool = True,
29
28
  output_fields=None,
30
29
  persist_path=None,
31
- branch_config={},
30
+ branch_config=None,
32
31
  explode=False,
33
32
  **kwargs,
34
33
  ) -> Any:
@@ -36,6 +35,8 @@ class PolyChat(BasePolyFlow):
36
35
  parallel chat
37
36
  """
38
37
 
38
+ if branch_config is None:
39
+ branch_config = {}
39
40
  return await self._parallel_chat(
40
41
  instruction,
41
42
  num_instances=num_instances,
@@ -67,6 +68,8 @@ class PolyChat(BasePolyFlow):
67
68
  persist_path=None,
68
69
  branch_config={},
69
70
  explode=False,
71
+ include_mapping=True,
72
+ default_key="response",
70
73
  **kwargs,
71
74
  ) -> Any:
72
75
  """
@@ -102,7 +105,16 @@ class PolyChat(BasePolyFlow):
102
105
  )
103
106
 
104
107
  branches[branch_.id_] = branch_
105
- return res_
108
+ if include_mapping:
109
+ return {
110
+ "instruction": ins_ or instruction,
111
+ "context": cxt_ or context,
112
+ "branch_id": branch_.id_,
113
+ default_key: res_,
114
+ }
115
+
116
+ else:
117
+ return res_
106
118
 
107
119
  async def _inner_2(i, ins_=None, cxt_=None):
108
120
  """returns num_instances of branches performing for same task/context"""
@@ -0,0 +1,8 @@
1
+ from .schema import BaseMail, StartMail
2
+ from .mail_manager import MailManager
3
+
4
+ __all__ = [
5
+ "BaseMail",
6
+ "StartMail",
7
+ "MailManager",
8
+ ]
@@ -1,49 +1,97 @@
1
- from typing import Dict, Any
2
1
  from collections import deque
3
- from lionagi.core.mail.schema import BaseMail
2
+ from lionagi.libs import AsyncUtil
3
+ from ..schema import BaseNode
4
+ from .schema import BaseMail
4
5
 
5
6
 
6
7
  class MailManager:
8
+ """
9
+ Manages the sending, receiving, and storage of mail items between various sources.
7
10
 
8
- def __init__(self, sources: Dict[str, Any]):
9
- self.sources = sources
11
+ This class acts as a central hub for managing mail transactions within a system. It allows for the addition
12
+ and deletion of sources, and it handles the collection and dispatch of mails to and from these sources.
13
+
14
+ Attributes:
15
+ sources (Dict[str, Any]): A dictionary mapping source identifiers to their attributes.
16
+ mails (Dict[str, Dict[str, deque]]): A nested dictionary storing queued mail items, organized by recipient
17
+ and sender.
18
+ """
19
+
20
+ def __init__(self, sources):
21
+ self.sources = {}
10
22
  self.mails = {}
11
- for key in self.sources.keys():
12
- self.mails[key] = {}
23
+ self.add_sources(sources)
24
+ self.execute_stop = False
25
+
26
+ def add_sources(self, sources):
27
+ if isinstance(sources, dict):
28
+ for _, v in sources.items():
29
+ if v.id_ not in self.sources:
30
+ self.sources[v.id_] = v
31
+ self.mails[v.id_] = {}
32
+ elif isinstance(sources, list):
33
+ for v in sources:
34
+ if v.id_ not in self.sources:
35
+ self.sources[v.id_] = v
36
+ self.mails[v.id_] = {}
13
37
 
14
38
  @staticmethod
15
- def create_mail(sender, recipient, category, package):
16
- return BaseMail(sender, recipient, category, package)
17
-
18
- def add_source(self, sources: Dict[str, Any]):
19
- for key in sources.keys():
20
- if key in self.sources:
21
- raise ValueError(f"{key} exists, please input a different name.")
22
- self.sources[key] = {}
23
-
24
- def delete_source(self, source_name):
25
- if source_name not in self.sources:
26
- raise ValueError(f"{source_name} does not exist.")
27
- self.sources.pop(source_name)
28
-
29
- def collect(self, sender):
30
- if sender not in self.sources:
31
- raise ValueError(f"{sender} does not exist.")
32
- while self.sources[sender].pending_outs:
33
- mail_ = self.sources[sender].pending_outs.popleft()
34
- if mail_.sender not in self.mails[mail_.recipient]:
35
- self.mails[mail_.recipient] = {mail_.sender: deque()}
36
- self.mails[mail_.recipient][mail_.sender].append(mail_)
37
-
38
- def send(self, to_name):
39
- if to_name not in self.sources:
40
- raise ValueError(f"{to_name} does not exist.")
41
- if not self.mails[to_name]:
39
+ def create_mail(sender_id, recipient_id, category, package):
40
+ return BaseMail(sender_id, recipient_id, category, package)
41
+
42
+ def add_source(self, sources: list[BaseNode]):
43
+ for source in sources:
44
+ if source.id_ in self.sources:
45
+ # raise ValueError(f"Source {source.id_} exists, please input a different name.")
46
+ continue
47
+ self.sources[source.id_] = source
48
+ self.mails[source.id_] = {}
49
+
50
+ def delete_source(self, source_id):
51
+ if source_id not in self.sources:
52
+ raise ValueError(f"Source {source_id} does not exist.")
53
+ # if self.mails[source_id]:
54
+ # raise ValueError(f"None empty pending mails in source {source_id}")
55
+ self.sources.pop(source_id)
56
+ self.mails.pop(source_id)
57
+
58
+ def collect(self, sender_id):
59
+ if sender_id not in self.sources:
60
+ raise ValueError(f"Sender source {sender_id} does not exist.")
61
+ while self.sources[sender_id].pending_outs:
62
+ mail_ = self.sources[sender_id].pending_outs.popleft()
63
+ if mail_.recipient_id not in self.sources:
64
+ raise ValueError(
65
+ f"Recipient source {mail_.recipient_id} does not exist"
66
+ )
67
+ if mail_.sender_id not in self.mails[mail_.recipient_id]:
68
+ self.mails[mail_.recipient_id] = {mail_.sender_id: deque()}
69
+ self.mails[mail_.recipient_id][mail_.sender_id].append(mail_)
70
+
71
+ def send(self, recipient_id):
72
+ if recipient_id not in self.sources:
73
+ raise ValueError(f"Recipient source {recipient_id} does not exist.")
74
+ if not self.mails[recipient_id]:
42
75
  return
43
- else:
44
- for key in list(self.mails[to_name].keys()):
45
- request = self.mails[to_name].pop(key)
46
- if key not in self.sources[to_name].pending_ins:
47
- self.sources[to_name].pending_ins[key] = request
48
- else:
49
- self.sources[to_name].pending_ins[key].append(request)
76
+ for key in list(self.mails[recipient_id].keys()):
77
+ mails_deque = self.mails[recipient_id].pop(key)
78
+ if key not in self.sources[recipient_id].pending_ins:
79
+ self.sources[recipient_id].pending_ins[key] = mails_deque
80
+ else:
81
+ while mails_deque:
82
+ mail_ = mails_deque.popleft()
83
+ self.sources[recipient_id].pending_ins[key].append(mail_)
84
+
85
+ def collect_all(self):
86
+ for ids in self.sources:
87
+ self.collect(ids)
88
+
89
+ def send_all(self):
90
+ for ids in self.sources:
91
+ self.send(ids)
92
+
93
+ async def execute(self, refresh_time=1):
94
+ while not self.execute_stop:
95
+ self.collect_all()
96
+ self.send_all()
97
+ await AsyncUtil.sleep(refresh_time)
@@ -1,18 +1,27 @@
1
+ from collections import deque
1
2
  from enum import Enum
2
3
 
4
+ from lionagi.core.schema.base_node import BaseRelatableNode
5
+
3
6
 
4
7
  class MailCategory(str, Enum):
5
8
  MESSAGES = "messages"
6
- TOOL = "tools"
7
- SERVICE = "provider"
9
+ TOOL = "tool"
10
+ SERVICE = "service"
8
11
  MODEL = "model"
12
+ NODE = "node"
13
+ NODE_LIST = "node_list"
14
+ NODE_ID = "node_id"
15
+ START = "start"
16
+ END = "end"
17
+ CONDITION = "condition"
9
18
 
10
19
 
11
20
  class BaseMail:
12
21
 
13
- def __init__(self, sender, recipient, category, package):
14
- self.sender = sender
15
- self.recipient = recipient
22
+ def __init__(self, sender_id, recipient_id, category, package):
23
+ self.sender_id = sender_id
24
+ self.recipient_id = recipient_id
16
25
  try:
17
26
  if isinstance(category, str):
18
27
  category = MailCategory(category)
@@ -26,5 +35,22 @@ class BaseMail:
26
35
  raise ValueError(
27
36
  f"Invalid request title. Valid titles are "
28
37
  f"{list(MailCategory)}, Error: {e}"
29
- )
38
+ ) from e
30
39
  self.package = package
40
+
41
+
42
+ class StartMail(BaseRelatableNode):
43
+
44
+ def __init__(self, **kwargs):
45
+ super().__init__(**kwargs)
46
+ self.pending_outs = deque()
47
+
48
+ def trigger(self, context, structure_id, executable_id):
49
+ start_mail_content = {"context": context, "structure_id": structure_id}
50
+ start_mail = BaseMail(
51
+ sender_id=self.id_,
52
+ recipient_id=executable_id,
53
+ category="start",
54
+ package=start_mail_content,
55
+ )
56
+ self.pending_outs.append(start_mail)
@@ -0,0 +1,3 @@
1
+ from .schema import System, Instruction, Response
2
+
3
+ __all__ = ["System", "Instruction", "Response"]