lionagi 0.18.0__py3-none-any.whl → 0.18.1__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 (78) hide show
  1. lionagi/_errors.py +0 -5
  2. lionagi/fields.py +83 -0
  3. lionagi/ln/__init__.py +3 -1
  4. lionagi/ln/concurrency/primitives.py +4 -4
  5. lionagi/ln/concurrency/task.py +1 -0
  6. lionagi/models/field_model.py +12 -4
  7. lionagi/models/hashable_model.py +2 -3
  8. lionagi/operations/ReAct/ReAct.py +1 -1
  9. lionagi/operations/act/act.py +3 -3
  10. lionagi/operations/builder.py +5 -7
  11. lionagi/operations/fields.py +380 -0
  12. lionagi/operations/flow.py +4 -6
  13. lionagi/operations/node.py +4 -4
  14. lionagi/operations/operate/operate.py +9 -7
  15. lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
  16. lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
  17. lionagi/operations/select/select.py +1 -1
  18. lionagi/operations/select/utils.py +7 -1
  19. lionagi/operations/types.py +1 -1
  20. lionagi/protocols/action/manager.py +5 -6
  21. lionagi/protocols/contracts.py +2 -2
  22. lionagi/protocols/generic/__init__.py +22 -0
  23. lionagi/protocols/generic/element.py +36 -127
  24. lionagi/protocols/generic/pile.py +9 -10
  25. lionagi/protocols/generic/progression.py +23 -22
  26. lionagi/protocols/graph/edge.py +6 -5
  27. lionagi/protocols/ids.py +6 -49
  28. lionagi/protocols/messages/__init__.py +3 -1
  29. lionagi/protocols/messages/base.py +7 -6
  30. lionagi/protocols/messages/instruction.py +0 -1
  31. lionagi/protocols/types.py +1 -11
  32. lionagi/service/connections/__init__.py +3 -0
  33. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  34. lionagi/service/hooks/_types.py +1 -1
  35. lionagi/service/hooks/_utils.py +1 -1
  36. lionagi/service/hooks/hook_event.py +3 -8
  37. lionagi/service/hooks/hook_registry.py +5 -5
  38. lionagi/service/hooks/hooked_event.py +61 -1
  39. lionagi/service/imodel.py +24 -20
  40. lionagi/service/third_party/claude_code.py +1 -2
  41. lionagi/service/third_party/openai_models.py +24 -22
  42. lionagi/service/token_calculator.py +1 -94
  43. lionagi/session/branch.py +26 -228
  44. lionagi/session/session.py +5 -90
  45. lionagi/version.py +1 -1
  46. {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -5
  47. {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/RECORD +49 -76
  48. lionagi/fields/__init__.py +0 -47
  49. lionagi/fields/action.py +0 -188
  50. lionagi/fields/base.py +0 -153
  51. lionagi/fields/code.py +0 -239
  52. lionagi/fields/file.py +0 -234
  53. lionagi/fields/instruct.py +0 -135
  54. lionagi/fields/reason.py +0 -55
  55. lionagi/fields/research.py +0 -52
  56. lionagi/operations/brainstorm/__init__.py +0 -2
  57. lionagi/operations/brainstorm/brainstorm.py +0 -498
  58. lionagi/operations/brainstorm/prompt.py +0 -11
  59. lionagi/operations/instruct/__init__.py +0 -2
  60. lionagi/operations/instruct/instruct.py +0 -28
  61. lionagi/operations/plan/__init__.py +0 -6
  62. lionagi/operations/plan/plan.py +0 -386
  63. lionagi/operations/plan/prompt.py +0 -25
  64. lionagi/operations/utils.py +0 -45
  65. lionagi/protocols/forms/__init__.py +0 -2
  66. lionagi/protocols/forms/base.py +0 -85
  67. lionagi/protocols/forms/flow.py +0 -79
  68. lionagi/protocols/forms/form.py +0 -86
  69. lionagi/protocols/forms/report.py +0 -48
  70. lionagi/protocols/mail/__init__.py +0 -2
  71. lionagi/protocols/mail/exchange.py +0 -220
  72. lionagi/protocols/mail/mail.py +0 -51
  73. lionagi/protocols/mail/mailbox.py +0 -103
  74. lionagi/protocols/mail/manager.py +0 -218
  75. lionagi/protocols/mail/package.py +0 -101
  76. lionagi/protocols/operatives/__init__.py +0 -2
  77. {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
  78. {lionagi-0.18.0.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
lionagi/_errors.py CHANGED
@@ -13,7 +13,6 @@ __all__ = (
13
13
  "ObservationError",
14
14
  "ResourceError",
15
15
  "RateLimitError",
16
- "IDError",
17
16
  "RelationError",
18
17
  "OperationError",
19
18
  "ExecutionError",
@@ -127,10 +126,6 @@ class RateLimitError(LionError):
127
126
  object.__setattr__(self, "retry_after", retry_after)
128
127
 
129
128
 
130
- class IDError(LionError):
131
- pass
132
-
133
-
134
129
  class RelationError(LionError):
135
130
  pass
136
131
 
lionagi/fields.py ADDED
@@ -0,0 +1,83 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from .operations.fields import (
5
+ ActionRequestModel,
6
+ ActionResponseModel,
7
+ Instruct,
8
+ Reason,
9
+ get_default_field,
10
+ )
11
+
12
+ _lazy_imports = {}
13
+
14
+
15
+ def __getattr__(name: str):
16
+ if name in _lazy_imports:
17
+ return _lazy_imports[name]
18
+
19
+ if name == "ActionRequestModel":
20
+ from .operations.fields import ActionRequestModel
21
+
22
+ _lazy_imports[name] = ActionRequestModel
23
+ return ActionRequestModel
24
+
25
+ if name == "ActionResponseModel":
26
+ from .operations.fields import ActionResponseModel
27
+
28
+ _lazy_imports[name] = ActionResponseModel
29
+ return ActionResponseModel
30
+
31
+ if name == "Instruct":
32
+ from .operations.fields import Instruct
33
+
34
+ _lazy_imports[name] = Instruct
35
+ return Instruct
36
+
37
+ if name == "Reason":
38
+ from .operations.fields import Reason
39
+
40
+ _lazy_imports[name] = Reason
41
+ return Reason
42
+
43
+ from .operations.fields import get_default_field
44
+
45
+ if name == "get_default_field":
46
+
47
+ _lazy_imports[name] = get_default_field
48
+ return get_default_field
49
+
50
+ if name == "ACTION_REQUESTS_FIELD":
51
+ return get_default_field("action_requests")
52
+
53
+ if name == "ACTION_RESPONSES_FIELD":
54
+ return get_default_field("action_responses")
55
+
56
+ if name == "ACTION_REQUIRED_FIELD":
57
+ return get_default_field("action_required")
58
+
59
+ if name == "INSTRUCT_FIELD":
60
+ return get_default_field("instruct")
61
+
62
+ if name == "LIST_INSTRUCT_FIELD_MODEL":
63
+ return get_default_field("instruct", listable=True)
64
+
65
+ if name == "REASON_FIELD":
66
+ return get_default_field("reason")
67
+
68
+ raise AttributeError(f"module {__name__} has no attribute {name}")
69
+
70
+
71
+ __all__ = (
72
+ "ACTION_REQUESTS_FIELD",
73
+ "ACTION_RESPONSES_FIELD",
74
+ "ACTION_REQUIRED_FIELD",
75
+ "INSTRUCT_FIELD",
76
+ "LIST_INSTRUCT_FIELD_MODEL",
77
+ "REASON_FIELD",
78
+ "ActionRequestModel",
79
+ "ActionResponseModel",
80
+ "Instruct",
81
+ "Reason",
82
+ "get_default_field",
83
+ )
lionagi/ln/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from ._async_call import alcall, bcall
1
+ from ._async_call import AlcallParams, BcallParams, alcall, bcall
2
2
  from ._hash import hash_dict
3
3
  from ._json_dump import (
4
4
  get_orjson_default,
@@ -80,4 +80,6 @@ __all__ = (
80
80
  "not_sentinel",
81
81
  "to_dict",
82
82
  "fuzzy_validate_mapping",
83
+ "AlcallParams",
84
+ "BcallParams",
83
85
  )
@@ -142,15 +142,15 @@ class CapacityLimiter:
142
142
  """Get the number of currently available tokens."""
143
143
  return self._lim.available_tokens
144
144
 
145
- def acquire_on_behalf_of(self, borrower: object) -> None:
146
- """Synchronously acquire capacity on behalf of another object.
145
+ async def acquire_on_behalf_of(self, borrower: object) -> None:
146
+ """Asynchronously acquire capacity on behalf of another object.
147
147
 
148
148
  For resource pooling where the acquirer differs from the releaser.
149
149
 
150
150
  Args:
151
151
  borrower: Object that will be responsible for releasing.
152
152
  """
153
- self._lim.acquire_on_behalf_of(borrower)
153
+ await self._lim.acquire_on_behalf_of(borrower)
154
154
 
155
155
  def release_on_behalf_of(self, borrower: object) -> None:
156
156
  """Release capacity that was acquired on behalf of an object.
@@ -326,6 +326,6 @@ class Condition:
326
326
  """Wake up all tasks waiting on this condition."""
327
327
  self._condition.notify_all()
328
328
 
329
- def statistics(self) -> anyio.abc.ConditionStatistics:
329
+ def statistics(self) -> anyio.ConditionStatistics:
330
330
  """Return statistics about waiting tasks."""
331
331
  return self._condition.statistics()
@@ -7,6 +7,7 @@ from contextlib import asynccontextmanager
7
7
  from typing import Any, TypeVar
8
8
 
9
9
  import anyio
10
+ import anyio.abc
10
11
 
11
12
  T = TypeVar("T")
12
13
  R = TypeVar("R")
@@ -84,7 +84,7 @@ class FieldModel(Params):
84
84
  base_type: type[Any]
85
85
  metadata: tuple[Meta, ...]
86
86
 
87
- def __init__(self, **kwargs: Any) -> None:
87
+ def __init__(self, base_type: type[Any] = None, **kwargs: Any) -> None:
88
88
  """Initialize FieldModel with legacy compatibility.
89
89
 
90
90
  Handles backward compatibility by converting old-style kwargs to the new
@@ -94,6 +94,8 @@ class FieldModel(Params):
94
94
  **kwargs: Arbitrary keyword arguments, including legacy ones
95
95
  """
96
96
  # Convert legacy kwargs to proper format
97
+ if base_type is not None:
98
+ kwargs["base_type"] = base_type
97
99
  converted = self._convert_kwargs_to_params(**kwargs)
98
100
 
99
101
  # Set fields directly and validate
@@ -538,7 +540,7 @@ class FieldModel(Params):
538
540
  field_info = PydanticField(**field_kwargs)
539
541
 
540
542
  # Set the annotation from base_type for backward compatibility
541
- field_info.annotation = self.base_type
543
+ field_info.annotation = self.annotation
542
544
 
543
545
  return field_info
544
546
 
@@ -745,8 +747,14 @@ class FieldModel(Params):
745
747
 
746
748
  @property
747
749
  def annotation(self) -> type[Any]:
748
- """Get field annotation (base_type) for backward compatibility."""
749
- return Any if self._is_sentinel(self.base_type) else self.base_type
750
+ if self._is_sentinel(self.base_type):
751
+ return Any
752
+ t_ = self.base_type
753
+ if self.is_listable:
754
+ t_ = list[t_]
755
+ if self.is_nullable:
756
+ t_ = t_ | None
757
+ return t_
750
758
 
751
759
  def to_dict(self) -> dict[str, Any]:
752
760
  """Convert field model to dictionary for backward compatibility.
@@ -94,12 +94,11 @@ class HashableModel(BaseModel):
94
94
  def _get_default_hashable_serializer():
95
95
  global _DEFAULT_HASHABLE_SERIALIZER
96
96
  if _DEFAULT_HASHABLE_SERIALIZER is None:
97
- from lionagi.protocols.ids import Element, IDType
97
+ from lionagi.protocols.ids import Element
98
98
 
99
99
  _DEFAULT_HASHABLE_SERIALIZER = ln.get_orjson_default(
100
- order=[IDType, Element, BaseModel],
100
+ order=[Element, BaseModel],
101
101
  additional={
102
- IDType: lambda o: str(o),
103
102
  Element: lambda o: o.to_dict(),
104
103
  BaseModel: lambda o: o.model_dump(mode="json"),
105
104
  },
@@ -9,7 +9,6 @@ from pydantic import BaseModel
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
- from lionagi.fields.instruct import Instruct
13
12
  from lionagi.libs.schema.as_readable import as_readable
14
13
  from lionagi.libs.validate.common_field_validators import (
15
14
  validate_model_to_type,
@@ -18,6 +17,7 @@ from lionagi.ln.fuzzy import FuzzyMatchKeysParams
18
17
  from lionagi.models.field_model import FieldModel
19
18
  from lionagi.service.imodel import iModel
20
19
 
20
+ from ..fields import Instruct
21
21
  from ..types import (
22
22
  ActionParam,
23
23
  ChatParam,
@@ -6,10 +6,10 @@ from typing import TYPE_CHECKING, Literal
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
- from lionagi.fields.action import ActionResponseModel
10
- from lionagi.ln._async_call import AlcallParams
11
- from lionagi.protocols.types import ActionRequest, ActionResponse
9
+ from lionagi.ln import AlcallParams
10
+ from lionagi.protocols.messages import ActionRequest, ActionResponse
12
11
 
12
+ from ..fields import ActionResponseModel
13
13
  from ..types import ActionParam
14
14
 
15
15
  if TYPE_CHECKING:
@@ -46,9 +46,9 @@ class OperationGraphBuilder:
46
46
  >>> result = await session.flow(graph)
47
47
  >>>
48
48
  >>> # Expand based on results
49
- >>> if hasattr(result, 'instruct_models'):
49
+ >>> if hasattr(result, 'instruct_model'):
50
50
  ... builder.expand_from_result(
51
- ... result.instruct_models,
51
+ ... result.instruct_model,
52
52
  ... source_node_id=builder.last_operation_id,
53
53
  ... operation="instruct"
54
54
  ... )
@@ -150,7 +150,7 @@ class OperationGraphBuilder:
150
150
  based on results.
151
151
 
152
152
  Args:
153
- items: Items from result to expand (e.g., instruct_models)
153
+ items: Items from result to expand (e.g., instruct_model)
154
154
  source_node_id: ID of node that produced these items
155
155
  operation: Operation to apply to each item
156
156
  strategy: How to organize the expanded operations
@@ -253,11 +253,9 @@ class OperationGraphBuilder:
253
253
  if not sources:
254
254
  raise ValueError("No source nodes for aggregation")
255
255
 
256
- # Add aggregation metadata - convert IDType to strings for JSON serialization
256
+ # Add aggregation metadata - convert UUID to strings for JSON serialization
257
257
  agg_params = {
258
- "aggregation_sources": [
259
- str(s) for s in sources
260
- ], # Convert IDType to strings
258
+ "aggregation_sources": [str(s) for s in sources],
261
259
  "aggregation_count": len(sources),
262
260
  **parameters,
263
261
  }
@@ -0,0 +1,380 @@
1
+ import re
2
+ from typing import Any, Literal
3
+
4
+ from pydantic import BaseModel, Field, JsonValue, field_validator
5
+
6
+ from lionagi.ln import extract_json, to_dict, to_list
7
+ from lionagi.ln.types import Unset
8
+ from lionagi.models import HashableModel
9
+
10
+ _DEFAULT_FIELDS = {}
11
+
12
+
13
+ class Instruct(HashableModel):
14
+ """Model for defining instruction parameters and execution requirements."""
15
+
16
+ instruction: str | None = Field(
17
+ None,
18
+ description=(
19
+ "A clear, actionable task definition. Specify:\n"
20
+ "1) The primary goal or objective\n"
21
+ "2) Key success criteria or constraints\n"
22
+ "\n"
23
+ "Guidelines:\n"
24
+ "- Start with a direct action verb (e.g., 'Analyze', 'Generate', 'Create')\n"
25
+ "- Include scope, boundaries, or constraints\n"
26
+ "- Provide success criteria if relevant\n"
27
+ "- For complex tasks, break them into logical steps"
28
+ ),
29
+ )
30
+
31
+ guidance: JsonValue | None = Field(
32
+ None,
33
+ description=(
34
+ "Strategic direction and constraints for executing the task. "
35
+ "Include:\n"
36
+ "1) Preferred methods or frameworks\n"
37
+ "2) Quality benchmarks (e.g., speed, clarity)\n"
38
+ "3) Resource or environmental constraints\n"
39
+ "4) Relevant compliance or standards\n"
40
+ "Use None if no special guidance."
41
+ ),
42
+ )
43
+
44
+ context: JsonValue | None = Field(
45
+ None,
46
+ description=(
47
+ "Background information and current-state data needed for the task. "
48
+ "Should be:\n"
49
+ "1) Directly relevant\n"
50
+ "2) Sufficient to perform the task\n"
51
+ "3) Free of extraneous detail\n"
52
+ "Include environment, prior outcomes, system states, or dependencies. "
53
+ "Use None if no additional context is needed."
54
+ ),
55
+ )
56
+
57
+ reason: bool | None = Field(
58
+ None,
59
+ description=(
60
+ "Include a thoughtful explanation of decisions, trade-offs, "
61
+ "and insights. Encourage deeper introspection on why certain "
62
+ "choices were made, potential alternatives, and how confidence "
63
+ "was shaped. If not needed, set to None."
64
+ ),
65
+ )
66
+ actions: bool | None = Field(
67
+ None,
68
+ description=(
69
+ "Controls execution mode. "
70
+ "True: Execute specified actions. "
71
+ "False: Analysis/recommendations only. "
72
+ "None: Contextual execution."
73
+ ),
74
+ )
75
+
76
+ action_strategy: Literal["sequential", "concurrent"] | None = Field(
77
+ None,
78
+ description="Action strategy to use for executing actions. Default "
79
+ "is 'concurrent'. Only provide for if actions are enabled.",
80
+ )
81
+
82
+ @field_validator("instruction", "guidance", "context", mode="before")
83
+ def _validate_instruction(cls, v):
84
+ from lionagi.libs.validate.common_field_validators import (
85
+ validate_nullable_jsonvalue_field,
86
+ )
87
+
88
+ return validate_nullable_jsonvalue_field(cls, v)
89
+
90
+ @field_validator("reason", "actions", mode="before")
91
+ def _validate_reason(cls, v):
92
+ from lionagi.libs.validate.common_field_validators import (
93
+ validate_boolean_field,
94
+ )
95
+
96
+ return validate_boolean_field(cls, v)
97
+
98
+ @field_validator("action_strategy", mode="before")
99
+ def _validate_action_strategy(cls, v):
100
+ if v not in ["batch", "sequential", "concurrent"]:
101
+ return "concurrent"
102
+ return v
103
+
104
+
105
+ class Reason(HashableModel):
106
+ title: str | None = None
107
+ content: str | None = None
108
+ confidence_score: float | None = Field(
109
+ None,
110
+ title="Confidence Score",
111
+ description=(
112
+ "Numeric confidence score (0.0 to 1.0, up to three decimals) indicating "
113
+ "how well you've met user expectations. Use this guide:\n"
114
+ " • 1.0: Highly confident\n"
115
+ " • 0.8-1.0: Reasonably sure\n"
116
+ " • 0.5-0.8: Re-check, refine or backtrack\n"
117
+ " • 0.0-0.5: Off track, stop"
118
+ ),
119
+ )
120
+
121
+ @field_validator("confidence_score", mode="before")
122
+ def _validate_confidence(cls, v):
123
+ if v is None:
124
+ return None
125
+ try:
126
+ from lionagi.libs.validate.to_num import to_num
127
+
128
+ return to_num(
129
+ v,
130
+ upper_bound=1,
131
+ lower_bound=0,
132
+ num_type=float,
133
+ precision=3,
134
+ )
135
+ except Exception:
136
+ return -1
137
+
138
+
139
+ class ActionRequestModel(HashableModel):
140
+ """
141
+ Captures a single action request, typically from a user or system message.
142
+ Includes the name of the function and the arguments to be passed.
143
+ """
144
+
145
+ function: str | None = Field(
146
+ None,
147
+ title="Function",
148
+ description=(
149
+ "Name of the function to call from the provided `tool_schemas`. "
150
+ "If no `tool_schemas` exist, set to None or leave blank. "
151
+ "Never invent new function names outside what's given."
152
+ ),
153
+ examples=["multiply", "create_user"],
154
+ )
155
+ arguments: dict[str, Any] | None = Field(
156
+ None,
157
+ title="Arguments",
158
+ description=(
159
+ "Dictionary of arguments for the chosen function. "
160
+ "Use only argument names/types defined in `tool_schemas`. "
161
+ "Never introduce extra argument names."
162
+ ),
163
+ )
164
+
165
+ @field_validator("arguments", mode="before")
166
+ def validate_arguments(cls, value: Any) -> dict[str, Any]:
167
+ """
168
+ Coerce arguments into a dictionary if possible, recursively.
169
+
170
+ Raises:
171
+ ValueError if the data can't be coerced.
172
+ """
173
+ return to_dict(
174
+ value,
175
+ fuzzy_parse=True,
176
+ recursive=True,
177
+ recursive_python_only=False,
178
+ )
179
+
180
+ @field_validator("function", mode="before")
181
+ def validate_function(cls, value: str) -> str:
182
+ """
183
+ Ensure the function name is a valid non-empty string (if provided).
184
+ """
185
+ from lionagi.libs.validate.common_field_validators import (
186
+ validate_nullable_string_field,
187
+ )
188
+
189
+ return validate_nullable_string_field(cls, value, "function", False)
190
+
191
+ @classmethod
192
+ def create(cls, content: str):
193
+ """
194
+ Attempt to parse a string (usually from a conversation or JSON) into
195
+ one or more ActionRequestModel instances.
196
+
197
+ If no valid structure is found, returns an empty list.
198
+ """
199
+
200
+ def parse_action_request(content: str | dict) -> list[dict]:
201
+
202
+ json_blocks = []
203
+
204
+ if isinstance(content, BaseModel):
205
+ json_blocks = [content.model_dump()]
206
+
207
+ elif isinstance(content, str):
208
+ json_blocks = extract_json(content, fuzzy_parse=True)
209
+ if not json_blocks:
210
+ pattern2 = r"```python\s*(.*?)\s*```"
211
+ _d = re.findall(pattern2, content, re.DOTALL)
212
+ json_blocks = [
213
+ extract_json(match, fuzzy_parse=True) for match in _d
214
+ ]
215
+ json_blocks = to_list(json_blocks, dropna=True)
216
+
217
+ print(json_blocks)
218
+
219
+ elif content and isinstance(content, dict):
220
+ json_blocks = [content]
221
+
222
+ if json_blocks and not isinstance(json_blocks, list):
223
+ json_blocks = [json_blocks]
224
+
225
+ out = []
226
+
227
+ for i in json_blocks:
228
+ j = {}
229
+ if isinstance(i, dict):
230
+ if "function" in i and isinstance(i["function"], dict):
231
+ if "name" in i["function"]:
232
+ i["function"] = i["function"]["name"]
233
+ for k, v in i.items():
234
+ k = (
235
+ k.replace("action_", "")
236
+ .replace("recipient_", "")
237
+ .replace("s", "")
238
+ )
239
+ if k in ["name", "function", "recipient"]:
240
+ j["function"] = v
241
+ elif k in ["parameter", "argument", "arg", "param"]:
242
+ j["arguments"] = to_dict(
243
+ v,
244
+ str_type="json",
245
+ fuzzy_parse=True,
246
+ suppress=True,
247
+ )
248
+ if (
249
+ j
250
+ and all(key in j for key in ["function", "arguments"])
251
+ and j["arguments"]
252
+ ):
253
+ out.append(j)
254
+
255
+ return out
256
+
257
+ try:
258
+ ctx = parse_action_request(content)
259
+ if ctx:
260
+ return [cls.model_validate(i) for i in ctx]
261
+ return []
262
+ except Exception:
263
+ return []
264
+
265
+
266
+ class ActionResponseModel(HashableModel):
267
+ """
268
+ Encapsulates a function's output after being called. Typically
269
+ references the original function name, arguments, and the result.
270
+ """
271
+
272
+ function: str = Field(default_factory=str, title="Function")
273
+ arguments: dict[str, Any] = Field(default_factory=dict)
274
+ output: Any = None
275
+
276
+
277
+ def get_default_field(
278
+ kind: Literal[
279
+ "action_requests",
280
+ "action_responses",
281
+ "action_required",
282
+ "instruct",
283
+ "reason",
284
+ ],
285
+ default: Any = Unset,
286
+ nullable: bool = True,
287
+ listable: bool = None,
288
+ ):
289
+ global _DEFAULT_FIELDS
290
+ key = (kind, str(default), nullable, listable)
291
+ if key not in _DEFAULT_FIELDS:
292
+ _DEFAULT_FIELDS[key] = _get_default_fields(
293
+ kind, default=default, nullable=nullable, listable=listable
294
+ )
295
+ return _DEFAULT_FIELDS[key]
296
+
297
+
298
+ def _get_default_fields(
299
+ kind: Literal[
300
+ "action_requests",
301
+ "action_responses",
302
+ "action_required",
303
+ "instruct",
304
+ "reason",
305
+ ],
306
+ default: Any = Unset,
307
+ nullable: bool = True,
308
+ listable: bool = None,
309
+ ):
310
+ from lionagi.models.field_model import FieldModel
311
+
312
+ fm = None
313
+
314
+ match kind:
315
+
316
+ case "instruct":
317
+ fm = FieldModel(Instruct, name="instruct_model")
318
+
319
+ case "action_required":
320
+ from lionagi.libs.validate.common_field_validators import (
321
+ validate_boolean_field,
322
+ )
323
+
324
+ fm = FieldModel(
325
+ bool,
326
+ name="action_required",
327
+ validator=lambda cls, v: validate_boolean_field(cls, v, False),
328
+ description=(
329
+ "Whether this step strictly requires performing actions. "
330
+ "If true, the requests in `action_requests` must be fulfilled, "
331
+ "assuming `tool_schemas` are available. "
332
+ "If false or no `tool_schemas` exist, actions are optional."
333
+ ),
334
+ )
335
+
336
+ case "action_requests":
337
+ fm = FieldModel(
338
+ ActionRequestModel,
339
+ name="action_requests",
340
+ listable=True,
341
+ description=(
342
+ "List of actions to be executed when `action_required` is true. "
343
+ "Each action must align with the available `tool_schemas`. "
344
+ "Leave empty if no actions are needed."
345
+ ),
346
+ )
347
+
348
+ case "action_responses":
349
+ fm = FieldModel(
350
+ ActionResponseModel, name="action_responses", listable=True
351
+ )
352
+
353
+ case "reason":
354
+ fm = FieldModel(Reason, name="reason")
355
+
356
+ case _:
357
+ raise ValueError(f"Unknown default field kind: {kind}")
358
+
359
+ if listable is not None:
360
+ if listable and not fm.is_listable:
361
+ fm = fm.as_listable()
362
+ else:
363
+ fm = fm.with_metadata("listable", False)
364
+
365
+ if nullable:
366
+ fm = fm.as_nullable()
367
+ default = None
368
+
369
+ if fm.is_listable and default is Unset:
370
+ default = list
371
+
372
+ if default is not Unset:
373
+ fm = fm.with_default(default)
374
+
375
+ if fm.is_listable:
376
+ fm = fm.with_validator(
377
+ lambda cls, x: to_list(x, dropna=True, flatten=True, unique=True)
378
+ )
379
+
380
+ return fm