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.
- lionagi/__init__.py +1 -2
- lionagi/_class_registry.py +1 -2
- lionagi/_errors.py +1 -2
- lionagi/adapters/async_postgres_adapter.py +2 -10
- lionagi/config.py +1 -2
- lionagi/fields/action.py +1 -2
- lionagi/fields/base.py +3 -0
- lionagi/fields/code.py +3 -0
- lionagi/fields/file.py +3 -0
- lionagi/fields/instruct.py +1 -2
- lionagi/fields/reason.py +1 -2
- lionagi/fields/research.py +3 -0
- lionagi/libs/__init__.py +1 -2
- lionagi/libs/file/__init__.py +1 -2
- lionagi/libs/file/chunk.py +1 -2
- lionagi/libs/file/process.py +1 -2
- lionagi/libs/schema/__init__.py +1 -2
- lionagi/libs/schema/as_readable.py +1 -2
- lionagi/libs/schema/extract_code_block.py +1 -2
- lionagi/libs/schema/extract_docstring.py +1 -2
- lionagi/libs/schema/function_to_schema.py +1 -2
- lionagi/libs/schema/load_pydantic_model_from_schema.py +1 -2
- lionagi/libs/schema/minimal_yaml.py +98 -0
- lionagi/libs/validate/__init__.py +1 -2
- lionagi/libs/validate/common_field_validators.py +1 -2
- lionagi/libs/validate/validate_boolean.py +1 -2
- lionagi/ln/fuzzy/_string_similarity.py +1 -2
- lionagi/ln/types.py +32 -5
- lionagi/models/__init__.py +1 -2
- lionagi/models/field_model.py +9 -1
- lionagi/models/hashable_model.py +4 -2
- lionagi/models/model_params.py +1 -2
- lionagi/models/operable_model.py +1 -2
- lionagi/models/schema_model.py +1 -2
- lionagi/operations/ReAct/ReAct.py +475 -239
- lionagi/operations/ReAct/__init__.py +1 -2
- lionagi/operations/ReAct/utils.py +4 -2
- lionagi/operations/__init__.py +1 -2
- lionagi/operations/act/__init__.py +2 -0
- lionagi/operations/act/act.py +206 -0
- lionagi/operations/brainstorm/__init__.py +1 -2
- lionagi/operations/brainstorm/brainstorm.py +1 -2
- lionagi/operations/brainstorm/prompt.py +1 -2
- lionagi/operations/builder.py +1 -2
- lionagi/operations/chat/__init__.py +1 -2
- lionagi/operations/chat/chat.py +131 -116
- lionagi/operations/communicate/communicate.py +102 -44
- lionagi/operations/flow.py +5 -6
- lionagi/operations/instruct/__init__.py +1 -2
- lionagi/operations/instruct/instruct.py +1 -2
- lionagi/operations/interpret/__init__.py +1 -2
- lionagi/operations/interpret/interpret.py +66 -22
- lionagi/operations/operate/__init__.py +1 -2
- lionagi/operations/operate/operate.py +213 -108
- lionagi/operations/parse/__init__.py +1 -2
- lionagi/operations/parse/parse.py +171 -144
- lionagi/operations/plan/__init__.py +1 -2
- lionagi/operations/plan/plan.py +1 -2
- lionagi/operations/plan/prompt.py +1 -2
- lionagi/operations/select/__init__.py +1 -2
- lionagi/operations/select/select.py +79 -19
- lionagi/operations/select/utils.py +2 -3
- lionagi/operations/types.py +120 -25
- lionagi/operations/utils.py +1 -2
- lionagi/protocols/__init__.py +1 -2
- lionagi/protocols/_concepts.py +1 -2
- lionagi/protocols/action/__init__.py +1 -2
- lionagi/protocols/action/function_calling.py +3 -20
- lionagi/protocols/action/manager.py +34 -4
- lionagi/protocols/action/tool.py +1 -2
- lionagi/protocols/contracts.py +1 -2
- lionagi/protocols/forms/__init__.py +1 -2
- lionagi/protocols/forms/base.py +1 -2
- lionagi/protocols/forms/flow.py +1 -2
- lionagi/protocols/forms/form.py +1 -2
- lionagi/protocols/forms/report.py +1 -2
- lionagi/protocols/generic/__init__.py +1 -2
- lionagi/protocols/generic/element.py +17 -65
- lionagi/protocols/generic/event.py +1 -2
- lionagi/protocols/generic/log.py +17 -14
- lionagi/protocols/generic/pile.py +3 -4
- lionagi/protocols/generic/processor.py +1 -2
- lionagi/protocols/generic/progression.py +1 -2
- lionagi/protocols/graph/__init__.py +1 -2
- lionagi/protocols/graph/edge.py +1 -2
- lionagi/protocols/graph/graph.py +1 -2
- lionagi/protocols/graph/node.py +1 -2
- lionagi/protocols/ids.py +1 -2
- lionagi/protocols/mail/__init__.py +1 -2
- lionagi/protocols/mail/exchange.py +1 -2
- lionagi/protocols/mail/mail.py +1 -2
- lionagi/protocols/mail/mailbox.py +1 -2
- lionagi/protocols/mail/manager.py +1 -2
- lionagi/protocols/mail/package.py +1 -2
- lionagi/protocols/messages/__init__.py +28 -2
- lionagi/protocols/messages/action_request.py +87 -186
- lionagi/protocols/messages/action_response.py +74 -133
- lionagi/protocols/messages/assistant_response.py +131 -161
- lionagi/protocols/messages/base.py +27 -20
- lionagi/protocols/messages/instruction.py +281 -626
- lionagi/protocols/messages/manager.py +113 -64
- lionagi/protocols/messages/message.py +88 -199
- lionagi/protocols/messages/system.py +53 -125
- lionagi/protocols/operatives/__init__.py +1 -2
- lionagi/protocols/operatives/operative.py +1 -2
- lionagi/protocols/operatives/step.py +1 -2
- lionagi/protocols/types.py +1 -4
- lionagi/service/connections/__init__.py +1 -2
- lionagi/service/connections/api_calling.py +1 -2
- lionagi/service/connections/endpoint.py +1 -10
- lionagi/service/connections/endpoint_config.py +1 -2
- lionagi/service/connections/header_factory.py +1 -2
- lionagi/service/connections/match_endpoint.py +1 -2
- lionagi/service/connections/mcp/__init__.py +1 -2
- lionagi/service/connections/mcp/wrapper.py +1 -2
- lionagi/service/connections/providers/__init__.py +1 -2
- lionagi/service/connections/providers/anthropic_.py +1 -2
- lionagi/service/connections/providers/claude_code_cli.py +1 -2
- lionagi/service/connections/providers/exa_.py +1 -2
- lionagi/service/connections/providers/nvidia_nim_.py +2 -27
- lionagi/service/connections/providers/oai_.py +30 -96
- lionagi/service/connections/providers/ollama_.py +4 -4
- lionagi/service/connections/providers/perplexity_.py +1 -2
- lionagi/service/hooks/__init__.py +1 -1
- lionagi/service/hooks/_types.py +1 -1
- lionagi/service/hooks/_utils.py +1 -1
- lionagi/service/hooks/hook_event.py +1 -1
- lionagi/service/hooks/hook_registry.py +1 -1
- lionagi/service/hooks/hooked_event.py +3 -4
- lionagi/service/imodel.py +1 -2
- lionagi/service/manager.py +1 -2
- lionagi/service/rate_limited_processor.py +1 -2
- lionagi/service/resilience.py +1 -2
- lionagi/service/third_party/anthropic_models.py +1 -2
- lionagi/service/third_party/claude_code.py +4 -4
- lionagi/service/third_party/openai_models.py +433 -0
- lionagi/service/token_calculator.py +1 -2
- lionagi/session/__init__.py +1 -2
- lionagi/session/branch.py +171 -180
- lionagi/session/session.py +4 -11
- lionagi/tools/__init__.py +1 -2
- lionagi/tools/base.py +1 -2
- lionagi/tools/file/__init__.py +1 -2
- lionagi/tools/file/reader.py +3 -4
- lionagi/tools/types.py +1 -2
- lionagi/utils.py +1 -2
- lionagi/version.py +1 -1
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/METADATA +1 -2
- lionagi-0.18.0.dist-info/RECORD +191 -0
- lionagi/operations/_act/__init__.py +0 -3
- lionagi/operations/_act/act.py +0 -87
- lionagi/protocols/messages/templates/README.md +0 -28
- lionagi/protocols/messages/templates/action_request.jinja2 +0 -5
- lionagi/protocols/messages/templates/action_response.jinja2 +0 -9
- lionagi/protocols/messages/templates/assistant_response.jinja2 +0 -6
- lionagi/protocols/messages/templates/instruction_message.jinja2 +0 -61
- lionagi/protocols/messages/templates/system_message.jinja2 +0 -11
- lionagi/protocols/messages/templates/tool_schemas.jinja2 +0 -7
- lionagi/service/connections/providers/types.py +0 -28
- lionagi/service/third_party/openai_model_names.py +0 -198
- lionagi/service/types.py +0 -59
- lionagi-0.17.10.dist-info/RECORD +0 -199
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/WHEEL +0 -0
- {lionagi-0.17.10.dist-info → lionagi-0.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,27 +1,38 @@
|
|
1
|
-
# Copyright (c) 2023
|
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
|
6
5
|
from collections.abc import AsyncGenerator
|
7
|
-
from typing import TYPE_CHECKING, Any, Literal
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeVar
|
8
7
|
|
9
8
|
from pydantic import BaseModel
|
10
9
|
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
11
12
|
from lionagi.fields.instruct import Instruct
|
12
13
|
from lionagi.libs.schema.as_readable import as_readable
|
13
14
|
from lionagi.libs.validate.common_field_validators import (
|
14
15
|
validate_model_to_type,
|
15
16
|
)
|
16
|
-
from lionagi.
|
17
|
+
from lionagi.ln.fuzzy import FuzzyMatchKeysParams
|
18
|
+
from lionagi.models.field_model import FieldModel
|
17
19
|
from lionagi.service.imodel import iModel
|
18
|
-
from lionagi.utils import copy
|
19
20
|
|
21
|
+
from ..types import (
|
22
|
+
ActionParam,
|
23
|
+
ChatParam,
|
24
|
+
HandleValidation,
|
25
|
+
InterpretParam,
|
26
|
+
ParseParam,
|
27
|
+
)
|
20
28
|
from .utils import Analysis, ReActAnalysis
|
21
29
|
|
22
30
|
if TYPE_CHECKING:
|
23
31
|
from lionagi.session.branch import Branch
|
24
32
|
|
33
|
+
B = TypeVar("B", bound=type[BaseModel])
|
34
|
+
logger = logging.getLogger(__name__)
|
35
|
+
|
25
36
|
|
26
37
|
async def ReAct(
|
27
38
|
branch: "Branch",
|
@@ -30,7 +41,7 @@ async def ReAct(
|
|
30
41
|
interpret_domain: str | None = None,
|
31
42
|
interpret_style: str | None = None,
|
32
43
|
interpret_sample: str | None = None,
|
33
|
-
interpret_model:
|
44
|
+
interpret_model: iModel | None = None,
|
34
45
|
interpret_kwargs: dict | None = None,
|
35
46
|
tools: Any = None,
|
36
47
|
tool_schemas: Any = None,
|
@@ -50,263 +61,469 @@ async def ReAct(
|
|
50
61
|
continue_after_failed_response: bool = False,
|
51
62
|
**kwargs,
|
52
63
|
):
|
64
|
+
"""ReAct reasoning loop with legacy API - wrapper around ReAct_v1."""
|
65
|
+
|
66
|
+
# Handle legacy verbose parameter
|
67
|
+
if "verbose" in kwargs:
|
68
|
+
verbose_analysis = kwargs.pop("verbose")
|
69
|
+
|
70
|
+
# Convert Instruct to dict if needed
|
71
|
+
instruct_dict = (
|
72
|
+
instruct.to_dict()
|
73
|
+
if isinstance(instruct, Instruct)
|
74
|
+
else dict(instruct)
|
75
|
+
)
|
76
|
+
|
77
|
+
# Build InterpretParam if interpretation requested
|
78
|
+
intp_param = None
|
79
|
+
if interpret:
|
80
|
+
intp_param = InterpretParam(
|
81
|
+
domain=interpret_domain or "general",
|
82
|
+
style=interpret_style or "concise",
|
83
|
+
sample_writing=interpret_sample or "",
|
84
|
+
imodel=interpret_model or analysis_model or branch.chat_model,
|
85
|
+
imodel_kw=interpret_kwargs or {},
|
86
|
+
)
|
87
|
+
|
88
|
+
# Build ChatParam
|
89
|
+
chat_param = ChatParam(
|
90
|
+
guidance=instruct_dict.get("guidance"),
|
91
|
+
context=instruct_dict.get("context"),
|
92
|
+
sender=branch.user or "user",
|
93
|
+
recipient=branch.id,
|
94
|
+
response_format=None, # Will be set in operate calls
|
95
|
+
progression=None,
|
96
|
+
tool_schemas=tool_schemas or [],
|
97
|
+
images=[],
|
98
|
+
image_detail="auto",
|
99
|
+
plain_content="",
|
100
|
+
include_token_usage_to_model=include_token_usage_to_model,
|
101
|
+
imodel=analysis_model or branch.chat_model,
|
102
|
+
imodel_kw=kwargs,
|
103
|
+
)
|
104
|
+
|
105
|
+
# Build ActionParam
|
106
|
+
action_param = None
|
107
|
+
if tools is not None or tool_schemas is not None:
|
108
|
+
from ..act.act import _get_default_call_params
|
109
|
+
|
110
|
+
action_param = ActionParam(
|
111
|
+
action_call_params=_get_default_call_params(),
|
112
|
+
tools=tools or True,
|
113
|
+
strategy="concurrent",
|
114
|
+
suppress_errors=True,
|
115
|
+
verbose_action=False,
|
116
|
+
)
|
117
|
+
|
118
|
+
# Build ParseParam
|
119
|
+
from ..parse.parse import get_default_call
|
120
|
+
|
121
|
+
parse_param = ParseParam(
|
122
|
+
response_format=ReActAnalysis, # Initial format
|
123
|
+
fuzzy_match_params=FuzzyMatchKeysParams(),
|
124
|
+
handle_validation="return_value",
|
125
|
+
alcall_params=get_default_call(),
|
126
|
+
imodel=analysis_model or branch.chat_model,
|
127
|
+
imodel_kw={},
|
128
|
+
)
|
129
|
+
|
130
|
+
# Response context for final answer
|
131
|
+
resp_ctx = response_kwargs or {}
|
132
|
+
if response_format:
|
133
|
+
resp_ctx["response_format"] = response_format
|
134
|
+
|
135
|
+
return await ReAct_v1(
|
136
|
+
branch,
|
137
|
+
instruction=instruct_dict.get("instruction", str(instruct)),
|
138
|
+
chat_param=chat_param,
|
139
|
+
action_param=action_param,
|
140
|
+
parse_param=parse_param,
|
141
|
+
intp_param=intp_param,
|
142
|
+
resp_ctx=resp_ctx,
|
143
|
+
reasoning_effort=reasoning_effort,
|
144
|
+
reason=True, # ReAct always uses reasoning
|
145
|
+
field_models=None,
|
146
|
+
handle_validation="return_value",
|
147
|
+
invoke_actions=True, # ReAct always invokes actions
|
148
|
+
clear_messages=False,
|
149
|
+
intermediate_response_options=intermediate_response_options,
|
150
|
+
intermediate_listable=intermediate_listable,
|
151
|
+
intermediate_nullable=False,
|
152
|
+
max_extensions=max_extensions,
|
153
|
+
extension_allowed=extension_allowed,
|
154
|
+
verbose_analysis=verbose_analysis,
|
155
|
+
display_as=display_as,
|
156
|
+
verbose_length=verbose_length,
|
157
|
+
continue_after_failed_response=continue_after_failed_response,
|
158
|
+
return_analysis=return_analysis,
|
159
|
+
)
|
160
|
+
|
161
|
+
|
162
|
+
async def ReAct_v1(
|
163
|
+
branch: "Branch",
|
164
|
+
instruction: str,
|
165
|
+
chat_param: ChatParam,
|
166
|
+
action_param: ActionParam | None = None,
|
167
|
+
parse_param: ParseParam | None = None,
|
168
|
+
intp_param: InterpretParam | None = None,
|
169
|
+
resp_ctx: dict | None = None,
|
170
|
+
reasoning_effort: Literal["low", "medium", "high"] | None = None,
|
171
|
+
reason: bool = False,
|
172
|
+
field_models: list[FieldModel] | None = None,
|
173
|
+
handle_validation: HandleValidation = "raise",
|
174
|
+
invoke_actions: bool = True,
|
175
|
+
clear_messages=False,
|
176
|
+
intermediate_response_options: B | list[B] = None,
|
177
|
+
intermediate_listable: bool = False,
|
178
|
+
intermediate_nullable: bool = False,
|
179
|
+
max_extensions: int | None = 0,
|
180
|
+
extension_allowed: bool = True,
|
181
|
+
verbose_analysis: bool = False,
|
182
|
+
display_as: Literal["yaml", "json"] = "yaml",
|
183
|
+
verbose_length: int = None,
|
184
|
+
continue_after_failed_response: bool = False,
|
185
|
+
return_analysis: bool = False,
|
186
|
+
):
|
187
|
+
"""
|
188
|
+
Context-based ReAct implementation - collects all outputs from ReActStream.
|
189
|
+
|
190
|
+
Args:
|
191
|
+
return_analysis: If True, returns list of all intermediate analyses.
|
192
|
+
If False, returns only the final result.
|
193
|
+
"""
|
53
194
|
outs = []
|
195
|
+
|
54
196
|
if verbose_analysis:
|
55
197
|
async for i in ReActStream(
|
56
198
|
branch=branch,
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
199
|
+
instruction=instruction,
|
200
|
+
chat_param=chat_param,
|
201
|
+
action_param=action_param,
|
202
|
+
parse_param=parse_param,
|
203
|
+
intp_param=intp_param,
|
204
|
+
resp_ctx=resp_ctx,
|
205
|
+
reasoning_effort=reasoning_effort,
|
206
|
+
reason=reason,
|
207
|
+
field_models=field_models,
|
208
|
+
handle_validation=handle_validation,
|
209
|
+
invoke_actions=invoke_actions,
|
210
|
+
clear_messages=clear_messages,
|
67
211
|
intermediate_response_options=intermediate_response_options,
|
68
212
|
intermediate_listable=intermediate_listable,
|
69
|
-
|
70
|
-
extension_allowed=extension_allowed,
|
213
|
+
intermediate_nullable=intermediate_nullable,
|
71
214
|
max_extensions=max_extensions,
|
72
|
-
|
73
|
-
analysis_model=analysis_model,
|
215
|
+
extension_allowed=extension_allowed,
|
74
216
|
verbose_analysis=verbose_analysis,
|
75
217
|
display_as=display_as,
|
76
218
|
verbose_length=verbose_length,
|
77
|
-
include_token_usage_to_model=include_token_usage_to_model,
|
78
219
|
continue_after_failed_response=continue_after_failed_response,
|
79
|
-
**kwargs,
|
80
220
|
):
|
81
221
|
analysis, str_ = i
|
82
|
-
str_
|
83
|
-
as_readable(
|
222
|
+
# str_ is already formatted markdown - just print it
|
223
|
+
as_readable(
|
224
|
+
str_,
|
225
|
+
md=True,
|
226
|
+
display_str=True,
|
227
|
+
)
|
84
228
|
outs.append(analysis)
|
85
229
|
else:
|
86
230
|
async for i in ReActStream(
|
87
231
|
branch=branch,
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
232
|
+
instruction=instruction,
|
233
|
+
chat_param=chat_param,
|
234
|
+
action_param=action_param,
|
235
|
+
parse_param=parse_param,
|
236
|
+
intp_param=intp_param,
|
237
|
+
resp_ctx=resp_ctx,
|
238
|
+
reasoning_effort=reasoning_effort,
|
239
|
+
reason=reason,
|
240
|
+
field_models=field_models,
|
241
|
+
handle_validation=handle_validation,
|
242
|
+
invoke_actions=invoke_actions,
|
243
|
+
clear_messages=clear_messages,
|
98
244
|
intermediate_response_options=intermediate_response_options,
|
99
245
|
intermediate_listable=intermediate_listable,
|
100
|
-
|
101
|
-
extension_allowed=extension_allowed,
|
246
|
+
intermediate_nullable=intermediate_nullable,
|
102
247
|
max_extensions=max_extensions,
|
103
|
-
|
104
|
-
analysis_model=analysis_model,
|
248
|
+
extension_allowed=extension_allowed,
|
105
249
|
display_as=display_as,
|
106
250
|
verbose_length=verbose_length,
|
107
|
-
include_token_usage_to_model=include_token_usage_to_model,
|
108
251
|
continue_after_failed_response=continue_after_failed_response,
|
109
|
-
**kwargs,
|
110
252
|
):
|
111
253
|
outs.append(i)
|
254
|
+
|
112
255
|
if return_analysis:
|
113
256
|
return outs
|
114
|
-
|
257
|
+
|
258
|
+
# Extract answer from the final Analysis object
|
259
|
+
final_result = outs[-1]
|
260
|
+
if hasattr(final_result, "answer"):
|
261
|
+
return final_result.answer
|
262
|
+
return final_result
|
263
|
+
|
264
|
+
|
265
|
+
async def handle_instruction_interpretation(
|
266
|
+
branch: "Branch",
|
267
|
+
instruction: str,
|
268
|
+
chat_param: ChatParam,
|
269
|
+
intp_param: InterpretParam | None,
|
270
|
+
):
|
271
|
+
"""Handle instruction interpretation if requested."""
|
272
|
+
if not intp_param:
|
273
|
+
return instruction
|
274
|
+
|
275
|
+
from ..interpret.interpret import interpret
|
276
|
+
|
277
|
+
return await interpret(branch, instruction, intp_param)
|
278
|
+
|
279
|
+
|
280
|
+
def handle_field_models(
|
281
|
+
field_models: list[FieldModel] | None,
|
282
|
+
intermediate_response_options: B | list[B] = None,
|
283
|
+
intermediate_listable: bool = False,
|
284
|
+
intermediate_nullable: bool = False,
|
285
|
+
):
|
286
|
+
"""Build field models including intermediate response options."""
|
287
|
+
fms = [] if not field_models else field_models
|
288
|
+
|
289
|
+
if intermediate_response_options:
|
290
|
+
|
291
|
+
def create_intermediate_response_field_model():
|
292
|
+
from lionagi.models import OperableModel
|
293
|
+
|
294
|
+
_iro = intermediate_response_options
|
295
|
+
iro = [_iro] if not isinstance(_iro, list) else _iro
|
296
|
+
opm = OperableModel()
|
297
|
+
|
298
|
+
for i in iro:
|
299
|
+
type_ = validate_model_to_type(None, i)
|
300
|
+
opm.add_field(
|
301
|
+
str(type_.__name__).lower(),
|
302
|
+
annotation=type_ | None,
|
303
|
+
# Remove lambda validator to avoid Pydantic serialization errors
|
304
|
+
)
|
305
|
+
|
306
|
+
m_ = opm.new_model(name="IntermediateResponseOptions")
|
307
|
+
irfm = FieldModel(
|
308
|
+
name="intermediate_response_options",
|
309
|
+
base_type=m_,
|
310
|
+
description="Intermediate deliverable outputs. fill as needed ",
|
311
|
+
# Remove lambda validator to avoid Pydantic serialization errors
|
312
|
+
)
|
313
|
+
|
314
|
+
if intermediate_listable:
|
315
|
+
irfm = irfm.as_listable()
|
316
|
+
|
317
|
+
if intermediate_nullable:
|
318
|
+
irfm = irfm.as_nullable()
|
319
|
+
|
320
|
+
return irfm
|
321
|
+
|
322
|
+
fms = [fms] if not isinstance(fms, list) else fms
|
323
|
+
fms += [create_intermediate_response_field_model()]
|
324
|
+
|
325
|
+
return fms
|
115
326
|
|
116
327
|
|
117
328
|
async def ReActStream(
|
118
329
|
branch: "Branch",
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
330
|
+
instruction: str,
|
331
|
+
chat_param: ChatParam,
|
332
|
+
action_param: ActionParam | None = None,
|
333
|
+
parse_param: ParseParam | None = None,
|
334
|
+
intp_param: InterpretParam | None = None,
|
335
|
+
resp_ctx: dict | None = None,
|
336
|
+
reasoning_effort: Literal["low", "medium", "high"] | None = None,
|
337
|
+
reason: bool = False,
|
338
|
+
field_models: list[FieldModel] | None = None,
|
339
|
+
handle_validation: HandleValidation = "raise",
|
340
|
+
invoke_actions: bool = True,
|
341
|
+
clear_messages=False,
|
342
|
+
intermediate_response_options: B | list[B] = None,
|
130
343
|
intermediate_listable: bool = False,
|
131
|
-
|
344
|
+
intermediate_nullable: bool = False,
|
345
|
+
max_extensions: int | None = 0,
|
132
346
|
extension_allowed: bool = True,
|
133
|
-
max_extensions: int | None = 3,
|
134
|
-
response_kwargs: dict | None = None,
|
135
|
-
analysis_model: iModel | None = None,
|
136
347
|
verbose_analysis: bool = False,
|
137
|
-
display_as: Literal["
|
348
|
+
display_as: Literal["yaml", "json"] = "yaml",
|
138
349
|
verbose_length: int = None,
|
139
|
-
include_token_usage_to_model: bool = True,
|
140
350
|
continue_after_failed_response: bool = False,
|
141
|
-
**kwargs,
|
142
351
|
) -> AsyncGenerator:
|
143
|
-
|
352
|
+
"""Core ReAct streaming implementation with context-based architecture."""
|
144
353
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
else intermediate_response_options
|
150
|
-
)
|
151
|
-
field_models = []
|
152
|
-
for i in iro:
|
153
|
-
type_ = validate_model_to_type(None, i)
|
154
|
-
fm = FieldModel(
|
155
|
-
name=str(type_.__name__).lower(),
|
156
|
-
annotation=type_ | None,
|
157
|
-
validator=lambda cls, x: None if x == {} else x,
|
158
|
-
)
|
159
|
-
field_models.append(fm)
|
160
|
-
|
161
|
-
m_ = ModelParams(
|
162
|
-
name="IntermediateResponseOptions", field_models=field_models
|
163
|
-
).create_new_model()
|
164
|
-
|
165
|
-
irfm = FieldModel(
|
166
|
-
name="intermediate_response_options",
|
167
|
-
annotation=(
|
168
|
-
m_ | None if not intermediate_listable else list[m_] | None
|
169
|
-
),
|
170
|
-
description="Optional intermediate deliverable outputs. fill as needed ",
|
171
|
-
validator=lambda cls, x: None if not x else x,
|
354
|
+
# Validate and clamp max_extensions
|
355
|
+
if max_extensions and max_extensions > 100:
|
356
|
+
logger.warning(
|
357
|
+
"max_extensions should not exceed 100; defaulting to 100."
|
172
358
|
)
|
359
|
+
max_extensions = 100
|
173
360
|
|
174
|
-
|
175
|
-
if not tools and not tool_schemas:
|
176
|
-
tools = True
|
177
|
-
|
178
|
-
# Possibly interpret the instruction to refine it
|
179
|
-
instruction_str = None
|
180
|
-
if interpret:
|
181
|
-
instruction_str = await branch.interpret(
|
182
|
-
str(
|
183
|
-
instruct.to_dict()
|
184
|
-
if isinstance(instruct, Instruct)
|
185
|
-
else instruct
|
186
|
-
),
|
187
|
-
domain=interpret_domain,
|
188
|
-
style=interpret_style,
|
189
|
-
sample_writing=interpret_sample,
|
190
|
-
interpret_model=interpret_model,
|
191
|
-
**(interpret_kwargs or {}),
|
192
|
-
)
|
361
|
+
def verbose_yield(title, s_):
|
193
362
|
if verbose_analysis:
|
194
|
-
str_ = "\n
|
363
|
+
str_ = title + "\n"
|
195
364
|
str_ += as_readable(
|
196
|
-
|
365
|
+
s_,
|
197
366
|
md=True,
|
198
367
|
format_curly=True if display_as == "yaml" else False,
|
199
368
|
max_chars=verbose_length,
|
200
369
|
)
|
201
|
-
|
370
|
+
return s_, str_
|
202
371
|
else:
|
203
|
-
|
204
|
-
|
205
|
-
#
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
372
|
+
return s_
|
373
|
+
|
374
|
+
# Step 1: Interpret instruction if requested
|
375
|
+
ins_str = await handle_instruction_interpretation(
|
376
|
+
branch,
|
377
|
+
instruction=instruction,
|
378
|
+
chat_param=chat_param,
|
379
|
+
intp_param=intp_param,
|
210
380
|
)
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
instruct_dict["instruction"] = (
|
215
|
-
instruction_str
|
216
|
-
or (instruct_dict.get("instruction") or "") # in case it's missing
|
217
|
-
) + max_ext_info
|
218
|
-
|
219
|
-
# Prepare a copy of user-provided kwargs for the first operate call
|
220
|
-
kwargs_for_operate = copy(kwargs)
|
221
|
-
kwargs_for_operate["actions"] = True
|
222
|
-
kwargs_for_operate["reason"] = True
|
223
|
-
kwargs_for_operate["include_token_usage_to_model"] = (
|
224
|
-
include_token_usage_to_model
|
225
|
-
)
|
226
|
-
|
227
|
-
# Step 1: Generate initial ReAct analysis
|
228
|
-
analysis: ReActAnalysis = await branch.operate(
|
229
|
-
instruct=instruct_dict,
|
230
|
-
response_format=ReActAnalysis,
|
231
|
-
tools=tools,
|
232
|
-
tool_schemas=tool_schemas,
|
233
|
-
chat_model=analysis_model or branch.chat_model,
|
234
|
-
**kwargs_for_operate,
|
235
|
-
)
|
236
|
-
# If verbose, show round #1 analysis
|
237
|
-
if verbose_analysis:
|
238
|
-
str_ = "\n### ReAct Round No.1 Analysis:\n"
|
381
|
+
# Print interpreted instruction if verbose (don't yield it - not an analysis object)
|
382
|
+
if verbose_analysis and intp_param:
|
383
|
+
str_ = "\n### Interpreted instruction:\n"
|
239
384
|
str_ += as_readable(
|
240
|
-
|
385
|
+
ins_str,
|
241
386
|
md=True,
|
242
387
|
format_curly=True if display_as == "yaml" else False,
|
243
388
|
max_chars=verbose_length,
|
244
389
|
)
|
245
|
-
yield analysis, str_
|
246
|
-
else:
|
247
|
-
yield analysis
|
248
390
|
|
249
|
-
#
|
250
|
-
|
251
|
-
|
252
|
-
|
391
|
+
# Step 2: Handle field models
|
392
|
+
fms = handle_field_models(
|
393
|
+
field_models,
|
394
|
+
intermediate_response_options,
|
395
|
+
intermediate_listable,
|
396
|
+
intermediate_nullable,
|
397
|
+
)
|
398
|
+
|
399
|
+
# Step 3: Initial ReAct analysis
|
400
|
+
from ..operate.operate import operate
|
401
|
+
|
402
|
+
# Build context for initial analysis
|
403
|
+
initial_chat_param = chat_param.with_updates(response_format=ReActAnalysis)
|
404
|
+
|
405
|
+
initial_parse_param = (
|
406
|
+
parse_param.with_updates(response_format=ReActAnalysis)
|
407
|
+
if parse_param
|
408
|
+
else None
|
409
|
+
)
|
410
|
+
|
411
|
+
# Add proper extension prompt for initial analysis
|
412
|
+
initial_instruction = ins_str
|
413
|
+
if extension_allowed and max_extensions:
|
414
|
+
initial_instruction += "\n\n" + ReActAnalysis.FIRST_EXT_PROMPT.format(
|
415
|
+
extensions=max_extensions
|
253
416
|
)
|
254
|
-
max_extensions = 100
|
255
417
|
|
256
|
-
|
257
|
-
|
418
|
+
analysis = await operate(
|
419
|
+
branch,
|
420
|
+
instruction=initial_instruction,
|
421
|
+
chat_param=initial_chat_param,
|
422
|
+
action_param=action_param,
|
423
|
+
parse_param=initial_parse_param,
|
424
|
+
handle_validation=handle_validation,
|
425
|
+
invoke_actions=invoke_actions,
|
426
|
+
skip_validation=False,
|
427
|
+
clear_messages=clear_messages,
|
428
|
+
reason=reason,
|
429
|
+
field_models=fms,
|
430
|
+
)
|
431
|
+
|
432
|
+
out = verbose_yield("\n### ReAct Round No.1 Analysis:\n", analysis)
|
433
|
+
yield out
|
434
|
+
|
435
|
+
# Step 4: Extension loop
|
436
|
+
extensions = max_extensions or 0
|
258
437
|
round_count = 1
|
259
438
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
analysis.get("extension_needed",
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
439
|
+
def _need_extension(analysis):
|
440
|
+
if hasattr(analysis, "extension_needed"):
|
441
|
+
return analysis.extension_needed
|
442
|
+
if isinstance(analysis, dict):
|
443
|
+
return analysis.get("extension_needed", False)
|
444
|
+
return False
|
445
|
+
|
446
|
+
def _extension_allowed(exts):
|
447
|
+
return extension_allowed and exts > 0
|
448
|
+
|
449
|
+
def prepare_analysis_kwargs(exts):
|
270
450
|
new_instruction = None
|
271
|
-
if
|
451
|
+
if exts == max_extensions:
|
272
452
|
new_instruction = ReActAnalysis.FIRST_EXT_PROMPT.format(
|
273
|
-
extensions=
|
453
|
+
extensions=exts
|
274
454
|
)
|
275
455
|
else:
|
276
456
|
new_instruction = ReActAnalysis.CONTINUE_EXT_PROMPT.format(
|
277
|
-
extensions=
|
457
|
+
extensions=exts
|
278
458
|
)
|
279
459
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
operate_kwargs["response_format"] = ReActAnalysis
|
284
|
-
operate_kwargs["action_strategy"] = analysis.action_strategy
|
285
|
-
operate_kwargs["include_token_usage_to_model"] = (
|
286
|
-
include_token_usage_to_model
|
287
|
-
)
|
288
|
-
if irfm:
|
289
|
-
operate_kwargs["field_models"] = operate_kwargs.get(
|
290
|
-
"field_models", []
|
291
|
-
) + [irfm]
|
460
|
+
# Use with_updates to create new context instances
|
461
|
+
updates = {"response_format": ReActAnalysis}
|
462
|
+
|
292
463
|
if reasoning_effort:
|
293
|
-
guide =
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
464
|
+
guide = {
|
465
|
+
"low": "Quick concise reasoning.\n",
|
466
|
+
"medium": "Reasonably balanced reasoning.\n",
|
467
|
+
"high": "Thorough, try as hard as you can in reasoning.\n",
|
468
|
+
}.get(reasoning_effort, "")
|
469
|
+
|
470
|
+
updates["guidance"] = (guide or "") + (chat_param.guidance or "")
|
471
|
+
updates["imodel_kw"] = {
|
472
|
+
**(chat_param.imodel_kw or {}),
|
473
|
+
"reasoning_effort": reasoning_effort,
|
474
|
+
}
|
475
|
+
|
476
|
+
_cctx = chat_param.with_updates(**updates)
|
477
|
+
|
478
|
+
# Import default call params if needed
|
479
|
+
from ..act.act import _get_default_call_params
|
480
|
+
|
481
|
+
_actx = (
|
482
|
+
action_param.with_updates(
|
483
|
+
strategy=getattr(analysis, "action_strategy", "concurrent")
|
484
|
+
)
|
485
|
+
if action_param
|
486
|
+
else ActionParam(
|
487
|
+
action_call_params=_get_default_call_params(),
|
488
|
+
tools=True,
|
489
|
+
strategy=getattr(analysis, "action_strategy", "concurrent"),
|
490
|
+
suppress_errors=True,
|
491
|
+
verbose_action=False,
|
302
492
|
)
|
303
|
-
|
493
|
+
)
|
304
494
|
|
305
|
-
|
306
|
-
instruction
|
307
|
-
|
308
|
-
|
309
|
-
|
495
|
+
return {
|
496
|
+
"instruction": new_instruction,
|
497
|
+
"chat_param": _cctx,
|
498
|
+
"action_param": _actx,
|
499
|
+
"reason": reason,
|
500
|
+
"field_models": fms,
|
501
|
+
}
|
502
|
+
|
503
|
+
while _extension_allowed(extensions) and _need_extension(analysis):
|
504
|
+
kwargs = prepare_analysis_kwargs(extensions)
|
505
|
+
|
506
|
+
# Build parse context for extension
|
507
|
+
ext_parse_param = (
|
508
|
+
parse_param.with_updates(
|
509
|
+
response_format=kwargs["chat_param"].response_format
|
510
|
+
)
|
511
|
+
if parse_param
|
512
|
+
else None
|
513
|
+
)
|
514
|
+
|
515
|
+
analysis = await operate(
|
516
|
+
branch,
|
517
|
+
instruction=kwargs["instruction"],
|
518
|
+
chat_param=kwargs["chat_param"],
|
519
|
+
action_param=kwargs.get("action_param"),
|
520
|
+
parse_param=ext_parse_param,
|
521
|
+
handle_validation=handle_validation,
|
522
|
+
invoke_actions=invoke_actions,
|
523
|
+
skip_validation=False,
|
524
|
+
clear_messages=False, # Keep messages to maintain context
|
525
|
+
reason=kwargs.get("reason", True),
|
526
|
+
field_models=kwargs.get("field_models"),
|
310
527
|
)
|
311
528
|
round_count += 1
|
312
529
|
|
@@ -320,40 +537,73 @@ async def ReActStream(
|
|
320
537
|
"Set `continue_after_failed_response=True` to ignore this error."
|
321
538
|
)
|
322
539
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
str_ += as_readable(
|
328
|
-
analysis,
|
329
|
-
md=True,
|
330
|
-
format_curly=True if display_as == "yaml" else False,
|
331
|
-
max_chars=verbose_length,
|
332
|
-
)
|
333
|
-
|
334
|
-
yield analysis, str_
|
335
|
-
else:
|
336
|
-
yield analysis
|
540
|
+
out = verbose_yield(
|
541
|
+
f"\n### ReAct Round No.{round_count} Analysis:\n", analysis
|
542
|
+
)
|
543
|
+
yield out
|
337
544
|
|
338
545
|
if extensions:
|
339
546
|
extensions -= 1
|
340
547
|
|
341
|
-
# Step
|
342
|
-
answer_prompt = ReActAnalysis.ANSWER_PROMPT.format(
|
343
|
-
|
548
|
+
# Step 5: Final answer
|
549
|
+
answer_prompt = ReActAnalysis.ANSWER_PROMPT.format(instruction=ins_str)
|
550
|
+
|
551
|
+
final_response_format = (
|
552
|
+
resp_ctx.get("response_format") if resp_ctx else None
|
553
|
+
)
|
554
|
+
if not final_response_format:
|
555
|
+
final_response_format = Analysis
|
556
|
+
|
557
|
+
# Build contexts for final answer
|
558
|
+
resp_ctx_updates = {"response_format": final_response_format}
|
559
|
+
if resp_ctx:
|
560
|
+
# Merge resp_ctx into updates (filter allowed keys)
|
561
|
+
for k, v in resp_ctx.items():
|
562
|
+
if k in chat_param.allowed() and k != "response_format":
|
563
|
+
resp_ctx_updates[k] = v
|
564
|
+
|
565
|
+
final_chat_param = chat_param.with_updates(**resp_ctx_updates)
|
566
|
+
|
567
|
+
final_parse_param = (
|
568
|
+
parse_param.with_updates(response_format=final_response_format)
|
569
|
+
if parse_param
|
570
|
+
else None
|
344
571
|
)
|
345
|
-
|
346
|
-
|
572
|
+
|
573
|
+
# Build operate kwargs, honoring response_kwargs
|
574
|
+
operate_kwargs = {
|
575
|
+
"branch": branch,
|
576
|
+
"instruction": answer_prompt,
|
577
|
+
"chat_param": final_chat_param,
|
578
|
+
"action_param": None, # No actions in final answer
|
579
|
+
"parse_param": final_parse_param,
|
580
|
+
"invoke_actions": False,
|
581
|
+
"clear_messages": False,
|
582
|
+
"reason": False, # No reasoning wrapper in final answer
|
583
|
+
"field_models": None,
|
584
|
+
# Defaults that can be overridden by resp_ctx
|
585
|
+
"handle_validation": handle_validation,
|
586
|
+
"skip_validation": False,
|
587
|
+
}
|
588
|
+
|
589
|
+
# Honor response_kwargs for final answer generation
|
590
|
+
if resp_ctx:
|
591
|
+
# Extract operate specific parameters from resp_ctx
|
592
|
+
operate_params = {
|
593
|
+
"skip_validation",
|
594
|
+
"handle_validation",
|
595
|
+
"clear_messages",
|
596
|
+
"reason",
|
597
|
+
"field_models",
|
598
|
+
}
|
599
|
+
for param in operate_params:
|
600
|
+
if param in resp_ctx:
|
601
|
+
operate_kwargs[param] = resp_ctx[param]
|
347
602
|
|
348
603
|
try:
|
349
|
-
out = await
|
350
|
-
|
351
|
-
|
352
|
-
**(response_kwargs or {}),
|
353
|
-
)
|
354
|
-
if isinstance(analysis, dict) and all(
|
355
|
-
i is None for i in analysis.values()
|
356
|
-
):
|
604
|
+
out = await operate(**operate_kwargs)
|
605
|
+
|
606
|
+
if isinstance(out, dict) and all(i is None for i in out.values()):
|
357
607
|
if not continue_after_failed_response:
|
358
608
|
raise ValueError(
|
359
609
|
"All values in the response are None. "
|
@@ -363,20 +613,6 @@ async def ReActStream(
|
|
363
613
|
except Exception:
|
364
614
|
out = branch.msgs.last_response.response
|
365
615
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
if verbose_analysis:
|
370
|
-
str_ = "\n### ReAct Final Answer:\n"
|
371
|
-
str_ += as_readable(
|
372
|
-
out,
|
373
|
-
md=True,
|
374
|
-
format_curly=True if display_as == "yaml" else False,
|
375
|
-
max_chars=verbose_length,
|
376
|
-
)
|
377
|
-
yield out, str_
|
378
|
-
else:
|
379
|
-
yield out
|
380
|
-
|
381
|
-
|
382
|
-
# TODO: Do partial intermeditate output for longer analysis with form and report
|
616
|
+
# Don't extract .answer - return the full Analysis object
|
617
|
+
_o = verbose_yield("\n### ReAct Final Answer:\n", out)
|
618
|
+
yield _o
|