lionagi 0.0.312__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. lionagi/__init__.py +61 -3
  2. lionagi/core/__init__.py +0 -14
  3. lionagi/core/_setting/_setting.py +59 -0
  4. lionagi/core/action/__init__.py +14 -0
  5. lionagi/core/action/function_calling.py +136 -0
  6. lionagi/core/action/manual.py +1 -0
  7. lionagi/core/action/node.py +109 -0
  8. lionagi/core/action/tool.py +114 -0
  9. lionagi/core/action/tool_manager.py +356 -0
  10. lionagi/core/agent/__init__.py +0 -3
  11. lionagi/core/agent/base_agent.py +45 -36
  12. lionagi/core/agent/eval/evaluator.py +1 -0
  13. lionagi/core/agent/eval/vote.py +40 -0
  14. lionagi/core/agent/learn/learner.py +59 -0
  15. lionagi/core/agent/plan/unit_template.py +1 -0
  16. lionagi/core/collections/__init__.py +17 -0
  17. lionagi/core/collections/_logger.py +319 -0
  18. lionagi/core/collections/abc/__init__.py +53 -0
  19. lionagi/core/collections/abc/component.py +615 -0
  20. lionagi/core/collections/abc/concepts.py +297 -0
  21. lionagi/core/collections/abc/exceptions.py +150 -0
  22. lionagi/core/collections/abc/util.py +45 -0
  23. lionagi/core/collections/exchange.py +161 -0
  24. lionagi/core/collections/flow.py +426 -0
  25. lionagi/core/collections/model.py +419 -0
  26. lionagi/core/collections/pile.py +913 -0
  27. lionagi/core/collections/progression.py +236 -0
  28. lionagi/core/collections/util.py +64 -0
  29. lionagi/core/director/direct.py +314 -0
  30. lionagi/core/director/director.py +2 -0
  31. lionagi/core/engine/branch_engine.py +333 -0
  32. lionagi/core/engine/instruction_map_engine.py +204 -0
  33. lionagi/core/engine/sandbox_.py +14 -0
  34. lionagi/core/engine/script_engine.py +99 -0
  35. lionagi/core/executor/base_executor.py +90 -0
  36. lionagi/core/executor/graph_executor.py +330 -0
  37. lionagi/core/executor/neo4j_executor.py +384 -0
  38. lionagi/core/generic/__init__.py +7 -0
  39. lionagi/core/generic/edge.py +112 -0
  40. lionagi/core/generic/edge_condition.py +16 -0
  41. lionagi/core/generic/graph.py +236 -0
  42. lionagi/core/generic/hyperedge.py +1 -0
  43. lionagi/core/generic/node.py +220 -0
  44. lionagi/core/generic/tree.py +48 -0
  45. lionagi/core/generic/tree_node.py +79 -0
  46. lionagi/core/mail/__init__.py +7 -3
  47. lionagi/core/mail/mail.py +25 -0
  48. lionagi/core/mail/mail_manager.py +142 -58
  49. lionagi/core/mail/package.py +45 -0
  50. lionagi/core/mail/start_mail.py +36 -0
  51. lionagi/core/message/__init__.py +19 -0
  52. lionagi/core/message/action_request.py +133 -0
  53. lionagi/core/message/action_response.py +135 -0
  54. lionagi/core/message/assistant_response.py +95 -0
  55. lionagi/core/message/instruction.py +234 -0
  56. lionagi/core/message/message.py +101 -0
  57. lionagi/core/message/system.py +86 -0
  58. lionagi/core/message/util.py +283 -0
  59. lionagi/core/report/__init__.py +4 -0
  60. lionagi/core/report/base.py +217 -0
  61. lionagi/core/report/form.py +231 -0
  62. lionagi/core/report/report.py +166 -0
  63. lionagi/core/report/util.py +28 -0
  64. lionagi/core/rule/__init__.py +0 -0
  65. lionagi/core/rule/_default.py +16 -0
  66. lionagi/core/rule/action.py +99 -0
  67. lionagi/core/rule/base.py +238 -0
  68. lionagi/core/rule/boolean.py +56 -0
  69. lionagi/core/rule/choice.py +47 -0
  70. lionagi/core/rule/mapping.py +96 -0
  71. lionagi/core/rule/number.py +71 -0
  72. lionagi/core/rule/rulebook.py +109 -0
  73. lionagi/core/rule/string.py +52 -0
  74. lionagi/core/rule/util.py +35 -0
  75. lionagi/core/session/__init__.py +0 -3
  76. lionagi/core/session/branch.py +431 -0
  77. lionagi/core/session/directive_mixin.py +287 -0
  78. lionagi/core/session/session.py +230 -902
  79. lionagi/core/structure/__init__.py +1 -0
  80. lionagi/core/structure/chain.py +1 -0
  81. lionagi/core/structure/forest.py +1 -0
  82. lionagi/core/structure/graph.py +1 -0
  83. lionagi/core/structure/tree.py +1 -0
  84. lionagi/core/unit/__init__.py +5 -0
  85. lionagi/core/unit/parallel_unit.py +245 -0
  86. lionagi/core/unit/template/__init__.py +0 -0
  87. lionagi/core/unit/template/action.py +81 -0
  88. lionagi/core/unit/template/base.py +51 -0
  89. lionagi/core/unit/template/plan.py +84 -0
  90. lionagi/core/unit/template/predict.py +109 -0
  91. lionagi/core/unit/template/score.py +124 -0
  92. lionagi/core/unit/template/select.py +104 -0
  93. lionagi/core/unit/unit.py +362 -0
  94. lionagi/core/unit/unit_form.py +305 -0
  95. lionagi/core/unit/unit_mixin.py +1168 -0
  96. lionagi/core/unit/util.py +71 -0
  97. lionagi/core/validator/__init__.py +0 -0
  98. lionagi/core/validator/validator.py +364 -0
  99. lionagi/core/work/__init__.py +0 -0
  100. lionagi/core/work/work.py +76 -0
  101. lionagi/core/work/work_function.py +101 -0
  102. lionagi/core/work/work_queue.py +103 -0
  103. lionagi/core/work/worker.py +258 -0
  104. lionagi/core/work/worklog.py +120 -0
  105. lionagi/experimental/__init__.py +0 -0
  106. lionagi/experimental/compressor/__init__.py +0 -0
  107. lionagi/experimental/compressor/base.py +46 -0
  108. lionagi/experimental/compressor/llm_compressor.py +247 -0
  109. lionagi/experimental/compressor/llm_summarizer.py +61 -0
  110. lionagi/experimental/compressor/util.py +70 -0
  111. lionagi/experimental/directive/__init__.py +19 -0
  112. lionagi/experimental/directive/parser/__init__.py +0 -0
  113. lionagi/experimental/directive/parser/base_parser.py +282 -0
  114. lionagi/experimental/directive/template/__init__.py +0 -0
  115. lionagi/experimental/directive/template/base_template.py +79 -0
  116. lionagi/experimental/directive/template/schema.py +36 -0
  117. lionagi/experimental/directive/tokenizer.py +73 -0
  118. lionagi/experimental/evaluator/__init__.py +0 -0
  119. lionagi/experimental/evaluator/ast_evaluator.py +131 -0
  120. lionagi/experimental/evaluator/base_evaluator.py +218 -0
  121. lionagi/experimental/knowledge/__init__.py +0 -0
  122. lionagi/experimental/knowledge/base.py +10 -0
  123. lionagi/experimental/knowledge/graph.py +0 -0
  124. lionagi/experimental/memory/__init__.py +0 -0
  125. lionagi/experimental/strategies/__init__.py +0 -0
  126. lionagi/experimental/strategies/base.py +1 -0
  127. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  128. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  129. lionagi/integrations/bridge/langchain_/documents.py +4 -0
  130. lionagi/integrations/bridge/llamaindex_/index.py +30 -0
  131. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
  132. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  133. lionagi/integrations/bridge/llamaindex_/node_parser.py +6 -9
  134. lionagi/integrations/bridge/pydantic_/pydantic_bridge.py +1 -0
  135. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  136. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  137. lionagi/integrations/chunker/__init__.py +0 -0
  138. lionagi/integrations/chunker/chunk.py +312 -0
  139. lionagi/integrations/config/oai_configs.py +38 -7
  140. lionagi/integrations/config/ollama_configs.py +1 -1
  141. lionagi/integrations/config/openrouter_configs.py +14 -2
  142. lionagi/integrations/loader/__init__.py +0 -0
  143. lionagi/integrations/loader/load.py +253 -0
  144. lionagi/integrations/loader/load_util.py +195 -0
  145. lionagi/integrations/provider/_mapping.py +46 -0
  146. lionagi/integrations/provider/litellm.py +2 -1
  147. lionagi/integrations/provider/mlx_service.py +16 -9
  148. lionagi/integrations/provider/oai.py +91 -4
  149. lionagi/integrations/provider/ollama.py +7 -6
  150. lionagi/integrations/provider/openrouter.py +115 -8
  151. lionagi/integrations/provider/services.py +2 -2
  152. lionagi/integrations/provider/transformers.py +18 -22
  153. lionagi/integrations/storage/__init__.py +3 -0
  154. lionagi/integrations/storage/neo4j.py +665 -0
  155. lionagi/integrations/storage/storage_util.py +287 -0
  156. lionagi/integrations/storage/structure_excel.py +285 -0
  157. lionagi/integrations/storage/to_csv.py +63 -0
  158. lionagi/integrations/storage/to_excel.py +83 -0
  159. lionagi/libs/__init__.py +26 -1
  160. lionagi/libs/ln_api.py +78 -23
  161. lionagi/libs/ln_context.py +37 -0
  162. lionagi/libs/ln_convert.py +21 -9
  163. lionagi/libs/ln_func_call.py +69 -28
  164. lionagi/libs/ln_image.py +107 -0
  165. lionagi/libs/ln_knowledge_graph.py +405 -0
  166. lionagi/libs/ln_nested.py +26 -11
  167. lionagi/libs/ln_parse.py +110 -14
  168. lionagi/libs/ln_queue.py +117 -0
  169. lionagi/libs/ln_tokenize.py +164 -0
  170. lionagi/{core/prompt/field_validator.py → libs/ln_validate.py} +79 -14
  171. lionagi/libs/special_tokens.py +172 -0
  172. lionagi/libs/sys_util.py +107 -2
  173. lionagi/lions/__init__.py +0 -0
  174. lionagi/lions/coder/__init__.py +0 -0
  175. lionagi/lions/coder/add_feature.py +20 -0
  176. lionagi/lions/coder/base_prompts.py +22 -0
  177. lionagi/lions/coder/code_form.py +13 -0
  178. lionagi/lions/coder/coder.py +168 -0
  179. lionagi/lions/coder/util.py +96 -0
  180. lionagi/lions/researcher/__init__.py +0 -0
  181. lionagi/lions/researcher/data_source/__init__.py +0 -0
  182. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  183. lionagi/lions/researcher/data_source/google_.py +199 -0
  184. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  185. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  186. lionagi/tests/integrations/__init__.py +0 -0
  187. lionagi/tests/libs/__init__.py +0 -0
  188. lionagi/tests/libs/test_field_validators.py +353 -0
  189. lionagi/tests/{test_libs → libs}/test_func_call.py +23 -21
  190. lionagi/tests/{test_libs → libs}/test_nested.py +36 -21
  191. lionagi/tests/{test_libs → libs}/test_parse.py +1 -1
  192. lionagi/tests/libs/test_queue.py +67 -0
  193. lionagi/tests/test_core/collections/__init__.py +0 -0
  194. lionagi/tests/test_core/collections/test_component.py +206 -0
  195. lionagi/tests/test_core/collections/test_exchange.py +138 -0
  196. lionagi/tests/test_core/collections/test_flow.py +145 -0
  197. lionagi/tests/test_core/collections/test_pile.py +171 -0
  198. lionagi/tests/test_core/collections/test_progression.py +129 -0
  199. lionagi/tests/test_core/generic/__init__.py +0 -0
  200. lionagi/tests/test_core/generic/test_edge.py +67 -0
  201. lionagi/tests/test_core/generic/test_graph.py +96 -0
  202. lionagi/tests/test_core/generic/test_node.py +106 -0
  203. lionagi/tests/test_core/generic/test_tree_node.py +73 -0
  204. lionagi/tests/test_core/test_branch.py +115 -292
  205. lionagi/tests/test_core/test_form.py +46 -0
  206. lionagi/tests/test_core/test_report.py +105 -0
  207. lionagi/tests/test_core/test_validator.py +111 -0
  208. lionagi/version.py +1 -1
  209. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/LICENSE +12 -11
  210. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/METADATA +19 -118
  211. lionagi-0.2.1.dist-info/RECORD +240 -0
  212. lionagi/core/branch/__init__.py +0 -4
  213. lionagi/core/branch/base_branch.py +0 -654
  214. lionagi/core/branch/branch.py +0 -471
  215. lionagi/core/branch/branch_flow_mixin.py +0 -96
  216. lionagi/core/branch/executable_branch.py +0 -347
  217. lionagi/core/branch/util.py +0 -323
  218. lionagi/core/direct/__init__.py +0 -6
  219. lionagi/core/direct/predict.py +0 -161
  220. lionagi/core/direct/score.py +0 -278
  221. lionagi/core/direct/select.py +0 -169
  222. lionagi/core/direct/utils.py +0 -87
  223. lionagi/core/direct/vote.py +0 -64
  224. lionagi/core/flow/base/baseflow.py +0 -23
  225. lionagi/core/flow/monoflow/ReAct.py +0 -238
  226. lionagi/core/flow/monoflow/__init__.py +0 -9
  227. lionagi/core/flow/monoflow/chat.py +0 -95
  228. lionagi/core/flow/monoflow/chat_mixin.py +0 -263
  229. lionagi/core/flow/monoflow/followup.py +0 -214
  230. lionagi/core/flow/polyflow/__init__.py +0 -1
  231. lionagi/core/flow/polyflow/chat.py +0 -248
  232. lionagi/core/mail/schema.py +0 -56
  233. lionagi/core/messages/__init__.py +0 -3
  234. lionagi/core/messages/schema.py +0 -533
  235. lionagi/core/prompt/prompt_template.py +0 -316
  236. lionagi/core/schema/__init__.py +0 -22
  237. lionagi/core/schema/action_node.py +0 -29
  238. lionagi/core/schema/base_mixin.py +0 -296
  239. lionagi/core/schema/base_node.py +0 -199
  240. lionagi/core/schema/condition.py +0 -24
  241. lionagi/core/schema/data_logger.py +0 -354
  242. lionagi/core/schema/data_node.py +0 -93
  243. lionagi/core/schema/prompt_template.py +0 -67
  244. lionagi/core/schema/structure.py +0 -910
  245. lionagi/core/tool/__init__.py +0 -3
  246. lionagi/core/tool/tool_manager.py +0 -280
  247. lionagi/integrations/bridge/pydantic_/base_model.py +0 -7
  248. lionagi/tests/test_core/test_base_branch.py +0 -427
  249. lionagi/tests/test_core/test_chat_flow.py +0 -63
  250. lionagi/tests/test_core/test_mail_manager.py +0 -75
  251. lionagi/tests/test_core/test_prompts.py +0 -51
  252. lionagi/tests/test_core/test_session.py +0 -254
  253. lionagi/tests/test_core/test_session_base_util.py +0 -312
  254. lionagi/tests/test_core/test_tool_manager.py +0 -95
  255. lionagi-0.0.312.dist-info/RECORD +0 -111
  256. /lionagi/core/{branch/base → _setting}/__init__.py +0 -0
  257. /lionagi/core/{flow → agent/eval}/__init__.py +0 -0
  258. /lionagi/core/{flow/base → agent/learn}/__init__.py +0 -0
  259. /lionagi/core/{prompt → agent/plan}/__init__.py +0 -0
  260. /lionagi/core/{tool/manual.py → agent/plan/plan.py} +0 -0
  261. /lionagi/{tests/test_integrations → core/director}/__init__.py +0 -0
  262. /lionagi/{tests/test_libs → core/engine}/__init__.py +0 -0
  263. /lionagi/{tests/test_libs/test_async.py → core/executor/__init__.py} +0 -0
  264. /lionagi/tests/{test_libs → libs}/test_api.py +0 -0
  265. /lionagi/tests/{test_libs → libs}/test_convert.py +0 -0
  266. /lionagi/tests/{test_libs → libs}/test_sys_util.py +0 -0
  267. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/WHEEL +0 -0
  268. {lionagi-0.0.312.dist-info → lionagi-0.2.1.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,14 @@
1
1
  from collections import deque
2
+ from pydantic import Field
2
3
  from lionagi.libs import AsyncUtil
3
- from ..schema import BaseNode
4
- from .schema import BaseMail
4
+ from lionagi.core.collections.abc import Executable, Element
5
+ from lionagi.core.collections import Exchange
6
+ from lionagi.core.collections.util import to_list_type, get_lion_id
7
+ from .mail import Mail, Package
8
+ from lionagi.core.collections import Pile, pile
5
9
 
6
10
 
7
- class MailManager:
11
+ class MailManager(Element, Executable):
8
12
  """
9
13
  Manages the sending, receiving, and storage of mail items between various sources.
10
14
 
@@ -15,82 +19,162 @@ class MailManager:
15
19
  sources (Dict[str, Any]): A dictionary mapping source identifiers to their attributes.
16
20
  mails (Dict[str, Dict[str, deque]]): A nested dictionary storing queued mail items, organized by recipient
17
21
  and sender.
22
+ execute_stop (bool): A flag indicating whether to stop execution.
18
23
  """
19
24
 
20
- def __init__(self, sources):
21
- self.sources = {}
22
- self.mails = {}
23
- self.add_sources(sources)
24
- self.execute_stop = False
25
+ sources: Pile[Element] = Field(
26
+ default_factory=lambda: pile(),
27
+ description="The pile of managed sources",
28
+ )
29
+
30
+ mails: dict[str, dict[str, deque]] = Field(
31
+ default_factory=dict,
32
+ description="The mails waiting to be sent",
33
+ examples=["{'recipient_id': {'sender_id': deque()}}"],
34
+ )
35
+
36
+ execute_stop: bool = Field(
37
+ False, description="A flag indicating whether to stop execution."
38
+ )
39
+
40
+ def __init__(self, sources=None):
41
+ """
42
+ Initializes the MailManager with optional sources.
43
+
44
+ Args:
45
+ sources (Optional[list]): A list of sources to be managed by the MailManager.
46
+ """
47
+ super().__init__()
48
+ if sources:
49
+ self.add_sources(sources)
25
50
 
26
51
  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_] = {}
