Repository: huggingface/smolagents

Issue:
Stop agent
I want to be able to stop an agent mid run based on some external event. Stop will return a final answer that rhe agent was stopped by the user.

Discussion:
@aymeric-roucher: Could you precise your use case? Would it be helpful for the agent to be reset to the state before current task, or just stop it exactly where it is?
The way I see to implement this would just be to have an agent attribute like `agent.keep_running` that acts as a condition to keep the loop running.
That means, whenever `agent.keep_running` is set to false by an external action, the agent just stops.
The next call to `agent.run()` will set the attribute to `True` again.

What do you think?@vitalyshalumov: I have some interrupts by the user with a new task. I want to cleanly stop the agent and re-run.
 agent.keep_running is good but if the agent mid-tool execution I still want to stop tool execution on stop the user with maybe final answer "Stopped by the user".@aymeric-roucher: I think if you want a sure way to interrupt the agent ASAP you could just interrupt its thread.
I've started this PR to add interruption: https://github.com/huggingface/smolagents/pull/1112@aymeric-roucher: Closed by #1112 

Solution patch:
diff --git a/docs/source/en/guided_tour.mdx b/docs/source/en/guided_tour.mdx
index 6c06a58..6d077fb 100644
--- a/docs/source/en/guided_tour.mdx
+++ b/docs/source/en/guided_tour.mdx
@@ -415,6 +415,9 @@ The `reset=False` flag means the agent's memory is not flushed before launching
 
 You can also use this `reset=False` argument to keep the conversation going in any other agentic application.
 
+In gradio UIs, if you want to allow users to interrupt a running agent, you could do this with a button that triggers method `agent.interrupt()`.
+This will stop the agent at the end of its current step, then raise an error.
+
 ## Next steps
 
 Finally, when you've configured your agent to your needs, you can share it to the Hub!
diff --git a/docs/source/en/tutorials/secure_code_execution.mdx b/docs/source/en/tutorials/secure_code_execution.mdx
index c0dedb4..6f1f942 100644
--- a/docs/source/en/tutorials/secure_code_execution.mdx
+++ b/docs/source/en/tutorials/secure_code_execution.mdx
@@ -59,7 +59,7 @@ To improve safety, we propose a range of measures that propose elevated levels o
 
 We advise you to keep in mind that no solution will be 100% safe.
 
-<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/code_execution_safety_diagram.png">
+<img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/code_execution_safety_diagram.png">
 
 ### Our local Python executor
 
diff --git a/src/smolagents/agents.py b/src/smolagents/agents.py
index ee3ba5d..3fab3ff 100644
--- a/src/smolagents/agents.py
+++ b/src/smolagents/agents.py
@@ -300,6 +300,7 @@ class MultiStepAgent:
         """
         max_steps = max_steps or self.max_steps
         self.task = task
+        self.interrupt_switch = False
         if additional_args is not None:
             self.state.update(additional_args)
             self.task += f"""
@@ -336,6 +337,8 @@ You have been provided with these additional arguments, that you can access usin
         final_answer = None
         self.step_number = 1
         while final_answer is None and self.step_number <= max_steps:
+            if self.interrupt_switch:
+                raise AgentError("Agent interrupted.", self.logger)
             step_start_time = time.time()
             if self.planning_interval is not None and self.step_number % self.planning_interval == 1:
                 planning_step = self._create_planning_step(
@@ -480,6 +483,10 @@ You have been provided with these additional arguments, that you can access usin
         """To be implemented in child classes"""
         pass
 
+    def interrupt(self):
+        """Interrupts the agent execution."""
+        self.interrupt_switch = True
+
     def write_memory_to_messages(
         self,
         summary_mode: Optional[bool] = False,


Test patch:
diff --git a/tests/test_agents.py b/tests/test_agents.py
index 33de9b1..86ab308 100644
--- a/tests/test_agents.py
+++ b/tests/test_agents.py
@@ -24,6 +24,7 @@ import pytest
 
 from smolagents.agent_types import AgentImage, AgentText
 from smolagents.agents import (
+    AgentError,
     AgentMaxStepsError,
     CodeAgent,
     MultiStepAgent,
@@ -796,6 +797,23 @@ class TestMultiStepAgent:
                 for content, expected_content in zip(message["content"], expected_message["content"]):
                     assert content == expected_content
 
+    def test_interrupt(self):
+        fake_model = MagicMock()
+        fake_model.return_value.content = "Model output."
+        fake_model.last_input_token_count = None
+
+        def interrupt_callback(memory_step, agent):
+            agent.interrupt()
+
+        agent = CodeAgent(
+            tools=[],
+            model=fake_model,
+            step_callbacks=[interrupt_callback],
+        )
+        with pytest.raises(AgentError) as e:
+            agent.run("Test task")
+        assert "Agent interrupted" in str(e)
+
     @pytest.mark.parametrize(
         "tools, managed_agents, name, expectation",
         [