Repository: huggingface/smolagents
Pull request #985: Reduce model calls in planning_step from 2 to 1
Author: login: aymeric-roucher
Created: 2025-03-15T02:40:33Z | Merged: 2025-03-19T21:06:41Z

This PR reduces the planning step to only one LLM call.


Diff:
diff --git a/src/smolagents/agents.py b/src/smolagents/agents.py
index a30e8fb2a..24018a63e 100644
--- a/src/smolagents/agents.py
+++ b/src/smolagents/agents.py
@@ -389,123 +389,77 @@ def _handle_max_steps_reached(self, task: str, images: List[str], step_start_tim
         return final_answer
 
     def planning_step(self, task, is_first_step: bool, step: int) -> None:
-        input_messages, facts_message, plan_message = (
-            self._generate_initial_plan(task) if is_first_step else self._generate_updated_plan(task, step)
-        )
-        self._record_planning_step(input_messages, facts_message, plan_message, is_first_step)
-
-    def _generate_initial_plan(self, task: str) -> Tuple[ChatMessage, ChatMessage]:
-        input_messages = [
-            {
+        if is_first_step:
+            input_messages = [
+                {
+                    "role": MessageRole.USER,
+                    "content": [
+                        {
+                            "type": "text",
+                            "text": populate_template(
+                                self.prompt_templates["planning"]["initial_plan"],
+                                variables={"task": task, "tools": self.tools, "managed_agents": self.managed_agents},
+                            ),
+                        }
+                    ],
+                }
+            ]
+            plan_message = self.model(input_messages, stop_sequences=["<end_plan>"])
+        else:
+            # Summary mode removes the system prompt and previous planning messages output by the model.
+            # Removing previous planning messages avoids influencing too much the new plan.
+            memory_messages = self.write_memory_to_messages(summary_mode=True)
+            plan_update_pre = {
+                "role": MessageRole.SYSTEM,
+                "content": [
+                    {
+                        "type": "text",
+                        "text": populate_template(
+                            self.prompt_templates["planning"]["update_plan_pre_messages"], variables={"task": task}
+                        ),
+                    }
+                ],
+            }
+            plan_update_post = {
                 "role": MessageRole.USER,
                 "content": [
                     {
                         "type": "text",
                         "text": populate_template(
-                            self.prompt_templates["planning"]["initial_facts"], variables={"task": task}
+                            self.prompt_templates["planning"]["update_plan_post_messages"],
+                            variables={
+                                "task": task,
+                                "tools": self.tools,
+                                "managed_agents": self.managed_agents,
+                                "remaining_steps": (self.max_steps - step),
+                            },
                         ),
                     }
                 ],
-            },
-        ]
-        facts_message = self.model(input_messages)
-
-        message_prompt_plan = {
-            "role": MessageRole.USER,
-            "content": [
-                {
-                    "type": "text",
-                    "text": populate_template(
-                        self.prompt_templates["planning"]["initial_plan"],
-                        variables={
-                            "task": task,
-                            "tools": self.tools,
-                            "managed_agents": self.managed_agents,
-                            "answer_facts": facts_message.content,
-                        },
-                    ),
-                }
-            ],
-        }
-        plan_message = self.model([message_prompt_plan], stop_sequences=["<end_plan>"])
-        return input_messages, facts_message, plan_message
-
-    def _generate_updated_plan(self, task: str, step: int) -> Tuple[ChatMessage, ChatMessage]:
-        # Do not take the system prompt message from the memory
-        # summary_mode=False: Do not take previous plan steps to avoid influencing the new plan
-        memory_messages = self.write_memory_to_messages()[1:]
-        facts_update_pre = {
-            "role": MessageRole.SYSTEM,
-            "content": [{"type": "text", "text": self.prompt_templates["planning"]["update_facts_pre_messages"]}],
-        }
-        facts_update_post = {
-            "role": MessageRole.USER,
-            "content": [{"type": "text", "text": self.prompt_templates["planning"]["update_facts_post_messages"]}],
-        }
-        input_messages = [facts_update_pre] + memory_messages + [facts_update_post]
-        facts_message = self.model(input_messages)
-
-        update_plan_pre = {
-            "role": MessageRole.SYSTEM,
-            "content": [
-                {
-                    "type": "text",
-                    "text": populate_template(
-                        self.prompt_templates["planning"]["update_plan_pre_messages"], variables={"task": task}
-                    ),
-                }
-            ],
-        }
-        update_plan_post = {
-            "role": MessageRole.USER,
-            "content": [
-                {
-                    "type": "text",
-                    "text": populate_template(
-                        self.prompt_templates["planning"]["update_plan_post_messages"],
-                        variables={
-                            "task": task,
-                            "tools": self.tools,
-                            "managed_agents": self.managed_agents,
-                            "facts_update": facts_message.content,
-                            "remaining_steps": (self.max_steps - step),
-                        },
-                    ),
-                }
-            ],
-        }
-        plan_message = self.model(
-            [update_plan_pre] + memory_messages + [update_plan_post], stop_sequences=["<end_plan>"]
-        )
-        return input_messages, facts_message, plan_message
+            }
+            input_messages = [plan_update_pre] + memory_messages + [plan_update_post]
+            plan_message = self.model(input_messages, stop_sequences=["<end_plan>"])
+        self._record_planning_step(input_messages, plan_message, is_first_step)
 
-    def _record_planning_step(
-        self, input_messages: list, facts_message: ChatMessage, plan_message: ChatMessage, is_first_step: bool
-    ) -> None:
+    def _record_planning_step(self, input_messages: list, plan_message: ChatMessage, is_first_step: bool) -> None:
         if is_first_step:
-            facts = textwrap.dedent(f"""Here are the facts that I know so far:\n```\n{facts_message.content}\n```""")
             plan = textwrap.dedent(
-                f"""Here is the plan of action that I will follow to solve the task:\n```\n{plan_message.content}\n```"""
+                f"""Here are the facts I know and the plan of action that I will follow to solve the task:\n```\n{plan_message.content}\n```"""
             )
-            log_message = "Initial plan"
+            log_headline = "Initial plan"
         else:
-            facts = textwrap.dedent(
-                f"""Here is the updated list of the facts that I know:\n```\n{facts_message.content}\n```"""
-            )
             plan = textwrap.dedent(
-                f"""I still need to solve the task I was given:\n```\n{self.task}\n```\n\nHere is my new/updated plan of action to solve the task:\n```\n{plan_message.content}\n```"""
+                f"""I still need to solve the task I was given:\n```\n{self.task}\n```\n\nHere are the facts I know and my new/updated plan of action to solve the task:\n```\n{plan_message.content}\n```"""
             )
-            log_message = "Updated plan"
+            log_headline = "Updated plan"
         self.memory.steps.append(
             PlanningStep(
                 model_input_messages=input_messages,
-                facts=facts,
                 plan=plan,
-                model_output_message_plan=plan_message,
-                model_output_message_facts=facts_message,
+                model_output_message=plan_message,
             )
         )
-        self.logger.log(Rule(f"[bold]{log_message}", style="orange"), Text(plan), level=LogLevel.INFO)
+        self.logger.log(Rule(f"[bold]{log_headline}", style="orange"), Text(plan), level=LogLevel.INFO)
 
     @property
     def logs(self):
@@ -1051,14 +1005,14 @@ def step(self, memory_step: ActionStep) -> Union[None, Any]:
             model_message: ChatMessage = self.model(
                 memory_messages,
                 tools_to_call_from=list(self.tools.values()),
-                stop_sequences=["Observation:"],
+                stop_sequences=["Observation:", "Calling tools:"],
             )
             memory_step.model_output_message = model_message
         except Exception as e:
             raise AgentGenerationError(f"Error in generating tool call with model:\n{e}", self.logger) from e
 
         self.logger.log_markdown(
-            content=str(model_message.raw),
+            content=model_message.content if model_message.content else str(model_message.raw),
             title="Output message of the LLM:",
             level=LogLevel.DEBUG,
         )
@@ -1229,7 +1183,7 @@ def step(self, memory_step: ActionStep) -> Union[None, Any]:
             additional_args = {"grammar": self.grammar} if self.grammar is not None else {}
             chat_message: ChatMessage = self.model(
                 self.input_messages,
-                stop_sequences=["<end_code>", "Observation:"],
+                stop_sequences=["<end_code>", "Observation:", "Calling tools:"],
                 **additional_args,
             )
             memory_step.model_output_message = chat_message
diff --git a/src/smolagents/memory.py b/src/smolagents/memory.py
index 7eabbea4f..f8d23e713 100644
--- a/src/smolagents/memory.py
+++ b/src/smolagents/memory.py
@@ -144,26 +144,13 @@ def to_messages(self, summary_mode: bool = False, show_model_input_messages: boo
 @dataclass
 class PlanningStep(MemoryStep):
     model_input_messages: List[Message]
-    model_output_message_facts: ChatMessage
-    facts: str
-    model_output_message_plan: ChatMessage
+    model_output_message: ChatMessage
     plan: str
 
     def to_messages(self, summary_mode: bool, **kwargs) -> List[Message]:
-        messages = []
-        messages.append(
-            Message(
-                role=MessageRole.ASSISTANT, content=[{"type": "text", "text": f"[FACTS LIST]:\n{self.facts.strip()}"}]
-            )
-        )
-
-        if not summary_mode:  # This step is not shown to a model writing a plan to avoid influencing the new plan
-            messages.append(
-                Message(
-                    role=MessageRole.ASSISTANT, content=[{"type": "text", "text": f"[PLAN]:\n{self.plan.strip()}"}]
-                )
-            )
-        return messages
+        if summary_mode:
+            return []
+        return [Message(role=MessageRole.ASSISTANT, content=[{"type": "text", "text": self.plan.strip()}])]
 
 
 @dataclass
diff --git a/src/smolagents/prompts/code_agent.yaml b/src/smolagents/prompts/code_agent.yaml
index f98b45990..6d0eabfdb 100644
--- a/src/smolagents/prompts/code_agent.yaml
+++ b/src/smolagents/prompts/code_agent.yaml
@@ -172,39 +172,34 @@ system_prompt: |-
 
   Now Begin! If you solve the task correctly, you will receive a reward of $1,000,000.
 planning:
-  initial_facts: |-
-    Below I will present you a task.
+  initial_plan : |-
+    You are a world express at analyzing a situation to derive facts, and plan accordingly towards solving a task.
+    Below I will present you a task. You will need to 1. build a survey of facts known or needed to solve the task, then 2. make a plan of action to solve the task.
 
-    You will now build a comprehensive preparatory survey of which facts we have at our disposal and which ones we still need.
+    1. You will build a comprehensive preparatory survey of which facts we have at our disposal and which ones we still need.
     To do so, you will have to read the task and identify things that must be discovered in order to successfully complete it.
     Don't make any assumptions. For each item, provide a thorough reasoning. Here is how you will structure this survey:
 
     ---
-    ### 1. Facts given in the task
+    ## Facts survey
+    ### 1.1. Facts given in the task
     List here the specific facts given in the task that could help you (there might be nothing here).
 
-    ### 2. Facts to look up
+    ### 1.2. Facts to look up
     List here any facts that we may need to look up.
     Also list where to find each of these, for instance a website, a file... - maybe the task contains some sources that you should re-use here.
 
-    ### 3. Facts to derive
+    ### 1.3. Facts to derive
     List here anything that we want to derive from the above by logical reasoning, for instance computation or simulation.
 
     Keep in mind that "facts" will typically be specific names, dates, values, etc. Your answer should use the below headings:
-    ### 1. Facts given in the task
-    ### 2. Facts to look up
-    ### 3. Facts to derive
+    ### 1.1. Facts given in the task
+    ### 1.2. Facts to look up
+    ### 1.3. Facts to derive
     Do not add anything else.
 
-    Here is the task:
-    ```
-    {{task}}
-    ```
-    Now begin!
-  initial_plan : |-
-    You are a world expert at making efficient plans to solve any task using a set of carefully crafted tools.
-
-    Now for the given task, develop a step-by-step high-level plan taking into account the above inputs and list of facts.
+    ## Plan
+    Then for the given task, develop a step-by-step high-level plan taking into account the above inputs and list of facts.
     This plan should involve individual tasks based on the available tools, that if executed correctly will yield the correct answer.
     Do not skip steps, do not add any superfluous steps. Only write the high-level plan, DO NOT DETAIL INDIVIDUAL TOOL CALLS.
     After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
@@ -215,6 +210,7 @@ planning:
     ```
     {{task}}
     ```
+
     You can leverage these tools:
     {%- for tool in tools.values() %}
     - {{ tool.name }}: {{ tool.description }}
@@ -232,46 +228,44 @@ planning:
     {%- endfor %}
     {%- endif %}
 
-    List of facts that you know:
+    Now begin! First in part 1, list the facts that you have at your disposal, then in part 2, make a plan to solve the task.
+  update_plan_pre_messages: |-
+    You are a world express at analyzing a situation to derive facts, and plan accordingly towards solving a task.
+    You have been given a task:
     ```
-    {{answer_facts}}
+    {{task}}
     ```
+    Below you will find a history of attempts made to solve the task. You will first have to produce a survey of known and unknown facts:
 
-    Now begin! Write your plan below.
-  update_facts_pre_messages: |-
-    You are a world expert at gathering known and unknown facts based on a conversation.
-    Below you will find a task, and a history of attempts made to solve the task. You will have to produce a list of these:
+    ## Facts survey
     ### 1. Facts given in the task
     ### 2. Facts that we have learned
     ### 3. Facts still to look up
     ### 4. Facts still to derive
+
+    Then you will have to propose an updated plan to solve the task.
+    If the previous tries so far have met some success, you can make an updated plan based on these actions.
+    If you are stalled, you can make a completely new plan starting from scratch.
+
     Find the task and history below:
-  update_facts_post_messages: |-
-    Earlier we've built a list of facts.
-    But since in your previous steps you may have learned useful new facts or invalidated some false ones.
-    Please update your list of facts based on the previous history, and provide these headings:
+  update_plan_post_messages: |-
+    Now write your updated facts below, taking into account the above history:
+
+    ## Updated facts survey
     ### 1. Facts given in the task
     ### 2. Facts that we have learned
     ### 3. Facts still to look up
     ### 4. Facts still to derive
 
-    Now write your new list of facts below.
-  update_plan_pre_messages: |-
-    You are a world expert at making efficient plans to solve any task using a set of carefully crafted tools.
-
-    You have been given a task:
-    ```
-    {{task}}
-    ```
+    Then write a step-by-step high-level plan to solve the task above.
+    ## Plan
+    ### 1. ...
+    Etc
 
-    Find below the record of what has been tried so far to solve it. Then you will be asked to make an updated plan to solve the task.
-    If the previous tries so far have met some success, you can make an updated plan based on these actions.
-    If you are stalled, you can make a completely new plan starting from scratch.
-  update_plan_post_messages: |-
-    You're still working towards solving this task:
-    ```
-    {{task}}
-    ```
+    This plan should involve individual tasks based on the available tools, that if executed correctly will yield the correct answer.
+    Beware that you have {remaining_steps} steps remaining.
+    Do not skip steps, do not add any superfluous steps. Only write the high-level plan, DO NOT DETAIL INDIVIDUAL TOOL CALLS.
+    After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
 
     You can leverage these tools:
     {%- for tool in tools.values() %}
@@ -290,17 +284,6 @@ planning:
     {%- endfor %}
     {%- endif %}
 
-    Here is the up to date list of facts that you know:
-    ```
-    {{facts_update}}
-    ```
-
-    Now for the given task, develop a step-by-step high-level plan taking into account the above inputs and list of facts.
-    This plan should involve individual tasks based on the available tools, that if executed correctly will yield the correct answer.
-    Beware that you have {remaining_steps} steps remaining.
-    Do not skip steps, do not add any superfluous steps. Only write the high-level plan, DO NOT DETAIL INDIVIDUAL TOOL CALLS.
-    After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
-
     Now write your new plan below.
 managed_agent:
   task: |-
diff --git a/src/smolagents/prompts/toolcalling_agent.yaml b/src/smolagents/prompts/toolcalling_agent.yaml
index 6d37844f7..a82061b5d 100644
--- a/src/smolagents/prompts/toolcalling_agent.yaml
+++ b/src/smolagents/prompts/toolcalling_agent.yaml
@@ -115,39 +115,34 @@ system_prompt: |-
 
   Now Begin! If you solve the task correctly, you will receive a reward of $1,000,000.
 planning:
-  initial_facts: |-
-    Below I will present you a task.
+  initial_plan : |-
+    You are a world express at analyzing a situation to derive facts, and plan accordingly towards solving a task.
+    Below I will present you a task. You will need to 1. build a survey of facts known or needed to solve the task, then 2. make a plan of action to solve the task.
 
-    You will now build a comprehensive preparatory survey of which facts we have at our disposal and which ones we still need.
+    1. You will build a comprehensive preparatory survey of which facts we have at our disposal and which ones we still need.
     To do so, you will have to read the task and identify things that must be discovered in order to successfully complete it.
     Don't make any assumptions. For each item, provide a thorough reasoning. Here is how you will structure this survey:
 
     ---
-    ### 1. Facts given in the task
+    ## Facts survey
+    ### 1.1. Facts given in the task
     List here the specific facts given in the task that could help you (there might be nothing here).
 
-    ### 2. Facts to look up
+    ### 1.2. Facts to look up
     List here any facts that we may need to look up.
     Also list where to find each of these, for instance a website, a file... - maybe the task contains some sources that you should re-use here.
 
-    ### 3. Facts to derive
+    ### 1.3. Facts to derive
     List here anything that we want to derive from the above by logical reasoning, for instance computation or simulation.
 
     Keep in mind that "facts" will typically be specific names, dates, values, etc. Your answer should use the below headings:
-    ### 1. Facts given in the task
-    ### 2. Facts to look up
-    ### 3. Facts to derive
+    ### 1.1. Facts given in the task
+    ### 1.2. Facts to look up
+    ### 1.3. Facts to derive
     Do not add anything else.
 
-    Here is the task:
-    ```
-    {{task}}
-    ```
-    Now begin!
-  initial_plan : |-
-    You are a world expert at making efficient plans to solve any task using a set of carefully crafted tools.
-
-    Now for the given task, develop a step-by-step high-level plan taking into account the above inputs and list of facts.
+    ## Plan
+    Then for the given task, develop a step-by-step high-level plan taking into account the above inputs and list of facts.
     This plan should involve individual tasks based on the available tools, that if executed correctly will yield the correct answer.
     Do not skip steps, do not add any superfluous steps. Only write the high-level plan, DO NOT DETAIL INDIVIDUAL TOOL CALLS.
     After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
@@ -158,6 +153,7 @@ planning:
     ```
     {{task}}
     ```
+
     You can leverage these tools:
     {%- for tool in tools.values() %}
     - {{ tool.name }}: {{ tool.description }}
@@ -175,46 +171,44 @@ planning:
     {%- endfor %}
     {%- endif %}
 
-    List of facts that you know:
+    Now begin! First in part 1, list the facts that you have at your disposal, then in part 2, make a plan to solve the task.
+  update_plan_pre_messages: |-
+    You are a world express at analyzing a situation to derive facts, and plan accordingly towards solving a task.
+    You have been given a task:
     ```
-    {{answer_facts}}
+    {{task}}
     ```
+    Below you will find a history of attempts made to solve the task. You will first have to produce a survey of known and unknown facts:
 
-    Now begin! Write your plan below.
-  update_facts_pre_messages: |-
-    You are a world expert at gathering known and unknown facts based on a conversation.
-    Below you will find a task, and a history of attempts made to solve the task. You will have to produce a list of these:
+    ## Facts survey
     ### 1. Facts given in the task
     ### 2. Facts that we have learned
     ### 3. Facts still to look up
     ### 4. Facts still to derive
+
+    Then you will have to propose an updated plan to solve the task.
+    If the previous tries so far have met some success, you can make an updated plan based on these actions.
+    If you are stalled, you can make a completely new plan starting from scratch.
+
     Find the task and history below:
-  update_facts_post_messages: |-
-    Earlier we've built a list of facts.
-    But since in your previous steps you may have learned useful new facts or invalidated some false ones.
-    Please update your list of facts based on the previous history, and provide these headings:
+  update_plan_post_messages: |-
+    Now write your updated facts below, taking into account the above history:
+
+    ## Updated facts survey
     ### 1. Facts given in the task
     ### 2. Facts that we have learned
     ### 3. Facts still to look up
     ### 4. Facts still to derive
 
-    Now write your new list of facts below.
-  update_plan_pre_messages: |-
-    You are a world expert at making efficient plans to solve any task using a set of carefully crafted tools.
-
-    You have been given a task:
-    ```
-    {{task}}
-    ```
+    Then write a step-by-step high-level plan to solve the task above.
+    ## Plan
+    ### 1. ...
+    Etc
 
-    Find below the record of what has been tried so far to solve it. Then you will be asked to make an updated plan to solve the task.
-    If the previous tries so far have met some success, you can make an updated plan based on these actions.
-    If you are stalled, you can make a completely new plan starting from scratch.
-  update_plan_post_messages: |-
-    You're still working towards solving this task:
-    ```
-    {{task}}
-    ```
+    This plan should involve individual tasks based on the available tools, that if executed correctly will yield the correct answer.
+    Beware that you have {remaining_steps} steps remaining.
+    Do not skip steps, do not add any superfluous steps. Only write the high-level plan, DO NOT DETAIL INDIVIDUAL TOOL CALLS.
+    After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
 
     You can leverage these tools:
     {%- for tool in tools.values() %}
@@ -233,17 +227,6 @@ planning:
     {%- endfor %}
     {%- endif %}
 
-    Here is the up to date list of facts that you know:
-    ```
-    {{facts_update}}
-    ```
-
-    Now for the given task, develop a step-by-step high-level plan taking into account the above inputs and list of facts.
-    This plan should involve individual tasks based on the available tools, that if executed correctly will yield the correct answer.
-    Beware that you have {remaining_steps} steps remaining.
-    Do not skip steps, do not add any superfluous steps. Only write the high-level plan, DO NOT DETAIL INDIVIDUAL TOOL CALLS.
-    After writing the final step of the plan, write the '\n<end_plan>' tag and stop there.
-
     Now write your new plan below.
 managed_agent:
   task: |-
diff --git a/tests/test_agents.py b/tests/test_agents.py
index 3fcbc4673..e7feff919 100644
--- a/tests/test_agents.py
+++ b/tests/test_agents.py
@@ -644,20 +644,12 @@ def test_step_number(self):
             (
                 1,
                 [
-                    [{"role": MessageRole.USER, "content": [{"type": "text", "text": "INITIAL_FACTS_USER_PROMPT"}]}],
                     [{"role": MessageRole.USER, "content": [{"type": "text", "text": "INITIAL_PLAN_USER_PROMPT"}]}],
                 ],
             ),
             (
                 2,
                 [
-                    [
-                        {
-                            "role": MessageRole.SYSTEM,
-                            "content": [{"type": "text", "text": "UPDATE_FACTS_SYSTEM_PROMPT"}],
-                        },
-                        {"role": MessageRole.USER, "content": [{"type": "text", "text": "UPDATE_FACTS_USER_PROMPT"}]},
-                    ],
                     [
                         {
                             "role": MessageRole.SYSTEM,
@@ -678,20 +670,15 @@ def test_planning_step(self, step, expected_messages_list):
         task = "Test task"
         agent.planning_step(task, is_first_step=(step == 1), step=step)
         expected_message_texts = {
-            "INITIAL_FACTS_USER_PROMPT": populate_template(
-                agent.prompt_templates["planning"]["initial_facts"], variables=dict(task=task)
-            ),
             "INITIAL_PLAN_USER_PROMPT": populate_template(
                 agent.prompt_templates["planning"]["initial_plan"],
                 variables=dict(
                     task=task,
                     tools=agent.tools,
                     managed_agents=agent.managed_agents,
-                    answer_facts=agent.memory.steps[0].model_output_message_facts.content,
+                    answer_facts=agent.memory.steps[0].model_output_message.content,
                 ),
             ),
-            "UPDATE_FACTS_SYSTEM_PROMPT": agent.prompt_templates["planning"]["update_facts_pre_messages"],
-            "UPDATE_FACTS_USER_PROMPT": agent.prompt_templates["planning"]["update_facts_post_messages"],
             "UPDATE_PLAN_SYSTEM_PROMPT": populate_template(
                 agent.prompt_templates["planning"]["update_plan_pre_messages"], variables=dict(task=task)
             ),
@@ -701,7 +688,7 @@ def test_planning_step(self, step, expected_messages_list):
                     task=task,
                     tools=agent.tools,
                     managed_agents=agent.managed_agents,
-                    facts_update=agent.memory.steps[0].model_output_message_facts.content,
+                    facts_update=agent.memory.steps[0].model_output_message.content,
                     remaining_steps=agent.max_steps - step,
                 ),
             ),
@@ -728,7 +715,7 @@ def test_planning_step(self, step, expected_messages_list):
             for content, expected_content in zip(message["content"], expected_message["content"]):
                 assert content == expected_content
         # Test calls to model
-        assert len(fake_model.call_args_list) == 2
+        assert len(fake_model.call_args_list) == 1
         for call_args, expected_messages in zip(fake_model.call_args_list, expected_messages_list):
             assert len(call_args.args) == 1
             messages = call_args.args[0]
@@ -1087,7 +1074,7 @@ def __call__(
             ):
                 return ChatMessage(
                     role="assistant",
-                    content="",
+                    content="Here is the secret content: FLAG1",
                     tool_calls=[
                         ChatMessageToolCall(
                             id="call_0",
@@ -1108,6 +1095,7 @@ def __call__(
             max_steps=10,
             name="search_agent",
             description="Runs web searches for you. Give it your request as an argument. Make the request as detailed as needed, you can ask for thorough reports",
+            verbosity_level=2,
         )
 
         manager_code_agent = CodeAgent(
@@ -1126,8 +1114,10 @@ def __call__(
             managed_agents=[web_agent],
         )
 
-        report = manager_toolcalling_agent.run("Fake question.")
+        with web_agent.logger.console.capture() as capture:
+            report = manager_toolcalling_agent.run("Fake question.")
         assert report == "Final report."
+        assert "FLAG1" in capture.get()  # Check that managed agent's output is properly logged
 
         # Test that visualization works
         with manager_toolcalling_agent.logger.console.capture() as capture:
diff --git a/tests/test_memory.py b/tests/test_memory.py
index c43517722..b7a746e9b 100644
--- a/tests/test_memory.py
+++ b/tests/test_memory.py
@@ -114,13 +114,11 @@ def test_action_step_to_messages_no_tool_calls_with_observations():
 def test_planning_step_to_messages():
     planning_step = PlanningStep(
         model_input_messages=[Message(role=MessageRole.USER, content="Hello")],
-        model_output_message_facts=ChatMessage(role=MessageRole.ASSISTANT, content="Facts"),
-        facts="These are facts.",
-        model_output_message_plan=ChatMessage(role=MessageRole.ASSISTANT, content="Plan"),
+        model_output_message=ChatMessage(role=MessageRole.ASSISTANT, content="Plan"),
         plan="This is a plan.",
     )
     messages = planning_step.to_messages(summary_mode=False)
-    assert len(messages) == 2
+    assert len(messages) == 1
     for message in messages:
         assert isinstance(message, dict)
         assert "role" in message


Reviews:
user: login: albertvillanova
body: Thanks.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T14:09:01Z
user: login: aymeric-roucher
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T18:33:29Z
user: login: albertvillanova
body: @aymeric-roucher maybe we should check if this change has an impact on performance (GAIA?).
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T18:36:36Z
user: login: aymeric-roucher
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T18:50:52Z

Review comments:
diff_hunk: @@ -389,123 +389,77 @@ def _handle_max_steps_reached(self, task: str, images: List[str], step_start_tim
         return final_answer
 
     def planning_step(self, task, is_first_step: bool, step: int) -> None:
-        input_messages, facts_message, plan_message = (
-            self._generate_initial_plan(task) if is_first_step else self._generate_updated_plan(task, step)
-        )
-        self._record_planning_step(input_messages, facts_message, plan_message, is_first_step)
-
-    def _generate_initial_plan(self, task: str) -> Tuple[ChatMessage, ChatMessage]:
-        input_messages = [
-            {
+        if is_first_step:
+            input_messages = [
+                {
+                    "role": MessageRole.USER,
+                    "content": [
+                        {
+                            "type": "text",
+                            "text": populate_template(
+                                self.prompt_templates["planning"]["initial_plan"],
+                                variables={"task": task, "tools": self.tools, "managed_agents": self.managed_agents},
+                            ),
+                        }
+                    ],
+                },
+            ]
+            facts_plan_message = self.model(input_messages)
path: src/smolagents/agents.py
user: login: albertvillanova
body: Before you called the model passing `stop_sequences`:
```python
plan_message = self.model([message_prompt_plan], stop_sequences=["<end_plan>"])
```
Is this change intended?
created_at: 2025-03-17T14:05:40Z
author_association: MEMBER
diff_hunk: @@ -144,25 +144,13 @@ def to_messages(self, summary_mode: bool = False, show_model_input_messages: boo
 @dataclass
 class PlanningStep(MemoryStep):
     model_input_messages: List[Message]
-    model_output_message_facts: ChatMessage
-    facts: str
-    model_output_message_plan: ChatMessage
+    model_output_message: ChatMessage
     plan: str
 
     def to_messages(self, summary_mode: bool, **kwargs) -> List[Message]:
         messages = []
-        messages.append(
-            Message(
-                role=MessageRole.ASSISTANT, content=[{"type": "text", "text": f"[FACTS LIST]:\n{self.facts.strip()}"}]
-            )
-        )
-
         if not summary_mode:  # This step is not shown to a model writing a plan to avoid influencing the new plan
-            messages.append(
-                Message(
-                    role=MessageRole.ASSISTANT, content=[{"type": "text", "text": f"[PLAN]:\n{self.plan.strip()}"}]
-                )
-            )
+            messages.append(Message(role=MessageRole.ASSISTANT, content=[{"type": "text", "text": self.plan.strip()}]))
path: src/smolagents/memory.py
user: login: albertvillanova
body: Now we only return non-empty messages `if not summary_mode`, whereas before we also returned non-empty messages `if summary_mode`.
Is this intended?
created_at: 2025-03-17T14:08:51Z
author_association: MEMBER
diff_hunk: @@ -389,123 +389,77 @@ def _handle_max_steps_reached(self, task: str, images: List[str], step_start_tim
         return final_answer
 
     def planning_step(self, task, is_first_step: bool, step: int) -> None:
-        input_messages, facts_message, plan_message = (
-            self._generate_initial_plan(task) if is_first_step else self._generate_updated_plan(task, step)
-        )
-        self._record_planning_step(input_messages, facts_message, plan_message, is_first_step)
-
-    def _generate_initial_plan(self, task: str) -> Tuple[ChatMessage, ChatMessage]:
-        input_messages = [
-            {
+        if is_first_step:
+            input_messages = [
+                {
+                    "role": MessageRole.USER,
+                    "content": [
+                        {
+                            "type": "text",
+                            "text": populate_template(
+                                self.prompt_templates["planning"]["initial_plan"],
+                                variables={"task": task, "tools": self.tools, "managed_agents": self.managed_agents},
+                            ),
+                        }
+                    ],
+                },
+            ]
+            facts_plan_message = self.model(input_messages)
path: src/smolagents/agents.py
user: login: aymeric-roucher
body: Good call! Unintended change indeed, it's better to be sure to end generation by adding the stop sequence.
created_at: 2025-03-19T18:33:29Z
author_association: COLLABORATOR
diff_hunk: @@ -144,25 +144,13 @@ def to_messages(self, summary_mode: bool = False, show_model_input_messages: boo
 @dataclass
 class PlanningStep(MemoryStep):
     model_input_messages: List[Message]
-    model_output_message_facts: ChatMessage
-    facts: str
-    model_output_message_plan: ChatMessage
+    model_output_message: ChatMessage
     plan: str
 
     def to_messages(self, summary_mode: bool, **kwargs) -> List[Message]:
         messages = []
-        messages.append(
-            Message(
-                role=MessageRole.ASSISTANT, content=[{"type": "text", "text": f"[FACTS LIST]:\n{self.facts.strip()}"}]
-            )
-        )
-
         if not summary_mode:  # This step is not shown to a model writing a plan to avoid influencing the new plan
-            messages.append(
-                Message(
-                    role=MessageRole.ASSISTANT, content=[{"type": "text", "text": f"[PLAN]:\n{self.plan.strip()}"}]
-                )
-            )
+            messages.append(Message(role=MessageRole.ASSISTANT, content=[{"type": "text", "text": self.plan.strip()}]))
path: src/smolagents/memory.py
user: login: aymeric-roucher
body: Yes it is! We prefer not to log the plan, so as to avoid influencing the new plan. Adding comments to clarify this!
created_at: 2025-03-19T18:50:52Z
author_association: COLLABORATOR

PR comments:
user: login: IlyaGusev
created_at: 2025-03-15T12:51:51Z
body: Hey, I guess it also fixes the same problem as in my PR https://github.com/huggingface/smolagents/pull/918, but in another way.

I guess both fixes are fine and can co-exist. The <end_code> fix solves the root cause, changing the history representation, and this PR fixes symptoms.

author_association: CONTRIBUTOR
user: login: aymeric-roucher
created_at: 2025-03-15T16:37:57Z
body: @IlyaGusev I wasn't finished adding the logic for this PR. But your PR #918 is useful as well, let's merge both 😃 
author_association: COLLABORATOR
user: login: aymeric-roucher
created_at: 2025-03-19T21:05:50Z
body: @albertvillanova For reference, on one run of 165 GAIA questions, the scores with the new version is 1% higher than the old! Only 1% difference is not significant IMO but it's a sign that performance is not degraded!
author_association: COLLABORATOR