lionagi 0.17.11__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 (109) hide show
  1. lionagi/_errors.py +0 -5
  2. lionagi/fields.py +83 -0
  3. lionagi/libs/schema/minimal_yaml.py +98 -0
  4. lionagi/ln/__init__.py +3 -1
  5. lionagi/ln/concurrency/primitives.py +4 -4
  6. lionagi/ln/concurrency/task.py +1 -0
  7. lionagi/ln/types.py +32 -5
  8. lionagi/models/field_model.py +21 -4
  9. lionagi/models/hashable_model.py +2 -3
  10. lionagi/operations/ReAct/ReAct.py +475 -238
  11. lionagi/operations/ReAct/utils.py +3 -0
  12. lionagi/operations/act/act.py +206 -0
  13. lionagi/operations/builder.py +5 -7
  14. lionagi/operations/chat/chat.py +130 -114
  15. lionagi/operations/communicate/communicate.py +101 -42
  16. lionagi/operations/fields.py +380 -0
  17. lionagi/operations/flow.py +8 -10
  18. lionagi/operations/interpret/interpret.py +65 -20
  19. lionagi/operations/node.py +4 -4
  20. lionagi/operations/operate/operate.py +216 -108
  21. lionagi/{protocols/operatives → operations/operate}/operative.py +4 -5
  22. lionagi/{protocols/operatives → operations/operate}/step.py +34 -39
  23. lionagi/operations/parse/parse.py +170 -142
  24. lionagi/operations/select/select.py +79 -18
  25. lionagi/operations/select/utils.py +8 -2
  26. lionagi/operations/types.py +119 -23
  27. lionagi/protocols/action/manager.py +5 -6
  28. lionagi/protocols/contracts.py +2 -2
  29. lionagi/protocols/generic/__init__.py +22 -0
  30. lionagi/protocols/generic/element.py +36 -127
  31. lionagi/protocols/generic/log.py +3 -2
  32. lionagi/protocols/generic/pile.py +9 -10
  33. lionagi/protocols/generic/progression.py +23 -22
  34. lionagi/protocols/graph/edge.py +6 -5
  35. lionagi/protocols/ids.py +6 -49
  36. lionagi/protocols/messages/__init__.py +29 -0
  37. lionagi/protocols/messages/action_request.py +86 -184
  38. lionagi/protocols/messages/action_response.py +73 -131
  39. lionagi/protocols/messages/assistant_response.py +130 -159
  40. lionagi/protocols/messages/base.py +31 -22
  41. lionagi/protocols/messages/instruction.py +280 -625
  42. lionagi/protocols/messages/manager.py +112 -62
  43. lionagi/protocols/messages/message.py +87 -197
  44. lionagi/protocols/messages/system.py +52 -123
  45. lionagi/protocols/types.py +1 -13
  46. lionagi/service/connections/__init__.py +3 -0
  47. lionagi/service/connections/endpoint.py +0 -8
  48. lionagi/service/connections/providers/claude_code_cli.py +3 -2
  49. lionagi/service/connections/providers/oai_.py +29 -94
  50. lionagi/service/connections/providers/ollama_.py +3 -2
  51. lionagi/service/hooks/_types.py +1 -1
  52. lionagi/service/hooks/_utils.py +1 -1
  53. lionagi/service/hooks/hook_event.py +3 -8
  54. lionagi/service/hooks/hook_registry.py +5 -5
  55. lionagi/service/hooks/hooked_event.py +63 -3
  56. lionagi/service/imodel.py +24 -20
  57. lionagi/service/third_party/claude_code.py +3 -3
  58. lionagi/service/third_party/openai_models.py +435 -0
  59. lionagi/service/token_calculator.py +1 -94
  60. lionagi/session/branch.py +190 -400
  61. lionagi/session/session.py +8 -99
  62. lionagi/tools/file/reader.py +2 -2
  63. lionagi/version.py +1 -1
  64. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/METADATA +6 -6
  65. lionagi-0.18.1.dist-info/RECORD +164 -0
  66. lionagi/fields/__init__.py +0 -47
  67. lionagi/fields/action.py +0 -188
  68. lionagi/fields/base.py +0 -153
  69. lionagi/fields/code.py +0 -239
  70. lionagi/fields/file.py +0 -234
  71. lionagi/fields/instruct.py +0 -135
  72. lionagi/fields/reason.py +0 -55
  73. lionagi/fields/research.py +0 -52
  74. lionagi/operations/_act/act.py +0 -86
  75. lionagi/operations/brainstorm/__init__.py +0 -2
  76. lionagi/operations/brainstorm/brainstorm.py +0 -498
  77. lionagi/operations/brainstorm/prompt.py +0 -11
  78. lionagi/operations/instruct/__init__.py +0 -2
  79. lionagi/operations/instruct/instruct.py +0 -28
  80. lionagi/operations/plan/__init__.py +0 -6
  81. lionagi/operations/plan/plan.py +0 -386
  82. lionagi/operations/plan/prompt.py +0 -25
  83. lionagi/operations/utils.py +0 -45
  84. lionagi/protocols/forms/__init__.py +0 -2
  85. lionagi/protocols/forms/base.py +0 -85
  86. lionagi/protocols/forms/flow.py +0 -79
  87. lionagi/protocols/forms/form.py +0 -86
  88. lionagi/protocols/forms/report.py +0 -48
  89. lionagi/protocols/mail/__init__.py +0 -2
  90. lionagi/protocols/mail/exchange.py +0 -220
  91. lionagi/protocols/mail/mail.py +0 -51
  92. lionagi/protocols/mail/mailbox.py +0 -103
  93. lionagi/protocols/mail/manager.py +0 -218
  94. lionagi/protocols/mail/package.py +0 -101
  95. lionagi/protocols/messages/templates/README.md +0 -28
  96. lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
  97. lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
  98. lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
  99. lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
  100. lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
  101. lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
  102. lionagi/protocols/operatives/__init__.py +0 -2
  103. lionagi/service/connections/providers/types.py +0 -28
  104. lionagi/service/third_party/openai_model_names.py +0 -198
  105. lionagi/service/types.py +0 -58
  106. lionagi-0.17.11.dist-info/RECORD +0 -199
  107. /lionagi/operations/{_act → act}/__init__.py +0 -0
  108. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/WHEEL +0 -0
  109. {lionagi-0.17.11.dist-info → lionagi-0.18.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,40 +3,85 @@
3
3
 
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from ..types import ChatParam, InterpretParam
7
+
6
8
  if TYPE_CHECKING:
9
+ from lionagi.service.imodel import iModel
7
10
  from lionagi.session.branch import Branch
8
11
 
9
12
 
10
- async def interpret(
13
+ def prepare_interpret_kw(
11
14
  branch: "Branch",
12
15
  text: str,
13
16
  domain: str | None = None,
14
17
  style: str | None = None,
15
18
  sample_writing: str | None = None,
16
- interpret_model: str | None = None,
19
+ interpret_model: "iModel | None" = None,
17
20
  **kwargs,
18
21
  ) -> str:
22
+ """Interpret and refine user input into clearer prompts."""
23
+
24
+ # Build InterpretParam
25
+ intp_param = InterpretParam(
26
+ domain=domain or "general",
27
+ style=style or "concise",
28
+ sample_writing=sample_writing or "",
29
+ imodel=interpret_model or branch.chat_model,
30
+ imodel_kw=kwargs,
31
+ )
32
+ return {
33
+ "text": text,
34
+ "intp_param": intp_param,
35
+ }
36
+
37
+
38
+ async def interpret(
39
+ branch: "Branch",
40
+ text: str,
41
+ intp_param: InterpretParam,
42
+ ) -> str:
43
+ """Execute interpretation with context - clean implementation."""
44
+
45
+ from ..chat.chat import chat
46
+
19
47
  instruction = (
20
- "You are given a user's raw instruction or question. Your task is to rewrite it into a clearer,"
48
+ "You are given a user's raw instruction or question. Your task is to rewrite it into a clearer, "
21
49
  "more structured prompt for an LLM or system, making any implicit or missing details explicit. "
22
50
  "Return only the re-written prompt. Do not assume any details not mentioned in the input, nor "
23
51
  "give additional instruction than what is explicitly stated."
24
52
  )
25
- guidance = f"Domain hint: {domain or 'general'}. Desired style: {style or 'concise'}. "
26
- if sample_writing:
27
- guidance += f" Sample writing: {sample_writing}"
28
-
29
- context = [f"User input: {text}"]
30
-
31
- # Default temperature if none provided
32
- kwargs["guidance"] = guidance + "\n" + kwargs.get("guidance", "")
33
- kwargs["instruction"] = instruction + "\n" + kwargs.get("instruction", "")
34
- kwargs["temperature"] = kwargs.get("temperature", 0.1)
35
- if interpret_model:
36
- kwargs["chat_model"] = interpret_model
37
-
38
- refined_prompt = await branch.chat(
39
- context=context,
40
- **kwargs,
53
+
54
+ guidance = (
55
+ f"Domain hint: {intp_param.domain}. Desired style: {intp_param.style}."
41
56
  )
42
- return str(refined_prompt)
57
+ if intp_param.sample_writing:
58
+ guidance += f" Sample writing: {intp_param.sample_writing}"
59
+
60
+ # Build ChatParam
61
+ chat_param = ChatParam(
62
+ guidance=guidance,
63
+ context=[f"User input: {text}"],
64
+ sender=branch.user or "user",
65
+ recipient=branch.id,
66
+ response_format=None,
67
+ progression=None,
68
+ tool_schemas=[],
69
+ images=[],
70
+ image_detail="auto",
71
+ plain_content="",
72
+ include_token_usage_to_model=False,
73
+ imodel=intp_param.imodel,
74
+ imodel_kw={
75
+ **intp_param.imodel_kw,
76
+ "temperature": intp_param.imodel_kw.get("temperature", 0.1),
77
+ },
78
+ )
79
+
80
+ result = await chat(
81
+ branch,
82
+ instruction=instruction,
83
+ chat_param=chat_param,
84
+ return_ins_res_message=False,
85
+ )
86
+
87
+ return str(result)
@@ -6,7 +6,7 @@ from uuid import UUID
6
6
  from anyio import get_cancelled_exc_class
7
7
  from pydantic import BaseModel, Field
8
8
 
9
- from lionagi.protocols.types import ID, Event, EventStatus, IDType, Node
9
+ from lionagi.protocols.types import ID, Event, EventStatus, Node
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from lionagi.session.branch import Branch
@@ -37,12 +37,12 @@ class Operation(Node, Event):
37
37
  )
38
38
 
39
39
  @property
40
- def branch_id(self) -> IDType | None:
40
+ def branch_id(self) -> UUID | None:
41
41
  if a := self.metadata.get("branch_id"):
42
42
  return ID.get_id(a)
43
43
 
44
44
  @branch_id.setter
45
- def branch_id(self, value: str | UUID | IDType | None):
45
+ def branch_id(self, value: str | UUID | None):
46
46
  if value is None:
47
47
  self.metadata.pop("branch_id", None)
48
48
  else:
@@ -54,7 +54,7 @@ class Operation(Node, Event):
54
54
  return ID.get_id(a)
55
55
 
56
56
  @graph_id.setter
57
- def graph_id(self, value: str | UUID | IDType | None):
57
+ def graph_id(self, value: str | UUID | None):
58
58
  if value is None:
59
59
  self.metadata.pop("graph_id", None)
60
60
  else:
@@ -1,46 +1,28 @@
1
1
  # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import logging
4
+ import warnings
5
5
  from typing import TYPE_CHECKING, Literal
6
6
 
7
7
  from pydantic import BaseModel, JsonValue
8
8
 
9
- from lionagi.fields.instruct import Instruct
9
+ from lionagi.ln import AlcallParams
10
+ from lionagi.ln.fuzzy import FuzzyMatchKeysParams
10
11
  from lionagi.models import FieldModel, ModelParams
11
- from lionagi.protocols.operatives.step import Operative, Step
12
- from lionagi.protocols.types import Instruction, Progression, SenderRecipient
13
- from lionagi.service.imodel import iModel
14
- from lionagi.session.branch import AlcallParams
12
+ from lionagi.protocols.generic import Progression
13
+ from lionagi.protocols.messages import Instruction, SenderRecipient
14
+
15
+ from ..fields import Instruct
16
+ from ..types import ActionParam, ChatParam, HandleValidation, ParseParam
15
17
 
16
18
  if TYPE_CHECKING:
19
+ from lionagi.service.imodel import iModel
17
20
  from lionagi.session.branch import Branch, ToolRef
18
21
 
19
-
20
- def _handle_response_format_kwargs(
21
- operative_model: type[BaseModel] = None,
22
- request_model: type[BaseModel] = None,
23
- response_format: type[BaseModel] = None,
24
- ):
25
- if operative_model:
26
- logging.warning(
27
- "`operative_model` is deprecated. Use `response_format` instead."
28
- )
29
- if (
30
- (operative_model and response_format)
31
- or (operative_model and request_model)
32
- or (response_format and request_model)
33
- ):
34
- raise ValueError(
35
- "Cannot specify both `operative_model` and `response_format` (or `request_model`) "
36
- "as they are aliases of each other."
37
- )
38
-
39
- # Use the final chosen format
40
- return response_format or operative_model or request_model
22
+ from .operative import Operative
41
23
 
42
24
 
43
- async def operate(
25
+ def prepare_operate_kw(
44
26
  branch: "Branch",
45
27
  *,
46
28
  instruct: Instruct = None,
@@ -50,18 +32,17 @@ async def operate(
50
32
  sender: SenderRecipient = None,
51
33
  recipient: SenderRecipient = None,
52
34
  progression: Progression = None,
53
- imodel: iModel = None, # deprecated, alias of chat_model
54
- chat_model: iModel = None,
35
+ imodel: "iModel" = None, # deprecated, alias of chat_model
36
+ chat_model: "iModel" = None,
55
37
  invoke_actions: bool = True,
56
38
  tool_schemas: list[dict] = None,
57
39
  images: list = None,
58
40
  image_detail: Literal["low", "high", "auto"] = None,
59
- parse_model: iModel = None,
41
+ parse_model: "iModel" = None,
60
42
  skip_validation: bool = False,
61
43
  tools: "ToolRef" = None,
62
44
  operative: "Operative" = None,
63
45
  response_format: type[BaseModel] = None, # alias of operative.request_type
64
- return_operative: bool = False,
65
46
  actions: bool = False,
66
47
  reason: bool = False,
67
48
  call_params: AlcallParams = None,
@@ -71,21 +52,38 @@ async def operate(
71
52
  exclude_fields: list | dict | None = None,
72
53
  request_params: ModelParams = None,
73
54
  request_param_kwargs: dict = None,
74
- response_params: ModelParams = None,
75
- response_param_kwargs: dict = None,
76
- handle_validation: Literal[
77
- "raise", "return_value", "return_none"
78
- ] = "return_value",
55
+ handle_validation: HandleValidation = "return_value",
79
56
  operative_model: type[BaseModel] = None,
80
57
  request_model: type[BaseModel] = None,
81
58
  include_token_usage_to_model: bool = False,
59
+ clear_messages: bool = False,
82
60
  **kwargs,
83
61
  ) -> list | BaseModel | None | dict | str:
84
- response_format = _handle_response_format_kwargs(
85
- operative_model, request_model, response_format
86
- )
62
+ # Handle deprecated parameters
63
+ if operative_model:
64
+ warnings.warn(
65
+ "Parameter 'operative_model' is deprecated. Use 'response_format' instead.",
66
+ DeprecationWarning,
67
+ stacklevel=2,
68
+ )
69
+ if imodel:
70
+ warnings.warn(
71
+ "Parameter 'imodel' is deprecated. Use 'chat_model' instead.",
72
+ DeprecationWarning,
73
+ stacklevel=2,
74
+ )
87
75
 
88
- # Decide which chat model to use
76
+ if (
77
+ (operative_model and response_format)
78
+ or (operative_model and request_model)
79
+ or (response_format and request_model)
80
+ ):
81
+ raise ValueError(
82
+ "Cannot specify both `operative_model` and `response_format` (or `request_model`) "
83
+ "as they are aliases of each other."
84
+ )
85
+
86
+ response_format = response_format or operative_model or request_model
89
87
  chat_model = chat_model or imodel or branch.chat_model
90
88
  parse_model = parse_model or chat_model
91
89
 
@@ -108,103 +106,213 @@ async def operate(
108
106
  if action_strategy:
109
107
  instruct.action_strategy = action_strategy
110
108
 
111
- # 1) Create or update the Operative
109
+ # Build the Operative - always create it for backwards compatibility
110
+ from .step import Step
111
+
112
112
  operative = Step.request_operative(
113
113
  request_params=request_params,
114
114
  reason=instruct.reason,
115
- actions=instruct.actions,
115
+ actions=instruct.actions or actions,
116
116
  exclude_fields=exclude_fields,
117
117
  base_type=response_format,
118
118
  field_models=field_models,
119
119
  **(request_param_kwargs or {}),
120
120
  )
121
+ # Use the operative's request_type which is a proper Pydantic model
122
+ # created from field_models if provided
123
+ final_response_format = operative.request_type
121
124
 
122
- # If the instruction signals actions, ensure tools are provided
123
- if instruct.actions:
124
- tools = tools or True
125
-
126
- # If we want to auto-invoke tools, fetch or generate the schemas
127
- if invoke_actions and tools:
128
- tool_schemas = tool_schemas or branch.acts.get_tool_schema(tools=tools)
129
-
130
- # 2) Send the instruction to the chat model
131
- ins, res = await branch.chat(
132
- instruction=instruct.instruction,
125
+ # Build contexts
126
+ chat_param = ChatParam(
133
127
  guidance=instruct.guidance,
134
128
  context=instruct.context,
135
- sender=sender,
136
- recipient=recipient,
137
- response_format=operative.request_type,
129
+ sender=sender or branch.user or "user",
130
+ recipient=recipient or branch.id,
131
+ response_format=final_response_format,
138
132
  progression=progression,
139
- imodel=chat_model, # or the override
133
+ tool_schemas=tool_schemas,
140
134
  images=images,
141
135
  image_detail=image_detail,
142
- tool_schemas=tool_schemas,
143
- return_ins_res_message=True,
136
+ plain_content=None,
144
137
  include_token_usage_to_model=include_token_usage_to_model,
145
- **kwargs,
138
+ imodel=chat_model,
139
+ imodel_kw=kwargs,
146
140
  )
147
- branch.msgs.add_message(instruction=ins)
148
- branch.msgs.add_message(assistant_response=res)
149
141
 
150
- # 3) Populate the operative with the raw response
151
- operative.response_str_dict = res.response
142
+ parse_param = None
143
+ if final_response_format and not skip_validation:
144
+ from ..parse.parse import get_default_call
152
145
 
153
- # 4) Possibly skip validation
154
- if skip_validation:
155
- return operative if return_operative else operative.response_str_dict
156
-
157
- # 5) Parse or validate the response into the operative's model
158
- response_model = operative.update_response_model(res.response)
159
- if not isinstance(response_model, BaseModel):
160
- # If the response isn't directly a model, attempt a parse
161
- response_model = await branch.parse(
162
- text=res.response,
163
- request_type=operative.request_type,
164
- max_retries=operative.max_retries,
146
+ parse_param = ParseParam(
147
+ response_format=final_response_format,
148
+ fuzzy_match_params=FuzzyMatchKeysParams(),
165
149
  handle_validation="return_value",
150
+ alcall_params=get_default_call(),
151
+ imodel=parse_model,
152
+ imodel_kw={},
166
153
  )
167
- operative.response_model = operative.update_response_model(
168
- text=response_model
154
+
155
+ action_param = None
156
+ if invoke_actions and (instruct.actions or actions):
157
+ from ..act.act import _get_default_call_params
158
+
159
+ action_param = ActionParam(
160
+ action_call_params=call_params or _get_default_call_params(),
161
+ tools=tools,
162
+ strategy=action_strategy
163
+ or instruct.action_strategy
164
+ or "concurrent",
165
+ suppress_errors=True,
166
+ verbose_action=verbose_action,
167
+ )
168
+
169
+ return {
170
+ "instruction": instruct.instruction,
171
+ "chat_param": chat_param,
172
+ "parse_param": parse_param,
173
+ "action_param": action_param,
174
+ "handle_validation": handle_validation,
175
+ "invoke_actions": invoke_actions,
176
+ "skip_validation": skip_validation,
177
+ "clear_messages": clear_messages,
178
+ }
179
+
180
+
181
+ async def operate(
182
+ branch: "Branch",
183
+ instruction: JsonValue | Instruction,
184
+ chat_param: ChatParam,
185
+ action_param: ActionParam | None = None,
186
+ parse_param: ParseParam | None = None,
187
+ handle_validation: HandleValidation = "return_value",
188
+ invoke_actions: bool = True,
189
+ skip_validation: bool = False,
190
+ clear_messages: bool = False,
191
+ reason: bool = False,
192
+ field_models: list[FieldModel] | None = None,
193
+ ) -> BaseModel | dict | str | None:
194
+
195
+ # 1. communicate chat context building to avoid changing parameters
196
+ # Start with base chat param
197
+ _cctx = chat_param
198
+ _pctx = (
199
+ parse_param.with_updates(handle_validation="return_value")
200
+ if parse_param
201
+ else ParseParam(
202
+ response_format=chat_param.response_format,
203
+ imodel=branch.parse_model,
204
+ handle_validation="return_value",
205
+ )
206
+ )
207
+
208
+ # Update tool schemas if needed
209
+ if tools := (action_param.tools or True) if action_param else None:
210
+ tool_schemas = branch.acts.get_tool_schema(tools=tools)
211
+ _cctx = _cctx.with_updates(tool_schemas=tool_schemas)
212
+
213
+ # Extract model class from response_format (can be class, instance, or dict)
214
+ model_class = None
215
+ if chat_param.response_format is not None:
216
+ if isinstance(chat_param.response_format, type) and issubclass(
217
+ chat_param.response_format, BaseModel
218
+ ):
219
+ model_class = chat_param.response_format
220
+ elif isinstance(chat_param.response_format, BaseModel):
221
+ model_class = type(chat_param.response_format)
222
+
223
+ def normalize_field_model(fms):
224
+ if not fms:
225
+ return []
226
+ if not isinstance(fms, list):
227
+ return [fms]
228
+ return fms
229
+
230
+ fms = normalize_field_model(field_models)
231
+ operative = None
232
+
233
+ if model_class:
234
+ from .step import Step
235
+
236
+ operative = Step.request_operative(
237
+ reason=reason,
238
+ actions=bool(action_param is not None),
239
+ base_type=model_class,
240
+ field_models=fms,
169
241
  )
242
+ # Update contexts with new response format
243
+ _cctx = _cctx.with_updates(response_format=operative.request_type)
244
+ _pctx = _pctx.with_updates(response_format=operative.request_type)
245
+ elif field_models:
246
+ dict_ = {}
247
+ for fm in fms:
248
+ if fm.name:
249
+ dict_[fm.name] = str(fm.annotated())
250
+ # Update contexts with dict format
251
+ _cctx = _cctx.with_updates(response_format=dict_)
252
+ _pctx = _pctx.with_updates(response_format=dict_)
253
+
254
+ from ..communicate.communicate import communicate
170
255
 
171
- # If we still fail to parse, handle according to user preference
172
- if not isinstance(response_model, BaseModel):
256
+ result = await communicate(
257
+ branch,
258
+ instruction,
259
+ _cctx,
260
+ _pctx,
261
+ clear_messages,
262
+ skip_validation=skip_validation,
263
+ request_fields=None,
264
+ )
265
+ if skip_validation:
266
+ return result
267
+ if model_class and not isinstance(result, model_class):
173
268
  match handle_validation:
174
269
  case "return_value":
175
- return response_model
270
+ return result
176
271
  case "return_none":
177
272
  return None
178
273
  case "raise":
179
274
  raise ValueError(
180
275
  "Failed to parse the LLM response into the requested format."
181
276
  )
182
-
183
- # 6) If no tool invocation is needed, return result or operative
184
277
  if not invoke_actions:
185
- return operative if return_operative else operative.response_model
278
+ return result
186
279
 
187
- # 7) If the model indicates an action is required, call the tools
188
- if (
189
- getattr(response_model, "action_required", None) is True
190
- and getattr(response_model, "action_requests", None) is not None
191
- ):
192
- action_strategy = (
193
- action_strategy or instruct.action_strategy or "concurrent"
194
- )
195
- action_response_models = await branch.act(
196
- response_model.action_requests,
197
- strategy=action_strategy,
198
- verbose_action=verbose_action,
199
- call_params=call_params,
200
- )
201
- # Possibly refine the operative with the tool outputs
202
- operative = Step.respond_operative(
203
- response_params=response_params,
204
- operative=operative,
205
- additional_data={"action_responses": action_response_models},
206
- **(response_param_kwargs or {}),
280
+ requests = (
281
+ getattr(result, "action_requests", None)
282
+ if model_class
283
+ else result.get("action_requests", None)
284
+ )
285
+
286
+ action_response_models = None
287
+ if action_param and requests is not None:
288
+ from ..act.act import act
289
+
290
+ action_response_models = await act(
291
+ branch,
292
+ requests,
293
+ action_param,
207
294
  )
208
295
 
209
- # Return final result or the full operative
210
- return operative if return_operative else operative.response_model
296
+ if not action_response_models:
297
+ return result
298
+
299
+ # Filter out None values from action responses
300
+ action_response_models = [
301
+ r for r in action_response_models if r is not None
302
+ ]
303
+
304
+ if not action_response_models: # All were None
305
+ return result
306
+
307
+ if not model_class: # Dict response
308
+ result.update({"action_responses": action_response_models})
309
+ return result
310
+
311
+ from .step import Step
312
+
313
+ operative.response_model = result
314
+ operative = Step.respond_operative(
315
+ operative=operative,
316
+ additional_data={"action_responses": action_response_models},
317
+ )
318
+ return operative.response_model
@@ -6,10 +6,9 @@ from typing import Any
6
6
  from pydantic import BaseModel
7
7
  from pydantic.fields import FieldInfo
8
8
 
9
- from lionagi.ln import extract_json
10
- from lionagi.ln.fuzzy._fuzzy_match import fuzzy_match_keys
9
+ from lionagi.ln import extract_json, fuzzy_match_keys
10
+ from lionagi.ln.types import Undefined
11
11
  from lionagi.models import FieldModel, ModelParams, OperableModel
12
- from lionagi.utils import UNDEFINED
13
12
 
14
13
 
15
14
  class Operative:
@@ -169,7 +168,7 @@ class Operative:
169
168
  d_ = fuzzy_match_keys(
170
169
  d_, self.request_type.model_fields, handle_unmatched="raise"
171
170
  )
172
- d_ = {k: v for k, v in d_.items() if v != UNDEFINED}
171
+ d_ = {k: v for k, v in d_.items() if v != Undefined}
173
172
  self.response_model = self.request_type.model_validate(d_)
174
173
  self._should_retry = False
175
174
  except Exception:
@@ -190,7 +189,7 @@ class Operative:
190
189
  d_ = fuzzy_match_keys(
191
190
  d_, self.request_type.model_fields, handle_unmatched="force"
192
191
  )
193
- d_ = {k: v for k, v in d_.items() if v != UNDEFINED}
192
+ d_ = {k: v for k, v in d_.items() if v != Undefined}
194
193
  self.response_model = self.request_type.model_validate(d_)
195
194
  self._should_retry = False
196
195
  except Exception:
@@ -4,14 +4,10 @@
4
4
  from pydantic import BaseModel
5
5
  from pydantic.fields import FieldInfo
6
6
 
7
- from lionagi.fields.action import (
8
- ACTION_REQUESTS_FIELD,
9
- ACTION_REQUIRED_FIELD,
10
- ACTION_RESPONSES_FIELD,
11
- )
12
- from lionagi.fields.reason import REASON_FIELD
13
7
  from lionagi.models import FieldModel, ModelParams
14
- from lionagi.protocols.operatives.operative import Operative
8
+
9
+ from ..fields import get_default_field
10
+ from .operative import Operative
15
11
 
16
12
 
17
13
  class Step:
@@ -71,18 +67,17 @@ class Step:
71
67
  field_models = field_models or []
72
68
  exclude_fields = exclude_fields or []
73
69
  field_descriptions = field_descriptions or {}
74
- if reason and REASON_FIELD not in field_models:
75
- field_models.append(REASON_FIELD)
76
- if actions and ACTION_REQUESTS_FIELD not in field_models:
77
- field_models.extend(
78
- [
79
- ACTION_REQUESTS_FIELD,
80
- ACTION_REQUIRED_FIELD,
81
- ]
82
- )
83
-
70
+ if reason and (fm := get_default_field("reason")) not in field_models:
71
+ field_models.append(fm)
72
+ if (
73
+ actions
74
+ and (fm := get_default_field("action_requests"))
75
+ not in field_models
76
+ ):
77
+ fm2 = get_default_field("action_required")
78
+ field_models.extend([fm, fm2])
84
79
  if isinstance(request_params, ModelParams):
85
- request_params = request_params.model_dump()
80
+ request_params = request_params.to_dict()
86
81
 
87
82
  request_params = request_params or {}
88
83
  request_params_fields = {
@@ -143,15 +138,17 @@ class Step:
143
138
  additional_data = additional_data or {}
144
139
  field_models = field_models or []
145
140
  if hasattr(operative.response_model, "action_required"):
146
- field_models.extend(
147
- [
148
- ACTION_RESPONSES_FIELD,
149
- ACTION_REQUIRED_FIELD,
150
- ACTION_REQUESTS_FIELD,
151
- ]
152
- )
141
+ for i in {
142
+ "action_requests",
143
+ "action_required",
144
+ "action_responses",
145
+ }:
146
+ fm = get_default_field(i)
147
+ if fm not in field_models:
148
+ field_models.append(fm)
149
+
153
150
  if "reason" in type(operative.response_model).model_fields:
154
- field_models.extend([REASON_FIELD])
151
+ field_models.append(get_default_field("reason"))
155
152
 
156
153
  operative = Step._create_response_type(
157
154
  operative=operative,
@@ -201,24 +198,22 @@ class Step:
201
198
  hasattr(operative.request_type, "action_required")
202
199
  and operative.response_model.action_required
203
200
  ):
204
- field_models.extend(
205
- [
206
- ACTION_RESPONSES_FIELD,
207
- ACTION_REQUIRED_FIELD,
208
- ACTION_REQUESTS_FIELD,
209
- ]
210
- )
211
- if hasattr(operative.request_type, "reason"):
212
- field_models.extend([REASON_FIELD])
201
+ for i in {
202
+ "action_requests",
203
+ "action_required",
204
+ "action_responses",
205
+ }:
206
+ fm = get_default_field(i)
207
+ if fm not in field_models:
208
+ field_models.append(fm)
213
209
 
214
- exclude_fields = exclude_fields or []
215
- # Note: We no longer have access to request_params.exclude_fields
216
- # since Operative doesn't store ModelParams anymore
210
+ if hasattr(operative.request_type, "reason"):
211
+ field_models.append(get_default_field("reason"))
217
212
 
218
213
  operative.create_response_type(
219
214
  response_params=response_params,
220
215
  field_models=field_models,
221
- exclude_fields=exclude_fields,
216
+ exclude_fields=exclude_fields or [],
222
217
  doc=response_doc,
223
218
  config_dict=response_config_dict,
224
219
  frozen=frozen_response,