52
+ """
53
+ Adds new sources to the MailManager.
54
+
55
+ Args:
56
+ sources (list): A list of sources to be added.
57
+
58
+ Raises:
59
+ ValueError: If failed to add sources.
60
+ """
61
+ try:
62
+ sources = to_list_type(sources)
63
+ self.sources.include(sources)
64
+ for item in sources:
65
+ self.mails[item.ln_id] = {}
66
+ except Exception as e:
67
+ raise ValueError(f"Failed to add source. Error {e}")
37
68
 
38
69
  @staticmethod
39
- def create_mail(sender_id, recipient_id, category, package):
40
- return BaseMail(sender_id, recipient_id, category, package)
70
+ def create_mail(sender, recipient, category, package):
71
+ """
72
+ Creates a mail item.
41
73
 
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_] = {}
74
+ Args:
75
+ sender (str): The sender of the mail.
76
+ recipient (str): The recipient of the mail.
77
+ category (str): The category of the mail.
78
+ package (Any): The content of the package.
79
+
80
+ Returns:
81
+ Mail: The created mail object.
82
+ """
83
+ pack = Package(category=category, package=package)
84
+ mail = Mail(
85
+ sender=sender,
86
+ recipient=recipient,
87
+ package=pack,
88
+ )
89
+ return mail
49
90
 
