diff --git a/llama-index-core/.gitignore b/llama-index-core/.gitignore
index d1a1c96f83f0f79ab244489bf5cc0023b445d615..09a904e848e03137e5fb007548194fa1c3a9f9ad 100644
--- a/llama-index-core/.gitignore
+++ b/llama-index-core/.gitignore
@@ -1,4 +1,5 @@
 llama_index/core/_static
+storage/
 .DS_Store
 # Byte-compiled / optimized / DLL files
 __pycache__/
diff --git a/llama-index-core/llama_index/core/agent/react/step.py b/llama-index-core/llama_index/core/agent/react/step.py
index 272ea226ded76962ea2955b1a614759717b830ab..d15fb4195c35a92733d0334b7b195eb4ec325bef 100644
--- a/llama-index-core/llama_index/core/agent/react/step.py
+++ b/llama-index-core/llama_index/core/agent/react/step.py
@@ -232,24 +232,27 @@ class ReActAgentWorker(BaseAgentWorker):
 
         # call tool with input
         reasoning_step = cast(ActionReasoningStep, current_reasoning[-1])
-        tool = tools_dict[reasoning_step.action]
-        with self.callback_manager.event(
-            CBEventType.FUNCTION_CALL,
-            payload={
-                EventPayload.FUNCTION_CALL: reasoning_step.action_input,
-                EventPayload.TOOL: tool.metadata,
-            },
-        ) as event:
-            try:
-                tool_output = tool.call(**reasoning_step.action_input)
-            except Exception as e:
-                tool_output = ToolOutput(
-                    content=f"Error: {e!s}",
-                    tool_name=tool.metadata.name,
-                    raw_input={"kwargs": reasoning_step.action_input},
-                    raw_output=e,
-                )
-            event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
+        if reasoning_step.action in tools_dict:
+            tool = tools_dict[reasoning_step.action]
+            with self.callback_manager.event(
+                CBEventType.FUNCTION_CALL,
+                payload={
+                    EventPayload.FUNCTION_CALL: reasoning_step.action_input,
+                    EventPayload.TOOL: tool.metadata,
+                },
+            ) as event:
+                try:
+                    tool_output = tool.call(**reasoning_step.action_input)
+                except Exception as e:
+                    tool_output = ToolOutput(
+                        content=f"Error: {e!s}",
+                        tool_name=tool.metadata.name,
+                        raw_input={"kwargs": reasoning_step.action_input},
+                        raw_output=e,
+                    )
+                event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
+        else:
+            tool_output = self._handle_nonexistent_tool_name(reasoning_step)
 
         task.extra_state["sources"].append(tool_output)
 
@@ -276,24 +279,27 @@ class ReActAgentWorker(BaseAgentWorker):
 
         # call tool with input
         reasoning_step = cast(ActionReasoningStep, current_reasoning[-1])
-        tool = tools_dict[reasoning_step.action]
-        with self.callback_manager.event(
-            CBEventType.FUNCTION_CALL,
-            payload={
-                EventPayload.FUNCTION_CALL: reasoning_step.action_input,
-                EventPayload.TOOL: tool.metadata,
-            },
-        ) as event:
-            try:
-                tool_output = await tool.acall(**reasoning_step.action_input)
-            except Exception as e:
-                tool_output = ToolOutput(
-                    content=f"Error: {e!s}",
-                    tool_name=tool.metadata.name,
-                    raw_input={"kwargs": reasoning_step.action_input},
-                    raw_output=e,
-                )
-            event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
+        if reasoning_step.action in tools_dict:
+            tool = tools_dict[reasoning_step.action]
+            with self.callback_manager.event(
+                CBEventType.FUNCTION_CALL,
+                payload={
+                    EventPayload.FUNCTION_CALL: reasoning_step.action_input,
+                    EventPayload.TOOL: tool.metadata,
+                },
+            ) as event:
+                try:
+                    tool_output = await tool.acall(**reasoning_step.action_input)
+                except Exception as e:
+                    tool_output = ToolOutput(
+                        content=f"Error: {e!s}",
+                        tool_name=tool.metadata.name,
+                        raw_input={"kwargs": reasoning_step.action_input},
+                        raw_output=e,
+                    )
+                event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
+        else:
+            tool_output = self._handle_nonexistent_tool_name(reasoning_step)
 
         task.extra_state["sources"].append(tool_output)
 
@@ -303,6 +309,26 @@ class ReActAgentWorker(BaseAgentWorker):
             print_text(f"{observation_step.get_content()}\n", color="blue")
         return current_reasoning, False
 
+    def _handle_nonexistent_tool_name(self, reasoning_step):
+        # We still emit a `tool_output` object to the task, so that the LLM can know
+        # it has hallucinated in the next reasoning step.
+        with self.callback_manager.event(
+            CBEventType.FUNCTION_CALL,
+            payload={
+                EventPayload.FUNCTION_CALL: reasoning_step.action_input,
+            },
+        ) as event:
+            # TODO(L10N): This should be localized.
+            content = f"Error: No such tool named `{reasoning_step.action}`."
+            tool_output = ToolOutput(
+                content=content,
+                tool_name=reasoning_step.action,
+                raw_input={"kwargs": reasoning_step.action_input},
+                raw_output=content,
+            )
+            event.on_end(payload={EventPayload.FUNCTION_OUTPUT: str(tool_output)})
+        return tool_output
+
     def _get_response(
         self,
         current_reasoning: List[BaseReasoningStep],