lionagi 0.0.306__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 (78) hide show
  1. lionagi/__init__.py +2 -5
  2. lionagi/core/__init__.py +7 -5
  3. lionagi/core/agent/__init__.py +3 -0
  4. lionagi/core/agent/base_agent.py +10 -12
  5. lionagi/core/branch/__init__.py +4 -0
  6. lionagi/core/branch/base_branch.py +81 -81
  7. lionagi/core/branch/branch.py +16 -28
  8. lionagi/core/branch/branch_flow_mixin.py +3 -7
  9. lionagi/core/branch/executable_branch.py +86 -56
  10. lionagi/core/branch/util.py +77 -162
  11. lionagi/core/{flow/direct → direct}/__init__.py +1 -1
  12. lionagi/core/{flow/direct/predict.py → direct/parallel_predict.py} +39 -17
  13. lionagi/core/direct/parallel_react.py +0 -0
  14. lionagi/core/direct/parallel_score.py +0 -0
  15. lionagi/core/direct/parallel_select.py +0 -0
  16. lionagi/core/direct/parallel_sentiment.py +0 -0
  17. lionagi/core/direct/predict.py +174 -0
  18. lionagi/core/{flow/direct → direct}/react.py +2 -2
  19. lionagi/core/{flow/direct → direct}/score.py +28 -23
  20. lionagi/core/{flow/direct → direct}/select.py +48 -45
  21. lionagi/core/direct/utils.py +83 -0
  22. lionagi/core/flow/monoflow/ReAct.py +6 -5
  23. lionagi/core/flow/monoflow/__init__.py +9 -0
  24. lionagi/core/flow/monoflow/chat.py +10 -10
  25. lionagi/core/flow/monoflow/chat_mixin.py +11 -10
  26. lionagi/core/flow/monoflow/followup.py +6 -5
  27. lionagi/core/flow/polyflow/__init__.py +1 -0
  28. lionagi/core/flow/polyflow/chat.py +15 -3
  29. lionagi/core/mail/mail_manager.py +18 -19
  30. lionagi/core/mail/schema.py +5 -4
  31. lionagi/core/messages/schema.py +18 -20
  32. lionagi/core/prompt/__init__.py +0 -0
  33. lionagi/core/prompt/prompt_template.py +0 -0
  34. lionagi/core/schema/__init__.py +2 -2
  35. lionagi/core/schema/action_node.py +11 -3
  36. lionagi/core/schema/base_mixin.py +56 -59
  37. lionagi/core/schema/base_node.py +35 -38
  38. lionagi/core/schema/condition.py +24 -0
  39. lionagi/core/schema/data_logger.py +96 -99
  40. lionagi/core/schema/data_node.py +19 -19
  41. lionagi/core/schema/prompt_template.py +0 -0
  42. lionagi/core/schema/structure.py +171 -169
  43. lionagi/core/session/__init__.py +1 -3
  44. lionagi/core/session/session.py +196 -214
  45. lionagi/core/tool/tool_manager.py +95 -103
  46. lionagi/integrations/__init__.py +1 -3
  47. lionagi/integrations/bridge/langchain_/documents.py +17 -18
  48. lionagi/integrations/bridge/langchain_/langchain_bridge.py +14 -14
  49. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +22 -22
  50. lionagi/integrations/bridge/llamaindex_/node_parser.py +12 -12
  51. lionagi/integrations/bridge/llamaindex_/reader.py +11 -11
  52. lionagi/integrations/bridge/llamaindex_/textnode.py +7 -7
  53. lionagi/integrations/config/openrouter_configs.py +0 -1
  54. lionagi/integrations/provider/oai.py +26 -26
  55. lionagi/integrations/provider/services.py +38 -38
  56. lionagi/libs/__init__.py +34 -1
  57. lionagi/libs/ln_api.py +211 -221
  58. lionagi/libs/ln_async.py +53 -60
  59. lionagi/libs/ln_convert.py +118 -120
  60. lionagi/libs/ln_dataframe.py +32 -33
  61. lionagi/libs/ln_func_call.py +334 -342
  62. lionagi/libs/ln_nested.py +99 -107
  63. lionagi/libs/ln_parse.py +161 -165
  64. lionagi/libs/sys_util.py +52 -52
  65. lionagi/tests/test_core/test_session.py +254 -266
  66. lionagi/tests/test_core/test_session_base_util.py +299 -300
  67. lionagi/tests/test_core/test_tool_manager.py +70 -74
  68. lionagi/tests/test_libs/test_nested.py +2 -7
  69. lionagi/tests/test_libs/test_parse.py +2 -2
  70. lionagi/version.py +1 -1
  71. {lionagi-0.0.306.dist-info → lionagi-0.0.307.dist-info}/METADATA +4 -2
  72. lionagi-0.0.307.dist-info/RECORD +115 -0
  73. lionagi/core/flow/direct/utils.py +0 -43
  74. lionagi-0.0.306.dist-info/RECORD +0 -106
  75. /lionagi/core/{flow/direct → direct}/sentiment.py +0 -0
  76. {lionagi-0.0.306.dist-info → lionagi-0.0.307.dist-info}/LICENSE +0 -0
  77. {lionagi-0.0.306.dist-info → lionagi-0.0.307.dist-info}/WHEEL +0 -0
  78. {lionagi-0.0.306.dist-info → lionagi-0.0.307.dist-info}/top_level.txt +0 -0
