lionagi 0.0.305__py3-none-any.whl → 0.0.307__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.
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"]