Skip to content

Commit 0becb75

Browse files
authored
ReAct Agent should be informed when it tried to use a non-existent tool (run-llama#12207)
* ReAct Agents should know when it tried to use a non-existent tool * ignore the storage folder * async
1 parent d740d9d commit 0becb75

File tree

2 files changed

+63
-36
lines changed

2 files changed

+63
-36
lines changed

llama-index-core/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
llama_index/core/_static
2+
storage/
23
.DS_Store
34
# Byte-compiled / optimized / DLL files
45
__pycache__/

llama-index-core/llama_index/core/agent/react/step.py

+62-36
Original file line numberDiff line numberDiff line change
@@ -232,24 +232,27 @@ def _process_actions(
232232

233233
# call tool with input
234234
reasoning_step = cast(ActionReasoningStep, current_reasoning[-1])
235-
tool = tools_dict[reasoning_step.action]
236-
with self.callback_manager.event(
237-
CBEventType.FUNCTION_CALL,
238-
payload={
239-
EventPayload.FUNCTION_CALL: reasoning_step.action_input,
240-
EventPayload.TOOL: tool.metadata,
241-
},
242-
) as event:
243-
try:
244-
tool_output = tool.call(**reasoning_step.action_input)
245-
except Exception as e:
246-
tool_output = ToolOutput(
247-
content=f"Error: {e!s}",
248-
tool_name=tool.metadata.name,
249-
raw_input={"kwargs": reasoning_step.action_input},
250-
raw_output=e,
251-
)
252-
event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
235+
if reasoning_step.action in tools_dict:
236+
tool = tools_dict[reasoning_step.action]
237+
with self.callback_manager.event(
238+
CBEventType.FUNCTION_CALL,
239+
payload={
240+
EventPayload.FUNCTION_CALL: reasoning_step.action_input,
241+
EventPayload.TOOL: tool.metadata,
242+
},
243+
) as event:
244+
try:
245+
tool_output = tool.call(**reasoning_step.action_input)
246+
except Exception as e:
247+
tool_output = ToolOutput(
248+
content=f"Error: {e!s}",
249+
tool_name=tool.metadata.name,
250+
raw_input={"kwargs": reasoning_step.action_input},
251+
raw_output=e,
252+
)
253+
event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
254+
else:
255+
tool_output = self._handle_nonexistent_tool_name(reasoning_step)
253256

254257
task.extra_state["sources"].append(tool_output)
255258

@@ -276,24 +279,27 @@ async def _aprocess_actions(
276279

277280
# call tool with input
278281
reasoning_step = cast(ActionReasoningStep, current_reasoning[-1])
279-
tool = tools_dict[reasoning_step.action]
280-
with self.callback_manager.event(
281-
CBEventType.FUNCTION_CALL,
282-
payload={
283-
EventPayload.FUNCTION_CALL: reasoning_step.action_input,
284-
EventPayload.TOOL: tool.metadata,
285-
},
286-
) as event:
287-
try:
288-
tool_output = await tool.acall(**reasoning_step.action_input)
289-
except Exception as e:
290-
tool_output = ToolOutput(
291-
content=f"Error: {e!s}",
292-
tool_name=tool.metadata.name,
293-
raw_input={"kwargs": reasoning_step.action_input},
294-
raw_output=e,
295-
)
296-
event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
282+
if reasoning_step.action in tools_dict:
283+
tool = tools_dict[reasoning_step.action]
284+
with self.callback_manager.event(
285+
CBEventType.FUNCTION_CALL,
286+
payload={
287+
EventPayload.FUNCTION_CALL: reasoning_step.action_input,
288+
EventPayload.TOOL: tool.metadata,
289+
},
290+
) as event:
291+
try:
292+
tool_output = await tool.acall(**reasoning_step.action_input)
293+
except Exception as e:
294+
tool_output = ToolOutput(
295+
content=f"Error: {e!s}",
296+
tool_name=tool.metadata.name,
297+
raw_input={"kwargs": reasoning_step.action_input},
298+
raw_output=e,
299+
)
300+
event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
301+
else:
302+
tool_output = self._handle_nonexistent_tool_name(reasoning_step)
297303

298304
task.extra_state["sources"].append(tool_output)
299305

@@ -303,6 +309,26 @@ async def _aprocess_actions(
303309
print_text(f"{observation_step.get_content()}\n", color="blue")
304310
return current_reasoning, False
305311

312+
def _handle_nonexistent_tool_name(self, reasoning_step):
313+
# We still emit a `tool_output` object to the task, so that the LLM can know
314+
# it has hallucinated in the next reasoning step.
315+
with self.callback_manager.event(
316+
CBEventType.FUNCTION_CALL,
317+
payload={
318+
EventPayload.FUNCTION_CALL: reasoning_step.action_input,
319+
},
320+
) as event:
321+
# TODO(L10N): This should be localized.
322+
content = f"Error: No such tool named `{reasoning_step.action}`."
323+
tool_output = ToolOutput(
324+
content=content,
325+
tool_name=reasoning_step.action,
326+
raw_input={"kwargs": reasoning_step.action_input},
327+
raw_output=content,
328+
)
329+
event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
330+
return tool_output
331+
306332
def _get_response(
307333
self,
308334
current_reasoning: List[BaseReasoningStep],

0 commit comments

Comments
 (0)