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