lionagi 0.17.11__py3-none-any.whl → 0.18.0__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 (52) hide show
  1. lionagi/libs/schema/minimal_yaml.py +98 -0
  2. lionagi/ln/types.py +32 -5
  3. lionagi/models/field_model.py +9 -0
  4. lionagi/operations/ReAct/ReAct.py +474 -237
  5. lionagi/operations/ReAct/utils.py +3 -0
  6. lionagi/operations/act/act.py +206 -0
  7. lionagi/operations/chat/chat.py +130 -114
  8. lionagi/operations/communicate/communicate.py +101 -42
  9. lionagi/operations/flow.py +4 -4
  10. lionagi/operations/interpret/interpret.py +65 -20
  11. lionagi/operations/operate/operate.py +212 -106
  12. lionagi/operations/parse/parse.py +170 -142
  13. lionagi/operations/select/select.py +78 -17
  14. lionagi/operations/select/utils.py +1 -1
  15. lionagi/operations/types.py +119 -23
  16. lionagi/protocols/generic/log.py +3 -2
  17. lionagi/protocols/messages/__init__.py +27 -0
  18. lionagi/protocols/messages/action_request.py +86 -184
  19. lionagi/protocols/messages/action_response.py +73 -131
  20. lionagi/protocols/messages/assistant_response.py +130 -159
  21. lionagi/protocols/messages/base.py +26 -18
  22. lionagi/protocols/messages/instruction.py +281 -625
  23. lionagi/protocols/messages/manager.py +112 -62
  24. lionagi/protocols/messages/message.py +87 -197
  25. lionagi/protocols/messages/system.py +52 -123
  26. lionagi/protocols/types.py +0 -2
  27. lionagi/service/connections/endpoint.py +0 -8
  28. lionagi/service/connections/providers/oai_.py +29 -94
  29. lionagi/service/connections/providers/ollama_.py +3 -2
  30. lionagi/service/hooks/hooked_event.py +2 -2
  31. lionagi/service/third_party/claude_code.py +3 -2
  32. lionagi/service/third_party/openai_models.py +433 -0
  33. lionagi/session/branch.py +170 -178
  34. lionagi/session/session.py +3 -9
  35. lionagi/tools/file/reader.py +2 -2
  36. lionagi/version.py +1 -1
  37. {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
  38. {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/RECORD +41 -49
  39. lionagi/operations/_act/act.py +0 -86
  40. lionagi/protocols/messages/templates/README.md +0 -28
  41. lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
  42. lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
  43. lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
  44. lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
  45. lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
  46. lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
  47. lionagi/service/connections/providers/types.py +0 -28
  48. lionagi/service/third_party/openai_model_names.py +0 -198
  49. lionagi/service/types.py +0 -58
  50. /lionagi/operations/{_act → act}/__init__.py +0 -0
  51. {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
  52. {lionagi-0.17.11.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,2 +1,29 @@
1
1
  # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
2
  # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from .action_request import ActionRequest, ActionRequestContent
5
+ from .action_response import ActionResponse, ActionResponseContent
6
+ from .assistant_response import AssistantResponse, AssistantResponseContent
7
+ from .base import MessageRole
8
+ from .instruction import Instruction, InstructionContent
9
+ from .manager import MessageManager
10
+ from .message import MessageContent, MessageRole, RoledMessage
11
+ from .system import System, SystemContent
12
+
13
+ __all__ = (
14
+ "ActionRequest",
15
+ "ActionRequestContent",
16
+ "ActionResponse",
17
+ "ActionResponseContent",
18
+ "AssistantResponse",
19
+ "AssistantResponseContent",
20
+ "Instruction",
21
+ "InstructionContent",
22
+ "MessageContent",
23
+ "MessageRole",
24
+ "RoledMessage",
25
+ "System",
26
+ "SystemContent",
27
+ "MessageManager",
28
+ "MessageRole",
29
+ )
@@ -2,208 +2,110 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  from collections.abc import Callable
5
+ from dataclasses import dataclass, field
5
6
  from typing import Any
6
7
 
7
- from lionagi.utils import copy, to_dict
8
-
9
- from ..generic.element import IDType
10
- from .base import SenderRecipient
11
- from .message import MessageRole, RoledMessage, Template, jinja_env
8
+ from pydantic import field_validator
12
9
 
10
+ from lionagi.utils import copy, to_dict
13
11
 
14
- def prepare_action_request(
15
- function: str | Callable,
16
- arguments: dict,
17
- ) -> dict[str, Any]:
18
- """
19
- Build a structured dict describing the request details.
20
-
21
- Args:
22
- function (str | Callable):
23
- The name (or callable) representing the function to invoke.
24
- arguments (dict):
25
- The arguments necessary for the function call.
26
-
27
- Returns:
28
- dict[str, Any]: A standardized dictionary containing
29
- 'action_request' -> {'function':..., 'arguments':...}
12
+ from .message import MessageContent, MessageRole, RoledMessage
30
13
 
31
- Raises:
32
- ValueError: If `function` is neither a string nor callable, or
33
- if `arguments` cannot be turned into a dictionary.
34
- """
35
- if isinstance(function, Callable):
36
- function = function.__name__
37
- if hasattr(function, "function"):
38
- function = function.function
39
- if not isinstance(function, str):
40
- raise ValueError("Function must be a string or callable.")
41
-
42
- arguments = copy(arguments)
43
- if not isinstance(arguments, dict):
44
- try:
45
- arguments = to_dict(arguments, fuzzy_parse=True)
46
- if isinstance(arguments, list | tuple) and len(arguments) > 0:
47
- arguments = arguments[0]
48
- except Exception:
49
- raise ValueError("Arguments must be a dictionary.")
50
- return {"action_request": {"function": function, "arguments": arguments}}
51
14
 
15
+ @dataclass(slots=True)
16
+ class ActionRequestContent(MessageContent):
17
+ """Content for action/function call requests.
52
18
 
53
- class ActionRequest(RoledMessage):
54
- """
55
- A message that requests an action or function to be executed.
56
- It inherits from `RoledMessage` and includes function name,
57
- arguments, and optional linking to a subsequent `ActionResponse`.
19
+ Fields:
20
+ function: Function name to invoke
21
+ arguments: Arguments for the function call
22
+ action_response_id: Link to corresponding response (if any)
58
23
  """
59
24
 
60
- template: Template | str | None = jinja_env.get_template(
61
- "action_request.jinja2"
62
- )
25
+ function: str = ""
26
+ arguments: dict[str, Any] = field(default_factory=dict)
27
+ action_response_id: str | None = None
63
28
 
64
29
  @property
65
- def action_response_id(self) -> IDType | None:
66
- """
67
- Get or set the ID of the corresponding action response.
68
-
69
- Returns:
70
- IDType | None: The ID of the action response, or None if none assigned.
71
- """
72
- return self.content.get("action_response_id", None)
73
-
74
- @action_response_id.setter
75
- def action_response_id(self, action_response_id: IDType) -> None:
76
- self.content["action_response_id"] = str(action_response_id)
30
+ def rendered(self) -> str:
31
+ """Render action request as YAML."""
32
+ from lionagi.libs.schema.minimal_yaml import minimal_yaml
77
33
 
78
- @property
79
- def request(self) -> dict[str, Any]:
80
- """
81
- Get the entire 'action_request' dictionary if present.
34
+ doc = {
35
+ "Function": self.function,
36
+ "Arguments": self.arguments,
37
+ }
38
+ return minimal_yaml(doc).strip()
82
39
 
83
- Returns:
84
- dict[str, Any]: The request content or empty dict if missing.
85
- """
86
- return copy(self.content.get("action_request", {}))
40
+ @classmethod
41
+ def from_dict(cls, data: dict[str, Any]) -> "ActionRequestContent":
42
+ """Construct ActionRequestContent from dictionary."""
43
+ # Handle nested structure from old format
44
+ if "action_request" in data:
45
+ req = data["action_request"]
46
+ function = req.get("function", "")
47
+ arguments = req.get("arguments", {})
48
+ else:
49
+ function = data.get("function", "")
50
+ arguments = data.get("arguments", {})
51
+
52
+ # Handle callable
53
+ if isinstance(function, Callable):
54
+ function = function.__name__
55
+ if hasattr(function, "function"):
56
+ function = function.function
57
+ if not isinstance(function, str):
58
+ raise ValueError("Function must be a string or callable")
59
+
60
+ # Normalize arguments
61
+ arguments = copy(arguments)
62
+ if not isinstance(arguments, dict):
63
+ try:
64
+ arguments = to_dict(arguments, fuzzy_parse=True)
65
+ if isinstance(arguments, list | tuple) and len(arguments) > 0:
66
+ arguments = arguments[0]
67
+ except Exception:
68
+ raise ValueError("Arguments must be a dictionary")
69
+
70
+ action_response_id = data.get("action_response_id")
71
+ if action_response_id:
72
+ action_response_id = str(action_response_id)
73
+
74
+ return cls(
75
+ function=function,
76
+ arguments=arguments,
77
+ action_response_id=action_response_id,
78
+ )
87
79
 
88
- @property
89
- def arguments(self) -> dict[str, Any]:
90
- """
91
- Access just the 'arguments' from the action request.
92
80
 
93
- Returns:
94
- dict[str, Any]: The arguments to be used by the function call.
95
- """
96
- return self.request.get("arguments", {})
81
+ class ActionRequest(RoledMessage):
82
+ """Message requesting an action or function execution."""
83
+
84
+ role: MessageRole = MessageRole.ACTION
85
+ content: ActionRequestContent
86
+
87
+ @field_validator("content", mode="before")
88
+ def _validate_content(cls, v):
89
+ if v is None:
90
+ return ActionRequestContent()
91
+ if isinstance(v, dict):
92
+ return ActionRequestContent.from_dict(v)
93
+ if isinstance(v, ActionRequestContent):
94
+ return v
95
+ raise TypeError(
96
+ "content must be dict or ActionRequestContent instance"
97
+ )
97
98
 
98
99
  @property
99
100
  def function(self) -> str:
100
- """
101
- Name of the function to be invoked.
102
-
103
- Returns:
104
- str: The function name or empty string if none provided.
105
- """
106
- return self.request.get("function", "")
101
+ """Access the function name."""
102
+ return self.content.function
107
103
 
108
- @classmethod
109
- def create(
110
- cls,
111
- function=None,
112
- arguments: dict | None = None,
113
- sender: SenderRecipient | None = None,
114
- recipient: SenderRecipient | None = None,
115
- template: Template | str | None = None,
116
- **kwargs,
117
- ) -> "ActionRequest":
118
- """
119
- Build a new ActionRequest.
120
-
121
- Args:
122
- function (str | Callable | None):
123
- The function or callable name.
124
- arguments (dict | None):
125
- Arguments for that function call.
126
- sender (SenderRecipient | None):
127
- The sender identifier or role.
128
- recipient (SenderRecipient | None):
129
- The recipient identifier or role.
130
- template (Template | str | None):
131
- Optional custom template.
132
- **kwargs:
133
- Extra key-value pairs to merge into the content.
134
-
135
- Returns:
136
- ActionRequest: A newly constructed instance.
137
- """
138
- content = prepare_action_request(function, arguments)
139
- content.update(kwargs)
140
- params = {
141
- "content": content,
142
- "sender": sender,
143
- "recipient": recipient,
144
- "role": MessageRole.ACTION,
145
- }
146
- if template:
147
- params["template"] = template
148
- return cls(**{k: v for k, v in params.items() if v is not None})
149
-
150
- def update(
151
- self,
152
- function: str = None,
153
- arguments: dict | None = None,
154
- sender: SenderRecipient = None,
155
- recipient: SenderRecipient = None,
156
- action_response: "ActionResponse" = None, # type: ignore
157
- template: Template | str | None = None,
158
- **kwargs,
159
- ):
160
- """
161
- Update this request with new function, arguments, or link to an
162
- action response.
163
-
164
- Args:
165
- function (str): New function name, if changing.
166
- arguments (dict): New arguments dictionary, if changing.
167
- sender (SenderRecipient): New sender.
168
- recipient (SenderRecipient): New recipient.
169
- action_response (ActionResponse):
170
- If provided, this request is flagged as responded.
171
- template (Template | str | None):
172
- Optional new template.
173
- **kwargs:
174
- Additional fields to store in content.
175
-
176
- Raises:
177
- ValueError: If the request is already responded to.
178
- """
179
- if self.is_responded():
180
- raise ValueError("Cannot update a responded action request.")
181
-
182
- # Link action response if given
183
- if (
184
- isinstance(action_response, RoledMessage)
185
- and action_response.class_name() == "ActionResponse"
186
- ):
187
- self.action_response_id = action_response.id
188
-
189
- # If new function or arguments, create new 'action_request' content
190
- if any([function, arguments]):
191
- action_request = prepare_action_request(
192
- function or self.function, arguments or self.arguments
193
- )
194
- self.content.update(action_request)
195
- super().update(
196
- sender=sender, recipient=recipient, template=template, **kwargs
197
- )
104
+ @property
105
+ def arguments(self) -> dict[str, Any]:
106
+ """Access the function arguments."""
107
+ return self.content.arguments
198
108
 
199
109
  def is_responded(self) -> bool:
200
- """
201
- Check if there's a linked action response.
202
-
203
- Returns:
204
- bool: True if an action response ID is present.
205
- """
206
- return self.action_response_id is not None
207
-
208
-
209
- # File: lionagi/protocols/messages/action_request.py
110
+ """Check if this request has been responded to."""
111
+ return self.content.action_response_id is not None
@@ -1,155 +1,97 @@
1
1
  # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
+ from dataclasses import dataclass, field
4
5
  from typing import Any
5
6
 
6
- from typing_extensions import override
7
+ from pydantic import field_validator
7
8
 
8
- from lionagi.protocols.generic.element import IDType
9
- from lionagi.utils import copy
9
+ from .message import MessageContent, MessageRole, RoledMessage
10
10
 
11
- from .action_request import ActionRequest
12
- from .base import MessageRole, SenderRecipient
13
- from .message import RoledMessage, Template, jinja_env
14
11
 
12
+ @dataclass(slots=True)
13
+ class ActionResponseContent(MessageContent):
14
+ """Content for action/function call responses.
15
15
 
16
- def prepare_action_response_content(
17
- action_request: ActionRequest,
18
- output: Any,
19
- ) -> dict:
16
+ Fields:
17
+ function: Function name that was invoked
18
+ arguments: Arguments used in the function call
19
+ output: Result returned from the function
20
+ action_request_id: Link to the original request
20
21
  """
21
- Convert an ActionRequest + function output into response-friendly dictionary.
22
22
 
23
- Args:
24
- action_request (ActionRequest): The original action request.
25
- output (Any): The result of the function call.
23
+ function: str = ""
24
+ arguments: dict[str, Any] = field(default_factory=dict)
25
+ output: Any = None
26
+ action_request_id: str | None = None
26
27
 
27
- Returns:
28
- dict: A dictionary containing `action_request_id` and `action_response`.
29
- """
30
- return {
31
- "action_request_id": str(action_request.id),
32
- "action_response": {
33
- "function": action_request.function,
34
- "arguments": action_request.arguments,
35
- "output": output,
36
- },
37
- }
28
+ @property
29
+ def rendered(self) -> str:
30
+ """Render action response as YAML."""
31
+ from lionagi.libs.schema.minimal_yaml import minimal_yaml
38
32
 
33
+ doc = {
34
+ "Function": self.function,
35
+ "Arguments": self.arguments,
36
+ "Output": self.output,
37
+ }
38
+ return minimal_yaml(doc).strip()
39
39
 
40
- class ActionResponse(RoledMessage):
41
- """
42
- A message fulfilling an `ActionRequest`. It stores the function name,
43
- the arguments used, and the output produced by the function.
44
- """
40
+ @classmethod
41
+ def from_dict(cls, data: dict[str, Any]) -> "ActionResponseContent":
42
+ """Construct ActionResponseContent from dictionary."""
43
+ # Handle nested structure from old format
44
+ if "action_response" in data:
45
+ resp = data["action_response"]
46
+ function = resp.get("function", "")
47
+ arguments = resp.get("arguments", {})
48
+ output = resp.get("output")
49
+ else:
50
+ function = data.get("function", "")
51
+ arguments = data.get("arguments", {})
52
+ output = data.get("output")
53
+
54
+ action_request_id = data.get("action_request_id")
55
+ if action_request_id:
56
+ action_request_id = str(action_request_id)
57
+
58
+ return cls(
59
+ function=function,
60
+ arguments=arguments,
61
+ output=output,
62
+ action_request_id=action_request_id,
63
+ )
45
64
 
46
- template: Template | str | None = jinja_env.get_template(
47
- "action_response.jinja2"
48
- )
65
+
66
+ class ActionResponse(RoledMessage):
67
+ """Message containing the result of an action/function execution."""
68
+
69
+ role: MessageRole = MessageRole.ACTION
70
+ content: ActionResponseContent
71
+
72
+ @field_validator("content", mode="before")
73
+ def _validate_content(cls, v):
74
+ if v is None:
75
+ return ActionResponseContent()
76
+ if isinstance(v, dict):
77
+ return ActionResponseContent.from_dict(v)
78
+ if isinstance(v, ActionResponseContent):
79
+ return v
80
+ raise TypeError(
81
+ "content must be dict or ActionResponseContent instance"
82
+ )
49
83
 
50
84
  @property
51
85
  def function(self) -> str:
52
- """Name of the function that was executed."""
53
- return self.content.get("action_response", {}).get("function", None)
86
+ """Access the function name."""
87
+ return self.content.function
54
88
 
55
89
  @property
56
90
  def arguments(self) -> dict[str, Any]:
57
- """Arguments used for the executed function."""
58
- return self.content.get("action_response", {}).get("arguments", {})
91
+ """Access the function arguments."""
92
+ return self.content.arguments
59
93
 
60
94
  @property
61
95
  def output(self) -> Any:
62
- """The result or returned data from the function call."""
63
- return self.content.get("action_response", {}).get("output", None)
64
-
65
- @property
66
- def response(self) -> dict[str, Any]:
67
- """
68
- A helper to get the entire 'action_response' dictionary.
69
-
70
- Returns:
71
- dict[str, Any]: The entire response, including function, arguments, and output.
72
- """
73
- return copy(self.content.get("action_response", {}))
74
-
75
- @property
76
- def action_request_id(self) -> IDType:
77
- """The ID of the original action request."""
78
- return IDType.validate(self.content.get("action_request_id"))
79
-
80
- @override
81
- @classmethod
82
- def create(
83
- cls,
84
- action_request: ActionRequest,
85
- output: Any | None = None,
86
- response_model=None,
87
- sender: SenderRecipient | None = None,
88
- recipient: SenderRecipient | None = None,
89
- ) -> "ActionResponse":
90
- """
91
- Build an ActionResponse from a matching `ActionRequest` and output.
92
-
93
- Args:
94
- action_request (ActionRequest): The original request being fulfilled.
95
- output (Any, optional): The function output or result.
96
- response_model (Any, optional):
97
- If present and has `.output`, this is used instead of `output`.
98
- sender (SenderRecipient, optional):
99
- The role or ID of the sender (defaults to the request's recipient).
100
- recipient (SenderRecipient, optional):
101
- The role or ID of the recipient (defaults to the request's sender).
102
-
103
- Returns:
104
- ActionResponse: A new instance referencing the `ActionRequest`.
105
- """
106
- if response_model:
107
- output = response_model.output
108
-
109
- instance = ActionResponse(
110
- content=prepare_action_response_content(
111
- action_request=response_model or action_request, output=output
112
- ),
113
- role=MessageRole.ACTION,
114
- sender=sender or action_request.recipient,
115
- recipient=recipient or action_request.sender,
116
- )
117
- action_request.action_response_id = instance.id
118
- return instance
119
-
120
- def update(
121
- self,
122
- action_request: ActionRequest = None,
123
- output: Any = None,
124
- response_model=None,
125
- sender: SenderRecipient = None,
126
- recipient: SenderRecipient = None,
127
- template: Template | str | None = None,
128
- **kwargs,
129
- ):
130
- """
131
- Update this response with a new request reference or new output.
132
-
133
- Args:
134
- action_request (ActionRequest): The updated request.
135
- output (Any): The new function output data.
136
- response_model: If present, uses response_model.output.
137
- sender (SenderRecipient): New sender ID or role.
138
- recipient (SenderRecipient): New recipient ID or role.
139
- template (Template | str | None): Optional new template.
140
- **kwargs: Additional fields to store in content.
141
- """
142
- if response_model:
143
- output = response_model.output
144
-
145
- if action_request:
146
- self.content = prepare_action_response_content(
147
- action_request=action_request, output=output or self.output
148
- )
149
- action_request.action_response_id = self.id
150
- super().update(
151
- sender=sender, recipient=recipient, template=template, **kwargs
152
- )
153
-
154
-
155
- # File: lionagi/protocols/messages/action_response.py
96
+ """Access the function output."""
97
+ return self.content.output