50
91
  def delete_source(self, source_id):
92
+ """
93
+ Deletes a source from the MailManager.
94
+
95
+ Args:
96
+ source_id (str): The ID of the source to be deleted.
97
+
98
+ Raises:
99
+ ValueError: If the source does not exist.
100
+ """
51
101
  if source_id not in self.sources:
52
102
  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
103
  self.sources.pop(source_id)
56
104
  self.mails.pop(source_id)
57
105
 
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]:
106
+ def collect(self, sender):
107
+ """
108
+ Collects mails from a sender's outbox and queues them for the recipient.
109
+
110
+ Args:
111
+ sender (str): The ID of the sender.
112
+
113
+ Raises:
114
+ ValueError: If the sender or recipient source does not exist.
115
+ """
116
+ if sender not in self.sources:
117
+ raise ValueError(f"Sender source {sender} does not exist.")
118
+ mailbox = (
119
+ self.sources[sender]
120
+ if isinstance(self.sources[sender], Exchange)
121
+ else self.sources[sender].mailbox
122
+ )
123
+ while mailbox.pending_outs.size() > 0:
124
+ mail_id = mailbox.pending_outs.popleft()
125
+ mail = mailbox.pile.pop(mail_id)
126
+ if mail.recipient not in self.sources:
127
+ raise ValueError(f"Recipient source {mail.recipient} does not exist")
128
+ if mail.sender not in self.mails[mail.recipient]:
129
+ self.mails[mail.recipient].update({mail.sender: deque()})
130
+ self.mails[mail.recipient][mail.sender].append(mail)
131
+
132
+ def send(self, recipient):
133
+ """
134
+ Sends mails to a recipient's inbox.
135
+
136
+ Args:
137
+ recipient (str): The ID of the recipient.
138
+
139
+ Raises:
140
+ ValueError: If the recipient source does not exist.
141
+ """
142
+ if recipient not in self.sources:
143
+ raise ValueError(f"Recipient source {recipient} does not exist.")
144
+ if not self.mails[recipient]:
75
145
  return
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_)
146
+ for key in list(self.mails[recipient].keys()):
147
+ pending_mails = self.mails[recipient].pop(key)
148
+ mailbox = (
149
+ self.sources[recipient]
150
+ if isinstance(self.sources[recipient], Exchange)
151
+ else self.sources[recipient].mailbox
152
+ )
153
+ while pending_mails:
154
+ mail = pending_mails.popleft()
155
+ mailbox.include(mail, "in")
84
156
 