@@ -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
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
57
  await self._invoke_tools(content_, func_calls_=func_calls_)
58
- except:
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,7 +76,7 @@ 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_
@@ -118,9 +119,9 @@ class MonoChatInvokeMixin(ABC):
118
119
 
119
120
  async def _call_chatcompletion(self, sender=None, with_sender=False, **kwargs):
120
121
  messages = (
121
- self.branch.chat_messages
122
- if not with_sender
123
- else self.branch.chat_messages_with_sender
122
+ self.branch.chat_messages_with_sender
123
+ if with_sender
124
+ else self.branch.chat_messages
124
125
  )
125
126
  payload, completion = await self.branch.service.serve_chat(
126
127
  messages=messages, **kwargs
@@ -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"""
@@ -1,7 +1,7 @@
1
1
  from collections import deque
2
- from lionagi.core.schema.base_node import BaseNode
3
- from lionagi.core.mail.schema import BaseMail
4
- from lionagi.libs.ln_async import AsyncUtil
2
+ from lionagi.libs import AsyncUtil
3
+ from ..schema import BaseNode
4
+ from .schema import BaseMail
5
5
 
6
6
 
7
7
  class MailManager:
@@ -12,12 +12,12 @@ class MailManager:
12
12
  and deletion of sources, and it handles the collection and dispatch of mails to and from these sources.
13
13
 
14
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.
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
18
  """
19
19
 
20
- def __init__(self, sources: list[BaseNode]):
20
+ def __init__(self, sources):
21
21
  self.sources = {}
22
22
  self.mails = {}
23
23
  self.add_sources(sources)
@@ -26,12 +26,12 @@ class MailManager:
26
26
  def add_sources(self, sources):
27
27
  if isinstance(sources, dict):
28
28
  for _, v in sources.items():
29
- if not v.id_ in self.sources:
29
+ if v.id_ not in self.sources:
30
30
  self.sources[v.id_] = v
31
31
  self.mails[v.id_] = {}
32
32
  elif isinstance(sources, list):
33
33
  for v in sources:
34
- if not v.id_ in self.sources:
34
+ if v.id_ not in self.sources:
35
35
  self.sources[v.id_] = v
36
36
  self.mails[v.id_] = {}
37
37
 
@@ -73,15 +73,14 @@ class MailManager:
73
73
  raise ValueError(f"Recipient source {recipient_id} does not exist.")
74
74
  if not self.mails[recipient_id]:
75
75
  return
76
- else:
77
- for key in list(self.mails[recipient_id].keys()):
78
- mails_deque = self.mails[recipient_id].pop(key)
79
- if key not in self.sources[recipient_id].pending_ins:
80
- self.sources[recipient_id].pending_ins[key] = mails_deque
81
- else:
82
- while mails_deque:
83
- mail_ = mails_deque.popleft()
84
- self.sources[recipient_id].pending_ins[key].append(mail_)
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_)
85
84
 
