Handling Tool Errors Gracefully — PokéAPI¶
This notebook demonstrates how the LLMAgent handles tool errors gracefully.
We define a get_pokemon tool that fetches data from the PokéAPI. When a Pokémon name is not found the tool raises a ValueError. We then give the agent a task with a deliberate typo and observe how it recognises the error and self-corrects.
# Uncomment the line below to install `llm-agents-from-scratch` from PyPI
# !pip install llm-agents-from-scratch
Running an Ollama service¶
To execute the code provided in this notebook, you'll need to have Ollama installed on your local machine and have its LLM hosting service running. To download Ollama, follow the instructions found on this page: https://ollama.com/download. After downloading and installing Ollama, you can start a service by opening a terminal and running the command ollama serve.
import os
import shutil
import subprocess
import time
import urllib.error
import urllib.request
def ensure_ollama(host="http://localhost:11434", timeout=15):
"""Start Ollama if not already running and wait until responsive."""
def _up():
try:
urllib.request.urlopen(f"{host}/api/tags", timeout=1)
return True
except (urllib.error.URLError, ConnectionError, TimeoutError):
return False
if _up():
return print(f"✓ Ollama already running at {host}")
# Lightning persistent path first, then standard locations
ollama_path = shutil.which("ollama")
if ollama_path is None:
for candidate in [
"/teamspace/studios/this_studio/.local/bin/ollama",
"/usr/local/bin/ollama",
"/usr/bin/ollama",
]:
if os.path.exists(candidate):
ollama_path = candidate
break
if ollama_path is None:
raise RuntimeError(
"Could not find the ollama binary. Install with: "
"curl -fsSL https://ollama.com/install.sh | sh",
)
print(f"Starting Ollama server ({ollama_path})...")
subprocess.Popen(
[ollama_path, "serve"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
deadline = time.time() + timeout
while time.time() < deadline:
if _up():
return print(f"✓ Ollama up and running at {host}")
time.sleep(0.5)
raise RuntimeError(f"Ollama did not start within {timeout}s")
ensure_ollama()
✓ Ollama already running at http://localhost:11434
Defining the Tool¶
import json
import urllib.error
import urllib.request
from llm_agents_from_scratch.tools.simple_function import SimpleFunctionTool
def get_pokemon(name: str) -> str:
"""Look up a Pokémon by name and return its types and base stats."""
url = f"https://pokeapi.co/api/v2/pokemon/{name.lower().strip()}"
req = urllib.request.Request(
url,
headers={"User-Agent": "llm-agents-from-scratch/1.0"},
)
try:
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
except urllib.error.HTTPError as e:
if e.code == 404: # noqa: PLR2004
raise ValueError(
f"Pokémon '{name}' not found. "
"Check the spelling and try again.",
) from e
raise
types = [t["type"]["name"] for t in data["types"]]
stats = {s["stat"]["name"]: s["base_stat"] for s in data["stats"]}
return json.dumps({"name": data["name"], "types": types, "stats": stats})
get_pokemon_tool = SimpleFunctionTool(func=get_pokemon)
Example 1 — Successful Lookup¶
A straightforward lookup with the correct name.
import logging
from llm_agents_from_scratch import LLMAgent
from llm_agents_from_scratch.data_structures import Task
from llm_agents_from_scratch.llms import OllamaLLM
from llm_agents_from_scratch.logger import enable_console_logging
enable_console_logging(logging.INFO)
llm = OllamaLLM(model="qwen3:14b", think=False)
agent = LLMAgent(llm=llm, tools=[get_pokemon_tool])
task = Task(instruction="What type is Pikachu and what are its base stats?")
result = await agent.run(task)
INFO (llm_agents_fs.LLMAgent) : 🚀 Starting task: What type is Pikachu and what are its base stats?
INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: What type is Pikachu and what are its base stats?
INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: I need to call the get_pokemon tool with the name "Pikachu" to retrieve its type and base stats.
INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Call the get_pokemon tool with the name 'Pikachu' to retrieve its type and base stats.
INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Call the get_pokemon tool with the name 'Pikachu' to retrieve its type and base stats.
INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: get_pokemon
INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: {"name": "pikachu", "types": ["electric"], "stats": {"hp": 35, "attack": 55, "defense": 40, "special-attack": 50, "special-def...[TRUNCATED]
INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: Pikachu is an Electric-type Pokémon. Its base stats are as follows:
- HP: 35
- Attack: 55
- Defense: 40
- Special Attack: 50
- Special...[TRUNCATED]
INFO (llm_agents_fs.TaskHandler) : No new step required.
INFO (llm_agents_fs.LLMAgent) : 🏁 Task completed: Pikachu is an Electric-type Pokémon. Its base stats are as follows:
- HP: 35
- Attack: 55
- Defense: 40
- Special Attack: 50
- Spec...[TRUNCATED]
print(result)
Pikachu is an Electric-type Pokémon. Its base stats are as follows: - HP: 35 - Attack: 55 - Defense: 40 - Special Attack: 50 - Special Defense: 50 - Speed: 90
Example 2 — Graceful Error Recovery¶
We give the agent a deliberate typo ('Pikachuu'). The tool returns a ValueError and the agent observes the error message, self-corrects the spelling, and retries.
llm = OllamaLLM(model="qwen3:14b", think=False)
agent = LLMAgent(llm=llm, tools=[get_pokemon_tool])
task = Task(instruction="What type is Pikachuu?")
result = await agent.run(task)
INFO (llm_agents_fs.LLMAgent) : 🚀 Starting task: What type is Pikachuu?
INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: What type is Pikachuu?
INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: I need to call the get_pokemon tool with the name "Pikachuu" to find out its type.
INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Call the get_pokemon tool with the name 'Pikachuu' to retrieve its type information.
INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Call the get_pokemon tool with the name 'Pikachuu' to retrieve its type information.
INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: get_pokemon
INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: {"error_type": "ValueError", "message": "Internal error while executing tool: Pok\u00e9mon 'Pikachuu' not found. Check the spe...[TRUNCATED]
INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: It seems that there was an error because the Pokémon "Pikachuu" could not be found. This might be due to a typo or incorrect spelling. ...[TRUNCATED]
INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Call the get_pokemon tool with the name 'Pikachu' to retrieve its type information.
INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Call the get_pokemon tool with the name 'Pikachu' to retrieve its type information.
INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: get_pokemon
INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: {"name": "pikachu", "types": ["electric"], "stats": {"hp": 35, "attack": 55, "defense": 40, "special-attack": 50, "special-def...[TRUNCATED]
INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: The Pokémon "Pikachu" is of the **electric** type.
INFO (llm_agents_fs.TaskHandler) : No new step required.
INFO (llm_agents_fs.LLMAgent) : 🏁 Task completed: The Pokémon "Pikachu" is of the **electric** type.
print(result)
The Pokémon "Pikachu" is of the **electric** type.