lionagi 0.17.10__py3-none-any.whl → 0.18.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. lionagi/__init__.py +1 -2
  2. lionagi/_class_registry.py +1 -2
  3. lionagi/_errors.py +1 -2
  4. lionagi/adapters/async_postgres_adapter.py +2 -10
  5. lionagi/config.py +1 -2
  6. lionagi/fields/action.py +1 -2
  7. lionagi/fields/base.py +3 -0
  8. lionagi/fields/code.py +3 -0
  9. lionagi/fields/file.py +3 -0
  10. lionagi/fields/instruct.py +1 -2
  11. lionagi/fields/reason.py +1 -2
  12. lionagi/fields/research.py +3 -0
  13. lionagi/libs/__init__.py +1 -2
  14. lionagi/libs/file/__init__.py +1 -2
  15. lionagi/libs/file/chunk.py +1 -2
  16. lionagi/libs/file/process.py +1 -2
  17. lionagi/libs/schema/__init__.py +1 -2
  18. lionagi/libs/schema/as_readable.py +1 -2
  19. lionagi/libs/schema/extract_code_block.py +1 -2
  20. lionagi/libs/schema/extract_docstring.py +1 -2
  21. lionagi/libs/schema/function_to_schema.py +1 -2
  22. lionagi/libs/schema/load_pydantic_model_from_schema.py +1 -2
  23. lionagi/libs/schema/minimal_yaml.py +98 -0
  24. lionagi/libs/validate/__init__.py +1 -2
  25. lionagi/libs/validate/common_field_validators.py +1 -2
  26. lionagi/libs/validate/validate_boolean.py +1 -2
  27. lionagi/ln/fuzzy/_string_similarity.py +1 -2
  28. lionagi/ln/types.py +32 -5
  29. lionagi/models/__init__.py +1 -2
  30. lionagi/models/field_model.py +9 -1
  31. lionagi/models/hashable_model.py +4 -2
  32. lionagi/models/model_params.py +1 -2
  33. lionagi/models/operable_model.py +1 -2
  34. lionagi/models/schema_model.py +1 -2
  35. lionagi/operations/ReAct/ReAct.py +475 -239
  36. lionagi/operations/ReAct/__init__.py +1 -2
  37. lionagi/operations/ReAct/utils.py +4 -2
  38. lionagi/operations/__init__.py +1 -2
  39. lionagi/operations/act/__init__.py +2 -0
  40. lionagi/operations/act/act.py +206 -0
  41. lionagi/operations/brainstorm/__init__.py +1 -2
  42. lionagi/operations/brainstorm/brainstorm.py +1 -2
  43. lionagi/operations/brainstorm/prompt.py +1 -2
  44. lionagi/operations/builder.py +1 -2
  45. lionagi/operations/chat/__init__.py +1 -2
  46. lionagi/operations/chat/chat.py +131 -116
  47. lionagi/operations/communicate/communicate.py +102 -44
  48. lionagi/operations/flow.py +5 -6
  49. lionagi/operations/instruct/__init__.py +1 -2
  50. lionagi/operations/instruct/instruct.py +1 -2
  51. lionagi/operations/interpret/__init__.py +1 -2
  52. lionagi/operations/interpret/interpret.py +66 -22
  53. lionagi/operations/operate/__init__.py +1 -2
  54. lionagi/operations/operate/operate.py +213 -108
  55. lionagi/operations/parse/__init__.py +1 -2
  56. lionagi/operations/parse/parse.py +171 -144
  57. lionagi/operations/plan/__init__.py +1 -2
  58. lionagi/operations/plan/plan.py +1 -2
  59. lionagi/operations/plan/prompt.py +1 -2
  60. lionagi/operations/select/__init__.py +1 -2
  61. lionagi/operations/select/select.py +79 -19
  62. lionagi/operations/select/utils.py +2 -3
  63. lionagi/operations/types.py +120 -25
  64. lionagi/operations/utils.py +1 -2
  65. lionagi/protocols/__init__.py +1 -2
  66. lionagi/protocols/_concepts.py +1 -2
  67. lionagi/protocols/action/__init__.py +1 -2
  68. lionagi/protocols/action/function_calling.py +3 -20
  69. lionagi/protocols/action/manager.py +34 -4
  70. lionagi/protocols/action/tool.py +1 -2
  71. lionagi/protocols/contracts.py +1 -2
  72. lionagi/protocols/forms/__init__.py +1 -2
  73. lionagi/protocols/forms/base.py +1 -2
  74. lionagi/protocols/forms/flow.py +1 -2
  75. lionagi/protocols/forms/form.py +1 -2
  76. lionagi/protocols/forms/report.py +1 -2
  77. lionagi/protocols/generic/__init__.py +1 -2
  78. lionagi/protocols/generic/element.py +17 -65
  79. lionagi/protocols/generic/event.py +1 -2
  80. lionagi/protocols/generic/log.py +17 -14
  81. lionagi/protocols/generic/pile.py +3 -4
  82. lionagi/protocols/generic/processor.py +1 -2
  83. lionagi/protocols/generic/progression.py +1 -2
  84. lionagi/protocols/graph/__init__.py +1 -2
  85. lionagi/protocols/graph/edge.py +1 -2
  86. lionagi/protocols/graph/graph.py +1 -2
  87. lionagi/protocols/graph/node.py +1 -2
  88. lionagi/protocols/ids.py +1 -2
  89. lionagi/protocols/mail/__init__.py +1 -2
  90. lionagi/protocols/mail/exchange.py +1 -2
  91. lionagi/protocols/mail/mail.py +1 -2
  92. lionagi/protocols/mail/mailbox.py +1 -2
  93. lionagi/protocols/mail/manager.py +1 -2
  94. lionagi/protocols/mail/package.py +1 -2
  95. lionagi/protocols/messages/__init__.py +28 -2
  96. lionagi/protocols/messages/action_request.py +87 -186
  97. lionagi/protocols/messages/action_response.py +74 -133
  98. lionagi/protocols/messages/assistant_response.py +131 -161
  99. lionagi/protocols/messages/base.py +27 -20
  100. lionagi/protocols/messages/instruction.py +281 -626
  101. lionagi/protocols/messages/manager.py +113 -64
  102. lionagi/protocols/messages/message.py +88 -199
  103. lionagi/protocols/messages/system.py +53 -125
  104. lionagi/protocols/operatives/__init__.py +1 -2
  105. lionagi/protocols/operatives/operative.py +1 -2
  106. lionagi/protocols/operatives/step.py +1 -2
  107. lionagi/protocols/types.py +1 -4
  108. lionagi/service/connections/__init__.py +1 -2
  109. lionagi/service/connections/api_calling.py +1 -2
  110. lionagi/service/connections/endpoint.py +1 -10
  111. lionagi/service/connections/endpoint_config.py +1 -2
  112. lionagi/service/connections/header_factory.py +1 -2
  113. lionagi/service/connections/match_endpoint.py +1 -2
  114. lionagi/service/connections/mcp/__init__.py +1 -2
  115. lionagi/service/connections/mcp/wrapper.py +1 -2
  116. lionagi/service/connections/providers/__init__.py +1 -2
  117. lionagi/service/connections/providers/anthropic_.py +1 -2
  118. lionagi/service/connections/providers/claude_code_cli.py +1 -2
  119. lionagi/service/connections/providers/exa_.py +1 -2
  120. lionagi/service/connections/providers/nvidia_nim_.py +2 -27
  121. lionagi/service/connections/providers/oai_.py +30 -96
  122. lionagi/service/connections/providers/ollama_.py +4 -4
  123. lionagi/service/connections/providers/perplexity_.py +1 -2
  124. lionagi/service/hooks/__init__.py +1 -1
  125. lionagi/service/hooks/_types.py +1 -1
  126. lionagi/service/hooks/_utils.py +1 -1
  127. lionagi/service/hooks/hook_event.py +1 -1
  128. lionagi/service/hooks/hook_registry.py +1 -1
  129. lionagi/service/hooks/hooked_event.py +3 -4
  130. lionagi/service/imodel.py +1 -2
  131. lionagi/service/manager.py +1 -2
  132. lionagi/service/rate_limited_processor.py +1 -2
  133. lionagi/service/resilience.py +1 -2
  134. lionagi/service/third_party/anthropic_models.py +1 -2
  135. lionagi/service/third_party/claude_code.py +4 -4
  136. lionagi/service/third_party/openai_models.py +433 -0
  137. lionagi/service/token_calculator.py +1 -2
  138. lionagi/session/__init__.py +1 -2
  139. lionagi/session/branch.py +171 -180
  140. lionagi/session/session.py +4 -11
  141. lionagi/tools/__init__.py +1 -2
  142. lionagi/tools/base.py +1 -2
  143. lionagi/tools/file/__init__.py +1 -2
  144. lionagi/tools/file/reader.py +3 -4
  145. lionagi/tools/types.py +1 -2
  146. lionagi/utils.py +1 -2
  147. lionagi/version.py +1 -1
  148. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
  149. lionagi-0.18.0.dist-info/RECORD +191 -0
  150. lionagi/operations/_act/__init__.py +0 -3
  151. lionagi/operations/_act/act.py +0 -87
  152. lionagi/protocols/messages/templates/README.md +0 -28
  153. lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
  154. lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
  155. lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
  156. lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
  157. lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
  158. lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
  159. lionagi/service/connections/providers/types.py +0 -28
  160. lionagi/service/third_party/openai_model_names.py +0 -198
  161. lionagi/service/types.py +0 -59
  162. lionagi-0.17.10.dist-info/RECORD +0 -199
  163. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
  164. {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from typing import ClassVar, Literal
@@ -102,6 +101,9 @@ class ReActAnalysis(HashableModel):
102
101
  ),