85
157
  def collect_all(self):
86
- for ids in self.sources:
87
- self.collect(ids)
158
+ """
159
+ Collects mails from all sources.
160
+ """
161
+ for source in self.sources:
162
+ self.collect(get_lion_id(source))
88
163
 
89
164
  def send_all(self):
90
- for ids in self.sources:
91
- self.send(ids)
165
+ """
166
+ Sends mails to all sources.
167
+ """
168
+ for source in self.sources:
169
+ self.send(get_lion_id(source))
92
170
 
93
171
  async def execute(self, refresh_time=1):
172
+ """
173
+ Continuously collects and sends mails until execution is stopped.
174
+
175
+ Args:
176
+ refresh_time (int): The time in seconds to wait between each cycle. Defaults to 1.
177
+ """
94
178
  while not self.execute_stop:
95
179
  self.collect_all()
96
180
  self.send_all()
@@ -0,0 +1,45 @@
1
+ from enum import Enum
2
+ from typing import Any
3
+ from pydantic import field_validator
4
+ from lionagi.core.collections.abc import Element, Field
5
+
6
+
7
+ class PackageCategory(str, Enum):
8
+ MESSAGE = "message"
9
+ TOOL = "tool"
10
+ IMODEL = "imodel"
11
+ NODE = "node"
12
+ NODE_LIST = "node_list"
13
+ NODE_ID = "node_id"
14
+ START = "start"
15
+ END = "end"
16
+ CONDITION = "condition"
17
+
18
+
19
+ class Package(Element):
20
+
21
+ request_source: str | None = None
22
+
23
+ category: PackageCategory = Field(
24
+ None,
25
+ title="Category",
26
+ description="The category of the package.",
27
+ )
28
+
29
+ package: Any = Field(
30
+ None,
31
+ title="Package",
32
+ description="The package to be delivered.",
33
+ )
34
+
35
+ @field_validator("category", mode="before")
36
+ def validate_category(cls, value: Any):
37
+ if value is None:
38
+ raise ValueError("Package category cannot be None.")
39
+ if isinstance(value, PackageCategory):
40
+ return value
41
+ else:
42
+ try:
43
+ return PackageCategory(value)
44
+ except Exception as e:
45
+ raise ValueError(f"Invalid value for category: {value}.") from e
@@ -0,0 +1,36 @@
1
+ from collections import deque
2
+ from pydantic import Field
3
+ from lionagi.core.generic.node import Node
4
+ from lionagi.core.mail.mail import Mail, Package
5
+ from lionagi.core.collections import Exchange
6
+
7
+
8
+ class StartMail(Node):
9
+ """
10
+ Represents a start mail node that triggers the initiation of a process.
11
+
12
+ Attributes:
13
+ mailbox (Exchange): The exchange object that holds pending start mails.
14
+ """
15
+
16
+ mailbox: Exchange = Field(
17
+ default_factory=Exchange[Mail], description="The pending start mail"
18
+ )
19
+
20
+ def trigger(self, context, structure_id, executable_id):
21
+ """
22
+ Triggers the start mail by including it in the mailbox.
23
+
24
+ Args:
25
+ context (Any): The context to be included in the start mail.
26
+ structure_id (str): The ID of the structure to be initiated.
27
+ executable_id (str): The ID of the executable to receive the start mail.
28
+ """
29
+ start_mail_content = {"context": context, "structure_id": structure_id}
30
+ pack = Package(category="start", package=start_mail_content)
31
+ start_mail = Mail(
32
+ sender=self.ln_id,
33
+ recipient=executable_id,
34
+ package=pack,
35
+ )
36
+ self.mailbox.include(start_mail, "out")
@@ -0,0 +1,19 @@
1
+ from .message import RoledMessage, MessageRole
2
+ from .system import System
3
+ from .instruction import Instruction
4
+ from .assistant_response import AssistantResponse
5
+ from .action_request import ActionRequest
6
+ from .action_response import ActionResponse
7
+ from .util import create_message
8
+
9
+
10
+ __all__ = [
11
+ "RoledMessage",
12
+ "MessageRole",
13
+ "System",
14
+ "Instruction",
15
+ "AssistantResponse",
16
+ "ActionRequest",
17
+ "ActionResponse",
18
+ "create_message",
19
+ ]
@@ -0,0 +1,133 @@
1
+ """
2
+ Copyright 2024 HaiyangLi
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ import inspect
18
+ from pydantic import Field
19
+ from lionagi.libs import convert, ParseUtil
20
+ from .message import RoledMessage, MessageRole
21
+
22
+
23
+ class ActionRequest(RoledMessage):
24
+ """
25
+ Represents a request for an action with function and arguments.
26
+
27
+ Inherits from `RoledMessage` and provides attributes specific to action requests.
28
+
29
+ Attributes:
30
+ function (str): The name of the function to be called.
31
+ arguments (dict): The keyword arguments to be passed to the function.
32
+ action_response (str): The ID of the action response that this request corresponds to.
33
+ """
34
+
35
+ function: str | None = Field(
36
+ None, description="The name of the function to be called"
37
+ )
38
+
39
+ arguments: dict | None = Field(
40
+ None, description="The keyword arguments to be passed to the function"
41
+ )
42
+
43
+ action_response: str | None = Field(
44
+ None,
45
+ description="The id of the action response that this request corresponds to",
46
+ )
47
+
48
+ def __init__(
49
+ self,
50
+ function=None,
51
+ arguments=None,
52
+ sender=None, # sender is the assistant who made the request
53
+ recipient=None, # recipient is the actionable component
54
+ **kwargs,
55
+ ):
56
+ """
57
+ Initializes the ActionRequest.
58
+
59
+ Args:
60
+ function (str or function, optional): The function to be called.
61
+ arguments (dict, optional): The keyword arguments for the function.
62
+ sender (str, optional): The sender of the request.
63
+ recipient (str, optional): The recipient of the request.
64
+ """
65
+ function = function.__name__ if inspect.isfunction(function) else function
66
+ arguments = _prepare_arguments(arguments)
67
+
68
+ super().__init__(
69
+ role=MessageRole.ASSISTANT,
70
+ sender=sender,
71
+ recipient=recipient,
72
+ content={"action_request": {"function": function, "arguments": arguments}},
73
+ **kwargs,
74
+ )
75
+ self.function = function
76
+ self.arguments = arguments
77
+
78
+ def is_responded(self):
79
+ """
80
+ Checks if the action request has been responded to.
81
+
82
+ Returns:
83
+ bool: True if the action request has a response, otherwise False.
84
+ """
85
+ return self.action_response is not None
86
+
87
+ def clone(self, **kwargs):
88
+ """
89
+ Creates a copy of the current ActionRequest object with optional additional arguments.
90
+
91
+ This method clones the current object, preserving its function and arguments.
92
+ It also retains the original `action_response` and metadata, while allowing
93
+ for the addition of new attributes through keyword arguments.
94
+
95
+ Args:
96
+ **kwargs: Optional keyword arguments to be included in the cloned object.
97
+
98
+ Returns:
99
+ ActionRequest: A new instance of the object with the same function, arguments,
100
+ and additional keyword arguments.
101
+ """
102
+ import json
103
+
104
+ arguments = json.dumps(self.arguments)
105
+ request_copy = ActionRequest(
106
+ function=self.function, arguments=json.loads(arguments), **kwargs
107
+ )
108
+ request_copy.action_response = self.action_response
109
+ request_copy.metadata["origin_ln_id"] = self.ln_id
110
+ return request_copy
111
+
112
+
113
+ def _prepare_arguments(arguments):
114
+ """
115
+ Prepares the arguments for the action request.
116
+
117
+ Args:
118
+ arguments (Any): The arguments to be prepared.
119
+
120
+ Returns:
121
+ dict: The prepared arguments.
122
+
123
+ Raises:
124
+ ValueError: If the arguments are invalid.
125
+ """
126
+ if not isinstance(arguments, dict):
127
+ try:
128
+ arguments = ParseUtil.fuzzy_parse_json(convert.to_str(arguments))
129
+ except Exception as e:
130
+ raise ValueError(f"Invalid arguments: {e}") from e
131
+ if isinstance(arguments, dict):
132
+ return arguments
133
+ raise ValueError(f"Invalid arguments: {arguments}")
@@ -0,0 +1,135 @@
1
+ """
2
+ Copyright 2024 HaiyangLi
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ from typing import Any
18
+ from pydantic import Field
19
+ from .message import RoledMessage, MessageRole
20
+ from .action_request import ActionRequest
21
+
22
+
23
+ # action response must correlates to a specific action request
24
+ class ActionResponse(RoledMessage):
25
+ """
26
+ Represents a response to a specific action request.
27
+
28
+ Inherits from `RoledMessage` and provides attributes specific to action responses.
29
+
30
+ Attributes:
31
+ action_request (str): The ID of the action request that this response corresponds to.
32
+ function (str): The name of the function called.
33
+ arguments (dict): The keyword arguments provided.
34
+ func_outputs (Any): The output of the function call.
35
+ """
36
+
37
+ action_request: str | None = Field(
38
+ None,
39
+ description="The id of the action request that this response corresponds to",
40
+ )
41
+
42
+ function: str | None = Field(None, description="The name of the function called")
43
+ arguments: dict | None = Field(None, description="The keyword arguments provided")
44
+ func_outputs: Any | None = Field(
45
+ None, description="The output of the function call"
46
+ )
47
+
48
+ def __init__(
49
+ self,
50
+ action_request: ActionRequest,
51
+ sender: str | None = None, # the sender of action request
52
+ func_outputs=None,
53
+ **kwargs,
54
+ ):
55
+ """
56
+ Initializes the ActionResponse.
57
+
58
+ Args:
59
+ action_request (ActionRequest): The action request that this response corresponds to.
60
+ sender (str, optional): The sender of the action request.
61
+ func_outputs (Any, optional): The output of the function call.
62
+
63
+ Raises:
64
+ ValueError: If the action request has already been responded to.
65
+ """
66
+ if action_request.is_responded():
67
+ raise ValueError("Action request has already been responded to")
68
+
69
+ super().__init__(
70
+ role=MessageRole.ASSISTANT,
71
+ sender=sender or "N/A", # sender is the actionable component
72
+ recipient=action_request.sender, # recipient is the assistant who made the request
73
+ content={
74
+ "action_response": {
75
+ "function": action_request.function,
76
+ "arguments": action_request.arguments,
77
+ "output": func_outputs,
78
+ }
79
+ },
80
+ **kwargs,
81
+ )
82
+ self.update_request(action_request)
83
+ self.func_outputs = func_outputs
84
+
85
+ def update_request(self, action_request: ActionRequest):
86
+ """
87
+ Updates the action request details in the action response.
88
+
89
+ Args:
90
+ action_request (ActionRequest): The action request to update from.
91
+ """
92
+ self.function = action_request.function
93
+ self.arguments = action_request.arguments
94
+ self.action_request = action_request.ln_id
95
+ action_request.action_response = self.ln_id
96
+
97
+ def _to_dict(self):
98
+ """
99
+ Converts the action response to a dictionary.
100
+
101
+ Returns:
102
+ dict: A dictionary representation of the action response.
103
+ """
104
+ return {
105
+ "function": self.function,
106
+ "arguments": self.arguments,
107
+ "output": self.func_outputs,
108
+ }
109
+
110
+ def clone(self, **kwargs):
111
+ """
112
+ Creates a copy of the current object with optional additional arguments.
113
+
114
+ This method clones the current object, preserving its function and arguments.
115
+ It also retains the original `action_request`, `func_outputs`, and metadata,
116
+ while allowing for the addition of new attributes through keyword arguments.
117
+
118
+ Args:
119
+ **kwargs: Optional keyword arguments to be included in the cloned object.
120
+
121
+ Returns:
122
+ ActionResponse: A new instance of the object with the same function, arguments,
123
+ and additional keyword arguments.
124
+ """
125
+ import json
126
+
127
+ arguments = json.dumps(self.arguments)
128
+ action_request = ActionRequest(
129
+ function=self.function, arguments=json.loads(arguments)
130
+ )
131
+ action_response_copy = ActionResponse(action_request=action_request, **kwargs)
132
+ action_response_copy.action_request = self.action_request
133
+ action_response_copy.func_outputs = self.func_outputs
134
+ action_response_copy.metadata["origin_ln_id"] = self.ln_id
135
+ return action_response_copy