nvidia-nat 1.3.0a20250928__py3-none-any.whl → 1.3.0a20250929__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.
- nat/agent/rewoo_agent/agent.py +288 -100
- nat/agent/rewoo_agent/prompt.py +19 -22
- {nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/METADATA +1 -1
- {nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/RECORD +9 -9
- {nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/WHEEL +0 -0
- {nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/entry_points.txt +0 -0
- {nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
- {nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/licenses/LICENSE.md +0 -0
- {nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/top_level.txt +0 -0
nat/agent/rewoo_agent/agent.py
CHANGED
|
@@ -13,9 +13,14 @@
|
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
|
|
16
|
+
import asyncio
|
|
16
17
|
import json
|
|
17
18
|
import logging
|
|
19
|
+
import re
|
|
18
20
|
from json import JSONDecodeError
|
|
21
|
+
from typing import Dict
|
|
22
|
+
from typing import List
|
|
23
|
+
from typing import Tuple
|
|
19
24
|
|
|
20
25
|
from langchain_core.callbacks.base import AsyncCallbackHandler
|
|
21
26
|
from langchain_core.language_models import BaseChatModel
|
|
@@ -50,15 +55,21 @@ class ReWOOGraphState(BaseModel):
|
|
|
50
55
|
default_factory=lambda: AIMessage(content="")) # the plan generated by the planner to solve the task
|
|
51
56
|
steps: AIMessage = Field(
|
|
52
57
|
default_factory=lambda: AIMessage(content="")) # the steps to solve the task, parsed from the plan
|
|
58
|
+
# New fields for parallel execution support
|
|
59
|
+
evidence_map: Dict[str, Dict] = Field(default_factory=dict) # mapping from placeholders to step info
|
|
60
|
+
execution_levels: List[List[str]] = Field(default_factory=list) # levels for parallel execution
|
|
61
|
+
current_level: int = Field(default=0) # current execution level
|
|
53
62
|
intermediate_results: dict[str, ToolMessage] = Field(default_factory=dict) # the intermediate results of each step
|
|
54
63
|
result: AIMessage = Field(
|
|
55
64
|
default_factory=lambda: AIMessage(content="")) # the final result of the task, generated by the solver
|
|
56
65
|
|
|
57
66
|
|
|
58
67
|
class ReWOOAgentGraph(BaseAgent):
|
|
59
|
-
"""Configurable
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
"""Configurable ReWOO Agent.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
detailed_logs: Toggles logging of inputs, outputs, and intermediate steps.
|
|
72
|
+
"""
|
|
62
73
|
|
|
63
74
|
def __init__(self,
|
|
64
75
|
llm: BaseChatModel,
|
|
@@ -109,16 +120,27 @@ class ReWOOAgentGraph(BaseAgent):
|
|
|
109
120
|
raise
|
|
110
121
|
|
|
111
122
|
@staticmethod
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
def _get_current_level_status(state: ReWOOGraphState) -> tuple[int, bool]:
|
|
124
|
+
"""
|
|
125
|
+
Get the current execution level and whether it's complete.
|
|
126
|
+
:param state: The ReWOO graph state.
|
|
127
|
+
:return: Tuple of (current_level, is_complete). Level -1 means all execution is complete.
|
|
128
|
+
:rtype: tuple[int, bool]
|
|
129
|
+
"""
|
|
130
|
+
if not state.execution_levels:
|
|
131
|
+
return -1, True
|
|
132
|
+
|
|
133
|
+
current_level = state.current_level
|
|
116
134
|
|
|
117
|
-
if
|
|
118
|
-
|
|
119
|
-
return -1
|
|
135
|
+
# Check if we've completed all levels
|
|
136
|
+
if current_level >= len(state.execution_levels):
|
|
137
|
+
return -1, True
|
|
120
138
|
|
|
121
|
-
|
|
139
|
+
# Check if current level is complete
|
|
140
|
+
current_level_placeholders = state.execution_levels[current_level]
|
|
141
|
+
level_complete = all(placeholder in state.intermediate_results for placeholder in current_level_placeholders)
|
|
142
|
+
|
|
143
|
+
return current_level, level_complete
|
|
122
144
|
|
|
123
145
|
@staticmethod
|
|
124
146
|
def _parse_planner_output(planner_output: str) -> AIMessage:
|
|
@@ -130,6 +152,75 @@ class ReWOOAgentGraph(BaseAgent):
|
|
|
130
152
|
|
|
131
153
|
return AIMessage(content=steps)
|
|
132
154
|
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _parse_planner_dependencies(steps: List[Dict]) -> Tuple[Dict[str, Dict], List[List[str]]]:
|
|
157
|
+
"""
|
|
158
|
+
Parse planner steps to identify dependencies and create execution levels for parallel processing.
|
|
159
|
+
This creates a dependency map and identifies which evidence placeholders can be executed in parallel.
|
|
160
|
+
|
|
161
|
+
:param steps: List of plan steps from the planner.
|
|
162
|
+
:type steps: List[Dict]
|
|
163
|
+
:return: A mapping from evidence placeholders to step info and execution levels for parallel processing.
|
|
164
|
+
:rtype: Tuple[Dict[str, Dict], List[List[str]]]
|
|
165
|
+
"""
|
|
166
|
+
evidences = {}
|
|
167
|
+
dependence = {}
|
|
168
|
+
|
|
169
|
+
# First pass: collect all evidence placeholders and their info
|
|
170
|
+
for step in steps:
|
|
171
|
+
if "evidence" not in step:
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
evidence_info = step["evidence"]
|
|
175
|
+
placeholder = evidence_info.get("placeholder", "")
|
|
176
|
+
|
|
177
|
+
if placeholder:
|
|
178
|
+
# Store the complete step info for this evidence
|
|
179
|
+
evidences[placeholder] = {"plan": step.get("plan", ""), "evidence": evidence_info}
|
|
180
|
+
|
|
181
|
+
# Second pass: find dependencies now that we have all placeholders
|
|
182
|
+
for step in steps:
|
|
183
|
+
if "evidence" not in step:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
evidence_info = step["evidence"]
|
|
187
|
+
placeholder = evidence_info.get("placeholder", "")
|
|
188
|
+
tool_input = evidence_info.get("tool_input", "")
|
|
189
|
+
|
|
190
|
+
if placeholder:
|
|
191
|
+
# Find dependencies by looking for other placeholders in tool_input
|
|
192
|
+
dependence[placeholder] = []
|
|
193
|
+
|
|
194
|
+
# Convert tool_input to string to search for placeholders
|
|
195
|
+
tool_input_str = str(tool_input)
|
|
196
|
+
for var in re.findall(r"#E\d+", tool_input_str):
|
|
197
|
+
if var in evidences and var != placeholder:
|
|
198
|
+
dependence[placeholder].append(var)
|
|
199
|
+
|
|
200
|
+
# Create execution levels using topological sort
|
|
201
|
+
levels = []
|
|
202
|
+
remaining = dict(dependence)
|
|
203
|
+
|
|
204
|
+
while remaining:
|
|
205
|
+
# Find items with no dependencies (can be executed in parallel)
|
|
206
|
+
ready = [placeholder for placeholder, deps in remaining.items() if not deps]
|
|
207
|
+
|
|
208
|
+
if not ready:
|
|
209
|
+
raise ValueError("Circular dependency detected in planner output")
|
|
210
|
+
|
|
211
|
+
levels.append(ready)
|
|
212
|
+
|
|
213
|
+
# Remove completed items from remaining
|
|
214
|
+
for placeholder in ready:
|
|
215
|
+
remaining.pop(placeholder)
|
|
216
|
+
|
|
217
|
+
# Remove completed items from other dependencies
|
|
218
|
+
# for placeholder in remaining.items():
|
|
219
|
+
# remaining[placeholder] = [dep for dep in remaining[placeholder] if dep not in ready]
|
|
220
|
+
for ph, deps in list(remaining.items()):
|
|
221
|
+
remaining[ph] = [dep for dep in deps if dep not in ready]
|
|
222
|
+
return evidences, levels
|
|
223
|
+
|
|
133
224
|
@staticmethod
|
|
134
225
|
def _replace_placeholder(placeholder: str, tool_input: str | dict, tool_output: str | dict) -> str | dict:
|
|
135
226
|
|
|
@@ -201,119 +292,204 @@ class ReWOOAgentGraph(BaseAgent):
|
|
|
201
292
|
|
|
202
293
|
steps = self._parse_planner_output(str(plan.content))
|
|
203
294
|
|
|
295
|
+
# Parse dependencies and create execution levels for parallel processing
|
|
296
|
+
evidence_map, execution_levels = self._parse_planner_dependencies(steps.content)
|
|
297
|
+
|
|
204
298
|
if self.detailed_logs:
|
|
205
299
|
agent_response_log_message = AGENT_CALL_LOG_MESSAGE % (task, str(plan.content))
|
|
206
300
|
logger.info("ReWOO agent planner output: %s", agent_response_log_message)
|
|
301
|
+
logger.info("ReWOO agent execution levels: %s", execution_levels)
|
|
207
302
|
|
|
208
|
-
return {
|
|
303
|
+
return {
|
|
304
|
+
"plan": plan,
|
|
305
|
+
"steps": steps,
|
|
306
|
+
"evidence_map": evidence_map,
|
|
307
|
+
"execution_levels": execution_levels,
|
|
308
|
+
"current_level": 0
|
|
309
|
+
}
|
|
209
310
|
|
|
210
311
|
except Exception as ex:
|
|
211
312
|
logger.error("%s Failed to call planner_node: %s", AGENT_LOG_PREFIX, ex)
|
|
212
313
|
raise
|
|
213
314
|
|
|
214
315
|
async def executor_node(self, state: ReWOOGraphState):
|
|
316
|
+
"""
|
|
317
|
+
Execute tools in parallel for the current dependency level.
|
|
318
|
+
|
|
319
|
+
This replaces the sequential execution with parallel execution of tools
|
|
320
|
+
that have no dependencies between them.
|
|
321
|
+
"""
|
|
215
322
|
try:
|
|
216
323
|
logger.debug("%s Starting the ReWOO Executor Node", AGENT_LOG_PREFIX)
|
|
217
324
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
325
|
+
current_level, level_complete = self._get_current_level_status(state)
|
|
326
|
+
|
|
327
|
+
# Should not be invoked if all levels are complete
|
|
328
|
+
if current_level < 0:
|
|
329
|
+
logger.error("%s ReWOO Executor invoked after all levels complete", AGENT_LOG_PREFIX)
|
|
330
|
+
raise RuntimeError("ReWOO Executor invoked after all levels complete")
|
|
331
|
+
|
|
332
|
+
# If current level is already complete, move to next level
|
|
333
|
+
if level_complete:
|
|
334
|
+
new_level = current_level + 1
|
|
335
|
+
logger.debug("%s Level %s complete, moving to level %s", AGENT_LOG_PREFIX, current_level, new_level)
|
|
336
|
+
return {"current_level": new_level}
|
|
337
|
+
|
|
338
|
+
# Get placeholders for current level
|
|
339
|
+
current_level_placeholders = state.execution_levels[current_level]
|
|
340
|
+
|
|
341
|
+
# Filter to only placeholders not yet completed
|
|
342
|
+
pending_placeholders = [p for p in current_level_placeholders if p not in state.intermediate_results]
|
|
343
|
+
|
|
344
|
+
if not pending_placeholders:
|
|
345
|
+
# All placeholders in this level are done, move to next level
|
|
346
|
+
new_level = current_level + 1
|
|
347
|
+
return {"current_level": new_level}
|
|
348
|
+
|
|
349
|
+
logger.debug("%s Executing level %s with %s tools in parallel: %s",
|
|
350
|
+
AGENT_LOG_PREFIX,
|
|
351
|
+
current_level,
|
|
352
|
+
len(pending_placeholders),
|
|
353
|
+
pending_placeholders)
|
|
354
|
+
|
|
355
|
+
# Execute all tools in current level in parallel
|
|
356
|
+
tasks = []
|
|
357
|
+
for placeholder in pending_placeholders:
|
|
358
|
+
step_info = state.evidence_map[placeholder]
|
|
359
|
+
task = self._execute_single_tool(placeholder, step_info, state.intermediate_results)
|
|
360
|
+
tasks.append(task)
|
|
361
|
+
|
|
362
|
+
# Wait for all tasks in current level to complete
|
|
363
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
364
|
+
|
|
365
|
+
# Process results and update intermediate_results
|
|
366
|
+
updated_intermediate_results = dict(state.intermediate_results)
|
|
367
|
+
|
|
368
|
+
for i, result in enumerate(results):
|
|
369
|
+
placeholder = pending_placeholders[i]
|
|
370
|
+
|
|
371
|
+
if isinstance(result, Exception):
|
|
372
|
+
logger.error("%s Tool execution failed for %s: %s", AGENT_LOG_PREFIX, placeholder, result)
|
|
373
|
+
# Create error tool message
|
|
374
|
+
error_message = f"Tool execution failed: {str(result)}"
|
|
375
|
+
updated_intermediate_results[placeholder] = ToolMessage(content=error_message,
|
|
376
|
+
tool_call_id=placeholder)
|
|
377
|
+
if self.raise_tool_call_error:
|
|
378
|
+
raise result
|
|
234
379
|
else:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
intermediate_results = state.intermediate_results
|
|
242
|
-
|
|
243
|
-
# Replace the placeholder in the tool input with the previous tool output
|
|
244
|
-
for _placeholder, _tool_output in intermediate_results.items():
|
|
245
|
-
_tool_output = _tool_output.content
|
|
246
|
-
# If the content is a list, get the first element which should be a dict
|
|
247
|
-
if isinstance(_tool_output, list):
|
|
248
|
-
_tool_output = _tool_output[0]
|
|
249
|
-
assert isinstance(_tool_output, dict)
|
|
250
|
-
|
|
251
|
-
tool_input = self._replace_placeholder(_placeholder, tool_input, _tool_output)
|
|
252
|
-
|
|
253
|
-
requested_tool = self._get_tool(tool)
|
|
254
|
-
if not requested_tool:
|
|
255
|
-
configured_tool_names = list(self.tools_dict.keys())
|
|
256
|
-
logger.warning(
|
|
257
|
-
"%s ReWOO Agent wants to call tool %s. In the ReWOO Agent's configuration within the config file,"
|
|
258
|
-
"there is no tool with that name: %s",
|
|
259
|
-
AGENT_LOG_PREFIX,
|
|
260
|
-
tool,
|
|
261
|
-
configured_tool_names)
|
|
262
|
-
|
|
263
|
-
intermediate_results[placeholder] = ToolMessage(content=TOOL_NOT_FOUND_ERROR_MESSAGE.format(
|
|
264
|
-
tool_name=tool, tools=configured_tool_names),
|
|
265
|
-
tool_call_id=tool)
|
|
266
|
-
return {"intermediate_results": intermediate_results}
|
|
267
|
-
|
|
268
|
-
if self.detailed_logs:
|
|
269
|
-
logger.debug("%s Calling tool %s with input: %s", AGENT_LOG_PREFIX, requested_tool.name, tool_input)
|
|
270
|
-
|
|
271
|
-
# Run the tool. Try to use structured input, if possible
|
|
272
|
-
tool_input_parsed = self._parse_tool_input(tool_input)
|
|
273
|
-
tool_response = await self._call_tool(requested_tool,
|
|
274
|
-
tool_input_parsed,
|
|
275
|
-
RunnableConfig(callbacks=self.callbacks),
|
|
276
|
-
max_retries=self.tool_call_max_retries)
|
|
380
|
+
updated_intermediate_results[placeholder] = result
|
|
381
|
+
# Check if the ToolMessage has error status and raise_tool_call_error is True
|
|
382
|
+
if (isinstance(result, ToolMessage) and hasattr(result, 'status') and result.status == "error"
|
|
383
|
+
and self.raise_tool_call_error):
|
|
384
|
+
logger.error("%s Tool call failed for %s: %s", AGENT_LOG_PREFIX, placeholder, result.content)
|
|
385
|
+
raise RuntimeError(f"Tool call failed: {result.content}")
|
|
277
386
|
|
|
278
387
|
if self.detailed_logs:
|
|
279
|
-
|
|
388
|
+
logger.info("%s Completed level %s with %s tools",
|
|
389
|
+
AGENT_LOG_PREFIX,
|
|
390
|
+
current_level,
|
|
391
|
+
len(pending_placeholders))
|
|
280
392
|
|
|
281
|
-
|
|
282
|
-
raise RuntimeError(f"Tool call failed: {tool_response.content}")
|
|
283
|
-
|
|
284
|
-
intermediate_results[placeholder] = tool_response
|
|
285
|
-
return {"intermediate_results": intermediate_results}
|
|
393
|
+
return {"intermediate_results": updated_intermediate_results}
|
|
286
394
|
|
|
287
395
|
except Exception as ex:
|
|
288
396
|
logger.error("%s Failed to call executor_node: %s", AGENT_LOG_PREFIX, ex)
|
|
289
397
|
raise
|
|
290
398
|
|
|
399
|
+
async def _execute_single_tool(self,
|
|
400
|
+
placeholder: str,
|
|
401
|
+
step_info: Dict,
|
|
402
|
+
intermediate_results: Dict[str, ToolMessage]) -> ToolMessage:
|
|
403
|
+
"""
|
|
404
|
+
Execute a single tool with proper placeholder replacement.
|
|
405
|
+
|
|
406
|
+
:param placeholder: The evidence placeholder (e.g., "#E1").
|
|
407
|
+
:param step_info: Step information containing tool and tool_input.
|
|
408
|
+
:param intermediate_results: Current intermediate results for placeholder replacement.
|
|
409
|
+
:return: ToolMessage with the tool execution result.
|
|
410
|
+
"""
|
|
411
|
+
evidence_info = step_info["evidence"]
|
|
412
|
+
tool_name = evidence_info.get("tool", "")
|
|
413
|
+
tool_input = evidence_info.get("tool_input", "")
|
|
414
|
+
|
|
415
|
+
# Replace placeholders in tool input with previous results
|
|
416
|
+
for _placeholder, _tool_output in intermediate_results.items():
|
|
417
|
+
_tool_output_content = _tool_output.content
|
|
418
|
+
# If the content is a list, get the first element which should be a dict
|
|
419
|
+
if isinstance(_tool_output_content, list):
|
|
420
|
+
_tool_output_content = _tool_output_content[0]
|
|
421
|
+
assert isinstance(_tool_output_content, dict)
|
|
422
|
+
|
|
423
|
+
tool_input = self._replace_placeholder(_placeholder, tool_input, _tool_output_content)
|
|
424
|
+
|
|
425
|
+
# Get the requested tool
|
|
426
|
+
requested_tool = self._get_tool(tool_name)
|
|
427
|
+
if not requested_tool:
|
|
428
|
+
configured_tool_names = list(self.tools_dict.keys())
|
|
429
|
+
logger.warning(
|
|
430
|
+
"%s ReWOO Agent wants to call tool %s. In the ReWOO Agent's configuration within the config file,"
|
|
431
|
+
"there is no tool with that name: %s",
|
|
432
|
+
AGENT_LOG_PREFIX,
|
|
433
|
+
tool_name,
|
|
434
|
+
configured_tool_names)
|
|
435
|
+
|
|
436
|
+
return ToolMessage(content=TOOL_NOT_FOUND_ERROR_MESSAGE.format(tool_name=tool_name,
|
|
437
|
+
tools=configured_tool_names),
|
|
438
|
+
tool_call_id=placeholder)
|
|
439
|
+
|
|
440
|
+
if self.detailed_logs:
|
|
441
|
+
logger.debug("%s Calling tool %s with input: %s", AGENT_LOG_PREFIX, requested_tool.name, tool_input)
|
|
442
|
+
|
|
443
|
+
# Parse and execute the tool
|
|
444
|
+
tool_input_parsed = self._parse_tool_input(tool_input)
|
|
445
|
+
tool_response = await self._call_tool(requested_tool,
|
|
446
|
+
tool_input_parsed,
|
|
447
|
+
RunnableConfig(callbacks=self.callbacks),
|
|
448
|
+
max_retries=self.tool_call_max_retries)
|
|
449
|
+
|
|
450
|
+
if self.detailed_logs:
|
|
451
|
+
self._log_tool_response(requested_tool.name, tool_input_parsed, str(tool_response))
|
|
452
|
+
|
|
453
|
+
return tool_response
|
|
454
|
+
|
|
291
455
|
async def solver_node(self, state: ReWOOGraphState):
|
|
292
456
|
try:
|
|
293
457
|
logger.debug("%s Starting the ReWOO Solver Node", AGENT_LOG_PREFIX)
|
|
294
458
|
|
|
295
459
|
plan = ""
|
|
296
|
-
# Add the tool outputs of each step to the plan
|
|
297
|
-
for
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
460
|
+
# Add the tool outputs of each step to the plan using evidence_map
|
|
461
|
+
for placeholder, step_info in state.evidence_map.items():
|
|
462
|
+
evidence_info = step_info["evidence"]
|
|
463
|
+
original_tool_input = evidence_info.get("tool_input", "")
|
|
464
|
+
tool_name = evidence_info.get("tool", "")
|
|
465
|
+
|
|
466
|
+
# Replace placeholders in tool input with actual results
|
|
467
|
+
final_tool_input = original_tool_input
|
|
468
|
+
for _placeholder, _tool_output in state.intermediate_results.items():
|
|
469
|
+
_tool_output_content = _tool_output.content
|
|
305
470
|
# If the content is a list, get the first element which should be a dict
|
|
306
|
-
if isinstance(
|
|
307
|
-
|
|
308
|
-
assert isinstance(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
471
|
+
if isinstance(_tool_output_content, list):
|
|
472
|
+
_tool_output_content = _tool_output_content[0]
|
|
473
|
+
assert isinstance(_tool_output_content, dict)
|
|
474
|
+
|
|
475
|
+
final_tool_input = self._replace_placeholder(_placeholder, final_tool_input, _tool_output_content)
|
|
476
|
+
|
|
477
|
+
# Get the final result for this placeholder
|
|
478
|
+
final_result = ""
|
|
479
|
+
if placeholder in state.intermediate_results:
|
|
480
|
+
result_content = state.intermediate_results[placeholder].content
|
|
481
|
+
if isinstance(result_content, list):
|
|
482
|
+
result_content = result_content[0]
|
|
483
|
+
if isinstance(result_content, dict):
|
|
484
|
+
final_result = str(result_content)
|
|
485
|
+
else:
|
|
486
|
+
final_result = str(result_content)
|
|
487
|
+
else:
|
|
488
|
+
final_result = str(result_content)
|
|
489
|
+
|
|
490
|
+
step_plan = step_info.get("plan", "")
|
|
491
|
+
plan += f"Plan: {step_plan}\n{placeholder} = \
|
|
492
|
+
{tool_name}[{final_tool_input}]\nResult: {final_result}\n\n"
|
|
317
493
|
|
|
318
494
|
task = str(state.task.content)
|
|
319
495
|
solver_prompt = self.solver_prompt.partial(plan=plan)
|
|
@@ -336,12 +512,24 @@ class ReWOOAgentGraph(BaseAgent):
|
|
|
336
512
|
try:
|
|
337
513
|
logger.debug("%s Starting the ReWOO Conditional Edge", AGENT_LOG_PREFIX)
|
|
338
514
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
515
|
+
current_level, level_complete = self._get_current_level_status(state)
|
|
516
|
+
|
|
517
|
+
# If all levels are complete, move to solver
|
|
518
|
+
if current_level == -1:
|
|
519
|
+
logger.debug("%s All execution levels complete, moving to solver", AGENT_LOG_PREFIX)
|
|
342
520
|
return AgentDecision.END
|
|
343
521
|
|
|
344
|
-
|
|
522
|
+
# If current level is complete, check if there are more levels
|
|
523
|
+
if level_complete:
|
|
524
|
+
next_level = current_level + 1
|
|
525
|
+
if next_level >= len(state.execution_levels):
|
|
526
|
+
logger.debug("%s All execution levels complete, moving to solver", AGENT_LOG_PREFIX)
|
|
527
|
+
return AgentDecision.END
|
|
528
|
+
|
|
529
|
+
logger.debug("%s Continuing with executor (level %s, complete: %s)",
|
|
530
|
+
AGENT_LOG_PREFIX,
|
|
531
|
+
current_level,
|
|
532
|
+
level_complete)
|
|
345
533
|
return AgentDecision.TOOL
|
|
346
534
|
|
|
347
535
|
except Exception as ex:
|
nat/agent/rewoo_agent/prompt.py
CHANGED
|
@@ -18,33 +18,29 @@ For the following task, make plans that can solve the problem step by step. For
|
|
|
18
18
|
which external tool together with tool input to retrieve evidence. You can store the evidence into a \
|
|
19
19
|
placeholder #E that can be called by later tools. (Plan, #E1, Plan, #E2, Plan, ...)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
The following tools and respective requirements are available to you:
|
|
22
22
|
|
|
23
23
|
{tools}
|
|
24
24
|
|
|
25
|
-
The
|
|
25
|
+
The tool calls you make should be one of the following: [{tool_names}]
|
|
26
26
|
|
|
27
27
|
You are not required to use all the tools listed. Choose only the ones that best fit the needs of each plan step.
|
|
28
28
|
|
|
29
|
-
Your output must be a JSON array where each element represents one planning step. Each step must be an object with
|
|
30
|
-
|
|
29
|
+
Your output must be a JSON array where each element represents one planning step. Each step must be an object with \
|
|
31
30
|
exactly two keys:
|
|
32
31
|
|
|
33
32
|
1. "plan": A string that describes in detail the action or reasoning for that step.
|
|
34
33
|
|
|
35
|
-
2. "evidence": An object representing the external tool call associated with that plan step. This object must have the
|
|
34
|
+
2. "evidence": An object representing the external tool call associated with that plan step. This object must have the \
|
|
36
35
|
following keys:
|
|
37
36
|
|
|
38
|
-
-"placeholder": A string that identifies the evidence placeholder (
|
|
39
|
-
|
|
37
|
+
-"placeholder": A string that identifies the evidence placeholder ("#E1", "#E2", ...). The numbering should \
|
|
38
|
+
be sequential based on the order of steps.
|
|
40
39
|
|
|
41
40
|
-"tool": A string specifying the name of the external tool used.
|
|
42
41
|
|
|
43
|
-
-"tool_input": The input to the tool. This can be a string, array, or object, depending on the requirements of the
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Do not include any additional keys or characters in your output, and do not wrap your response with markdown formatting.
|
|
47
|
-
Your output must be strictly valid JSON.
|
|
42
|
+
-"tool_input": The input to the tool. This can be a string, array, or object, depending on the requirements of the \
|
|
43
|
+
tool. Be careful about type assumptions because the output of former tools might contain noise.
|
|
48
44
|
|
|
49
45
|
Important instructions:
|
|
50
46
|
|
|
@@ -58,27 +54,28 @@ Here is an example of how a valid JSON output should look:
|
|
|
58
54
|
|
|
59
55
|
[
|
|
60
56
|
\'{{
|
|
61
|
-
"plan": "
|
|
57
|
+
"plan": "Find Alex's schedule on Sep 25, 2025",
|
|
62
58
|
"evidence": \'{{
|
|
63
59
|
"placeholder": "#E1",
|
|
64
|
-
"tool": "
|
|
65
|
-
"tool_input":
|
|
60
|
+
"tool": "search_calendar",
|
|
61
|
+
"tool_input": ("Alex", "09/25/2025")
|
|
66
62
|
}}\'
|
|
67
63
|
}}\',
|
|
68
64
|
\'{{
|
|
69
|
-
"plan": "
|
|
65
|
+
"plan": "Find Bill's schedule on sep 25, 2025",
|
|
70
66
|
"evidence": \'{{
|
|
71
67
|
"placeholder": "#E2",
|
|
72
|
-
"tool": "
|
|
73
|
-
"tool_input": "
|
|
68
|
+
"tool": "search_calendar",
|
|
69
|
+
"tool_input": ("Bill", "09/25/2025")
|
|
74
70
|
}}\'
|
|
75
71
|
}}\',
|
|
76
72
|
\'{{
|
|
77
|
-
"plan": "
|
|
73
|
+
"plan": "Suggest a time for 1-hour meeting given Alex's and Bill's schedule.",
|
|
78
74
|
"evidence": \'{{
|
|
79
75
|
"placeholder": "#E3",
|
|
80
|
-
"tool": "
|
|
81
|
-
"tool_input": "
|
|
76
|
+
"tool": "llm_chat",
|
|
77
|
+
"tool_input": "Find a common 1-hour time slot for Alex and Bill given their schedules. \
|
|
78
|
+
Alex's schedule: #E1; Bill's schedule: #E2?"
|
|
82
79
|
}}\'
|
|
83
80
|
}}\'
|
|
84
81
|
]
|
|
@@ -94,7 +91,7 @@ task: {task}
|
|
|
94
91
|
"""
|
|
95
92
|
|
|
96
93
|
SOLVER_SYSTEM_PROMPT = """
|
|
97
|
-
Solve the following task or problem. To solve the problem, we have made
|
|
94
|
+
Solve the following task or problem. To solve the problem, we have made some Plans ahead and \
|
|
98
95
|
retrieved corresponding Evidence to each Plan. Use them with caution since long evidence might \
|
|
99
96
|
contain irrelevant information.
|
|
100
97
|
|
|
@@ -14,8 +14,8 @@ nat/agent/react_agent/register.py,sha256=b97dfNtA0I3bNBOGdr9_akQ89UDwPHPPb7LqpsZ
|
|
|
14
14
|
nat/agent/reasoning_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
nat/agent/reasoning_agent/reasoning_agent.py,sha256=k_0wEDqACQn1Rn1MAKxoXyqOKsthHCQ1gt990YYUqHU,9575
|
|
16
16
|
nat/agent/rewoo_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
nat/agent/rewoo_agent/agent.py,sha256=
|
|
18
|
-
nat/agent/rewoo_agent/prompt.py,sha256=
|
|
17
|
+
nat/agent/rewoo_agent/agent.py,sha256=920IYuVBq9Kg4h3pbe_p4Gpz2mBjpGJxdLivYYo3ce8,27594
|
|
18
|
+
nat/agent/rewoo_agent/prompt.py,sha256=B0JeL1xDX4VKcShlkkviEcAsOKAwzSlX8NcAQdmUUPw,3645
|
|
19
19
|
nat/agent/rewoo_agent/register.py,sha256=668zAag6eqajX_PIfh6c-0I0UQN5D-lRiz_mNKHXXjM,8954
|
|
20
20
|
nat/agent/tool_calling_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
nat/agent/tool_calling_agent/agent.py,sha256=4SIp29I56oznPRQu7B3HCoX53Ri3_o3BRRYNJjeBkF8,11006
|
|
@@ -469,10 +469,10 @@ nat/utils/reactive/base/observer_base.py,sha256=6BiQfx26EMumotJ3KoVcdmFBYR_fnAss
|
|
|
469
469
|
nat/utils/reactive/base/subject_base.py,sha256=UQOxlkZTIeeyYmG5qLtDpNf_63Y7p-doEeUA08_R8ME,2521
|
|
470
470
|
nat/utils/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
471
471
|
nat/utils/settings/global_settings.py,sha256=9JaO6pxKT_Pjw6rxJRsRlFCXdVKCl_xUKU2QHZQWWNM,7294
|
|
472
|
-
nvidia_nat-1.3.
|
|
473
|
-
nvidia_nat-1.3.
|
|
474
|
-
nvidia_nat-1.3.
|
|
475
|
-
nvidia_nat-1.3.
|
|
476
|
-
nvidia_nat-1.3.
|
|
477
|
-
nvidia_nat-1.3.
|
|
478
|
-
nvidia_nat-1.3.
|
|
472
|
+
nvidia_nat-1.3.0a20250929.dist-info/licenses/LICENSE-3rd-party.txt,sha256=fOk5jMmCX9YoKWyYzTtfgl-SUy477audFC5hNY4oP7Q,284609
|
|
473
|
+
nvidia_nat-1.3.0a20250929.dist-info/licenses/LICENSE.md,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
474
|
+
nvidia_nat-1.3.0a20250929.dist-info/METADATA,sha256=3m2Kp0KWB9oXqVUBoOmGesvEgobUxMEenPMV6W1llDo,22918
|
|
475
|
+
nvidia_nat-1.3.0a20250929.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
476
|
+
nvidia_nat-1.3.0a20250929.dist-info/entry_points.txt,sha256=4jCqjyETMpyoWbCBf4GalZU8I_wbstpzwQNezdAVbbo,698
|
|
477
|
+
nvidia_nat-1.3.0a20250929.dist-info/top_level.txt,sha256=lgJWLkigiVZuZ_O1nxVnD_ziYBwgpE2OStdaCduMEGc,8
|
|
478
|
+
nvidia_nat-1.3.0a20250929.dist-info/RECORD,,
|
|
File without changes
|
{nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{nvidia_nat-1.3.0a20250928.dist-info → nvidia_nat-1.3.0a20250929.dist-info}/licenses/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|