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.
- lionagi/__init__.py +2 -5
- lionagi/core/__init__.py +7 -4
- lionagi/core/agent/__init__.py +3 -0
- lionagi/core/agent/base_agent.py +46 -0
- lionagi/core/branch/__init__.py +4 -0
- lionagi/core/branch/base/__init__.py +0 -0
- lionagi/core/branch/base_branch.py +100 -78
- lionagi/core/branch/branch.py +22 -34
- lionagi/core/branch/branch_flow_mixin.py +3 -7
- lionagi/core/branch/executable_branch.py +192 -0
- lionagi/core/branch/util.py +77 -162
- lionagi/core/direct/__init__.py +13 -0
- lionagi/core/direct/parallel_predict.py +127 -0
- lionagi/core/direct/parallel_react.py +0 -0
- lionagi/core/direct/parallel_score.py +0 -0
- lionagi/core/direct/parallel_select.py +0 -0
- lionagi/core/direct/parallel_sentiment.py +0 -0
- lionagi/core/direct/predict.py +174 -0
- lionagi/core/direct/react.py +33 -0
- lionagi/core/direct/score.py +163 -0
- lionagi/core/direct/select.py +144 -0
- lionagi/core/direct/sentiment.py +51 -0
- lionagi/core/direct/utils.py +83 -0
- lionagi/core/flow/__init__.py +0 -3
- lionagi/core/flow/monoflow/{mono_react.py → ReAct.py} +52 -9
- lionagi/core/flow/monoflow/__init__.py +9 -0
- lionagi/core/flow/monoflow/{mono_chat.py → chat.py} +11 -11
- lionagi/core/flow/monoflow/{mono_chat_mixin.py → chat_mixin.py} +33 -27
- lionagi/core/flow/monoflow/{mono_followup.py → followup.py} +7 -6
- lionagi/core/flow/polyflow/__init__.py +1 -0
- lionagi/core/flow/polyflow/{polychat.py → chat.py} +15 -3
- lionagi/core/mail/__init__.py +8 -0
- lionagi/core/mail/mail_manager.py +88 -40
- lionagi/core/mail/schema.py +32 -6
- lionagi/core/messages/__init__.py +3 -0
- lionagi/core/messages/schema.py +56 -25
- lionagi/core/prompt/__init__.py +0 -0
- lionagi/core/prompt/prompt_template.py +0 -0
- lionagi/core/schema/__init__.py +7 -5
- lionagi/core/schema/action_node.py +29 -0
- lionagi/core/schema/base_mixin.py +56 -59
- lionagi/core/schema/base_node.py +35 -38
- lionagi/core/schema/condition.py +24 -0
- lionagi/core/schema/data_logger.py +98 -98
- lionagi/core/schema/data_node.py +19 -19
- lionagi/core/schema/prompt_template.py +0 -0
- lionagi/core/schema/structure.py +293 -190
- lionagi/core/session/__init__.py +1 -3
- lionagi/core/session/session.py +196 -214
- lionagi/core/tool/tool_manager.py +95 -103
- lionagi/integrations/__init__.py +1 -3
- lionagi/integrations/bridge/langchain_/documents.py +17 -18
- lionagi/integrations/bridge/langchain_/langchain_bridge.py +14 -14
- lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +22 -22
- lionagi/integrations/bridge/llamaindex_/node_parser.py +12 -12
- lionagi/integrations/bridge/llamaindex_/reader.py +11 -11
- lionagi/integrations/bridge/llamaindex_/textnode.py +7 -7
- lionagi/integrations/config/openrouter_configs.py +0 -1
- lionagi/integrations/provider/oai.py +26 -26
- lionagi/integrations/provider/services.py +38 -38
- lionagi/libs/__init__.py +34 -1
- lionagi/libs/ln_api.py +211 -221
- lionagi/libs/ln_async.py +53 -60
- lionagi/libs/ln_convert.py +118 -120
- lionagi/libs/ln_dataframe.py +32 -33
- lionagi/libs/ln_func_call.py +334 -342
- lionagi/libs/ln_nested.py +99 -107
- lionagi/libs/ln_parse.py +175 -158
- lionagi/libs/sys_util.py +52 -52
- lionagi/tests/test_core/test_base_branch.py +427 -427
- lionagi/tests/test_core/test_branch.py +292 -292
- lionagi/tests/test_core/test_mail_manager.py +57 -57
- lionagi/tests/test_core/test_session.py +254 -266
- lionagi/tests/test_core/test_session_base_util.py +299 -300
- lionagi/tests/test_core/test_tool_manager.py +70 -74
- lionagi/tests/test_libs/test_nested.py +2 -7
- lionagi/tests/test_libs/test_parse.py +2 -2
- lionagi/version.py +1 -1
- {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/METADATA +4 -2
- lionagi-0.0.307.dist-info/RECORD +115 -0
- lionagi-0.0.305.dist-info/RECORD +0 -94
- {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/LICENSE +0 -0
- {lionagi-0.0.305.dist-info → lionagi-0.0.307.dist-info}/WHEEL +0 -0
- {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]
|
lionagi/core/flow/__init__.py
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
from .
|
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
|
-
|
51
|
-
|
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
|
-
|
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
|
|
@@ -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.
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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 =
|
40
|
-
|
41
|
-
|
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
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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(
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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.
|
117
|
-
if
|
118
|
-
else self.branch.
|
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 .
|
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
|
-
|
53
|
-
|
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
|
-
|
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"""
|
lionagi/core/mail/__init__.py
CHANGED
@@ -1,49 +1,97 @@
|
|
1
|
-
from typing import Dict, Any
|
2
1
|
from collections import deque
|
3
|
-
from lionagi.
|
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
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
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(
|
16
|
-
return BaseMail(
|
17
|
-
|
18
|
-
def add_source(self, sources:
|
19
|
-
for
|
20
|
-
if
|
21
|
-
raise ValueError(f"{
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
self.sources
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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)
|
lionagi/core/mail/schema.py
CHANGED
@@ -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 = "
|
7
|
-
SERVICE = "
|
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,
|
14
|
-
self.
|
15
|
-
self.
|
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)
|