86
85
  def collect_all(self):
87
86
  for ids in self.sources:
@@ -95,4 +94,4 @@ class MailManager:
95
94
  while not self.execute_stop:
96
95
  self.collect_all()
97
96
  self.send_all()
98
- await AsyncUtil.sleep(refresh_time)
97
+ await AsyncUtil.sleep(refresh_time)
@@ -10,10 +10,11 @@ class MailCategory(str, Enum):
10
10
  SERVICE = "service"
11
11
  MODEL = "model"
12
12
  NODE = "node"
13
- CONTEXT = "context"
13
+ NODE_LIST = "node_list"
14
14
  NODE_ID = "node_id"
15
15
  START = "start"
16
16
  END = "end"
17
+ CONDITION = "condition"
17
18
 
18
19
 
19
20
  class BaseMail:
@@ -34,14 +35,14 @@ class BaseMail:
34
35
  raise ValueError(
35
36
  f"Invalid request title. Valid titles are "
36
37
  f"{list(MailCategory)}, Error: {e}"
37
- )
38
+ ) from e
38
39
  self.package = package
39
40
 
40
41
 
41
42
  class StartMail(BaseRelatableNode):
42
43
 
43
- def __init__(self, *args, **kwargs):
44
- super().__init__(*args, **kwargs)
44
+ def __init__(self, **kwargs):
45
+ super().__init__(**kwargs)
45
46
  self.pending_outs = deque()
46
47
 
47
48
  def trigger(self, context, structure_id, executable_id):
@@ -1,12 +1,11 @@
1
1
  from enum import Enum
2
2
 
3
- from lionagi.libs import ln_nested as nested
4
- from lionagi.libs import ln_convert as convert
5
-
6
- from lionagi.core.schema.data_node import DataNode
3
+ from lionagi.libs import nested, convert
4
+ from ..schema import DataNode
7
5
 
8
6
  _message_fields = ["node_id", "timestamp", "role", "sender", "recipient", "content"]
9
7
 
8
+
10
9
  # ToDo: actually implement the new message classes
11
10
 
12
11
 
@@ -99,9 +98,9 @@ class BaseMessage(DataNode):
99
98
  Represents a message in a chatbot-like system, inheriting from BaseNode.
100
99
 
101
100
  Attributes:
102
- role (str | None): The role of the entity sending the message, e.g., 'user', 'system'.
103
- sender (str | None): The identifier of the sender of the message.
104
- content (Any): The actual content of the message.
101
+ role (str | None): The role of the entity sending the message, e.g., 'user', 'system'.
102
+ sender (str | None): The identifier of the sender of the message.
103
+ content (Any): The actual content of the message.
105
104
  """
106
105
 
107
106
  role: str | None = None
@@ -114,7 +113,7 @@ class BaseMessage(DataNode):
114
113
  Constructs and returns a dictionary representation of the message.
115
114
 
116
115
  Returns:
117
- A dictionary representation of the message with 'role' and 'content' keys.
116
+ A dictionary representation of the message with 'role' and 'content' keys.
118
117
  """
119
118
  return self._to_message()
120
119
 
@@ -124,7 +123,7 @@ class BaseMessage(DataNode):
124
123
  Gets the 'content' field of the message.
125
124
 
126
125
  Returns:
127
- The 'content' part of the message.
126
+ The 'content' part of the message.
128
127
  """
129
128
  return self.msg["content"]
130
129
 
@@ -133,14 +132,13 @@ class BaseMessage(DataNode):
133
132
  Constructs and returns a dictionary representation of the message.
134
133
 
135
134
  Returns:
136
- dict: A dictionary representation of the message with 'role' and 'content' keys.
135
+ dict: A dictionary representation of the message with 'role' and 'content' keys.
137
136
  """
138
- out = {"role": self.role, "content": convert.to_str(self.content)}
139
- return out
137
+ return {"role": self.role, "content": convert.to_str(self.content)}
140
138
 
141
139
  def __str__(self):
142
140
  content_preview = (
143
- (str(self.content)[:75] + "...")
141
+ f"{str(self.content)[:75]}..."
144
142
  if self.content and len(self.content) > 75
145
143
  else str(self.content)
146
144
  )