103
102
  )
104
103
 
104
+ # Note: action_requests and action_responses are added dynamically by Step.request_operative()
105
+ # when actions=True, so they don't need to be defined here. The operate() function will add them.
106
+
105
107
 
106
108
  class Analysis(HashableModel):
107
109
  answer: str | None = None
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  from .builder import ExpansionStrategy, OperationGraphBuilder
@@ -0,0 +1,2 @@
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,206 @@
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ import logging
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from lionagi.fields.action import ActionResponseModel
10
+ from lionagi.ln._async_call import AlcallParams
11
+ from lionagi.protocols.types import ActionRequest, ActionResponse
12
+
13
+ from ..types import ActionParam
14
+
15
+ if TYPE_CHECKING:
16
+ from lionagi.session.branch import Branch
17
+
18
+ _DEFAULT_ALCALL_PARAMS = None
19
+
20
+
21
+ async def _act(
22
+ branch: "Branch",
23
+ action_request: BaseModel | dict | ActionRequest,
24
+ suppress_errors: bool = False,
25
+ verbose_action: bool = False,
26
+ ):
27
+
28
+ _request = action_request
29
+ if isinstance(action_request, ActionRequest):
30
+ _request = {
31
+ "function": action_request.function,
32
+ "arguments": action_request.arguments,
33
+ }
34
+ elif isinstance(action_request, BaseModel) and set(
35
+ action_request.__class__.model_fields.keys()
36
+ ) >= {"function", "arguments"}:
37
+ _request = {
38
+ "function": action_request.function,
39
+ "arguments": action_request.arguments,
40
+ }
41
+ if not isinstance(_request, dict) or not {"function", "arguments"} <= set(
42
+ _request.keys()
43
+ ):
44
+ raise ValueError(
45
+ "action_request must be an ActionRequest, BaseModel with 'function'"
46
+ " and 'arguments', or dict with 'function' and 'arguments'."
47
+ )
48
+
49
+ try:
50
+ if verbose_action:
51
+ args_ = str(_request["arguments"])
52
+ args_ = args_[:50] + "..." if len(args_) > 50 else args_
53
+ print(f"Invoking action {_request['function']} with {args_}.")
54
+
55
+ func_call = await branch._action_manager.invoke(_request)
56
+ if verbose_action:
57
+ print(
58
+ f"Action {_request['function']} invoked, status: {func_call.status}."
59
+ )
60
+
61
+ except Exception as e:
62
+ content = {
63
+ "error": str(e),
64
+ "function": _request.get("function"),
65
+ "arguments": _request.get("arguments"),
66
+ "branch": str(branch.id),
67
+ }
68
+ branch._log_manager.log(content)
69
+ if verbose_action:
70
+ print(f"Action {_request['function']} failed, error: {str(e)}.")
71
+ if suppress_errors:
72
+ error_msg = f"Error invoking action '{_request['function']}': {e}"
73
+ logging.error(error_msg)
74
+
75
+ # Return error as action response so model knows it failed
76
+ return ActionResponseModel(
77
+ function=_request.get("function", "unknown"),
78
+ arguments=_request.get("arguments", {}),
79
+ output={"error": str(e), "message": error_msg},
80
+ )
81
+ raise e
82
+
83
+ branch._log_manager.log(func_call)
84
+
85
+ if not isinstance(action_request, ActionRequest):
86
+ action_request = ActionRequest(
87
+ content=_request,
88
+ sender=branch.id,
89
+ recipient=func_call.func_tool.id,
90
+ )
91
+
92
+ # Add the action request/response to the message manager, if not present
93
+ if action_request not in branch.messages:
94
+ branch.msgs.add_message(action_request=action_request)
95
+
96
+ branch.msgs.add_message(
97
+ action_request=action_request,
98
+ action_output=func_call.response,
99
+ )
100
+
101
+ return ActionResponseModel(
102
+ function=action_request.function,
103
+ arguments=action_request.arguments,
104
+ output=func_call.response,
105
+ )
106
+
107
+
108
+ def prepare_act_kw(
109
+ branch: "Branch",
110
+ action_request: list | ActionRequest | BaseModel | dict,
111
+ *,
112
+ strategy: Literal["concurrent", "sequential"] = "concurrent",
113
+ verbose_action: bool = False,
114
+ suppress_errors: bool = True,
115
+ call_params: AlcallParams = None,
116
+ ):
117
+
118
+ action_param = ActionParam(
119
+ action_call_params=call_params or _get_default_call_params(),
120
+ tools=None, # Not used in this context
121
+ strategy=strategy,
122
+ suppress_errors=suppress_errors,
123
+ verbose_action=verbose_action,
124
+ )
125
+ return {
126
+ "action_request": action_request,
127
+ "action_param": action_param,
128
+ }
129
+
130
+
131
+ async def act(
132
+ branch: "Branch",
133
+ action_request: list | ActionRequest | BaseModel | dict,
134
+ action_param: ActionParam,
135
+ ) -> list[ActionResponse]:
136
+ """Execute action requests with ActionParam."""
137
+
138
+ match action_param.strategy:
139
+ case "concurrent":
140
+ return await _concurrent_act(
141
+ branch,
142
+ action_request,
143
+ action_param.action_call_params,
144
+ suppress_errors=action_param.suppress_errors,
145
+ verbose_action=action_param.verbose_action,
146
+ )
147
+ case "sequential":
148
+ return await _sequential_act(
149
+ branch,
150
+ action_request,
151
+ suppress_errors=action_param.suppress_errors,
152
+ verbose_action=action_param.verbose_action,
153
+ )
154
+ case _:
155
+ raise ValueError(
156
+ "Invalid strategy. Choose 'concurrent' or 'sequential'."
157
+ )
158
+
159
+
160
+ async def _concurrent_act(
161
+ branch: "Branch",
162
+ action_request: list | ActionRequest | BaseModel | dict,
163
+ call_params: AlcallParams,
164
+ suppress_errors: bool = True,
165
+ verbose_action: bool = False,
166
+ ) -> list:
167
+ """Execute actions concurrently using AlcallParams."""
168
+
169
+ async def _wrapper(req):
170
+ return await _act(branch, req, suppress_errors, verbose_action)
171
+
172
+ # AlcallParams expects a list as first argument
173
+ action_request_list = (
174
+ action_request
175
+ if isinstance(action_request, list)
176
+ else [action_request]
177
+ )
178
+
179
+ return await call_params(action_request_list, _wrapper)
180
+
181
+
182
+ async def _sequential_act(
183
+ branch: "Branch",
184
+ action_request: list | ActionRequest | BaseModel | dict,
185
+ suppress_errors: bool = True,
186
+ verbose_action: bool = False,
187
+ ) -> list:
188
+ """Execute actions sequentially."""
189
+ action_request = (
190
+ action_request
191
+ if isinstance(action_request, list)
192
+ else [action_request]
193
+ )
194
+ results = []
195
+ for req in action_request:
196
+ result = await _act(branch, req, suppress_errors, verbose_action)
197
+ results.append(result)
198
+ return results
199
+
200
+
201
+ def _get_default_call_params() -> AlcallParams:
202
+ """Get or create default AlcallParams."""
203
+ global _DEFAULT_ALCALL_PARAMS
204
+ if _DEFAULT_ALCALL_PARAMS is None:
205
+ _DEFAULT_ALCALL_PARAMS = AlcallParams(output_dropna=True)
206
+ return _DEFAULT_ALCALL_PARAMS
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  import logging
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  PROMPT = """Perform a brainstorm session. Generate {num_instruct} concise and distinct instructions (Instruct), each representing a potential next step. We will run them in parallel under the same context. Ensure each idea:
@@ -1,5 +1,4 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
4
  """
