diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 5ff7e93..a595bed 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -56,4 +56,7 @@ jobs:
uv run pytest -sv ./tests/test_tools.py
- name: Types tests
run: |
- uv run pytest -sv ./tests/test_types.py
\ No newline at end of file
+ uv run pytest -sv ./tests/test_types.py
+ - name: Utils tests
+ run: |
+ uv run pytest -sv ./tests/test_utils.py
\ No newline at end of file
diff --git a/examples/benchmark.ipynb b/examples/benchmark.ipynb
index dcb3532..09beeb6 100644
--- a/examples/benchmark.ipynb
+++ b/examples/benchmark.ipynb
@@ -1,14 +1,188 @@
{
"cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.2.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n",
+ "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
+ ]
+ }
+ ],
+ "source": [
+ "!pip install -e .. sympy numpy matplotlib seaborn -q # Install dev version of smolagents + some packages"
+ ]
+ },
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Using the latest cached version of the dataset since m-ric/smolagentsbenchmark couldn't be found on the Hugging Face Hub\n",
+ "Found the latest cached dataset configuration 'default' at /Users/aymeric/.cache/huggingface/datasets/m-ric___smolagentsbenchmark/default/0.0.0/0ad5fb2293ab185eece723a4ac0e4a7188f71add (last modified on Wed Jan 8 17:50:13 2025).\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " question | \n",
+ " source | \n",
+ " true_answer | \n",
+ " true_reasoning | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " If Eliud Kipchoge could maintain his record-ma... | \n",
+ " GAIA | \n",
+ " 17 | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " How many studio albums were published by Merce... | \n",
+ " GAIA | \n",
+ " 3 | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " Here's a fun riddle that I think you'll enjoy.... | \n",
+ " GAIA | \n",
+ " 3 | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " My family reunion is this week, and I was assi... | \n",
+ " GAIA | \n",
+ " 2 | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " In Emily Midkiff's June 2014 article in a jour... | \n",
+ " GAIA | \n",
+ " fluffy | \n",
+ " None | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " 127 | \n",
+ " What year was the municipality of San Carlos, ... | \n",
+ " SimpleQA | \n",
+ " 1786 | \n",
+ " ['https://en.wikipedia.org/wiki/San_Carlos,_An... | \n",
+ "
\n",
+ " \n",
+ " 128 | \n",
+ " In which year was Maria Elena Walsh named Illu... | \n",
+ " SimpleQA | \n",
+ " 1985 | \n",
+ " ['https://en.wikipedia.org/wiki/Mar%C3%ADa_Ele... | \n",
+ "
\n",
+ " \n",
+ " 129 | \n",
+ " What is the durability of the Istarelle spear ... | \n",
+ " SimpleQA | \n",
+ " 800 | \n",
+ " ['http://demonssouls.wikidot.com/spear', 'http... | \n",
+ "
\n",
+ " \n",
+ " 130 | \n",
+ " What is the number of the executive order that... | \n",
+ " SimpleQA | \n",
+ " 7034 | \n",
+ " ['https://www.loc.gov/collections/federal-thea... | \n",
+ "
\n",
+ " \n",
+ " 131 | \n",
+ " Within plus or minus one minute, when was Marq... | \n",
+ " SimpleQA | \n",
+ " 77 | \n",
+ " ['https://www.fifa.com/fifaplus/en/match-centr... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
132 rows × 4 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " question source true_answer \\\n",
+ "0 If Eliud Kipchoge could maintain his record-ma... GAIA 17 \n",
+ "1 How many studio albums were published by Merce... GAIA 3 \n",
+ "2 Here's a fun riddle that I think you'll enjoy.... GAIA 3 \n",
+ "3 My family reunion is this week, and I was assi... GAIA 2 \n",
+ "4 In Emily Midkiff's June 2014 article in a jour... GAIA fluffy \n",
+ ".. ... ... ... \n",
+ "127 What year was the municipality of San Carlos, ... SimpleQA 1786 \n",
+ "128 In which year was Maria Elena Walsh named Illu... SimpleQA 1985 \n",
+ "129 What is the durability of the Istarelle spear ... SimpleQA 800 \n",
+ "130 What is the number of the executive order that... SimpleQA 7034 \n",
+ "131 Within plus or minus one minute, when was Marq... SimpleQA 77 \n",
+ "\n",
+ " true_reasoning \n",
+ "0 None \n",
+ "1 None \n",
+ "2 None \n",
+ "3 None \n",
+ "4 None \n",
+ ".. ... \n",
+ "127 ['https://en.wikipedia.org/wiki/San_Carlos,_An... \n",
+ "128 ['https://en.wikipedia.org/wiki/Mar%C3%ADa_Ele... \n",
+ "129 ['http://demonssouls.wikidot.com/spear', 'http... \n",
+ "130 ['https://www.loc.gov/collections/federal-thea... \n",
+ "131 ['https://www.fifa.com/fifaplus/en/match-centr... \n",
+ "\n",
+ "[132 rows x 4 columns]"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"import datasets\n",
+ "import pandas as pd\n",
"\n",
- "eval_ds = datasets.load_dataset(\"m-ric/agents_medium_benchmark_2\")[\"train\"]"
+ "eval_ds = datasets.load_dataset(\"m-ric/smolagentsbenchmark\")[\"train\"]\n",
+ "pd.DataFrame(eval_ds)"
]
},
{
@@ -69,6 +243,8 @@
" question = example[\"question\"]\n",
" if example[\"source\"] == \"SimpleQA\":\n",
" question += \" Answer with only the final number.\"\n",
+ " if example[\"source\"] == \"MATH\":\n",
+ " question += \" Write code, not latex.\"\n",
" if question in answered_questions:\n",
" continue\n",
" start_time = time.time()\n",
@@ -223,26 +399,27 @@
" \"Qwen/Qwen2.5-72B-Instruct\",\n",
" \"Qwen/Qwen2.5-Coder-32B-Instruct\",\n",
" \"meta-llama/Llama-3.2-3B-Instruct\",\n",
+ " \"meta-llama/Llama-3.1-8B-Instruct\",\n",
" # \"HuggingFaceTB/SmolLM2-1.7B-Instruct\",\n",
" # \"meta-llama/Llama-3.1-70B-Instruct\",\n",
"]\n",
"\n",
"for model_id in open_model_ids:\n",
" print(f\"Evaluating '{model_id}'...\")\n",
- " action_type = \"tool_calling\"\n",
- " agent = ToolCallingAgent(\n",
- " tools=[GoogleSearchTool(), VisitWebpageTool(), PythonInterpreterTool()],\n",
- " model=HfApiModel(model_id),\n",
- " max_steps=10,\n",
- " )\n",
- " file_name = f\"output/{model_id.replace('/', '_')}-{action_type}-26-dec-2024.jsonl\"\n",
- " answer_questions(eval_ds, file_name, agent, model_id, action_type)\n",
+ " # action_type = \"tool_calling\"\n",
+ " # agent = ToolCallingAgent(\n",
+ " # tools=[GoogleSearchTool(), VisitWebpageTool(), PythonInterpreterTool()],\n",
+ " # model=HfApiModel(model_id),\n",
+ " # max_steps=10,\n",
+ " # )\n",
+ " # file_name = f\"output/{model_id.replace('/', '_')}-{action_type}-26-dec-2024.jsonl\"\n",
+ " # answer_questions(eval_ds, file_name, agent, model_id, action_type)\n",
"\n",
" action_type = \"code\"\n",
" agent = CodeAgent(\n",
" tools=[GoogleSearchTool(), VisitWebpageTool()],\n",
" model=HfApiModel(model_id),\n",
- " additional_authorized_imports=[\"numpy\"],\n",
+ " additional_authorized_imports=[\"numpy\", \"sympy\"],\n",
" max_steps=10,\n",
" )\n",
" file_name = f\"output/{model_id.replace('/', '_')}-{action_type}-26-dec-2024.jsonl\"\n",
@@ -270,7 +447,11 @@
" print(f\"Evaluating '{model_id}'...\")\n",
" action_type = \"tool_calling\"\n",
" agent = ToolCallingAgent(\n",
- " tools=[GoogleSearchTool(), VisitWebpageTool(), PythonInterpreterTool()],\n",
+ " tools=[\n",
+ " GoogleSearchTool(),\n",
+ " VisitWebpageTool(),\n",
+ " PythonInterpreterTool([\"numpy\", \"sympy\"]),\n",
+ " ],\n",
" model=LiteLLMModel(model_id),\n",
" max_steps=10,\n",
" )\n",
@@ -292,7 +473,38 @@
"cell_type": "code",
"execution_count": 3,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "output/Qwen_Qwen2.5-Coder-32B-Instruct-code-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/meta-llama_Llama-3.3-70B-Instruct-code-26-dec-2024.jsonl\n",
+ "Removed 124 lines.\n",
+ "output/Qwen_Qwen2.5-72B-Instruct-code-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/anthropic_claude-3-5-sonnet-latest-tool_calling-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/meta-llama_Llama-3.3-70B-Instruct-tool_calling-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/anthropic_claude-3-5-sonnet-latest-code-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/Qwen_Qwen2.5-72B-Instruct-tool_calling-26-dec-2024.jsonl\n",
+ "Removed 99 lines.\n",
+ "output/HuggingFaceTB_SmolLM2-1.7B-Instruct-code-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/gpt-4o-code-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/meta-llama_Llama-3.1-70B-Instruct-code-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/meta-llama_Llama-3.2-3B-Instruct-code-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n",
+ "output/gpt-4o-tool_calling-26-dec-2024.jsonl\n",
+ "Removed 109 lines.\n"
+ ]
+ }
+ ],
"source": [
"# import glob\n",
"# import json\n",
@@ -307,7 +519,7 @@
"# for line in f:\n",
"# try:\n",
"# data = json.loads(line.strip())\n",
- "# if data[\"source\"] == \"SimpleQA\" and \"Answer with only the final number.\" not in data[\"question\"]:\n",
+ "# if not any([question in data[\"question\"] for question in eval_ds[\"question\"]]):\n",
"# removed +=1\n",
"# else:\n",
"# filtered_lines.append(line)\n",
@@ -329,15 +541,17 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 66,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
- "/var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/ipykernel_37227/1724525657.py:154: UserWarning: Answer lists have different lengths, returning False.\n",
- " warnings.warn(\n"
+ "/var/folders/6m/9b1tts6d5w960j80wbw9tx3m0000gn/T/ipykernel_17219/1724525657.py:154: UserWarning:\n",
+ "\n",
+ "Answer lists have different lengths, returning False.\n",
+ "\n"
]
}
],
@@ -352,7 +566,7 @@
"\n",
"\n",
"def get_correct(row):\n",
- " if row[\"source\"] == \"GSM8K\":\n",
+ " if row[\"source\"] == \"MATH\":\n",
" numbers_answer = extract_numbers(str(row[\"answer\"]))\n",
" if len(numbers_answer) == 0:\n",
" return False\n",
@@ -385,7 +599,7 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 67,
"metadata": {},
"outputs": [],
"source": [
@@ -397,6 +611,16 @@
" [\"gpt-4o\", \"GSM8K\", 94.3],\n",
" [\"anthropic/claude-3-5-sonnet-latest\", \"GSM8K\", 96.4],\n",
" [\"meta-llama/Llama-3.3-70B-Instruct\", \"GSM8K\", 95.1],\n",
+ " [\n",
+ " \"meta-llama/Llama-3.3-70B-Instruct\",\n",
+ " \"MATH\",\n",
+ " 30.7,\n",
+ " ], # As per Open LLM Leaderboard for 3.1, score for 3.3 is too low. https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard#/?search=llama-3.1\n",
+ " [\n",
+ " \"Qwen/Qwen2.5-Coder-32B-Instruct\",\n",
+ " \"MATH\",\n",
+ " 30.6,\n",
+ " ], # As per Open LLM Leaderboard for the base model, score for instruct too low. https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard#/?search=llama-3.1\n",
"]\n",
"\n",
"df2 = pd.DataFrame(vanilla_data, columns=[\"model_id\", \"source\", \"correct\"])\n",
@@ -412,6 +636,15 @@
").reset_index()"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pivot_df = pivot_df.loc[~pivot_df[\"source\"].isin([\"GSM8K\"])]"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -421,7 +654,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 69,
"metadata": {},
"outputs": [
{
@@ -460,101 +693,101 @@
" NaN | \n",
" \n",
" \n",
- " 1 | \n",
+ " 2 | \n",
" Qwen/Qwen2.5-72B-Instruct | \n",
- " GSM8K | \n",
- " 82.9 | \n",
+ " MATH | \n",
+ " 77.5 | \n",
" NaN | \n",
"
\n",
" \n",
- " 2 | \n",
+ " 3 | \n",
" Qwen/Qwen2.5-72B-Instruct | \n",
" SimpleQA | \n",
" 42.5 | \n",
" 9.1 | \n",
"
\n",
" \n",
- " 3 | \n",
+ " 4 | \n",
" Qwen/Qwen2.5-Coder-32B-Instruct | \n",
" GAIA | \n",
" 28.1 | \n",
" NaN | \n",
"
\n",
" \n",
- " 4 | \n",
+ " 6 | \n",
" Qwen/Qwen2.5-Coder-32B-Instruct | \n",
- " GSM8K | \n",
- " 92.9 | \n",
- " NaN | \n",
+ " MATH | \n",
+ " 85.0 | \n",
+ " 30.6 | \n",
"
\n",
" \n",
- " 5 | \n",
+ " 7 | \n",
" Qwen/Qwen2.5-Coder-32B-Instruct | \n",
" SimpleQA | \n",
" 42.5 | \n",
" NaN | \n",
"
\n",
" \n",
- " 6 | \n",
+ " 8 | \n",
" anthropic/claude-3-5-sonnet-latest | \n",
" GAIA | \n",
" 43.8 | \n",
" NaN | \n",
"
\n",
" \n",
- " 7 | \n",
+ " 10 | \n",
" anthropic/claude-3-5-sonnet-latest | \n",
- " GSM8K | \n",
- " 91.4 | \n",
- " 96.4 | \n",
+ " MATH | \n",
+ " 85.0 | \n",
+ " NaN | \n",
"
\n",
" \n",
- " 8 | \n",
+ " 11 | \n",
" anthropic/claude-3-5-sonnet-latest | \n",
" SimpleQA | \n",
" 47.5 | \n",
" 28.4 | \n",
"
\n",
" \n",
- " 9 | \n",
+ " 12 | \n",
" gpt-4o | \n",
" GAIA | \n",
" 25.0 | \n",
" 9.3 | \n",
"
\n",
" \n",
- " 10 | \n",
+ " 14 | \n",
" gpt-4o | \n",
- " GSM8K | \n",
- " 91.4 | \n",
- " 94.3 | \n",
+ " MATH | \n",
+ " 77.5 | \n",
+ " NaN | \n",
"
\n",
" \n",
- " 11 | \n",
+ " 15 | \n",
" gpt-4o | \n",
" SimpleQA | \n",
" 60.0 | \n",
" 38.2 | \n",
"
\n",
" \n",
- " 12 | \n",
+ " 16 | \n",
" meta-llama/Llama-3.3-70B-Instruct | \n",
" GAIA | \n",
" 21.9 | \n",
" NaN | \n",
"
\n",
" \n",
- " 13 | \n",
+ " 18 | \n",
" meta-llama/Llama-3.3-70B-Instruct | \n",
- " GSM8K | \n",
- " 95.7 | \n",
- " 95.1 | \n",
+ " MATH | \n",
+ " 82.1 | \n",
+ " 30.7 | \n",
"
\n",
" \n",
- " 14 | \n",
+ " 19 | \n",
" meta-llama/Llama-3.3-70B-Instruct | \n",
" SimpleQA | \n",
- " 30.0 | \n",
+ " 30.9 | \n",
" NaN | \n",
"
\n",
" \n",
@@ -564,20 +797,20 @@
"text/plain": [
"type model_id source agent vanilla\n",
"0 Qwen/Qwen2.5-72B-Instruct GAIA 12.5 NaN\n",
- "1 Qwen/Qwen2.5-72B-Instruct GSM8K 82.9 NaN\n",
- "2 Qwen/Qwen2.5-72B-Instruct SimpleQA 42.5 9.1\n",
- "3 Qwen/Qwen2.5-Coder-32B-Instruct GAIA 28.1 NaN\n",
- "4 Qwen/Qwen2.5-Coder-32B-Instruct GSM8K 92.9 NaN\n",
- "5 Qwen/Qwen2.5-Coder-32B-Instruct SimpleQA 42.5 NaN\n",
- "6 anthropic/claude-3-5-sonnet-latest GAIA 43.8 NaN\n",
- "7 anthropic/claude-3-5-sonnet-latest GSM8K 91.4 96.4\n",
- "8 anthropic/claude-3-5-sonnet-latest SimpleQA 47.5 28.4\n",
- "9 gpt-4o GAIA 25.0 9.3\n",
- "10 gpt-4o GSM8K 91.4 94.3\n",
- "11 gpt-4o SimpleQA 60.0 38.2\n",
- "12 meta-llama/Llama-3.3-70B-Instruct GAIA 21.9 NaN\n",
- "13 meta-llama/Llama-3.3-70B-Instruct GSM8K 95.7 95.1\n",
- "14 meta-llama/Llama-3.3-70B-Instruct SimpleQA 30.0 NaN"
+ "2 Qwen/Qwen2.5-72B-Instruct MATH 77.5 NaN\n",
+ "3 Qwen/Qwen2.5-72B-Instruct SimpleQA 42.5 9.1\n",
+ "4 Qwen/Qwen2.5-Coder-32B-Instruct GAIA 28.1 NaN\n",
+ "6 Qwen/Qwen2.5-Coder-32B-Instruct MATH 85.0 30.6\n",
+ "7 Qwen/Qwen2.5-Coder-32B-Instruct SimpleQA 42.5 NaN\n",
+ "8 anthropic/claude-3-5-sonnet-latest GAIA 43.8 NaN\n",
+ "10 anthropic/claude-3-5-sonnet-latest MATH 85.0 NaN\n",
+ "11 anthropic/claude-3-5-sonnet-latest SimpleQA 47.5 28.4\n",
+ "12 gpt-4o GAIA 25.0 9.3\n",
+ "14 gpt-4o MATH 77.5 NaN\n",
+ "15 gpt-4o SimpleQA 60.0 38.2\n",
+ "16 meta-llama/Llama-3.3-70B-Instruct GAIA 21.9 NaN\n",
+ "18 meta-llama/Llama-3.3-70B-Instruct MATH 82.1 30.7\n",
+ "19 meta-llama/Llama-3.3-70B-Instruct SimpleQA 30.9 NaN"
]
},
"metadata": {},
@@ -590,37 +823,129 @@
},
{
"cell_type": "code",
- "execution_count": 29,
+ "execution_count": 84,
"metadata": {},
"outputs": [
{
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\\begin{array}{llcc}\n",
- "\\text{Model} & \\text{Task} & \\text{Agent} & \\text{Vanilla} \\\\\n",
- "\\hline\n",
- "\\textit{Qwen/Qwen2.5-72B-Instruct} & GAIA & 12.500 & - \\\\\n",
- "\\; & GSM8K & 82.900 & - \\\\\n",
- "\\; & SimpleQA & \\textbf{42.500} & 9.100 \\\\\n",
- "\\hline\n",
- "\\textit{Qwen/Qwen2.5-Coder-32B-Instruct} & GAIA & 28.100 & - \\\\\n",
- "\\; & GSM8K & 92.900 & - \\\\\n",
- "\\; & SimpleQA & 42.500 & - \\\\\n",
- "\\hline\n",
- "\\textit{anthropic/claude-3-5-sonnet-latest} & GAIA & 43.800 & - \\\\\n",
- "\\; & GSM8K & 91.400 & \\textbf{96.400} \\\\\n",
- "\\; & SimpleQA & \\textbf{47.500} & 28.400 \\\\\n",
- "\\hline\n",
- "gpt-4o & GAIA & \\textbf{25.000} & 9.300 \\\\\n",
- "\\; & GSM8K & 91.400 & \\textbf{94.300} \\\\\n",
- "\\; & SimpleQA & \\textbf{60.000} & 38.200 \\\\\n",
- "\\hline\n",
- "meta-llama/Llama-3.3-70B-Instruct & GAIA & 21.900 & - \\\\\n",
- "\\; & GSM8K & \\textbf{95.700} & 95.100 \\\\\n",
- "\\; & SimpleQA & 30.000 & - \\\\\n",
- "\\hline\n",
- "\\end{array}\n"
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABdYAAAJOCAYAAAC6HlVrAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAttlJREFUeJzs3Qd8G+X9x/Gfhi07tuMQx0mAhAAhCTObWfZeLZRRKBAK7b+MMloohQClUMqGAmWV1bILZRcoe6WETSAJIUASZkII2AlJHMeWbEn/1++xTz4rHif7dDpLn3deejmSTqe7043n+erR8wSSyWRSAAAAAAAAAACAI0FnkwEAAAAAAAAAAEWwDgAAAAAAAABABgjWAQAAAAAAAADIAME6AAAAAAAAAAAZIFgHAAAAAAAAACADBOsAAAAAAAAAAGSAYB0AAAAAAAAAgAwQrAMAAAAAAAAAkAGCdQAAAAAAAAAAMkCwDgAAXHP99dfLmDFjzO2mm27qctqLLrooNe2iRYtcXY4777zTzPfRRx/t0eunTJliXr9y5UrH62u/bbbZZrL11lub+fznP/+RbJk2bZr8+Mc/li222MK831tvvZW19yp0X375pVx++eWy//77y4QJE2T8+PHy05/+VG655RZZvXq1FJq3337b7OsXX3xxrhcFAAAAyIlwbt4WAADkuxdeeEF+85vfdPhcMpmU559/XvLFbrvtJptssknqfnNzsyxbtkyeeeYZOfPMM+Xzzz+X0047zdX3XLFihfz2t7+VeDwuBx10kJSVlcnIkSNdfQ+0uO++++TSSy812/pHP/qRuUWjURMuX3311fLEE0/IPffcIwMHDpRCse6668rJJ58s48aNy/WiAAAAADlBsA4AAFxXXV0tc+fONS3Rhw0btsbzH3zwgXz33XfSr1+/vGjtu/vuu5twO92vfvUr06r5tttuk5/97GcmjHSLhvUNDQ2mxfqf//xn1+aL9vQXBxdeeKGMGDFC/v73v7f78kKD9muuucZ8vieccII8+OCDUij0uD7llFNyvRgAAABAztAVDAAAyEoLbvXiiy92+Pxzzz0nFRUVMnnyZMln66+/vtkWGsBOnz7d1XnHYjHzd6211nJ1vmijXQH95S9/kaKiIvnHP/6xxi8CQqGQnHHGGaZrmFmzZsn//ve/nC0rAAAAAG8RrAMAANdts8020r9//067e9HHd911VxNYduT111+XY489ViZOnChjx441rb61O45EIrHGtBreH3bYYabP65122sm0Ku5oOlVTUyMXXHCB7LjjjrL55pubZbjyyitl1apVki1Dhgwxf5cvX97uce0m5vDDDzehrK7nL37xizX6SLf6sf7Xv/4lp59+utkW22+/venD/eijjzbT3H333WaaqVOnpl43e/Zs0w2P9ruu/a/vu+++cvPNN6fCeIuuv/YD/8gjj8h2221nluWyyy4zvzSw+snXz0q3v763Tn/HHXeY186YMUOOOOIIs931ce1vXrvAsdPucLRf8n322cd0GaK3/fbbzyyLfVprPbVP/IcffjjVb7x+Tvp6bZnf0eeuy65fzuh6HnPMMfLuu++uMd2bb75p9qVJkyaZZdV95dlnn3X02ekXQHV1dWZ5hg8f3ul0Gq7rfrXRRhv1aD/W7afL/+mnn5pfOejnoOv0pz/9yay7/rrjd7/7nVmHbbfd1ryfbluL9Xlp63ndr3Qb6/vttdde8s9//rPD4+Hxxx8322/LLbc0x4LuV7///e9l4cKF7aaz9i39zHRb603HMOioj/X6+nq55JJLZO+99zafny6rdhfz0UcfrfH+X3zxhVkP3e/0/fVXH1dccYXZ3nb63vo+2vXR+eefb7rh0XnrL0T08wEAAAByha5gAACA6zQw17BQ+56ura2VQYMGtQt9Fy9ebMI3DVHTaV/VOrCptmjfY489THcxr732mumO47333jN9WgcCATPtQw89JH/84x+lqqpKfvKTn5gQUgNAfW06fc+f//znJqTcZZddTOvjjz/+WG6//XZ54403TOCp7+W2r7/+ul3Arv72t7+Z0Fq7htGwVddHw14NYTXYPuCAA9rN48YbbzTLdtRRR8mCBQtk5513NtvxscceM2H1DjvskOrjXQNn7Xs9GAyasFK3vQb2GrrqdtRgvLi4ODXv+fPnm22r79nU1GTCZ4uG6rqc+llpoKqfpy7fN998I//+979N8K3bVJf9hhtuMF+m6BcESgNS7f7m22+/NfuCLouGwdr3vi6LBqVnnXVWu/W89957Zd68ebLnnnuaddJpNRj+/vvv5a9//WtqOh0wVPcD/dw1PI5EIvLUU0+ZcPrWW2814au1f5x33nmm73P9ckG34UsvvWS2j/Z5r923dMVqga7L0hUrcO7pfmyF47otdfvrFy46rW5j/UJmzpw55nPU7andKD355JNmX9f9wk5fo9tG9w/dBjq4rX4xodtUPzeLPqbbdeONN07tf/qlhG5D/cJEP8+SkpJ289XPQqfV41n3ufQvaZSG/7rN9PjSz1unffrpp82vNfRLkw033NBMp6379bNqbGw00+qXFjNnzjS/CnjllVfk/vvvlwEDBrSbtx4bui30SxrtPkq3gX6OevzqlwIAAACA55IAAAAuue6665KjR49OvvDCC8kXX3zR/P+BBx5oN83ll1+enDhxYjIajSZPPPFEM83ChQvNc19//XVy0003Te68887m/5b6+vrk0UcfbaZ97LHHzGMrVqxITpo0Kbnjjjsmv/3229S0s2fPTo4dO9ZM+8gjj6Qe//Wvf50cM2ZM8pVXXmm3PHfddZeZVpfLctRRR5nH9D2crK/9fex0WXR9dHmWLl1qHps1a5ZZDn2P1atXp6ZdtmxZco899kiOGzcuNe1bb71l5q+Pff/99+3mbT130UUXpR6rq6tLbrnllmb7zpkzJ/V4U1NT8ve//72Z/oYbbkg9vssuu5jH7r777nbz1s9DH7c+S8trr72Wevzee+9dY/pDDjkk9dgtt9xiHnvwwQfbzXvx4sXJzTffPPmjH/1ojXXZZJNNku+//37q8ZUrVya32WYbsw1XrVplHvv888/N/b333rvdNvnyyy+T48ePT+6///7mvu4T+j777LOP2baWhoaG5GGHHZbceOONk59++mmyK7o+ulwfffRRMhOZ7Mf2z8H+Weq+p5+7Pn7qqacmE4mEeby5udnsJ/q4tf/YP6/bb7+93fv97Gc/M4/rNlZLliwx637kkUeaednpMaLT6udsseb70ksvdbn/6bbU+2eeeWa76Z555hnz+GWXXZZa/j333NNsn2nTprWb9sorrzTTnn322anHzjrrrNS+petjeeKJJ8zjv/vd7xx+KgAAAIC76AoGAABkhbYi1Va66d3BWN3A2FtNW7RFtHYRctJJJ7XrekPnoy3TlXZborQ1rraK1i5Rhg4dmppWu4k48MAD281XWzxrS1rtKkZb89ppK/C1117btP7uKW0lrl2hWDdtkX3qqafKkUceadbnzDPPNK2mlbbSTyaT5rHS0tLUPLSv9F//+temJbJ252GnXYnogLBOlkNbgus20e5iLOFwWM455xzT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SXZ8rc14Wn26tcyt93V5OEfk9zlCtwtQqAJJ62yYA9qXug5eOm3aNHNiUX//+9/liSeekOHDh8vo0aPljDPOSE1/5ZVXmiD+5ptvXmNeu+22m/n70ksvebgGAIDemPr8pTl538v2PDsn7wvkO45pAAD8iWs0AORZVzBz5syRESNGpEJ1temmm8rixYtlyJAhUltb2256vZ/ePQwAAAAAAAAAoHf0lwnXX3+9acC8+eaby8477yyXXnqprFq1yjy/6667mrEy3TZ16lRzy8RDDz0kBx10kIwfP16233578/qFCxd2OK0+PmbMGPnDH/4gedMVjIbkX331lflpjfXzHB3AdNiwYabP9dtuu8383ER/GqN/dWDTE044IZeLDAAAAAAAAAAZm3n6mY6nHX/1FWs8tuT5FyVYXCSBYPu20slEQqI1tZJoapJIdbWEIsUSLCqSqm23yWj5rrrqKnnjjTfkoosuMr2JaCB98cUXm/xWexB5+OGHTXdAufbHP/5RXnnlFdPTyZZbbmkaY99+++1m3M677rrLhOh2Tz/9tKy33nry4osvmq6XtFulPt9iXb/l0D6edGN88cUX8vLLL5sPacqUKbL33nvLypUrzYe3YMEC81f7d9IBTgEAAAAAAACgkGioPnjnnaR6xx1SNw3Pk01NpmHy0L32kKF77GYe15A9U4899pj89re/NeNfasNn/XvBBReYEPv777+XgQMHtut5JBe0S/H//Oc/cuedd8pPf/pTs5zaal1b2k+cOFHOOeecNV7z1FNPyVFHHWVy6Oeee861ZclpsF5RUWE2Qk1NjRxyyCHmpwUnnniiHHbYYVJeXi633HKLzJgxwzTrnzVrltx6662++FYEAAAAAAAAALyU3lJdw/OaV6dJ04qVUr3LThKpqurd/AMBeeutt1KDEqsJEybIf//7X1lrrbXadQUzZcoU+cc//iHHHnusjB071mS72rL9vPPOM6/Zc8895Z133jHTvv3227LjjjvK3XffLVtvvbVst912ZpzNzrzwwguy7777mh5NdL7WfNSDDz4ou+++u4waNWqNZT/ppJNM1+Mff/xx6nFtsD1v3jzzvjvssIP58iAvgnW10UYbyR133GECdN1oxxxzTGrUaf1QdGVnz55t+s3R/tcBAAAAAAAAoJC5Haqro48+Wu655x4ToJ9//vmmdXdjY6PJb7W1d7obb7xRfvazn5mwva6uzoTggwYNMl3GaPCtXcpYli5dKo8//rj885//lAsvvNB03aIhebpPPvlEzjrrLNP4+oknnpCf/OQn8utf/9qE9kobX2tm3BHNjktLS02WbG+tvu6668rGG29s+o5/99135ZtvvpG8CNYBAAAAAAAAAO6F6vpcprTF95VXXilDhw41ofepp55qWnk/8sgjHU6/yy67mG67NXjXVuTaA4m+ZuTIkSZw17E0Lc3NzXLJJZfIZpttZqb9xS9+IQ888MAa89RW8PraH//4xzJixAgT9mtr9/vvv988v3z58k77SNfG2tpDyg8//NCuf3X9okDttNNOZpxPDfjdQLAOAAAAAAAAAHkSqq/46KMeBetKW4hr4K2DmOpgptry/NxzzzVdrKQbNmxY6v/a9/o666yT6olE7zfZ+nnX7r211bhl8803bxe8Wz777DO59957TXcy1k37eP/yyy/N8wMGDJDvvvuuw2VPJpOyatUqE64rbbmuLd01yFcayGs3NNpHuxvCrswFAAAAAAAAAJA1yUTCUai+YvYcKarsn9G8tQsWbck9depUc1/7VNdW43vttZfpL137Xk8XDrePloNpfcB3Na32426F8HbxeNx0/XLggQe2e9waNFW7geko5FeffvqprF692rSKV9o3vPrlL3/Z7n01gNduySdNmiS9QYt1AAAAAAAAAPC5aE2to1C9cuzmGQfrGmjrOJhz585t97h2naKh9sCBA3u17CtXrpRFixal7n/44YcyZsyYNabbYIMNzHTaDYx1+/e//y3/+9//zPOHHXaYTJs2LdWPunYNo8H/k08+KTfddJOMHj3aDHqqAfozzzwjBxxwgPnCwLrpeJ7aZY0b3cEQrAMAAAAAAABAH+gGxkmoXtnaYjsT2sp75513lt/85jcmpNZwe+bMmWYQ01gsZsLr3jrvvPNk3rx5ZlBUHST1yCOPXGOaY445xvSLfvfdd8vXX38td955p7mtv/76qX7StQ/2E044wYTkOmiqhudnnHGGme8555xjWsK/9957psuYKVOmmLDdum2yySamuxsN3aPRaK/Wh65gAAAAAAAAAMDnItXVWQnVLddee63cfPPNcsMNN8jixYtNv+jbb7+96fNcW3n31o477ihHHHGEme/pp59uuppJN378eLniiivk+uuvN3/XW289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AAAAgLzUo2D9qquukmHDhsk999wjFRUVqcfr6urkF7/4hRnU9IYbbnBzOQEAAAAAAAAA8IVgT1707rvvygknnNAuVFd6/7jjjjPPAwAAAAAAAACQj3oUrIfDYYlEIh0+V1xcLLFYrLfLBQAAAAAAAABA/gTrW2yxhfzrX/+SZDLZ7nG9f99998nmm2/u1vIBAAAAAAAAAND3+1j/7W9/Kz//+c/lJz/5iey9995SXV1tBi999tln5YsvvpA77rjD/SUFAAAAAAAAAKCvBuvaYv3222+Xv/71r2aQUm2pHggETEv12267Tbbcckv3lxQAAAAAAAAAgL4arKttttlGHnjgAdOf+sqVK6V///7S3Ny8xoCmAAAAAAAAAABIoQfrTU1NctFFF8mcOXPkkUcekdLSUnnjjTfkuOOOkylTpsgf/vAHCQZ71H07AABAn3TaNa/m7L2vOW3nnL03AAAAABSiHqXf119/vTzxxBOy3377pR7bdNNN5YwzzpAHH3zQdBMDAAAAAAAAAEA+6lGL9SeffFLOOussOfzww1OPDRgwQI455hgJh8Ny9913m9brAAAAAAAAAADkmx61WP/hhx9k+PDhHT634YYbypIlS3q7XAAAAAAAAAAA5E+wruH5c8891+FzL7/8sowYMaK3ywUAAAAAAAAAQP50BXP00UfL1KlTZfny5bL77rtLVVWVLFu2TF555RV55pln5NJLL3V/SQEAAAAAAAAA6KvB+oEHHij19fVy0003yfPPP596fK211pLzzjvPPA8AAAAAAAAAQD7qUbCujjzySDniiCPkiy++MC3XE4mEjBo1SiorK91dQgApM08/MyfvO/7qK3LyvkC+45gGAAAAAKAA+lifPXu2nHDCCfL444+b+4FAQN544w059thjZcqUKbLTTjvJP/7xj2wtKwAAAAAAAAAAfSdY/+STT0x4/vHHH0u/fv3MYx9++KFcfPHFMnz4cLn++uvlN7/5jVxzzTXy4osvZnOZAQAAAAAAAADwf1cwt9xyi2y88cZy5513SmlpqXns7rvvNn+vuuoq85yqra2Ve+65xwxqCgAAAAAAAABAwbZYf/fdd02LdStUV9OnTzet1a1QXW2//fYyd+5c95cUAAAAAAAAAIC+FKzrAKVDhw5N3f/ss8/khx9+kK233rrddBq8x2Ixd5cSAAAAAAAAAIC+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Bx4oF1xwgcyePVtefPFF+ec//ylHH310rhcNAAAAAAAAAFCgfD14qWpoaDDB+vPPPy/l5eXyq1/9So455phcLxYAAAAAAAAAoED5usW61Wr98ssvlw8++EBee+01QnX4ytKlS+Wiiy6SXXbZRcaNGyf77bef/OMf/zDjAgDoWziegfylA9w/+OCDqftTpkyR66+/3rP3HzNmjLz99tuevR9Q6FatWiWPP/64o2n1On/AAQd4ek4A0Fb+fuaZZ3r8+qlTp5qb0mNYr+8A4CXfB+uAX3333Xdy6KGHyhdffCFXXnmlPPXUU3LSSSfJfffdJyeeeKIkEolcLyIAhziegfz23//+V26++eacvf/06dNlwoQJOXt/oNDceeed8sgjjziaVrsa/eSTT7K+TADWdNVVV8m0adNyvRgA0GPhnr8UKGyXXHKJrLvuunLrrbdKKBQyjw0fPlzGjx9vWrref//9cuSRR+Z6MQE4wPEM5Ldc93xYXV2d0/cHCo3TY/6rr76Su+++WzbaaKOsLxMA/12fAaC3aLEO9MAPP/xgBtP99a9/nQrhLOuss44cfPDB8sADD8hWW20lr7zySuq5PffcU84666zU/auvvlrOOOMM8/958+aZn66NHTtW9tprL9NS1qI/a/v9738v559/vkycOFG23XZbue222zxZVyDfcTwDfceMGTPk5z//uemuSb/40uP2+++/l0cffdQcc9ddd51svfXWMnnyZLn00ktNhV27YDn77LPlm2++MV2yLFq0KPVLlf/7v/+TLbbYwhynb7zxRup9dLq//e1vZl4nnHCCeUy7JdT31vfdddddzRduFv0ZunYlpdPqcX/ggQfK+++/32FXMKtXr5Y//elPZt56O++88yQajXq4FYG+Y+HChaYrUD3mf/zjH5su2vT402Nej0dt7aq/Btl5553loYceMq/R52644QZ55513zLHXFT0WTznlFBk4cOAaz+l89tlnH3NMH3TQQfLuu+9mbT2BvkSvo3psvfrqq+Z41GNQr4Fa/tVjRa+Txx9/vOmSSWk52ppOr9Wffvppqkz82GOPmZs+rxYsWGDG1dNp9fp8xBFHyGeffdaj5dRzwt577y2bb765ud7++c9/lng8nrpu669Uf/e735nzy7777itz586Va665xpQhdtxxx3Zd1HRW/gAAgnWgBz766CPTH6MWtDuiYZkWLLbccktTqLcq8F9//XW7ivbrr78uO+ywgzQ2NpqL86RJk+SJJ54wYd1NN93Urm/I5557TiKRiCl4aGFDKxLabQWA3uF4BvqGuro6U1H/0Y9+ZLpr0oBNj0P9pYkVfOtxpIG3htXaClXDcq2cn3POOTJ06FDTJcvaa69tptdjUivS2k2MVrrPPPPMdi3n9Is0nZd+YaaV+l/84hfmPKBhmwZxOgbQCy+8kJpegwNt9arHtU533HHHybJly9ZYjz/+8Y+mgq7nBe2CQv9/7bXXerINgb5Er816zPfv399066LHlAbmlg8//FA+/vhj+fe//y0nn3yyCc30GNfj+pe//KU59vV+Z3Se+qXWz372szWe0+P8L3/5i3l/PVdst9125v31+g+ghV5/9Vqmx8o999xjjkNtPKLX55kzZ8rDDz8sL7/8sjlu9bqs10ctHx999NGyYsUKc5zql1d602m160X9glp/Rfqf//zHXFc1CNcAPFNaZtew//TTT5dnn33WnB/0PV566aXUNHfddZdpOKPl9QEDBpjrvPb5rucUDfq1EYwuU3flDwCFjWAd6GELV1VWVtbh85WVlebvJptskmqh9t5775mL8eLFi6W2ttYUJrQ/Rw3innzySamqqjLfmK+//vrmQq6FCg0FLHqx14BuxIgRpoWd3p8zZ44n6wvkM45noG/QL61+85vfmPEPtKsmrZzrL0fmz59vntfKt1buN9xwQzMQ4cYbb2yCt+LiYqmoqDC/SNEuWaxfpmgrdW1Zt95665kvw2pqakyF2nLYYYeZeWlYrgOfbrrppqaCro/99Kc/laOOOkpuv/321PQ6nYbwI0eONC3k9dzx9NNPt1sHPVdoBV9byeryb7bZZnLhhReaX8cAaO+tt96Sb7/91nTXpseXtljX484SCATkiiuukNGjR8shhxxium7TY7WkpET69esnRUVFnXbDpMe6/tJMjz+dTzoNCbVlrf76RI95Pbb1fe69996srjPQl+g1Wa+1+++/vyn76jGo5WO9vukvMj///HNzndRQepdddjHlYi0fa3CuYbaWvfV41Zv+akSv84cffrhpTa7XZr1G6vVWW7FnSs8BF198sSknDBs2zLRc1+u4VWZQ+qW6tojX8riuQ0NDg/nyW6/jevzrNVvL+d2VPwAUNvpYB3pAQzClrVb0Qp1u5cqV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77/dmDFjCnJen332mZs5c2ZB9iWEEGLdbWg+dqCkWb16tRswYICrV6+eO/jgg91ll13mFi1alLj9t99+6zp06OC3PfbYY/3LiTMRt+v2GT9+vP9+5cqV7vrrr3eHHXaYq1+/vnvwwQfX8nPsU7VqVdeoUaOsx/z+++/99vxbnm1nNn9BCFFySFgXZcLixYvdrbfe6ho2bOhq1arlTjrpJPfAAw+4VatWlep5sBLSfffd543qIYcc4tq0aePmzZtXkEDdjHC6z7vvvuu3+fLLL1379u39sTmHe+65x61Zs8Z/N27cuCK/qVatml9f0hyH0hBH3nrrLX+OJYGC8rKjIvQ/g/7Ei85OPfVUf61cM9e+bNmydT5v+ibnXEg++eQTLxZyjoiH8X4+bdo0d9ppp/nA45RTTnGvvPJK6rtrr722SJuwj1yEx0IFYtzHRx991JUUL7zwgn9mRcXlvffe82srd+7c2T3//PO+H1x00UXuzz//TPwNYwn9/Y033kh96Pv/NhAIsvXlXKHt8DMKQZcuXdw333xTkH0JIYT4b4BfP3HiRP9uhCeffNL9+uuv7pprrkn00S+++GJXqVIl9/TTT7vevXu7ESNGuOeeey5x/6FN53PhhRe63XbbzR133HH++/79+7tZs2Z53//GG290w4YNcy+++GLafbz88svexg4fPtyfc2lQkrazkP6CECI/JKyLUmfBggXurLPOcl9//bW744473IQJE7yRQZjp1KlTSlguDZ544gmfye7Vq5cbO3as23333X2w/tdffxU7UN9ll13WMv4nn3yyq1Gjhqtdu7Y/Bs7ETjvt5J566ilv/HECeEGTsfPOO6d+O2nSJL99z5493QcffOBKg7Zt22asMigOCsrLhorS/wwqZehXHTt29Nfat29fn9DBEaeqpTzw+++/++tGLLT7QT9///33/feff/6569q1qzvzzDO94I5oznXxd6NZs2apNiE4QVy/5JJLMgqPhYJE4c0331wi+/7hhx/c5ZdfnvhMiIrBL7/84kV1kkt77LGH7yMkxzIlfvmucuXKbocddkh9ttpqK1eR0WuVhBBClHTF+nXXXecOP/xwt//++/sCDvNn4xBjHnTQQe6mm25ye++9tzvmmGPcUUcdlbg9hDZ9xYoV7pFHHvGxwJZbbumWL1/uxXwS8RSkHX/88d7fjxd/2O93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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "from matplotlib.legend_handler import HandlerTuple # Added import\n",
+ "\n",
+ "# Assuming pivot_df is your original dataframe\n",
+ "models = pivot_df[\"model_id\"].unique()\n",
+ "sources = pivot_df[\"source\"].unique()\n",
+ "\n",
+ "# Create figure and axis\n",
+ "plt.style.use(\"seaborn-v0_8-white\")\n",
+ "fig, ax = plt.subplots(figsize=(15, 6))\n",
+ "\n",
+ "# Set the width of each bar group and positions of the bars\n",
+ "width = 0.15 # width of each bar\n",
+ "spacing = 0.02 # space between bars within a group\n",
+ "group_spacing = 0.2 # space between model groups\n",
+ "\n",
+ "# Calculate positions for the bars\n",
+ "num_sources = len(sources)\n",
+ "total_width_per_group = (width + spacing) * num_sources * 2 # *2 for agent and vanilla\n",
+ "x = np.arange(len(models)) * (total_width_per_group + group_spacing)\n",
+ "\n",
+ "# Plot bars for each source\n",
+ "for i, source in enumerate(sources):\n",
+ " source_data = pivot_df[pivot_df[\"source\"] == source]\n",
+ " agent_scores = [\n",
+ " source_data[source_data[\"model_id\"] == model][\"agent\"].values[0]\n",
+ " if len(source_data[source_data[\"model_id\"] == model]) > 0\n",
+ " else np.nan\n",
+ " for model in models\n",
+ " ]\n",
+ " vanilla_scores = [\n",
+ " source_data[source_data[\"model_id\"] == model][\"vanilla\"].values[0]\n",
+ " if len(source_data[source_data[\"model_id\"] == model]) > 0\n",
+ " else np.nan\n",
+ " for model in models\n",
+ " ]\n",
+ "\n",
+ " # Position calculation for each pair of bars\n",
+ " pos = x + i * (width * 2 + spacing)\n",
+ "\n",
+ " agent_bars = ax.bar(pos, agent_scores, width, label=f\"{source} (Agent)\", alpha=0.8)\n",
+ " vanilla_bars = ax.bar(\n",
+ " pos + width * 0.6,\n",
+ " vanilla_scores,\n",
+ " width,\n",
+ " hatch=\"////\",\n",
+ " alpha=0.5,\n",
+ " hatch_linewidth=2,\n",
+ " label=f\"{source} (Vanilla)\",\n",
+ " color=\"white\",\n",
+ " edgecolor=agent_bars[0].get_facecolor(),\n",
+ " )\n",
+ "\n",
+ "# Customize the plot\n",
+ "ax.set_ylabel(\"Score\")\n",
+ "ax.set_title(\"Model Performance Comparison\")\n",
+ "\n",
+ "# Set x-axis ticks in the middle of each group\n",
+ "group_centers = x + (total_width_per_group - spacing) / 2\n",
+ "ax.set_xticks(group_centers)\n",
+ "\n",
+ "# Wrap long model names to prevent overlap\n",
+ "wrapped_labels = [\"\\n\".join(model.split(\"/\")) for model in models]\n",
+ "ax.set_xticklabels(wrapped_labels, rotation=0, ha=\"center\")\n",
+ "\n",
+ "# Modify legend to combine agent and vanilla entries\n",
+ "handles, labels = ax.get_legend_handles_labels()\n",
+ "unique_sources = sources\n",
+ "legend_elements = [\n",
+ " (handles[i * 2], handles[i * 2 + 1], labels[i * 2].replace(\" (Agent)\", \"\"))\n",
+ " for i in range(len(unique_sources))\n",
+ "]\n",
+ "custom_legend = ax.legend(\n",
+ " [\n",
+ " (agent_handle, vanilla_handle)\n",
+ " for agent_handle, vanilla_handle, _ in legend_elements\n",
+ " ],\n",
+ " [label for _, _, label in legend_elements],\n",
+ " handler_map={tuple: HandlerTuple(ndivide=None)},\n",
+ " bbox_to_anchor=(1.05, 1),\n",
+ " loc=\"upper left\",\n",
+ ")\n",
+ "\n",
+ "ax.yaxis.grid(True, linestyle=\"--\", alpha=0.3)\n",
+ "ax.set_ylim(bottom=0)\n",
+ "plt.tight_layout()\n",
+ "ax.spines[\"top\"].set_visible(False)\n",
+ "ax.spines[\"right\"].set_visible(False)\n",
+ "\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "NameError",
+ "evalue": "name 'formatted_df' is not defined",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
+ "Cell \u001b[0;32mIn[12], line 45\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m mathjax_table\n\u001b[1;32m 44\u001b[0m \u001b[38;5;66;03m# Usage (after running your previous data processing code):\u001b[39;00m\n\u001b[0;32m---> 45\u001b[0m mathjax_table \u001b[38;5;241m=\u001b[39m create_mathjax_table(pivot_df, \u001b[43mformatted_df\u001b[49m)\n\u001b[1;32m 46\u001b[0m \u001b[38;5;28mprint\u001b[39m(mathjax_table)\n",
+ "\u001b[0;31mNameError\u001b[0m: name 'formatted_df' is not defined"
]
}
],
@@ -676,7 +1001,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "compare-agents",
+ "display_name": "test",
"language": "python",
"name": "python3"
},
diff --git a/src/smolagents/agents.py b/src/smolagents/agents.py
index eac9231..05be772 100644
--- a/src/smolagents/agents.py
+++ b/src/smolagents/agents.py
@@ -26,7 +26,11 @@ from rich.text import Text
from .default_tools import FinalAnswerTool
from .e2b_executor import E2BExecutor
-from .local_python_executor import BASE_BUILTIN_MODULES, LocalPythonInterpreter
+from .local_python_executor import (
+ BASE_BUILTIN_MODULES,
+ LocalPythonInterpreter,
+ fix_final_answer_code,
+)
from .models import MessageRole
from .monitoring import Monitor
from .prompts import (
@@ -895,7 +899,6 @@ class CodeAgent(MultiStepAgent):
)
log_entry.llm_output = llm_output
except Exception as e:
- console.print_exception()
raise AgentGenerationError(f"Error in generating model output:\n{e}")
if self.verbose:
@@ -917,10 +920,11 @@ class CodeAgent(MultiStepAgent):
# Parse
try:
- code_action = parse_code_blob(llm_output)
+ code_action = fix_final_answer_code(parse_code_blob(llm_output))
except Exception as e:
- console.print_exception()
- error_msg = f"Error in code parsing: {e}. Make sure to provide correct code"
+ error_msg = (
+ f"Error in code parsing:\n{e}\nMake sure to provide correct code blobs."
+ )
raise AgentParsingError(error_msg)
log_entry.tool_call = ToolCall(
@@ -944,8 +948,9 @@ class CodeAgent(MultiStepAgent):
)
)
observation = ""
+ is_final_answer = False
try:
- output, execution_logs = self.python_executor(
+ output, execution_logs, is_final_answer = self.python_executor(
code_action,
self.state,
)
@@ -976,12 +981,6 @@ class CodeAgent(MultiStepAgent):
observation += "Last output from code snippet:\n" + truncated_output
log_entry.observations = observation
- is_final_answer = False
- for line in code_action.split("\n"):
- if line[: len("final_answer")] == "final_answer":
- is_final_answer = True
- break
-
execution_outputs_console += [
Text(
f"{('Out - Final answer' if is_final_answer else 'Out')}: {truncated_output}",
diff --git a/src/smolagents/default_tools.py b/src/smolagents/default_tools.py
index 5628930..5959cda 100644
--- a/src/smolagents/default_tools.py
+++ b/src/smolagents/default_tools.py
@@ -112,7 +112,7 @@ class PythonInterpreterTool(Tool):
state=state,
static_tools=self.base_python_tools,
authorized_imports=self.authorized_imports,
- )
+ )[0] # The second element is boolean is_final_answer
)
return f"Stdout:\n{state['print_outputs']}\nOutput: {output}"
except Exception as e:
diff --git a/src/smolagents/local_python_executor.py b/src/smolagents/local_python_executor.py
index 6c7cb4a..17095a1 100644
--- a/src/smolagents/local_python_executor.py
+++ b/src/smolagents/local_python_executor.py
@@ -18,6 +18,7 @@ import ast
import builtins
import difflib
import math
+import re
from collections.abc import Mapping
from importlib import import_module
from typing import Any, Callable, Dict, List, Optional, Tuple
@@ -129,6 +130,34 @@ def get_iterable(obj):
raise InterpreterError("Object is not iterable")
+def fix_final_answer_code(code: str) -> str:
+ """
+ Sometimes an LLM can try to assign a variable to final_answer, which would break the final_answer() tool.
+ This function fixes this behaviour by replacing variable assignments to final_answer with final_answer_variable,
+ while preserving function calls to final_answer().
+ """
+ # First, find if there's a direct assignment to final_answer
+ # Use word boundary and negative lookbehind to ensure it's not an object attribute
+ assignment_pattern = r"(? 1
- ): # Check for user-defined classes
- # Instantiate the class using its constructor
- obj = func.__new__(func) # Create a new instance of the class
- if hasattr(obj, "__init__"): # Check if the class has an __init__ method
- obj.__init__(*args, **kwargs) # Call the __init__ method correctly
- return obj
- else:
- if func_name == "super":
- if not args:
- if "__class__" in state and "self" in state:
- return super(state["__class__"], state["self"])
- else:
- raise InterpreterError("super() needs at least one argument")
- cls = args[0]
- if not isinstance(cls, type):
- raise InterpreterError("super() argument 1 must be type")
- if len(args) == 1:
- return super(cls)
- elif len(args) == 2:
- instance = args[1]
- return super(cls, instance)
+ if func_name == "super":
+ if not args:
+ if "__class__" in state and "self" in state:
+ return super(state["__class__"], state["self"])
else:
- raise InterpreterError("super() takes at most 2 arguments")
+ raise InterpreterError("super() needs at least one argument")
+ cls = args[0]
+ if not isinstance(cls, type):
+ raise InterpreterError("super() argument 1 must be type")
+ if len(args) == 1:
+ return super(cls)
+ elif len(args) == 2:
+ instance = args[1]
+ return super(cls, instance)
else:
- if func_name == "print":
- output = " ".join(map(str, args))
- global PRINT_OUTPUTS
- PRINT_OUTPUTS += output + "\n"
- # cap the number of lines
- return None
- else: # Assume it's a callable object
- output = func(*args, **kwargs)
- return output
+ raise InterpreterError("super() takes at most 2 arguments")
+ else:
+ if func_name == "print":
+ output = " ".join(map(str, args))
+ global PRINT_OUTPUTS
+ PRINT_OUTPUTS += output + "\n"
+ # cap the number of lines
+ return None
+ else: # Assume it's a callable object
+ return func(*args, **kwargs)
def evaluate_subscript(subscript, state, static_tools, custom_tools):
@@ -990,6 +1013,11 @@ def truncate_print_outputs(
return f"Print outputs:\n{print_outputs[:max_len_outputs]}\n_Print outputs have been truncated over the limit of {max_len_outputs} characters._\n"
+class FinalAnswerException(Exception):
+ def __init__(self, value):
+ self.value = value
+
+
def evaluate_python_code(
code: str,
static_tools: Optional[Dict[str, Callable]] = None,
@@ -1029,6 +1057,12 @@ def evaluate_python_code(
PRINT_OUTPUTS = ""
global OPERATIONS_COUNT
OPERATIONS_COUNT = 0
+
+ def final_answer(value):
+ raise FinalAnswerException(value)
+
+ static_tools["final_answer"] = final_answer
+
try:
for node in expression.body:
result = evaluate_ast(
@@ -1037,7 +1071,14 @@ def evaluate_python_code(
state["print_outputs"] = truncate_content(
PRINT_OUTPUTS, max_length=MAX_LEN_OUTPUT
)
- return result
+ is_final_answer = False
+ return result, is_final_answer
+ except FinalAnswerException as e:
+ state["print_outputs"] = truncate_content(
+ PRINT_OUTPUTS, max_length=MAX_LEN_OUTPUT
+ )
+ is_final_answer = True
+ return e.value, is_final_answer
except InterpreterError as e:
msg = truncate_content(PRINT_OUTPUTS, max_length=MAX_LEN_OUTPUT)
msg += f"Code execution failed at line '{ast.get_source_segment(code, node)}' because of the following error:\n{e}"
@@ -1059,9 +1100,11 @@ class LocalPythonInterpreter:
}
# TODO: assert self.authorized imports are all installed locally
- def __call__(self, code_action: str, additional_variables: Dict) -> Tuple[Any, str]:
+ def __call__(
+ self, code_action: str, additional_variables: Dict
+ ) -> Tuple[Any, str, bool]:
self.state.update(additional_variables)
- output = evaluate_python_code(
+ output, is_final_answer = evaluate_python_code(
code_action,
static_tools=self.static_tools,
custom_tools=self.custom_tools,
@@ -1069,7 +1112,7 @@ class LocalPythonInterpreter:
authorized_imports=self.authorized_imports,
)
logs = self.state["print_outputs"]
- return output, logs
+ return output, logs, is_final_answer
__all__ = ["evaluate_python_code", "LocalPythonInterpreter"]
diff --git a/src/smolagents/prompts.py b/src/smolagents/prompts.py
index e85ab19..af68b27 100644
--- a/src/smolagents/prompts.py
+++ b/src/smolagents/prompts.py
@@ -373,7 +373,7 @@ Here are the rules you should always follow to solve your task:
4. Take care to not chain too many sequential tool calls in the same code block, especially when the output format is unpredictable. For instance, a call to search has an unpredictable return format, so do not have another tool call that depends on its output in the same block: rather output results with print() to use them in the next block.
5. Call a tool only when needed, and never re-do a tool call that you previously did with the exact same parameters.
6. Don't name any new variable with the same name as a tool: for instance don't name a variable 'final_answer'.
-7. Never create any notional variables in our code, as having these in your logs might derail you from the true variables.
+7. Never create any notional variables in our code, as having these in your logs will derail you from the true variables.
8. You can use imports in your code, but only from the following list of modules: {{authorized_imports}}
9. The state persists between code executions: so if in one step you've created variables or imported modules, these will all persist.
10. Don't give up! You're in charge of solving the task, not providing directions to solve it.
diff --git a/src/smolagents/utils.py b/src/smolagents/utils.py
index 902ebb7..fe006fd 100644
--- a/src/smolagents/utils.py
+++ b/src/smolagents/utils.py
@@ -106,26 +106,35 @@ def parse_json_blob(json_blob: str) -> Dict[str, str]:
def parse_code_blob(code_blob: str) -> str:
- try:
- pattern = r"```(?:py|python)?\n(.*?)\n```"
- match = re.search(pattern, code_blob, re.DOTALL)
- if match is None:
- raise ValueError(
- f"No match ground for regex pattern {pattern} in {code_blob=}."
- )
- return match.group(1).strip()
+ """Parses the LLM's output to get any code blob inside. Will retrun the code directly if it's code."""
+ pattern = r"```(?:py|python)?\n(.*?)\n```"
+ match = re.search(pattern, code_blob, re.DOTALL)
+ if match is None:
+ try: # Maybe the LLM outputted a code blob directly
+ ast.parse(code_blob)
+ return code_blob
+ except SyntaxError:
+ pass
- except Exception as e:
+ if "final" in code_blob and "answer" in code_blob:
+ raise ValueError(
+ f"""
+The code blob is invalid, because the regex pattern {pattern} was not found in {code_blob=}. It seems like you're trying to return the final answer, you can do it as follows:
+Code:
+```py
+final_answer("YOUR FINAL ANSWER HERE")
+```""".strip()
+ )
raise ValueError(
f"""
-The code blob you used is invalid: due to the following error: {e}
-This means that the regex pattern {pattern} was not respected: make sure to include code with the correct pattern, for instance:
+The code blob is invalid, because the regex pattern {pattern} was not found in {code_blob=}. Make sure to include code with the correct pattern, for instance:
Thoughts: Your thoughts
Code:
```py
# Your python code here
-```"""
+```""".strip()
)
+ return match.group(1).strip()
def parse_json_tool_call(json_blob: str) -> Tuple[str, Union[str, None]]:
diff --git a/tests/test_agents.py b/tests/test_agents.py
index 06d9554..2d666e6 100644
--- a/tests/test_agents.py
+++ b/tests/test_agents.py
@@ -444,3 +444,18 @@ final_answer("Final report.")
report = manager_toolcalling_agent.run("Fake question.")
assert report == "Final report."
+
+ def test_code_nontrivial_final_answer_works(self):
+ def fake_code_model_final_answer(messages, stop_sequences=None, grammar=None):
+ return """Code:
+```py
+def nested_answer():
+ final_answer("Correct!")
+
+nested_answer()
+```"""
+
+ agent = CodeAgent(tools=[], model=fake_code_model_final_answer)
+
+ output = agent.run("Count to 3")
+ assert output == "Correct!"
diff --git a/tests/test_python_interpreter.py b/tests/test_python_interpreter.py
index 3508161..5f4ffc4 100644
--- a/tests/test_python_interpreter.py
+++ b/tests/test_python_interpreter.py
@@ -23,6 +23,7 @@ from smolagents.default_tools import BASE_PYTHON_TOOLS
from smolagents.local_python_executor import (
InterpreterError,
evaluate_python_code,
+ fix_final_answer_code,
)
from smolagents.types import AGENT_TYPE_MAPPING
@@ -79,19 +80,19 @@ class PythonInterpreterTester(unittest.TestCase):
def test_evaluate_assign(self):
code = "x = 3"
state = {}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
assert result == 3
self.assertDictEqual(state, {"x": 3, "print_outputs": ""})
code = "x = y"
state = {"y": 5}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
# evaluate returns the value of the last assignment.
assert result == 5
self.assertDictEqual(state, {"x": 5, "y": 5, "print_outputs": ""})
code = "a=1;b=None"
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
# evaluate returns the value of the last assignment.
assert result is None
@@ -107,7 +108,7 @@ class PythonInterpreterTester(unittest.TestCase):
def test_evaluate_call(self):
code = "y = add_two(x)"
state = {"x": 3}
- result = evaluate_python_code(code, {"add_two": add_two}, state=state)
+ result, _ = evaluate_python_code(code, {"add_two": add_two}, state=state)
assert result == 5
self.assertDictEqual(state, {"x": 3, "y": 5, "print_outputs": ""})
@@ -119,14 +120,14 @@ class PythonInterpreterTester(unittest.TestCase):
def test_evaluate_constant(self):
code = "x = 3"
state = {}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
assert result == 3
self.assertDictEqual(state, {"x": 3, "print_outputs": ""})
def test_evaluate_dict(self):
code = "test_dict = {'x': x, 'y': add_two(x)}"
state = {"x": 3}
- result = evaluate_python_code(code, {"add_two": add_two}, state=state)
+ result, _ = evaluate_python_code(code, {"add_two": add_two}, state=state)
self.assertDictEqual(result, {"x": 3, "y": 5})
self.assertDictEqual(
state, {"x": 3, "test_dict": {"x": 3, "y": 5}, "print_outputs": ""}
@@ -135,7 +136,7 @@ class PythonInterpreterTester(unittest.TestCase):
def test_evaluate_expression(self):
code = "x = 3\ny = 5"
state = {}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
# evaluate returns the value of the last assignment.
assert result == 5
self.assertDictEqual(state, {"x": 3, "y": 5, "print_outputs": ""})
@@ -143,7 +144,7 @@ class PythonInterpreterTester(unittest.TestCase):
def test_evaluate_f_string(self):
code = "text = f'This is x: {x}.'"
state = {"x": 3}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
# evaluate returns the value of the last assignment.
assert result == "This is x: 3."
self.assertDictEqual(
@@ -153,13 +154,13 @@ class PythonInterpreterTester(unittest.TestCase):
def test_evaluate_if(self):
code = "if x <= 3:\n y = 2\nelse:\n y = 5"
state = {"x": 3}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
# evaluate returns the value of the last assignment.
assert result == 2
self.assertDictEqual(state, {"x": 3, "y": 2, "print_outputs": ""})
state = {"x": 8}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
# evaluate returns the value of the last assignment.
assert result == 5
self.assertDictEqual(state, {"x": 8, "y": 5, "print_outputs": ""})
@@ -167,27 +168,27 @@ class PythonInterpreterTester(unittest.TestCase):
def test_evaluate_list(self):
code = "test_list = [x, add_two(x)]"
state = {"x": 3}
- result = evaluate_python_code(code, {"add_two": add_two}, state=state)
+ result, _ = evaluate_python_code(code, {"add_two": add_two}, state=state)
self.assertListEqual(result, [3, 5])
self.assertDictEqual(state, {"x": 3, "test_list": [3, 5], "print_outputs": ""})
def test_evaluate_name(self):
code = "y = x"
state = {"x": 3}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
assert result == 3
self.assertDictEqual(state, {"x": 3, "y": 3, "print_outputs": ""})
def test_evaluate_subscript(self):
code = "test_list = [x, add_two(x)]\ntest_list[1]"
state = {"x": 3}
- result = evaluate_python_code(code, {"add_two": add_two}, state=state)
+ result, _ = evaluate_python_code(code, {"add_two": add_two}, state=state)
assert result == 5
self.assertDictEqual(state, {"x": 3, "test_list": [3, 5], "print_outputs": ""})
code = "test_dict = {'x': x, 'y': add_two(x)}\ntest_dict['y']"
state = {"x": 3}
- result = evaluate_python_code(code, {"add_two": add_two}, state=state)
+ result, _ = evaluate_python_code(code, {"add_two": add_two}, state=state)
assert result == 5
self.assertDictEqual(
state, {"x": 3, "test_dict": {"x": 3, "y": 5}, "print_outputs": ""}
@@ -215,14 +216,14 @@ for result in search_results:
def test_evaluate_for(self):
code = "x = 0\nfor i in range(3):\n x = i"
state = {}
- result = evaluate_python_code(code, {"range": range}, state=state)
+ result, _ = evaluate_python_code(code, {"range": range}, state=state)
assert result == 2
self.assertDictEqual(state, {"x": 2, "i": 2, "print_outputs": ""})
def test_evaluate_binop(self):
code = "y + x"
state = {"x": 3, "y": 6}
- result = evaluate_python_code(code, {}, state=state)
+ result, _ = evaluate_python_code(code, {}, state=state)
assert result == 9
self.assertDictEqual(state, {"x": 3, "y": 6, "print_outputs": ""})
@@ -234,27 +235,27 @@ def recur_fibo(n):
else:
return(recur_fibo(n-1) + recur_fibo(n-2))
recur_fibo(6)"""
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
assert result == 8
def test_evaluate_string_methods(self):
code = "'hello'.replace('h', 'o').split('e')"
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
assert result == ["o", "llo"]
def test_evaluate_slicing(self):
code = "'hello'[1:3][::-1]"
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
assert result == "le"
def test_access_attributes(self):
code = "integer = 1\nobj_class = integer.__class__\nobj_class"
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
assert result is int
def test_list_comprehension(self):
code = "sentence = 'THESEAGULL43'\nmeaningful_sentence = '-'.join([char.lower() for char in sentence if char.isalpha()])"
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
assert result == "t-h-e-s-e-a-g-u-l-l"
def test_string_indexing(self):
@@ -267,12 +268,12 @@ for block in text_block:
for col in range(len(text_block[0])):
sentence += block[col]
"""
- result = evaluate_python_code(code, {"len": len, "range": range}, state={})
+ result, _ = evaluate_python_code(code, {"len": len, "range": range}, state={})
assert result == "THESEAGULL"
def test_tuples(self):
code = "x = (1, 2, 3)\nx[1]"
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
assert result == 2
code = """
@@ -325,35 +326,35 @@ print(check_digits)
def test_listcomp(self):
code = "x = [i for i in range(3)]"
- result = evaluate_python_code(code, {"range": range}, state={})
+ result, _ = evaluate_python_code(code, {"range": range}, state={})
assert result == [0, 1, 2]
def test_break_continue(self):
code = "for i in range(10):\n if i == 5:\n break\ni"
- result = evaluate_python_code(code, {"range": range}, state={})
+ result, _ = evaluate_python_code(code, {"range": range}, state={})
assert result == 5
code = "for i in range(10):\n if i == 5:\n continue\ni"
- result = evaluate_python_code(code, {"range": range}, state={})
+ result, _ = evaluate_python_code(code, {"range": range}, state={})
assert result == 9
def test_call_int(self):
code = "import math\nstr(math.ceil(149))"
- result = evaluate_python_code(code, {"str": lambda x: str(x)}, state={})
+ result, _ = evaluate_python_code(code, {"str": lambda x: str(x)}, state={})
assert result == "149"
def test_lambda(self):
code = "f = lambda x: x + 2\nf(3)"
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
assert result == 5
def test_dictcomp(self):
code = "x = {i: i**2 for i in range(3)}"
- result = evaluate_python_code(code, {"range": range}, state={})
+ result, _ = evaluate_python_code(code, {"range": range}, state={})
assert result == {0: 0, 1: 1, 2: 4}
code = "{num: name for num, name in {101: 'a', 102: 'b'}.items() if name not in ['a']}"
- result = evaluate_python_code(
+ result, _ = evaluate_python_code(
code, {"print": print}, state={}, authorized_imports=["pandas"]
)
assert result == {102: "b"}
@@ -362,17 +363,17 @@ print(check_digits)
shifts = {'A': ('6:45', '8:00'), 'B': ('10:00', '11:45')}
shift_minutes = {worker: ('a', 'b') for worker, (start, end) in shifts.items()}
"""
- result = evaluate_python_code(code, {}, state={})
+ result, _ = evaluate_python_code(code, {}, state={})
assert result == {"A": ("a", "b"), "B": ("a", "b")}
def test_tuple_assignment(self):
code = "a, b = 0, 1\nb"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == 1
def test_while(self):
code = "i = 0\nwhile i < 3:\n i += 1\ni"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == 3
# test infinite loop
@@ -393,7 +394,7 @@ while i < n and house_positions[i] <= loc:
def test_generator(self):
code = "a = [1, 2, 3, 4, 5]; b = (i**2 for i in a); list(b)"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == [1, 4, 9, 16, 25]
def test_boolops(self):
@@ -403,7 +404,7 @@ else:
best_city = "Manhattan"
best_city
"""
- result = evaluate_python_code(
+ result, _ = evaluate_python_code(
code, BASE_PYTHON_TOOLS, state={"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
)
assert result == "Brooklyn"
@@ -416,7 +417,7 @@ else:
best_city = "Manhattan"
best_city
"""
- result = evaluate_python_code(
+ result, _ = evaluate_python_code(
code, BASE_PYTHON_TOOLS, state={"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
)
assert result == "Sacramento"
@@ -431,51 +432,51 @@ if char.isalpha():
def test_imports(self):
code = "import math\nmath.sqrt(4)"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == 2.0
code = (
"from random import choice, seed\nseed(12)\nchoice(['win', 'lose', 'draw'])"
)
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == "lose"
code = "import time, re\ntime.sleep(0.1)"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result is None
code = "from queue import Queue\nq = Queue()\nq.put(1)\nq.get()"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == 1
code = "import itertools\nlist(itertools.islice(range(10), 3))"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == [0, 1, 2]
code = "import re\nre.search('a', 'abc').group()"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == "a"
code = "import stat\nstat.S_ISREG(0o100644)"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result
code = "import statistics\nstatistics.mean([1, 2, 3, 4, 4])"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == 2.8
code = "import unicodedata\nunicodedata.name('A')"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == "LATIN CAPITAL LETTER A"
# Test submodules are handled properly, thus not raising error
code = "import numpy.random as rd\nrng = rd.default_rng(12345)\nrng.random()"
- result = evaluate_python_code(
+ result, _ = evaluate_python_code(
code, BASE_PYTHON_TOOLS, state={}, authorized_imports=["numpy"]
)
code = "from numpy.random import default_rng as d_rng\nrng = d_rng(12345)\nrng.random()"
- result = evaluate_python_code(
+ result, _ = evaluate_python_code(
code, BASE_PYTHON_TOOLS, state={}, authorized_imports=["numpy"]
)
@@ -491,25 +492,25 @@ if char.isalpha():
def test_multiple_comparators(self):
code = "0 <= -1 < 4 and 0 <= -5 < 4"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert not result
code = "0 <= 1 < 4 and 0 <= -5 < 4"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert not result
code = "0 <= 4 < 4 and 0 <= 3 < 4"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert not result
code = "0 <= 3 < 4 and 0 <= 3 < 4"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result
def test_print_output(self):
code = "print('Hello world!')\nprint('Ok no one cares')"
state = {}
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state=state)
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state=state)
assert result is None
assert state["print_outputs"] == "Hello world!\nOk no one cares\n"
@@ -525,7 +526,7 @@ function()"""
def test_tuple_target_in_iterator(self):
code = "for a, b in [('Ralf Weikert', 'Austria'), ('Samuel Seungwon Lee', 'South Korea')]:res = a.split()[0]"
- result = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
+ result, _ = evaluate_python_code(code, BASE_PYTHON_TOOLS, state={})
assert result == "Samuel"
def test_classes(self):
@@ -618,7 +619,7 @@ def var_args_method(self, *args, **kwargs):
var_args_method(1, 2, 3, x=4, y=5)
"""
state = {}
- result = evaluate_python_code(code, {"sum": sum}, state=state)
+ result, _ = evaluate_python_code(code, {"sum": sum}, state=state)
assert result == 15
def test_exceptions(self):
@@ -648,7 +649,7 @@ except ValueError as e:
def test_types_as_objects(self):
code = "type_a = float(2); type_b = str; type_c = int"
state = {}
- result = evaluate_python_code(
+ result, is_final_answer = evaluate_python_code(
code, {"float": float, "str": str, "int": int}, state=state
)
assert result is int
@@ -659,7 +660,7 @@ food_items = {"apple": 2, "banana": 3, "orange": 1, "pear": 1}
unique_food_items = [item for item, count in food_item_counts.items() if count == 1]
"""
state = {}
- result = evaluate_python_code(code, {}, state=state)
+ result, is_final_answer = evaluate_python_code(code, {}, state=state)
assert result == ["orange", "pear"]
def test_nonsimple_augassign(self):
@@ -742,8 +743,9 @@ def f(a, b=333, n=1000):
return b + n
n = f(1, n=667)
"""
- res = evaluate_python_code(code, {}, {})
+ res, is_final_answer = evaluate_python_code(code, {}, {})
assert res == 1000
+ assert not is_final_answer
def test_set(self):
code = """
@@ -767,8 +769,11 @@ while True:
break
i"""
- result = evaluate_python_code(code, {"print": print, "round": round}, state={})
+ result, is_final_answer = evaluate_python_code(
+ code, {"print": print, "round": round}, state={}
+ )
assert result == 3
+ assert not is_final_answer
def test_return(self):
# test early returns
@@ -781,7 +786,7 @@ def add_one(n, shift):
add_one(1, 1)
"""
state = {}
- result = evaluate_python_code(
+ result, is_final_answer = evaluate_python_code(
code, {"print": print, "range": range, "ord": ord, "chr": chr}, state=state
)
assert result == 2
@@ -794,7 +799,7 @@ def returns_none(a):
returns_none(1)
"""
state = {}
- result = evaluate_python_code(
+ result, is_final_answer = evaluate_python_code(
code, {"print": print, "range": range, "ord": ord, "chr": chr}, state=state
)
assert result is None
@@ -812,7 +817,7 @@ out = [i for sublist in all_res for i in sublist]
out[:10]
"""
state = {}
- result = evaluate_python_code(
+ result, is_final_answer = evaluate_python_code(
code, {"print": print, "range": range}, state=state
)
assert result == [0, 0, 1, 0, 1, 2, 0, 1, 2, 3]
@@ -829,7 +834,7 @@ parts_with_5_set_count = df[df['SetCount'] == 5.0]
parts_with_5_set_count[['Quantity', 'SetCount']].values[1]
"""
state = {}
- result = evaluate_python_code(
+ result, _ = evaluate_python_code(
code, {}, state=state, authorized_imports=["pandas"]
)
assert np.array_equal(result, [-1, 5])
@@ -842,7 +847,7 @@ df = pd.DataFrame.from_dict({"AtomicNumber": [111, 104, 105], "ok": [0, 1, 2]})
# Filter the DataFrame to get only the rows with outdated atomic numbers
filtered_df = df.loc[df['AtomicNumber'].isin([104])]
"""
- result = evaluate_python_code(
+ result, _ = evaluate_python_code(
code, {"print": print}, state={}, authorized_imports=["pandas"]
)
assert np.array_equal(result.values[0], [104, 1])
@@ -855,7 +860,9 @@ data = pd.DataFrame.from_dict([
])
survival_rate_by_class = data.groupby('Pclass')['Survived'].mean()
"""
- result = evaluate_python_code(code, {}, state={}, authorized_imports=["pandas"])
+ result, _ = evaluate_python_code(
+ code, {}, state={}, authorized_imports=["pandas"]
+ )
assert result.values[1] == 0.5
def test_starred(self):
@@ -877,7 +884,7 @@ coords_barcelona = (41.3869, 2.1660)
distance_geneva_barcelona = haversine(*coords_geneva, *coords_barcelona)
"""
- result = evaluate_python_code(
+ result, _ = evaluate_python_code(
code, {"print": print, "map": map}, state={}, authorized_imports=["math"]
)
assert round(result, 1) == 622395.4
@@ -894,5 +901,42 @@ for worker, (start, end) in shifts.items():
shift_intervals[worker] = end
shift_intervals
"""
- result = evaluate_python_code(code, {"print": print, "map": map}, state={})
+ result, _ = evaluate_python_code(code, {"print": print, "map": map}, state={})
assert result == {"Worker A": "8:00 pm", "Worker B": "11:45 am"}
+
+ def test_fix_final_answer_code(self):
+ test_cases = [
+ (
+ "final_answer = 3.21\nfinal_answer(final_answer)",
+ "final_answer_variable = 3.21\nfinal_answer(final_answer_variable)",
+ ),
+ (
+ "x = final_answer(5)\nfinal_answer = x + 1\nfinal_answer(final_answer)",
+ "x = final_answer(5)\nfinal_answer_variable = x + 1\nfinal_answer(final_answer_variable)",
+ ),
+ (
+ "def func():\n final_answer = 42\n return final_answer(final_answer)",
+ "def func():\n final_answer_variable = 42\n return final_answer(final_answer_variable)",
+ ),
+ (
+ "final_answer(5) # Should not change function calls",
+ "final_answer(5) # Should not change function calls",
+ ),
+ (
+ "obj.final_answer = 5 # Should not change object attributes",
+ "obj.final_answer = 5 # Should not change object attributes",
+ ),
+ (
+ "final_answer=3.21;final_answer(final_answer)",
+ "final_answer_variable=3.21;final_answer(final_answer_variable)",
+ ),
+ ]
+
+ for i, (input_code, expected) in enumerate(test_cases, 1):
+ result = fix_final_answer_code(input_code)
+ assert result == expected, f"""
+ Test case {i} failed:
+ Input: {input_code}
+ Expected: {expected}
+ Got: {result}
+ """
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 4bd0f81..1ec6343 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,86 +1,39 @@
-import os
-import shutil
-import tempfile
+# coding=utf-8
+# Copyright 2024 HuggingFace Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
import unittest
-from pathlib import Path
+import pytest
+
+from smolagents.utils import parse_code_blob
-def str_to_bool(value) -> int:
- """
- Converts a string representation of truth to `True` (1) or `False` (0).
+class AgentTextTests(unittest.TestCase):
+ def test_parse_code_blob(self):
+ with pytest.raises(ValueError):
+ parse_code_blob("Wrong blob!")
- True values are `y`, `yes`, `t`, `true`, `on`, and `1`; False value are `n`, `no`, `f`, `false`, `off`, and `0`;
- """
- value = value.lower()
- if value in ("y", "yes", "t", "true", "on", "1"):
- return 1
- elif value in ("n", "no", "f", "false", "off", "0"):
- return 0
- else:
- raise ValueError(f"invalid truth value {value}")
+ # Parsing mardkwon with code blobs should work
+ output = parse_code_blob("""
+Here is how to solve the problem:
+Code:
+```py
+import numpy as np
+```
+""")
+ assert output == "import numpy as np"
-
-def get_int_from_env(env_keys, default):
- """Returns the first positive env value found in the `env_keys` list or the default."""
- for e in env_keys:
- val = int(os.environ.get(e, -1))
- if val >= 0:
- return val
- return default
-
-
-def parse_flag_from_env(key, default=False):
- """Returns truthy value for `key` from the env if available else the default."""
- value = os.environ.get(key, str(default))
- return (
- str_to_bool(value) == 1
- ) # As its name indicates `str_to_bool` actually returns an int...
-
-
-_run_slow_tests = parse_flag_from_env("RUN_SLOW", default=False)
-
-
-def skip(test_case):
- "Decorator that skips a test unconditionally"
- return unittest.skip("Test was skipped")(test_case)
-
-
-def slow(test_case):
- """
- Decorator marking a test as slow. Slow tests are skipped by default. Set the RUN_SLOW environment variable to a
- truthy value to run them.
- """
- return unittest.skipUnless(_run_slow_tests, "test is slow")(test_case)
-
-
-class TempDirTestCase(unittest.TestCase):
- """
- A TestCase class that keeps a single `tempfile.TemporaryDirectory` open for the duration of the class, wipes its
- data at the start of a test, and then destroyes it at the end of the TestCase.
-
- Useful for when a class or API requires a single constant folder throughout it's use, such as Weights and Biases
-
- The temporary directory location will be stored in `self.tmpdir`
- """
-
- clear_on_setup = True
-
- @classmethod
- def setUpClass(cls):
- "Creates a `tempfile.TemporaryDirectory` and stores it in `cls.tmpdir`"
- cls.tmpdir = Path(tempfile.mkdtemp())
-
- @classmethod
- def tearDownClass(cls):
- "Remove `cls.tmpdir` after test suite has finished"
- if os.path.exists(cls.tmpdir):
- shutil.rmtree(cls.tmpdir)
-
- def setUp(self):
- "Destroy all contents in `self.tmpdir`, but not `self.tmpdir`"
- if self.clear_on_setup:
- for path in self.tmpdir.glob("**/*"):
- if path.is_file():
- path.unlink()
- elif path.is_dir():
- shutil.rmtree(path)
+ # Parsing code blobs should work
+ code_blob = "import numpy as np"
+ output = parse_code_blob(code_blob)
+ assert output == code_blob