lionagi 0.7.0__py3-none-any.whl → 0.7.2__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 (43) hide show
  1. lionagi/operations/ReAct/ReAct.py +2 -2
  2. lionagi/operations/_act/act.py +10 -3
  3. lionagi/operations/communicate/communicate.py +0 -59
  4. lionagi/operations/interpret/interpret.py +1 -2
  5. lionagi/operations/operate/operate.py +10 -5
  6. lionagi/operations/parse/parse.py +0 -36
  7. lionagi/operations/plan/plan.py +3 -3
  8. lionagi/operatives/action/manager.py +105 -82
  9. lionagi/operatives/action/request_response_model.py +31 -0
  10. lionagi/operatives/action/tool.py +50 -20
  11. lionagi/protocols/_concepts.py +1 -1
  12. lionagi/protocols/adapters/adapter.py +25 -0
  13. lionagi/protocols/adapters/json_adapter.py +107 -27
  14. lionagi/protocols/adapters/pandas_/csv_adapter.py +55 -11
  15. lionagi/protocols/adapters/pandas_/excel_adapter.py +52 -10
  16. lionagi/protocols/adapters/pandas_/pd_dataframe_adapter.py +54 -4
  17. lionagi/protocols/adapters/pandas_/pd_series_adapter.py +40 -0
  18. lionagi/protocols/generic/element.py +1 -1
  19. lionagi/protocols/generic/pile.py +5 -8
  20. lionagi/protocols/graph/edge.py +1 -1
  21. lionagi/protocols/graph/graph.py +16 -8
  22. lionagi/protocols/graph/node.py +1 -1
  23. lionagi/protocols/mail/exchange.py +126 -15
  24. lionagi/protocols/mail/mail.py +33 -0
  25. lionagi/protocols/mail/mailbox.py +62 -0
  26. lionagi/protocols/mail/manager.py +97 -41
  27. lionagi/protocols/mail/package.py +57 -3
  28. lionagi/protocols/messages/action_request.py +77 -26
  29. lionagi/protocols/messages/action_response.py +55 -26
  30. lionagi/protocols/messages/assistant_response.py +50 -15
  31. lionagi/protocols/messages/base.py +36 -0
  32. lionagi/protocols/messages/instruction.py +175 -145
  33. lionagi/protocols/messages/manager.py +152 -56
  34. lionagi/protocols/messages/message.py +61 -25
  35. lionagi/protocols/messages/system.py +54 -19
  36. lionagi/service/imodel.py +24 -0
  37. lionagi/session/branch.py +40 -32
  38. lionagi/utils.py +1 -0
  39. lionagi/version.py +1 -1
  40. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/METADATA +1 -1
  41. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/RECORD +43 -43
  42. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/WHEEL +0 -0
  43. {lionagi-0.7.0.dist-info → lionagi-0.7.2.dist-info}/licenses/LICENSE +0 -0
