lionagi 0.7.0__py3-none-any.whl → 0.7.2__py3-none-any.whl

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