@@ -1,3 +1,2 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
@@ -1,20 +1,18 @@
1
- # Copyright (c) 2023 - 2025, HaiyangLi <quantocean.li at gmail dot com>
2
- #
1
+ # Copyright (c) 2023-2025, HaiyangLi <quantocean.li at gmail dot com>
3
2
  # SPDX-License-Identifier: Apache-2.0
4
3
 
5
- from typing import TYPE_CHECKING, Literal
4
+ from typing import TYPE_CHECKING
6
5
 
7
- from pydantic import BaseModel
6
+ from pydantic import JsonValue
8
7
 
9
- from lionagi.protocols.types import (
8
+ from lionagi.ln._to_list import to_list
9
+ from lionagi.protocols.messages import (
10
10
  ActionResponse,
11
11
  AssistantResponse,
12
12
  Instruction,
13
- Log,
14
- RoledMessage,
15
13
  )
16
- from lionagi.service.imodel import iModel
17
- from lionagi.utils import copy
14
+
15
+ from ..types import ChatParam
18
16
 
19
17
  if TYPE_CHECKING:
20
18
  from lionagi.session.branch import Branch
@@ -22,98 +20,94 @@ if TYPE_CHECKING:
22
20
 
23
21
  async def chat(
24
22
  branch: "Branch",
25
- instruction=None,
26
- guidance=None,
27
- context=None,
28
- sender=None,
29
- recipient=None,
30
- request_fields=None,
31
- response_format: type[BaseModel] = None,
32
- progression=None,
33
- imodel: iModel = None,
34
- tool_schemas=None,
35
- images: list = None,
36
- image_detail: Literal["low", "high", "auto"] = None,
37
- plain_content: str = None,
23
+ instruction: JsonValue | Instruction,
24
+ chat_param: ChatParam,
38
25
  return_ins_res_message: bool = False,
39
- include_token_usage_to_model: bool = False,
40
- **kwargs,
41
- ) -> tuple[Instruction, AssistantResponse]:
42
- ins: Instruction = branch.msgs.create_instruction(
43
- instruction=instruction,
44
- guidance=guidance,
45
- context=context,
46
- sender=sender or branch.user or "user",
47
- recipient=recipient or branch.id,
48
- response_format=response_format,
49
- request_fields=request_fields,
50
- images=images,
51
- image_detail=image_detail,
52
- tool_schemas=tool_schemas,
53
- plain_content=plain_content,
26
+ ) -> tuple[Instruction, AssistantResponse] | str:
27
+ params = chat_param.to_dict(
28
+ exclude={
29
+ "imodel",
30
+ "imodel_kw",
31
+ "include_token_usage_to_model",
32
+ "progression",
33
+ }
54
34
  )
35
+ params["sender"] = chat_param.sender or branch.user or "user"
36
+ params["recipient"] = chat_param.recipient or branch.id
37
+ params["instruction"] = instruction
55
38
 
56
- progression = progression or branch.msgs.progression
57
- messages: list[RoledMessage] = [
58
- branch.msgs.messages[i] for i in progression
59
- ]
60
-
61
- use_ins = None
62
- _to_use = []
63
- _action_responses: set[ActionResponse] = set()
64
-
65
- for i in messages:
66
- if isinstance(i, ActionResponse):
67
- _action_responses.add(i)
68
- if isinstance(i, AssistantResponse):
69
- j = AssistantResponse(
70
- role=i.role,
71
- content=copy(i.content),
72
- sender=i.sender,
73
- recipient=i.recipient,
74
- template=i.template,
75
- )
76
- _to_use.append(j)
77
- if isinstance(i, Instruction):
78
- j = Instruction(
79
- role=i.role,
80
- content=copy(i.content),
81
- sender=i.sender,
82
- recipient=i.recipient,
83
- template=i.template,
39
+ ins = branch.msgs.create_instruction(**params)
40
+
41
+ _use_ins, _use_msgs, _act_res = None, [], []
42
+ progression = chat_param.progression or branch.msgs.progression
43
+
44
+ for msg in (branch.msgs.messages[j] for j in progression):
45
+ if isinstance(msg, ActionResponse):
46
+ _act_res.append(msg)
47
+
48
+ if isinstance(msg, AssistantResponse):
49
+ _use_msgs.append(
50
+ msg.model_copy(update={"content": msg.content.with_updates()})
84
51
  )
85
- j.tool_schemas = None
86
- j.respond_schema_info = None
87
- j.request_response_format = None
88
-
89
- if _action_responses:
90
- d_ = [k.content for k in _action_responses]
91
- for z in d_:
92
- if z not in j.context:
93
- j.context.append(z)
94
-
95
- _to_use.append(j)
96
- _action_responses = set()
97
- else:
98
- _to_use.append(j)
99
52
 
100
- messages = _to_use
101
- if _action_responses:
102
- j = ins.model_copy()
103
- d_ = [k.content for k in _action_responses]
104
- for z in d_:
105
- if z not in j.context:
106
- j.context.append(z)
107
- use_ins = j
53
+ if isinstance(msg, Instruction):
54
+ j = msg.model_copy(update={"content": msg.content.with_updates()})
55
+ j.content.tool_schemas.clear()
56
+ j.content.response_format = None
57
+ j.content._schema_dict = None
58
+ j.content._model_class = None
59
+
60
+ if _act_res:
61
+ # Convert ActionResponseContent to dicts for proper rendering
62
+ d_ = []
63
+ for k in to_list(_act_res, flatten=True, unique=True):
64
+ if hasattr(k.content, "function"): # ActionResponseContent
65
+ d_.append(
66
+ {
67
+ "function": k.content.function,
68
+ "arguments": k.content.arguments,
69
+ "output": k.content.output,
70
+ }
71
+ )
72
+ else:
73
+ d_.append(k.content)
74
+ j.content.prompt_context.extend(
75
+ [z for z in d_ if z not in j.content.prompt_context]
76
+ )
77
+ _use_msgs.append(j)
78
+ _act_res = []
79
+ else:
80
+ _use_msgs.append(j)
81
+
82
+ if _act_res:
83
+ j = ins.model_copy(update={"content": ins.content.with_updates()})
84
+ # Convert ActionResponseContent to dicts for proper rendering
85
+ d_ = []
86
+ for k in to_list(_act_res, flatten=True, unique=True):
87
+ if hasattr(k.content, "function"): # ActionResponseContent
88
+ d_.append(
89
+ {
90
+ "function": k.content.function,
91
+ "arguments": k.content.arguments,
92
+ "output": k.content.output,
93
+ }
94
+ )
95
+ else:
96
+ d_.append(k.content)
97
+ j.content.prompt_context.extend(
98
+ [z for z in d_ if z not in j.content.prompt_context]
99
+ )
100
+ _use_ins = j
108
101
 
109
- if messages and len(messages) > 1:
110
- _msgs = [messages[0]]
102
+ messages = _use_msgs
103
+ if _use_msgs and len(_use_msgs) > 1:
104
+ _msgs = [_use_msgs[0]]
111
105
 
112
- for i in messages[1:]:
106
+ for i in _use_msgs[1:]:
113
107
  if isinstance(i, AssistantResponse):
114
108
  if isinstance(_msgs[-1], AssistantResponse):
115
- _msgs[-1].response = (
116
- f"{_msgs[-1].response}\n\n{i.response}"
109
+ _msgs[-1].content.assistant_response = (
110
+ f"{_msgs[-1].content.assistant_response}\n\n{i.content.assistant_response}"
117
111
  )
118
112
  else:
119
113
  _msgs.append(i)
@@ -126,11 +120,10 @@ async def chat(
126
120
  if branch.msgs.system:
127
121
  messages = [msg for msg in messages if msg.role != "system"]
128
122
  first_instruction = None
129
-
123
+ f = lambda x: branch.msgs.system.rendered + (x.content.guidance or "")
130
124
  if len(messages) == 0:
131
- first_instruction = ins.model_copy()
132
- first_instruction.guidance = branch.msgs.system.rendered + (
133
- first_instruction.guidance or ""
125
+ first_instruction = ins.model_copy(
126
+ update={"content": ins.content.with_updates(guidance=f(ins))}
134
127
  )
135
128
  messages.append(first_instruction)
136
129
  elif len(messages) >= 1:
@@ -139,37 +132,59 @@ async def chat(
139
132
  raise ValueError(
140
133
  "First message in progression must be an Instruction or System"
141
134
  )
142
- first_instruction = first_instruction.model_copy()
143
- first_instruction.guidance = branch.msgs.system.rendered + (
144
- first_instruction.guidance or ""
135
+ first_instruction = first_instruction.model_copy(
136
+ update={
137
+ "content": first_instruction.content.with_updates(
138
+ guidance=f(first_instruction)
139
+ )
140
+ }
145
141
  )
146
142
  messages[0] = first_instruction
147
- messages.append(use_ins or ins)
143
+ msg_to_append = _use_ins or ins
144
+ if msg_to_append is not None:
145
+ messages.append(msg_to_append)
148
146
 
149
147
  else:
150
- messages.append(use_ins or ins)
151
-
152
- kwargs["messages"] = [i.chat_msg for i in messages]
153
- imodel = imodel or branch.chat_model
154
-
155
- meth = imodel.invoke
156
- if "stream" not in kwargs or not kwargs["stream"]:
157
- kwargs["include_token_usage_to_model"] = include_token_usage_to_model
158
- else:
159
- meth = imodel.stream
148
+ msg_to_append = _use_ins or ins
149
+ if msg_to_append is not None:
150
+ messages.append(msg_to_append)
151
+
152
+ kw = (chat_param.imodel_kw or {}).copy()
153
+
154
+ # Filter out messages with None chat_msg
155
+ chat_msgs = []
156
+ for msg in messages:
157
+ if msg is not None and hasattr(msg, "chat_msg"):
158
+ chat_msg = msg.chat_msg
159
+ if chat_msg is not None:
160
+ chat_msgs.append(chat_msg)
161
+
162
+ kw["messages"] = chat_msgs
163
+
164
+ imodel = chat_param.imodel or branch.chat_model
165
+ meth = imodel.stream if "stream" in kw and kw["stream"] else imodel.invoke
166
+
167
+ if meth is imodel.invoke:
168
+ # Only set if it's not the Unset sentinel value
169
+ if not chat_param._is_sentinel(
170
+ chat_param.include_token_usage_to_model
171
+ ):
172
+ kw["include_token_usage_to_model"] = (
173
+ chat_param.include_token_usage_to_model
174
+ )
175
+ api_call = await meth(**kw)
160
176
 
161
- api_call = await meth(**kwargs)
162
- branch._log_manager.log(Log.create(api_call))
177
+ branch._log_manager.log(api_call)
163
178
 
164
179
  if return_ins_res_message:
165
180
  # Wrap result in `AssistantResponse` and return
166
- return ins, AssistantResponse.create(
167
- assistant_response=api_call.response,
181
+ return ins, AssistantResponse.from_response(
182
+ api_call.response,
168
183
  sender=branch.id,
169
184
  recipient=branch.user,
170
185
  )
171
- return AssistantResponse.create(
172
- assistant_response=api_call.response,
186
+ return AssistantResponse.from_response(
187
+ api_call.response,
173
188
  sender=branch.id,
174
189
  recipient=branch.user,
175
190
  ).response