@@ -162,7 +160,7 @@ class Instruction(BaseMessage):
162
160
  sender: str | None = None,
163
161
  output_fields=None,
164
162
  recipient=None,
165
- ):
163
+ ): # sourcery skip: avoid-builtin-shadow
166
164
  super().__init__(
167
165
  role="user",
168
166
  sender=sender or "user",
@@ -256,13 +254,13 @@ class Response(BaseMessage):
256
254
  content_key = content_key or "response"
257
255
  sender = sender or "assistant"
258
256
  recipient = recipient or "user"
259
- except:
257
+ except Exception:
260
258
  content_ = response["content"]
261
259
  content_key = content_key or "response"
262
260
  sender = sender or "assistant"
263
261
  recipient = recipient or "user"
264
262
 
265
- except:
263
+ except Exception:
266
264
  sender = sender or "action_response"
267
265
  content_ = response
268
266
  content_key = content_key or "action_response"
@@ -281,13 +279,13 @@ class Response(BaseMessage):
281
279
  Processes an action request response and extracts relevant information.
282
280
 
283
281
  Args:
284
- response (dict): The response dictionary containing tool calls and other information.
282
+ response (dict): The response dictionary containing tool calls and other information.
285
283
 
286
284
  Returns:
287
- list: A list of dictionaries, each representing a function call with action and arguments.
285
+ list: A list of dictionaries, each representing a function call with action and arguments.
288
286
 
289
287
  Raises:
290
- ValueError: If the response does not conform to the expected format for action requests.
288
+ ValueError: If the response does not conform to the expected format for action requests.
291
289
  """
292
290
  try:
293
291
  tool_count = 0
@@ -300,7 +298,7 @@ class Response(BaseMessage):
300
298
  _path2 = ["tool_calls", tool_count, "function", "arguments"]
301
299
 
302
300
  func_content = {
303
- "action": ("action_" + nested.nget(response, _path1)),
301
+ "action": f"action_{nested.nget(response, _path1)}",
304
302
  "arguments": nested.nget(response, _path2),
305
303
  }
306
304
  func_list.append(func_content)
File without changes
File without changes
@@ -1,10 +1,9 @@
1
- from .base_node import BaseNode, BaseRelatableNode, Tool
1
+ from .base_node import BaseNode, BaseRelatableNode, Tool, TOOL_TYPE
2
2
  from .data_node import DataNode
3
3
  from .data_logger import DLog, DataLogger
4
4
  from .structure import Relationship, Graph, Structure
5
5
  from .action_node import ActionNode
6
6
 
7
-
8
7
  __all__ = [
9
8
  "BaseNode",
10
9
  "BaseRelatableNode",
@@ -16,4 +15,5 @@ __all__ = [
16
15
  "Graph",
17
16
  "Structure",
18
17
  "ActionNode",
18
+ "TOOL_TYPE",
19
19
  ]
@@ -1,11 +1,13 @@
1
1
  from enum import Enum
2
2
 
3
- from lionagi.core.schema.base_node import BaseNode
3
+ from .base_node import BaseNode
4
4
 
5
5
 
6
6
  class ActionSelection(BaseNode):
7
7
 
8
- def __init__(self, action: str = "chat", action_kwargs={}):
8
+ def __init__(self, action: str = "chat", action_kwargs=None):
9
+ if action_kwargs is None:
10
+ action_kwargs = {}
9
11
  super().__init__()
10
12
  self.action = action
11
13
  self.action_kwargs = action_kwargs
@@ -13,7 +15,13 @@ class ActionSelection(BaseNode):
13
15
 
14
16
  class ActionNode(BaseNode):
15
17
 
16
- def __init__(self, instruction, action: str = "chat", tools=[], action_kwargs={}):
18
+ def __init__(
19
+ self, instruction, action: str = "chat", tools=None, action_kwargs=None
20
+ ):
21
+ if tools is None:
22
+ tools = []
23
+ if action_kwargs is None:
24
+ action_kwargs = {}
17
25
  super().__init__()
18
26
  self.instruction = instruction
19
27
  self.action = action