Repository: huggingface/smolagents

Issue:
"Error in generating tool call with model: 'NoneType' object is not subscriptable" when using FinalAnswerTool
I am trying to reproduce the agentic RAG cookbook example (https://github.com/huggingface/cookbook/blob/main/notebooks/en/agent_rag.ipynb). But I noticed that the FinalAnswerTool was almost always throwing me an error. The error is : 

```python
Error is : Error in generating tool call with model:
'NoneType' object is not subscriptable
```

So I decided to remove every RAG-related component in my pipeline and I just kept the FinalAnswerTool in my agent to make sure the RAG logic as nothing to do with this, see the code below :

```python
from  smolagents import Tool

from smolagents import ToolCallingAgent
 
from smolagents import Model, get_clean_message_list, ChatMessage, tool_role_conversions
from smolagents.models import parse_tool_args_if_needed
from typing import Optional, Dict, List
from openai import OpenAI
 
import httpx
 
from typing import Union
 
from httpx_auth import OAuth2ClientCredentials
 
 
# CUSTOMIZE SERVER OBJECT
class CustomOpenAIServerModel(Model):
    """This model connects to an OpenAI-compatible API server.
 
    Parameters:
        model_id (`str`):
            The model identifier to use on the server (e.g. "gpt-3.5-turbo").
        api_base (`str`, *optional*):
            The base URL of the OpenAI-compatible API server.
        api_key (`str`, *optional*):
            The API key to use for authentication.
        custom_role_conversions (`dict[str, str]`, *optional*):
            Custom role conversion mapping to convert message roles in others.
            Useful for specific models that do not support specific message roles like "system".
        **kwargs:
            Additional keyword arguments to pass to the OpenAI API.
    """
 
    def __init__(
        self,
        model_id: str,
        client: OpenAI,
        custom_role_conversions: Optional[Dict[str, str]] = None,
        **kwargs,
    ):
        try:
            import openai
        except ModuleNotFoundError:
            raise ModuleNotFoundError(
                "Please install 'openai' extra to use OpenAIServerModel: `pip install 'smolagents[openai]'`"
            ) from None
 
        super().__init__(**kwargs)
        self.model_id = model_id
        self.client = client
        self.custom_role_conversions = custom_role_conversions
 
    def __call__(
        self,
        messages: List[Dict[str, str]],
        stop_sequences: Optional[List[str]] = None,
        grammar: Optional[str] = None,
        tools_to_call_from: Optional[List[Tool]] = None,
        **kwargs,
    ) -> ChatMessage:
        completion_kwargs = self._prepare_completion_kwargs(
            messages=messages,
            stop_sequences=stop_sequences,
            grammar=grammar,
            tools_to_call_from=tools_to_call_from,
            model=self.model_id,
            custom_role_conversions=self.custom_role_conversions,
            convert_images_to_image_urls=True,
            **kwargs,
        )
 
        response = self.client.chat.completions.create(**completion_kwargs)
        self.last_input_token_count = response.usage.prompt_tokens
        self.last_output_token_count = response.usage.completion_tokens
 
        message = ChatMessage.from_dict(
            response.choices[0].message.model_dump(include={"role", "content", "tool_calls"})
        )
        if tools_to_call_from is not None:
            return parse_tool_args_if_needed(message)
        return message
 
client = OpenAI(base_url=my_base_url, api_key="fake_key", organization="my_orga_id")

model = CustomOpenAIServerModel(model_id="mistral-large", client = client)
 
agent = ToolCallingAgent(
    tools=[], model=model
)
 
agent_output = agent.run("Qui a construit la tour Eiffel ?")
```


I use smolagents 1.5.1 and I tried running a mistral-large available in an OpenAI compatible API I have access to, I also tried with gpt4o in Azure OpenAI.
Note : I create a CustomOpenAIServerModel but if you check it it's just because my OpenAI-compatible API needs the `organization` parameter, I don't change anything else.

As previously said, the FinalAnswerTool tool almost fail everytime, sometimes it works at step 2 or 3, sometimes I reach the maximum number of errors. 

What can I do ? My initial objective is to add a retriever tool to my agent but as the FinalAnswerTool Tool fails almost all the time I can’t trust this approach. 


Discussion:
aymeric-roucher: @touseefahmed96 normally you shouldn't need a PR fort these parameters to work, they should work as kwargs. Tell me if it's not the case!

Patch:
diff --git a/docs/source/zh/examples/rag.md b/docs/source/zh/examples/rag.md
index acbdf14..23efa9e 100644
--- a/docs/source/zh/examples/rag.md
+++ b/docs/source/zh/examples/rag.md
@@ -17,39 +17,34 @@ rendered properly in your Markdown viewer.
 
 [[open-in-colab]]
 
-Retrieval-Augmented-Generation (RAG) is “using an LLM to answer a user query, but basing the answer on information retrieved from a knowledge base”. It has many advantages over using a vanilla or fine-tuned LLM: to name a few, it allows to ground the answer on true facts and reduce confabulations, it allows to provide the LLM with domain-specific knowledge, and it allows fine-grained control of access to information from the knowledge base.
+Retrieval-Augmented-Generation (RAG) 是“使用大语言模型（LLM）来回答用户查询，但基于从知识库中检索的信息”。它比使用普通或微调的 LLM 具有许多优势：举几个例子，它允许将答案基于真实事实并减少虚构；它允许提供 LLM 领域特定的知识；并允许对知识库中的信息访问进行精细控制。
 
-But vanilla RAG has limitations, most importantly these two:
-- It performs only one retrieval step: if the results are bad, the generation in turn will be bad.
-- Semantic similarity is computed with the user query as a reference, which might be suboptimal: for instance, the user query will often be a question and the document containing the true answer will be in affirmative voice, so its similarity score will be downgraded compared to other source documents in the interrogative form, leading to a risk of missing the relevant information.
+但是，普通的 RAG 存在一些局限性，以下两点尤为突出：
 
-We can alleviate these problems by making a RAG agent: very simply, an agent armed with a retriever tool!
+- 它只执行一次检索步骤：如果结果不好，生成的内容也会不好。
+- 语义相似性是以用户查询为参考计算的，这可能不是最优的：例如，用户查询通常是一个问题，而包含真实答案的文档通常是肯定语态，因此其相似性得分会比其他以疑问形式呈现的源文档低，从而导致错失相关信息的风险。
 
-This agent will: ✅ Formulate the query itself and ✅ Critique to re-retrieve if needed.
+我们可以通过制作一个 RAG  agent来缓解这些问题：非常简单，一个配备了检索工具的agent！这个 agent 将
+会：✅ 自己构建查询和检索，✅ 如果需要的话会重新检索。
 
-So it should naively recover some advanced RAG techniques!
-- Instead of directly using the user query as the reference in semantic search, the agent formulates itself a reference sentence that can be closer to the targeted documents, as in [HyDE](https://huggingface.co/papers/2212.10496).
-The agent can use the generated snippets and re-retrieve if needed, as in [Self-Query](https://docs.llamaindex.ai/en/stable/examples/evaluation/RetryQuery/).
+因此，它将比普通 RAG 更智能，因为它可以自己构建查询，而不是直接使用用户查询作为参考。这样，它可以更
+接近目标文档，从而提高检索的准确性， [HyDE](https://huggingface.co/papers/2212.10496)。此 agent 可以
+使用生成的片段，并在需要时重新检索，就像 [Self-Query](https://docs.llamaindex.ai/en/stable/examples/evaluation/RetryQuery/)。
 
-Let's build this system. 🛠️
+我们现在开始构建这个系统. 🛠️
 
-Run the line below to install required dependencies:
+运行以下代码以安装所需的依赖包：
 ```bash
 !pip install smolagents pandas langchain langchain-community sentence-transformers rank_bm25 --upgrade -q
 ```
-To call the HF Inference API, you will need a valid token as your environment variable `HF_TOKEN`.
-We use python-dotenv to load it.
+
+你需要一个有效的 token 作为环境变量 `HF_TOKEN` 来调用 HF Inference API。我们使用 python-dotenv 来加载它。
 ```py
 from dotenv import load_dotenv
 load_dotenv()
 ```
 
-We first load a knowledge base on which we want to perform RAG: this dataset is a compilation of the documentation pages for many Hugging Face libraries, stored as markdown. We will keep only the documentation for the `transformers` library.
-
-Then prepare the knowledge base by processing the dataset and storing it into a vector database to be used by the retriever.
-
-We use [LangChain](https://python.langchain.com/docs/introduction/) for its excellent vector database utilities.
-
+我们首先加载一个知识库以在其上执行 RAG：此数据集是许多 Hugging Face 库的文档页面的汇编，存储为 markdown 格式。我们将仅保留 `transformers` 库的文档。然后通过处理数据集并将其存储到向量数据库中，为检索器准备知识库。我们将使用 [LangChain](https://python.langchain.com/docs/introduction/) 来利用其出色的向量数据库工具。
 ```py
 import datasets
 from langchain.docstore.document import Document
@@ -74,13 +69,10 @@ text_splitter = RecursiveCharacterTextSplitter(
 docs_processed = text_splitter.split_documents(source_docs)
 ```
 
-Now the documents are ready.
-
-So let’s build our agentic RAG system!
+现在文档已准备好。我们来一起构建我们的 agent RAG 系统！
+👉 我们只需要一个 RetrieverTool，我们的 agent 可以利用它从知识库中检索信息。
 
-👉 We only need a RetrieverTool that our agent can leverage to retrieve information from the knowledge base.
-
-Since we need to add a vectordb as an attribute of the tool, we cannot simply use the simple tool constructor with a `@tool` decorator: so we will follow the advanced setup highlighted in the [tools tutorial](../tutorials/tools).
+由于我们需要将 vectordb 添加为工具的属性，我们不能简单地使用带有 `@tool` 装饰器的简单工具构造函数：因此我们将遵循 [tools 教程](../tutorials/tools) 中突出显示的高级设置。
 
 ```py
 from smolagents import Tool
@@ -117,21 +109,21 @@ class RetrieverTool(Tool):
 
 retriever_tool = RetrieverTool(docs_processed)
 ```
-We have used BM25, a classic retrieval method, because it's lightning fast to setup.
-To improve retrieval accuracy, you could use replace BM25 with semantic search using vector representations for documents: thus you can head to the [MTEB Leaderboard](https://huggingface.co/spaces/mteb/leaderboard) to select a good embedding model.
+BM25 检索方法是一个经典的检索方法，因为它的设置速度非常快。为了提高检索准确性，你可以使用语义搜索，使用文档的向量表示替换 BM25：因此你可以前往 [MTEB Leaderboard](https://huggingface.co/spaces/mteb/leaderboard) 选择一个好的嵌入模型。
 
-Now it’s straightforward to create an agent that leverages this `retriever_tool`!
+现在我们已经创建了一个可以从知识库中检索信息的工具，现在我们可以很容易地创建一个利用这个
+`retriever_tool` 的 agent！此 agent 将使用如下参数初始化：
+- `tools`：代理将能够调用的工具列表。
+- `model`：为代理提供动力的 LLM。
 
-The agent will need these arguments upon initialization:
-- `tools`: a list of tools that the agent will be able to call.
-- `model`: the LLM that powers the agent.
-Our `model` must be a callable that takes as input a list of messages and returns text. It also needs to accept a stop_sequences argument that indicates when to stop its generation. For convenience, we directly use the HfEngine class provided in the package to get a LLM engine that calls Hugging Face's Inference API.
+我们的 `model` 必须是一个可调用对象，它接受一个消息的 list 作为输入，并返回文本。它还需要接受一个 stop_sequences 参数，指示何时停止生成。为了方便起见，我们直接使用包中提供的 `HfEngine` 类来获取调用 Hugging Face 的 Inference API 的 LLM 引擎。
 
-And we use [meta-llama/Llama-3.3-70B-Instruct](meta-llama/Llama-3.3-70B-Instruct) as the llm engine because:
-- It has a long 128k context, which is helpful for processing long source documents
-- It is served for free at all times on HF's Inference API!
+接着，我们将使用 [meta-llama/Llama-3.3-70B-Instruct](meta-llama/Llama-3.3-70B-Instruct) 作为 llm 引
+擎，因为：
+- 它有一个长 128k 上下文，这对处理长源文档很有用。
+- 它在 HF 的 Inference API 上始终免费提供！
 
-_Note:_ The Inference API hosts models based on various criteria, and deployed models may be updated or replaced without prior notice. Learn more about it [here](https://huggingface.co/docs/api-inference/supported-models).
+_Note:_ 此 Inference API 托管基于各种标准的模型，部署的模型可能会在没有事先通知的情况下进行更新或替换。了解更多信息，请点击[这里](https://huggingface.co/docs/api-inference/supported-models)。
 
 ```py
 from smolagents import HfApiModel, CodeAgent
@@ -141,9 +133,7 @@ agent = CodeAgent(
 )
 ```
 
-Upon initializing the CodeAgent, it has been automatically given a default system prompt that tells the LLM engine to process step-by-step and generate tool calls as code snippets, but you could replace this prompt template with your own as needed.
-
-Then when its `.run()` method is launched, the agent takes care of calling the LLM engine, and executing the tool calls, all in a loop that ends only when tool `final_answer` is called with the final answer as its argument.
+当我们初始化 CodeAgent 时，它已经自动获得了一个默认的系统提示，告诉 LLM 引擎按步骤处理并生成工具调用作为代码片段，但你可以根据需要替换此提示模板。接着，当其 `.run()` 方法被调用时，代理将负责调用 LLM 引擎，并在循环中执行工具调用，直到工具 `final_answer` 被调用，而其参数为最终答案。
 
 ```py
 agent_output = agent.run("For a transformers model training, which is slower, the forward or the backward pass?")
@@ -151,6 +141,3 @@ agent_output = agent.run("For a transformers model training, which is slower, th
 print("Final output:")
 print(agent_output)
 ```
-
-
-
diff --git a/docs/source/zh/examples/text_to_sql.md b/docs/source/zh/examples/text_to_sql.md
index 12d0c5e..419c451 100644
--- a/docs/source/zh/examples/text_to_sql.md
+++ b/docs/source/zh/examples/text_to_sql.md
@@ -17,17 +17,17 @@ rendered properly in your Markdown viewer.
 
 [[open-in-colab]]
 
-In this tutorial, we’ll see how to implement an agent that leverages SQL using `smolagents`.
+在此教程中，我们将看到如何使用 `smolagents` 实现一个利用 SQL 的 agent。
 
-> Let's start with the golden question: why not keep it simple and use a standard text-to-SQL pipeline?
+> 让我们从经典问题开始：为什么不简单地使用标准的 text-to-SQL pipeline 呢？
 
-A standard text-to-sql pipeline is brittle, since the generated SQL query can be incorrect. Even worse, the query could be incorrect, but not raise an error, instead giving some incorrect/useless outputs without raising an alarm.
+标准的 text-to-SQL pipeline 很脆弱，因为生成的 SQL 查询可能会出错。更糟糕的是，查询可能出错却不引发错误警报，从而返回一些不正确或无用的结果。
 
-👉 Instead, an agent system is able to critically inspect outputs and decide if the query needs to be changed or not, thus giving it a huge performance boost.
+👉 相反，agent 系统则可以检视输出结果并决定查询是否需要被更改，因此带来巨大的性能提升。
 
-Let’s build this agent! 💪
+让我们来一起构建这个 agent! 💪
 
-First, we setup the SQL environment:
+首先，我们构建一个 SQL 的环境：
 ```py
 from sqlalchemy import (
     create_engine,
@@ -69,11 +69,9 @@ for row in rows:
         cursor = connection.execute(stmt)
 ```
 
-### Build our agent
+### 构建 agent
 
-Now let’s make our SQL table retrievable by a tool.
-
-The tool’s description attribute will be embedded in the LLM’s prompt by the agent system: it gives the LLM information about how to use the tool. This is where we want to describe the SQL table.
+现在，我们构建一个 agent，它将使用 SQL 查询来回答问题。工具的 description 属性将被 agent 系统嵌入到 LLM 的提示中：它为 LLM 提供有关如何使用该工具的信息。这正是我们描述 SQL 表的地方。
 
 ```py
 inspector = inspect(engine)
@@ -91,9 +89,10 @@ Columns:
   - tip: FLOAT
 ```
 
-Now let’s build our tool. It needs the following: (read [the tool doc](../tutorials/tools) for more detail)
-- A docstring with an `Args:` part listing arguments.
-- Type hints on both inputs and output.
+现在让我们构建我们的工具。它需要以下内容：（更多细节请参阅[工具文档](../tutorials/tools)）
+
+- 一个带有 `Args:` 部分列出参数的 docstring。
+- 输入和输出的type hints。
 
 ```py
 from smolagents import tool
@@ -120,11 +119,9 @@ def sql_engine(query: str) -> str:
     return output
 ```
 
-Now let us create an agent that leverages this tool.
+我们现在使用这个工具来创建一个 agent。我们使用 `CodeAgent`，这是 smolagent 的主要 agent 类：一个在代码中编写操作并根据 ReAct 框架迭代先前输出的 agent。
 
-We use the `CodeAgent`, which is smolagents’ main agent class: an agent that writes actions in code and can iterate on previous output according to the ReAct framework.
-
-The model is the LLM that powers the agent system. HfApiModel allows you to call LLMs using HF’s Inference API, either via Serverless or Dedicated endpoint, but you could also use any proprietary API.
+这个模型是驱动 agent 系统的 LLM。`HfApiModel` 允许你使用 HF  Inference API 调用 LLM，无论是通过 Serverless 还是 Dedicated endpoint，但你也可以使用任何专有 API。
 
 ```py
 from smolagents import CodeAgent, HfApiModel
@@ -136,11 +133,9 @@ agent = CodeAgent(
 agent.run("Can you give me the name of the client who got the most expensive receipt?")
 ```
 
-### Level 2: Table joins
-
-Now let’s make it more challenging! We want our agent to handle joins across multiple tables.
+### Level 2: 表连接
 
-So let’s make a second table recording the names of waiters for each receipt_id!
+现在让我们增加一些挑战！我们希望我们的 agent 能够处理跨多个表的连接。因此，我们创建一个新表，记录每个 receipt_id 的服务员名字！
 
 ```py
 table_name = "waiters"
@@ -163,7 +158,8 @@ for row in rows:
     with engine.begin() as connection:
         cursor = connection.execute(stmt)
 ```
-Since we changed the table, we update the `SQLExecutorTool` with this table’s description to let the LLM properly leverage information from this table.
+
+因为我们改变了表，我们需要更新 `SQLExecutorTool`，让 LLM 能够正确利用这个表的信息。
 
 ```py
 updated_description = """Allows you to perform SQL queries on the table. Beware that this tool's output is a string representation of the execution output.
@@ -180,7 +176,8 @@ for table in ["receipts", "waiters"]:
 
 print(updated_description)
 ```
-Since this request is a bit harder than the previous one, we’ll switch the LLM engine to use the more powerful [Qwen/Qwen2.5-Coder-32B-Instruct](https://huggingface.co/Qwen/Qwen2.5-Coder-32B-Instruct)!
+
+因为这个request 比之前的要难一些，我们将 LLM 引擎切换到更强大的 [Qwen/Qwen2.5-Coder-32B-Instruct](https://huggingface.co/Qwen/Qwen2.5-Coder-32B-Instruct)！
 
 ```py
 sql_engine.description = updated_description
@@ -192,11 +189,13 @@ agent = CodeAgent(
 
 agent.run("Which waiter got more total money from tips?")
 ```
-It directly works! The setup was surprisingly simple, wasn’t it?
 
-This example is done! We've touched upon these concepts:
-- Building new tools.
-- Updating a tool's description.
-- Switching to a stronger LLM helps agent reasoning.
+它直接就能工作！设置过程非常简单，难道不是吗？
+
+这个例子到此结束！我们涵盖了这些概念：
+
+- 构建新工具。
+- 更新工具的描述。
+- 切换到更强大的 LLM 有助于 agent 推理。
 
-✅ Now you can go build this text-to-SQL system you’ve always dreamt of! ✨
\ No newline at end of file
+✅ 现在你可以构建你一直梦寐以求的 text-to-SQL 系统了！✨
diff --git a/examples/vlm_web_browser.py b/examples/vlm_web_browser.py
index 01d50a5..19a4d62 100644
--- a/examples/vlm_web_browser.py
+++ b/examples/vlm_web_browser.py
@@ -18,7 +18,7 @@ load_dotenv()
 import os
 
 
-# Let's use Qwen-2VL-72B via an inference provider like Fireworks AI
+# Let's use Qwen2-VL-72B via an inference provider like Fireworks AI
 
 model = OpenAIServerModel(
     api_key=os.getenv("FIREWORKS_API_KEY"),
diff --git a/pyproject.toml b/pyproject.toml
index 22c1252..42c5fc3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,7 +41,7 @@ e2b = [
   "python-dotenv>=1.0.1",
 ]
 gradio = [
-  "gradio>=5.8.0",
+  "gradio>=5.13.0",
 ]
 litellm = [
   "litellm>=1.55.10",
diff --git a/src/smolagents/agents.py b/src/smolagents/agents.py
index 2ad9af5..c9decd9 100644
--- a/src/smolagents/agents.py
+++ b/src/smolagents/agents.py
@@ -265,14 +265,14 @@ class MultiStepAgent:
             elif isinstance(step_log, PlanningStep):
                 thought_message = {
                     "role": MessageRole.ASSISTANT,
-                    "content": "[FACTS LIST]:\n" + step_log.facts.strip(),
+                    "content": [{"type": "text", "text": "[FACTS LIST]:\n" + step_log.facts.strip()}],
                 }
                 memory.append(thought_message)
 
                 if not summary_mode:
                     thought_message = {
                         "role": MessageRole.ASSISTANT,
-                        "content": "[PLAN]:\n" + step_log.plan.strip(),
+                        "content": [{"type": "text", "text": "[PLAN]:\n" + step_log.plan.strip()}],
                     }
                     memory.append(thought_message)
 
@@ -700,28 +700,33 @@ Now begin!""",
             # Redact updated facts
             facts_update_system_prompt = {
                 "role": MessageRole.SYSTEM,
-                "content": SYSTEM_PROMPT_FACTS_UPDATE,
+                "content": [{"type": "text", "text": SYSTEM_PROMPT_FACTS_UPDATE}],
             }
             facts_update_message = {
                 "role": MessageRole.USER,
-                "content": USER_PROMPT_FACTS_UPDATE,
+                "content": [{"type": "text", "text": USER_PROMPT_FACTS_UPDATE}],
             }
             facts_update = self.model([facts_update_system_prompt] + agent_memory + [facts_update_message]).content
 
             # Redact updated plan
             plan_update_message = {
                 "role": MessageRole.SYSTEM,
-                "content": SYSTEM_PROMPT_PLAN_UPDATE.format(task=task),
+                "content": [{"type": "text", "text": SYSTEM_PROMPT_PLAN_UPDATE.format(task=task)}],
             }
             plan_update_message_user = {
                 "role": MessageRole.USER,
-                "content": USER_PROMPT_PLAN_UPDATE.format(
-                    task=task,
-                    tool_descriptions=get_tool_descriptions(self.tools, self.tool_description_template),
-                    managed_agents_descriptions=(show_agents_descriptions(self.managed_agents)),
-                    facts_update=facts_update,
-                    remaining_steps=(self.max_steps - step),
-                ),
+                "content": [
+                    {
+                        "type": "text",
+                        "text": USER_PROMPT_PLAN_UPDATE.format(
+                            task=task,
+                            tool_descriptions=get_tool_descriptions(self.tools, self.tool_description_template),
+                            managed_agents_descriptions=(show_agents_descriptions(self.managed_agents)),
+                            facts_update=facts_update,
+                            remaining_steps=(self.max_steps - step),
+                        ),
+                    }
+                ],
             }
             plan_update = self.model(
                 [plan_update_message] + agent_memory + [plan_update_message_user],
diff --git a/src/smolagents/gradio_ui.py b/src/smolagents/gradio_ui.py
index dc27c31..d7d1211 100644
--- a/src/smolagents/gradio_ui.py
+++ b/src/smolagents/gradio_ui.py
@@ -24,31 +24,102 @@ from .types import AgentAudio, AgentImage, AgentText, handle_agent_output_types
 from .utils import _is_package_available
 
 
-def pull_messages_from_step(step_log: AgentStepLog):
-    """Extract ChatMessage objects from agent steps"""
+def pull_messages_from_step(
+    step_log: AgentStepLog,
+):
+    """Extract ChatMessage objects from agent steps with proper nesting"""
     import gradio as gr
 
     if isinstance(step_log, ActionStep):
-        yield gr.ChatMessage(role="assistant", content=step_log.llm_output or "")
-        if step_log.tool_calls is not None:
+        # Output the step number
+        step_number = f"Step {step_log.step_number}" if step_log.step_number is not None else ""
+        yield gr.ChatMessage(role="assistant", content=f"**{step_number}**")
+
+        # First yield the thought/reasoning from the LLM
+        if hasattr(step_log, "llm_output") and step_log.llm_output is not None:
+            # Clean up the LLM output
+            llm_output = step_log.llm_output.strip()
+            # Remove any trailing <end_code> and extra backticks, handling multiple possible formats
+            llm_output = re.sub(r"```\s*<end_code>", "```", llm_output)  # handles ```<end_code>
+            llm_output = re.sub(r"<end_code>\s*```", "```", llm_output)  # handles <end_code>```
+            llm_output = re.sub(r"```\s*\n\s*<end_code>", "```", llm_output)  # handles ```\n<end_code>
+            llm_output = llm_output.strip()
+            yield gr.ChatMessage(role="assistant", content=llm_output)
+
+        # For tool calls, create a parent message
+        if hasattr(step_log, "tool_calls") and step_log.tool_calls is not None:
             first_tool_call = step_log.tool_calls[0]
-            used_code = first_tool_call.name == "code interpreter"
-            content = first_tool_call.arguments
+            used_code = first_tool_call.name == "python_interpreter"
+            parent_id = f"call_{len(step_log.tool_calls)}"
+
+            # Tool call becomes the parent message with timing info
+            # First we will handle arguments based on type
+            args = first_tool_call.arguments
+            if isinstance(args, dict):
+                content = str(args.get("answer", str(args)))
+            else:
+                content = str(args).strip()
+
             if used_code:
-                content = f"```py\n{content}\n```"
-            yield gr.ChatMessage(
+                # Clean up the content by removing any end code tags
+                content = re.sub(r"```.*?\n", "", content)  # Remove existing code blocks
+                content = re.sub(r"\s*<end_code>\s*", "", content)  # Remove end_code tags
+                content = content.strip()
+                if not content.startswith("```python"):
+                    content = f"```python\n{content}\n```"
+
+            parent_message_tool = gr.ChatMessage(
                 role="assistant",
-                metadata={"title": f"🛠️ Used tool {first_tool_call.name}"},
-                content=str(content),
+                content=content,
+                metadata={
+                    "title": f"🛠️ Used tool {first_tool_call.name}",
+                    "id": parent_id,
+                    "status": "pending",
+                },
             )
-        if step_log.observations is not None:
-            yield gr.ChatMessage(role="assistant", content=step_log.observations)
-        if step_log.error is not None:
-            yield gr.ChatMessage(
-                role="assistant",
-                content=str(step_log.error),
-                metadata={"title": "💥 Error"},
+            yield parent_message_tool
+
+            # Nesting execution logs under the tool call if they exist
+            if hasattr(step_log, "observations") and (
+                step_log.observations is not None and step_log.observations.strip()
+            ):  # Only yield execution logs if there's actual content
+                log_content = step_log.observations.strip()
+                if log_content:
+                    log_content = re.sub(r"^Execution logs:\s*", "", log_content)
+                    yield gr.ChatMessage(
+                        role="assistant",
+                        content=f"{log_content}",
+                        metadata={"title": "📝 Execution Logs", "parent_id": parent_id, "status": "done"},
+                    )
+
+            # Nesting any errors under the tool call
+            if hasattr(step_log, "error") and step_log.error is not None:
+                yield gr.ChatMessage(
+                    role="assistant",
+                    content=str(step_log.error),
+                    metadata={"title": "💥 Error", "parent_id": parent_id, "status": "done"},
+                )
+
+            # Update parent message metadata to done status without yielding a new message
+            parent_message_tool.metadata["status"] = "done"
+
+        # Handle standalone errors but not from tool calls
+        elif hasattr(step_log, "error") and step_log.error is not None:
+            yield gr.ChatMessage(role="assistant", content=str(step_log.error), metadata={"title": "💥 Error"})
+
+        # Calculate duration and token information
+        step_footnote = f"{step_number}"
+        if hasattr(step_log, "input_token_count") and hasattr(step_log, "output_token_count"):
+            token_str = (
+                f" | Input-tokens:{step_log.input_token_count:,} | Output-tokens:{step_log.output_token_count:,}"
             )
+            step_footnote += token_str
+        if hasattr(step_log, "duration"):
+            step_duration = f" | Duration: {round(float(step_log.duration), 2)}" if step_log.duration else None
+            step_footnote += step_duration
+        step_footnote = f"""<span style="color: #bbbbc2; font-size: 12px;">{step_footnote}</span> """
+        yield gr.ChatMessage(role="assistant", content=f"{step_footnote}")
+        yield gr.ChatMessage(role="assistant", content="-----")
 
 
 def stream_to_gradio(
@@ -60,12 +131,25 @@ def stream_to_gradio(
     """Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages."""
     if not _is_package_available("gradio"):
         raise ModuleNotFoundError(
-            "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[audio]'`"
+            "Please install 'gradio' extra to use the GradioUI: `pip install 'smolagents[gradio]'`"
         )
     import gradio as gr
 
+    total_input_tokens = 0
+    total_output_tokens = 0
+
     for step_log in agent.run(task, stream=True, reset=reset_agent_memory, additional_args=additional_args):
-        for message in pull_messages_from_step(step_log):
+        # Track tokens if model provides them
+        if hasattr(agent.model, "last_input_token_count"):
+            total_input_tokens += agent.model.last_input_token_count
+            total_output_tokens += agent.model.last_output_token_count
+            if isinstance(step_log, ActionStep):
+                step_log.input_token_count = agent.model.last_input_token_count
+                step_log.output_token_count = agent.model.last_output_token_count
+
+        for message in pull_messages_from_step(
+            step_log,
+        ):
             yield message
 
     final_answer = step_log  # Last log is the run's final_answer
@@ -87,7 +171,7 @@ def stream_to_gradio(
             content={"path": final_answer.to_string(), "mime_type": "audio/wav"},
         )
     else:
-        yield gr.ChatMessage(role="assistant", content=str(final_answer))
+        yield gr.ChatMessage(role="assistant", content=f"**Final answer:** {str(final_answer)}")
 
 
 class GradioUI:
@@ -176,7 +260,7 @@ class GradioUI:
     def launch(self, **kwargs):
         import gradio as gr
 
-        with gr.Blocks() as demo:
+        with gr.Blocks(fill_height=True) as demo:
             stored_messages = gr.State([])
             file_uploads_log = gr.State([])
             chatbot = gr.Chatbot(
@@ -187,6 +271,7 @@ class GradioUI:
                     "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
                 ),
                 resizeable=True,
+                scale=1,
             )
             # If an upload folder is provided, enable the upload feature
             if self.file_upload_folder is not None:
@@ -204,7 +289,7 @@ class GradioUI:
                 [stored_messages, text_input],
             ).then(self.interact_with_agent, [stored_messages, chatbot], [chatbot])
 
-        demo.launch(**kwargs)
+        demo.launch(debug=True, share=True, **kwargs)
 
 
 __all__ = ["stream_to_gradio", "GradioUI"]
diff --git a/src/smolagents/models.py b/src/smolagents/models.py
index 6e31c89..0630692 100644
--- a/src/smolagents/models.py
+++ b/src/smolagents/models.py
@@ -698,6 +698,10 @@ class OpenAIServerModel(Model):
             The base URL of the OpenAI-compatible API server.
         api_key (`str`, *optional*):
             The API key to use for authentication.
+        organization (`str`, *optional*):
+            The organization to use for the API request.
+        project (`str`, *optional*):
+            The project to use for the API request.
         custom_role_conversions (`dict[str, str]`, *optional*):
             Custom role conversion mapping to convert message roles in others.
             Useful for specific models that do not support specific message roles like "system".
@@ -710,6 +714,8 @@ class OpenAIServerModel(Model):
         model_id: str,
         api_base: Optional[str] = None,
         api_key: Optional[str] = None,
+        organization: Optional[str] | None = None,
+        project: Optional[str] | None = None,
         custom_role_conversions: Optional[Dict[str, str]] = None,
         **kwargs,
     ):
@@ -725,6 +731,8 @@ class OpenAIServerModel(Model):
         self.client = openai.OpenAI(
             base_url=api_base,
             api_key=api_key,
+            organization=organization,
+            project=project,
         )
         self.custom_role_conversions = custom_role_conversions
 
diff --git a/src/smolagents/prompts.py b/src/smolagents/prompts.py
index ce905c8..c8dfb37 100644
--- a/src/smolagents/prompts.py
+++ b/src/smolagents/prompts.py
@@ -433,7 +433,7 @@ Now begin! Write your plan below."""
 
 SYSTEM_PROMPT_FACTS_UPDATE = """
 You are a world expert at gathering known and unknown facts based on a conversation.
-Below you will find a task, and ahistory of attempts made to solve the task. You will have to produce a list of these:
+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:
 ### 1. Facts given in the task
 ### 2. Facts that we have learned
 ### 3. Facts still to look up
diff --git a/src/smolagents/types.py b/src/smolagents/types.py
index 7077daa..b0d4ee1 100644
--- a/src/smolagents/types.py
+++ b/src/smolagents/types.py
@@ -172,7 +172,7 @@ class AgentAudio(AgentType, str):
     """
 
     def __init__(self, value, samplerate=16_000):
-        if not _is_package_available("soundfile") or not is_torch_available:
+        if not _is_package_available("soundfile") or not is_torch_available():
             raise ModuleNotFoundError(
                 "Please install 'audio' extra to use AgentAudio: `pip install 'smolagents[audio]'`"
             )


Test patch:
diff --git a/tests/test_monitoring.py b/tests/test_monitoring.py
index 9fa30bb..abe1740 100644
--- a/tests/test_monitoring.py
+++ b/tests/test_monitoring.py
@@ -71,7 +71,7 @@ class MonitoringTester(unittest.TestCase):
         self.assertEqual(agent.monitor.total_input_token_count, 10)
         self.assertEqual(agent.monitor.total_output_token_count, 20)
 
-    def test_json_agent_metrics(self):
+    def test_toolcalling_agent_metrics(self):
         agent = ToolCallingAgent(
             tools=[],
             model=FakeLLMModel(),
@@ -134,7 +134,7 @@ class MonitoringTester(unittest.TestCase):
         # Use stream_to_gradio to capture the output
         outputs = list(stream_to_gradio(agent, task="Test task"))
 
-        self.assertEqual(len(outputs), 4)
+        self.assertEqual(len(outputs), 7)
         final_message = outputs[-1]
         self.assertEqual(final_message.role, "assistant")
         self.assertIn("This is the final answer.", final_message.content)
@@ -155,7 +155,7 @@ class MonitoringTester(unittest.TestCase):
             )
         )
 
-        self.assertEqual(len(outputs), 3)
+        self.assertEqual(len(outputs), 5)
         final_message = outputs[-1]
         self.assertEqual(final_message.role, "assistant")
         self.assertIsInstance(final_message.content, dict)
@@ -177,7 +177,7 @@ class MonitoringTester(unittest.TestCase):
         # Use stream_to_gradio to capture the output
         outputs = list(stream_to_gradio(agent, task="Test task"))
 
-        self.assertEqual(len(outputs), 5)
+        self.assertEqual(len(outputs), 9)
         final_message = outputs[-1]
         self.assertEqual(final_message.role, "assistant")
         self.assertIn("Simulated agent error", final_message.content)