@@ -21,7 +21,7 @@ async def ReAct(
21
21
  interpret: bool = False,
22
22
  tools: Any = None,
23
23
  tool_schemas: Any = None,
24
- response_format: type[BaseModel] = None,
24
+ response_format: type[BaseModel] | BaseModel = None,
25
25
  extension_allowed: bool = False,
26
26
  max_extensions: int | None = None,
27
27
  response_kwargs: dict | None = None,
@@ -60,7 +60,7 @@ async def ReAct(
60
60
  kwargs_for_operate["actions"] = True
61
61
  kwargs_for_operate["reason"] = True
62
62
 
63
- # We'll pass the refined instruct_dict plus the users other kwargs
63
+ # We'll pass the refined instruct_dict plus the user's other kwargs
64
64
  from .utils import ReActAnalysis
65
65
 
66
66
  # Step 1: Generate initial ReAct analysis
@@ -7,9 +7,10 @@ from typing import TYPE_CHECKING
7
7
 
8
8
  from pydantic import BaseModel
9
9
 
10
- from lionagi.protocols.types import ActionResponse, Log
10
+ from lionagi.protocols.types import Log
11
11
 
12
12
  if TYPE_CHECKING:
13
+ from lionagi.operatives.types import ActionResponseModel
13
14
  from lionagi.session.branch import Branch
14
15
 
15
16
 
@@ -17,7 +18,7 @@ async def _act(
17
18
  branch: "Branch",
18
19
  action_request: BaseModel | dict,
19
20
  suppress_errors: bool = False,
20
- ) -> ActionResponse:
21
+ ) -> "ActionResponseModel":
21
22
 
22
23
  _request = {}
23
24
 
@@ -35,7 +36,13 @@ async def _act(
35
36
  try:
36
37
  func_call = await branch._action_manager.invoke(_request)
37
38
  except Exception as e:
38
- branch._log_manager.log(Log(content={"error": str(e)}))
39
+ content = {
40
+ "error": str(e),
41
+ "function": _request.get("function"),
42
+ "arguments": _request.get("arguments"),
43
+ "branch": str(branch.id),
44
+ }
45
+ branch._log_manager.log(Log(content=content))
39
46
  if suppress_errors:
40
47
  logging.error(
41
48
  f"Error invoking action '{_request['function']}': {e}"
@@ -37,65 +37,6 @@ async def communicate(
37
37
  operative_model=None,
38
38
  **kwargs,
39
39
  ):
40
- """
41
- A simpler orchestration than `operate()`, typically without tool invocation.
42
-
43
- **Flow**:
44
- 1. Sends an instruction (or conversation) to the chat model.
45
- 2. Optionally parses the response into a structured model or fields.
46
- 3. Returns either the raw string, the parsed model, or a dict of fields.
47
-
48
- Args:
49
- instruction (Instruction | dict, optional):
50
- The user's main query or data.
51
- guidance (JsonValue, optional):
52
- Additional instructions or context for the LLM.
53
- context (JsonValue, optional):
54
- Extra data or context.
55
- plain_content (str, optional):
56
- Plain text content appended to the instruction.
57
- sender (SenderRecipient, optional):
58
- Sender ID (defaults to `Branch.user`).
59
- recipient (SenderRecipient, optional):
60
- Recipient ID (defaults to `self.id`).
61
- progression (ID.IDSeq, optional):
62
- Custom ordering of messages.
63
- request_model (type[BaseModel] | BaseModel | None, optional):
64
- Model for validating or structuring the LLM's response.
65
- response_format (type[BaseModel], optional):
66
- Alias for `request_model`. If both are provided, raises ValueError.
67
- request_fields (dict|list[str], optional):
68
- If you only need certain fields from the LLM's response.
69
- imodel (iModel, optional):
70
- Deprecated alias for `chat_model`.
71
- chat_model (iModel, optional):
72
- An alternative to the default chat model.
73
- parse_model (iModel, optional):
74
- If parsing is needed, you can override the default parse model.
75
- skip_validation (bool, optional):
76
- If True, returns the raw response string unvalidated.
77
- images (list, optional):
78
- Any relevant images.
79
- image_detail (Literal["low","high","auto"], optional):
80
- Image detail level (if used).
81
- num_parse_retries (int, optional):
82
- Maximum parsing retries (capped at 5).
83
- fuzzy_match_kwargs (dict, optional):
84
- Additional settings for fuzzy field matching (if used).
85
- clear_messages (bool, optional):
86
- Whether to clear stored messages before sending.
87
- operative_model (type[BaseModel], optional):
88
- Deprecated, alias for `response_format`.
89
- **kwargs:
90
- Additional arguments for the underlying LLM call.
91
-
92
- Returns:
93
- Any:
94
- - Raw string (if `skip_validation=True`),
95
- - A validated Pydantic model,
96
- - A dict of the requested fields,
97
- - or `None` if parsing fails and `handle_validation='return_none'`.
98
- """
99
40
  if operative_model:
100
41
  logging.warning(
101
42
  "operative_model is deprecated. Use response_format instead."
@@ -30,11 +30,10 @@ async def interpret(
30
30
  # Default temperature if none provided
31
31
  kwargs["temperature"] = kwargs.get("temperature", 0.1)
32
32
 
33
- refined_prompt = await branch.communicate(
33
+ refined_prompt = await branch.chat(
34
34
  instruction=instruction,
35
35
  guidance=guidance,
36
36
  context=context,
37
- skip_validation=True,
38
37
  **kwargs,
39
38
  )
40
39
  return str(refined_prompt)
@@ -7,9 +7,14 @@ from typing import TYPE_CHECKING, Literal
7
7
 
8
8
  from pydantic import BaseModel, JsonValue
9
9
 
10
- from lionagi.operatives.models.field_model import FieldModel
11
- from lionagi.operatives.models.model_params import ModelParams
12
- from lionagi.operatives.types import Instruct, Operative, Step, ToolRef
10
+ from lionagi.operatives.types import (
11
+ FieldModel,
12
+ Instruct,
13
+ ModelParams,
14
+ Operative,
15
+ Step,
16
+ ToolRef,
17
+ )
13
18
  from lionagi.protocols.types import Instruction, Progression, SenderRecipient
14
19
  from lionagi.service.imodel import iModel
15
20
 
@@ -110,7 +115,7 @@ async def operate(
110
115
 
111
116
  # If we want to auto-invoke tools, fetch or generate the schemas
112
117
  if invoke_actions and tools:
113
- tool_schemas = branch.acts.get_tool_schema(tools=tools)
118
+ tool_schemas = tool_schemas or branch.acts.get_tool_schema(tools=tools)
114
119
 
115
120
  # 2) Send the instruction to the chat model
116
121
  ins, res = await branch.chat(
@@ -138,7 +143,7 @@ async def operate(
138
143
  if skip_validation:
139
144
  return operative if return_operative else operative.response_str_dict
140
145
 
141
- # 5) Parse or validate the response into the operatives model
146
+ # 5) Parse or validate the response into the operative's model
142
147
  response_model = operative.update_response_model(res.response)
143
148
  if not isinstance(response_model, BaseModel):
144
149
  # If the response isn't directly a model, attempt a parse
@@ -35,42 +35,6 @@ async def parse(
35
35
  suppress_conversion_errors: bool = False,
36
36
  response_format=None,
37
37
  ):
38
- """Attempts to parse text into a structured Pydantic model.
39
-
40
- Uses optional fuzzy matching to handle partial or unclear fields.
41
-
42
- Args:
43
- text (str): The raw text to parse.
44
- handle_validation (Literal["raise","return_value","return_none"]):
45
- What to do if parsing fails. Defaults to "return_value".
46
- max_retries (int):
47
- How many times to retry parsing if it fails.
48
- request_type (type[BaseModel], optional):
49
- The Pydantic model to parse into.
50
- operative (Operative, optional):
51
- If provided, uses its model and max_retries setting.
52
- similarity_algo (str):
53
- The similarity algorithm for fuzzy field matching.
54
- similarity_threshold (float):
55
- A threshold for fuzzy matching (0.0 - 1.0).
56
- fuzzy_match (bool):
57
- If True, tries to match unrecognized keys to known ones.
58
- handle_unmatched (Literal["ignore","raise","remove","fill","force"]):
59
- How to handle unmatched fields.
60
- fill_value (Any):
61
- A default value used when fill is needed.
62
- fill_mapping (dict[str, Any] | None):
63
- A mapping from field -> fill value override.
64
- strict (bool):
65
- If True, raises errors on ambiguous fields or data types.
66
- suppress_conversion_errors (bool):
67
- If True, logs or ignores errors during data conversion.
68
-
69
- Returns:
70
- BaseModel | Any | None:
71
- The parsed model instance, or a dict/string/None depending
72
- on the handling mode.
73
- """
74
38
  _should_try = True
75
39
  num_try = 0
76
40
  response_model = text
@@ -6,7 +6,7 @@ from typing import Any, Literal
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
- from lionagi.operatives.instruct.instruct import (
9
+ from lionagi.operatives.types import (
10
10
  LIST_INSTRUCT_FIELD_MODEL,
11
11
  Instruct,
12
12
  InstructResponse,
@@ -293,7 +293,7 @@ async def plan(
293
293
  # ---------------------------------------------------------
294
294
  # Strategy C: SEQUENTIAL_CONCURRENT_CHUNK
295
295
  # - process plan steps in chunks (one chunk after another),
296
- # - each chunks steps run in parallel.
296
+ # - each chunk's steps run in parallel.
297
297
  # ---------------------------------------------------------
298
298
  case "sequential_concurrent_chunk":
299
299
  chunk_size = (execution_kwargs or {}).get("chunk_size", 5)
@@ -334,7 +334,7 @@ async def plan(
334
334
  # Strategy D: CONCURRENT_SEQUENTIAL_CHUNK
335
335
  # - split plan steps into chunks,
336
336
  # - run all chunks in parallel,
337
- # - but each chunks steps run sequentially.
337
+ # - but each chunk's steps run sequentially.
338
338
  # ---------------------------------------------------------
339
339
  case "concurrent_sequential_chunk":
340
340
  chunk_size = (execution_kwargs or {}).get("chunk_size", 5)
@@ -2,6 +2,12 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ """
6
+ Defines the `ActionManager` class, a specialized Manager that registers
7
+ `Tool` objects (or callables) for function invocation. It can match
8
+ incoming requests (ActionRequest) to a registered tool, then run it.
9
+ """
10
+
5
11
  from typing import Any
6
12
 
7
13
  from lionagi.protocols._concepts import Manager
@@ -17,9 +23,22 @@ __all__ = ("ActionManager",)
17
23
 
18
24
 
19
25
  class ActionManager(Manager):
26
+ """
27
+ A manager that registers function-based tools and invokes them
28
+ when triggered by an ActionRequest. Tools can be registered
29
+ individually or in bulk, and each tool must have a unique name.
30
+ """
20
31
 
21
32
  def __init__(self, *args: FuncTool, **kwargs) -> None:
33
+ """
34
+ Create an ActionManager, optionally registering initial tools.
22
35
 
36
+ Args:
37
+ *args (FuncTool):
38
+ A variable number of tools or callables.
39
+ **kwargs:
40
+ Additional named arguments that are also considered tools.
41
+ """
23
42
  super().__init__()
24
43
  self.registry: dict[str, Tool] = {}
25
44
 
@@ -27,24 +46,19 @@ class ActionManager(Manager):
27
46
  if args:
28
47
  tools.extend(to_list(args, dropna=True, flatten=True))
29
48
  if kwargs:
30
- tools.extend(
31
- to_list(kwargs, dropna=True, flatten=True, use_values=True)
32
- )
49
+ tools.extend(to_list(kwargs.values(), dropna=True, flatten=True))
50
+
33
51
  self.register_tools(tools, update=True)
34
52
 
35
53
  def __contains__(self, tool: FuncToolRef) -> bool:
36
- """Check if a tool is registered in the registry.
37
-
38
- Supports checking by:
39
- - Tool object
40
- - Tool name (string)
41
- - Callable function
42
-
43
- Args:
44
- tool: The tool to check for registration.
54
+ """
55
+ Check if a tool is registered, by either:
56
+ - The Tool object itself,
57
+ - A string name for the function,
58
+ - Or the callable's __name__.
45
59
 
46
60
  Returns:
47
- bool: True if tool is registered, False otherwise.
61
+ bool: True if found, else False.
48
62
  """
49
63
  if isinstance(tool, Tool):
50
64
  return tool.function in self.registry
@@ -54,24 +68,21 @@ class ActionManager(Manager):
54
68
  return tool.__name__ in self.registry
55
69
  return False
56
70
 
57
- def register_tool(
58
- self,
59
- tool: FuncTool,
60
- update: bool = False,
61
- ) -> None:
62
- """Register a single tool in the registry.
63
-
64
- If the tool is a callable function, it is automatically converted
65
- to a Tool object. Existing tools can be updated if update=True.
71
+ def register_tool(self, tool: FuncTool, update: bool = False) -> None:
72
+ """
73
+ Register a single tool/callable in the manager.
66
74
 
67
75
  Args:
68
- tool: The tool to register (Tool object or callable).
69
- update: If True, update existing tool; if False, raise error.
76
+ tool (FuncTool):
77
+ A `Tool` object or a raw callable function.
78
+ update (bool):
79
+ If True, allow replacing an existing tool with the same name.
70
80
 
71
81
  Raises:
72
82
  ValueError: If tool already registered and update=False.
73
- TypeError: If tool is not a Tool object or callable.
83
+ TypeError: If `tool` is not a Tool or callable.
74
84
  """
85
+ # Check if tool already exists
75
86
  if not update and tool in self:
76
87
  name = None
77
88
  if isinstance(tool, Tool):
@@ -80,81 +91,85 @@ class ActionManager(Manager):
80
91
  name = tool.__name__
81
92
  raise ValueError(f"Tool {name} is already registered.")
82
93
 
94
+ # Convert raw callable to a Tool if needed
83
95
  if callable(tool):
84
96
  tool = Tool(func_callable=tool)
85
97
  if not isinstance(tool, Tool):
86
- raise TypeError("Please register a Tool object or callable.")
87
-
98
+ raise TypeError(
99
+ "Must provide a `Tool` object or a callable function."
100
+ )
88
101
  self.registry[tool.function] = tool
89
102
 
90
103
  def register_tools(
91
- self,
92
- tools: list[FuncTool] | FuncTool,
93
- update: bool = False,
104
+ self, tools: list[FuncTool] | FuncTool, update: bool = False
94
105
  ) -> None:
95
- """Register multiple tools in the registry.
96
-
97
- Handles both single tools and lists of tools. Each tool can be
98
- either a Tool object or a callable function.
106
+ """
107
+ Register multiple tools at once.
99
108
 
100
109
  Args:
101
- tools: Single tool or list of tools to register.
102
- update: If True, update existing tools; if False, raise error.
110
+ tools (list[FuncTool] | FuncTool):
111
+ A single or list of tools/callables.
112
+ update (bool):
113
+ If True, allow updating existing tools.
103
114
 
104
115
  Raises:
105
- ValueError: If any tool is already registered.
106
- TypeError: If any tool is not a Tool object or callable.
116
+ ValueError: If a duplicate tool is found and update=False.
117
+ TypeError: If any item is not a Tool or callable.
107
118
  """
108
119
  tools_list = tools if isinstance(tools, list) else [tools]
109
- [
110
- self.register_tool(tool, update=update)
111
- for tool in to_list(tools_list, dropna=True, flatten=True)
112
- ]
120
+ for t in tools_list:
121
+ self.register_tool(t, update=update)
113
122
 
114
123
  def match_tool(
115
124
  self, action_request: ActionRequest | ActionRequestModel | dict
116
125
  ) -> FunctionCalling:
126
+ """
127
+ Convert an ActionRequest (or dict with "function"/"arguments")
128
+ into a `FunctionCalling` instance by finding the matching tool.
129
+
130
+ Raises:
131
+ TypeError: If `action_request` is an unsupported type.
132
+ ValueError: If no matching tool is found in the registry.
133
+
134
+ Returns:
135
+ FunctionCalling: The event object that can be invoked.
136
+ """
117
137
  if not isinstance(
118
138
  action_request, ActionRequest | ActionRequestModel | dict
119
139
  ):
120
140
  raise TypeError(f"Unsupported type {type(action_request)}")
121
141
 
122
- func = (
123
- action_request["function"]
124
- if isinstance(action_request, dict)
125
- else action_request.function
126
- )
127
- args = (
128
- action_request["arguments"]
129
- if isinstance(action_request, dict)
130
- else action_request.arguments
131
- )
132
- tool = self.registry.get(func, None)
142
+ func, args = None, None
143
+ if isinstance(action_request, dict):
144
+ func = action_request["function"]
145
+ args = action_request["arguments"]
146
+ else:
147
+ func = action_request.function
148
+ args = action_request.arguments
133
149
 
150
+ tool = self.registry.get(func, None)
134
151
  if not isinstance(tool, Tool):
135
152
  raise ValueError(f"Function {func} is not registered.")
153
+
136
154
  return FunctionCalling(func_tool=tool, arguments=args)
137
155
 
138
156
  async def invoke(
139
- self, func_call: ActionRequestModel | ActionRequest
140
- ) -> FunctionCalling | Execution | None:
141
- """Invoke a tool based on the provided function call.
157
+ self,
158
+ func_call: ActionRequestModel | ActionRequest,
159
+ ) -> FunctionCalling:
160
+ """
161
+ High-level API to parse and run a function call.
142
162
 
143
- 1. Matches function call to registered tool
144
- 2. Creates FunctionCalling instance
145
- 3. Invokes function with arguments
146
- 4. Logs execution details
147
- 5. Returns result
163
+ Steps:
164
+ 1) Convert `func_call` to FunctionCalling via `match_tool`.
165
+ 2) `invoke()` the resulting object.
166
+ 3) Return the `FunctionCalling`, which includes `execution`.
148
167
 
149
168
  Args:
150
- func_call: Function call specification in supported format.
151
- log_manager: Optional logger for execution tracking.
169
+ func_call: The action request model or ActionRequest object.
152
170
 
153
171
  Returns:
154
- Result of tool invocation after processing pipeline.
155
-
156
- Raises:
157
- ValueError: If function not registered or call format invalid.
172
+ `FunctionCalling` event after it completes execution.
158
173
  """
159
174
  function_calling = self.match_tool(func_call)
160
175
  await function_calling.invoke()
@@ -162,11 +177,7 @@ class ActionManager(Manager):
162
177
 
163
178
  @property
164
179
  def schema_list(self) -> list[dict[str, Any]]:
165
- """List all tool schemas currently registered.
166
-
167
- Returns:
168
- List of OpenAI function schemas for all registered tools.
169
- """
180
+ """Return the list of JSON schemas for all registered tools."""
170
181
  return [tool.tool_schema for tool in self.registry.values()]
171
182
 
172
183
  def get_tool_schema(
@@ -175,20 +186,25 @@ class ActionManager(Manager):
175
186
  auto_register: bool = True,
176
187
  update: bool = False,
177
188
  ) -> dict:
178
- """Retrieve the schema for specific tools or all tools.
189
+ """
190
+ Retrieve schemas for a subset of tools or for all.
179
191
 
180
192
  Args:
181
- tools: Specification of which tools to retrieve schemas for.
182
- If True, return all tools. If False, return empty dict.
183
- Can also be a specific tool or list of tools.
184
- **kwargs: Additional keyword arguments to include in output.
193
+ tools (ToolRef):
194
+ - If True, return schema for all tools.
195
+ - If False, return an empty dict.
196
+ - If specific tool(s), returns only those schemas.
197
+ auto_register (bool):
198
+ If a tool (callable) is not yet in the registry, register if True.
199
+ update (bool):
200
+ If True, allow updating existing tools.
185
201
 
186
202
  Returns:
187
- Dictionary containing tool schemas and additional kwargs.
203
+ dict: e.g., {"tools": [list of schemas]}
188
204
 
189
205
  Raises:
190
- ValueError: If a specified tool is not registered.
191
- TypeError: If an unsupported tool type is provided.
206
+ ValueError: If requested tool is not found and auto_register=False.
207
+ TypeError: If tool specification is invalid.
192
208
  """
193
209
  if isinstance(tools, list | tuple) and len(tools) == 1:
194
210
  tools = tools[0]
@@ -207,10 +223,15 @@ class ActionManager(Manager):
207
223
  tool: Any,
208
224
  auto_register: bool = True,
209
225
  update: bool = False,
210
- ) -> dict[str, Any] | list[dict[str, Any]]:
226
+ ) -> list[dict[str, Any]] | dict[str, Any]:
227
+ """
228
+ Internal helper to handle retrieval or registration of a single or
229
+ multiple tools, returning their schema(s).
230
+ """
211
231
  if isinstance(tool, dict):
212
- return tool
232
+ return tool # Already a schema
213
233
  if callable(tool):
234
+ # Possibly unregistered function
214
235
  name = tool.__name__
215
236
  if name not in self.registry:
216
237
  if auto_register:
@@ -233,3 +254,5 @@ class ActionManager(Manager):
233
254
 
234
255
 
235
256
  __all__ = ["ActionManager"]
257
+
258
+ # File: lionagi/operatives/action/manager.py
@@ -2,6 +2,12 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ """
6
+ Defines Pydantic models for action requests and responses. They typically map
7
+ to conversation messages describing which function is called, with what arguments,
8
+ and any returned output.
9
+ """
10
+
5
11
  from typing import Any
6
12
 
7
13
  from pydantic import Field, field_validator
@@ -28,6 +34,10 @@ __all__ = (
28
34
 
29
35
 
30
36
  class ActionRequestModel(HashableModel):
37
+ """
38
+ Captures a single action request, typically from a user or system message.
39
+ Includes the name of the function and the arguments to be passed.
40
+ """
31
41
 
32
42
  function: str | None = Field(
33
43
  None,
@@ -43,6 +53,12 @@ class ActionRequestModel(HashableModel):
43
53
 
44
54
  @field_validator("arguments", mode="before")
45
55
  def validate_arguments(cls, value: Any) -> dict[str, Any]:
56
+ """
57
+ Coerce arguments into a dictionary if possible, recursively.
58
+
59
+ Raises:
60
+ ValueError if the data can't be coerced.
61
+ """
46
62
  return to_dict(
47
63
  value,
48
64
  fuzzy_parse=True,
@@ -52,10 +68,19 @@ class ActionRequestModel(HashableModel):
52
68
 
53
69
  @field_validator("function", mode="before")
54
70
  def validate_function(cls, value: str) -> str:
71
+ """
72
+ Ensure the function name is a valid non-empty string (if provided).
73
+ """
55
74
  return validate_nullable_string_field(cls, value, "function", False)
56
75
 
57
76
  @classmethod
58
77
  def create(cls, content: str):
78
+ """
79
+ Attempt to parse a string (usually from a conversation or JSON) into
80
+ one or more ActionRequestModel instances.
81
+
82
+ If no valid structure is found, returns an empty list.
83
+ """
59
84
  try:
60
85
  content = parse_action_request(content)
61
86
  if content:
@@ -75,6 +100,10 @@ ACTION_REQUESTS_FIELD = FieldModel(
75
100
 
76
101
 
77
102
  class ActionResponseModel(HashableModel):
103
+ """
104
+ Encapsulates a function's output after being called. Typically
105
+ references the original function name, arguments, and the result.
106
+ """
78
107
 
79
108
  function: str = Field(default_factory=str, title="Function")
80
109
  arguments: dict[str, Any] = Field(default_factory=dict)
@@ -88,3 +117,5 @@ ACTION_RESPONSES_FIELD = FieldModel(
88
117
  title="Actions",
89
118
  description="**do not fill**",
90
119
  )
120
+
121
+ # File: lionagi/operatives/action/request_response_model.py