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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (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