Skip to content

Commit cb9830a

Browse files
Support OpenTelemetry (#136)
* Support OpenTelemetry
1 parent cf04285 commit cb9830a

File tree

9 files changed

+117
-226
lines changed

9 files changed

+117
-226
lines changed

docs/source/en/reference/tools.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ contains the API docs for the underlying classes.
3939

4040
[[autodoc]] Tool
4141

42-
### Toolbox
43-
44-
[[autodoc]] Toolbox
45-
4642
### launch_gradio_demo
4743

4844
[[autodoc]] launch_gradio_demo

docs/source/en/tutorials/tools.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ from smolagents import HfApiModel
187187
model = HfApiModel("Qwen/Qwen2.5-Coder-32B-Instruct")
188188

189189
agent = CodeAgent(tools=[], model=model, add_base_tools=True)
190-
agent.toolbox.add_tool(model_download_tool)
190+
agent.tools.append(model_download_tool)
191191
```
192192
Now we can leverage the new tool:
193193

@@ -202,11 +202,6 @@ agent.run(
202202
> Beware of not adding too many tools to an agent: this can overwhelm weaker LLM engines.
203203
204204

205-
Use the `agent.toolbox.update_tool()` method to replace an existing tool in the agent's toolbox.
206-
This is useful if your new tool is a one-to-one replacement of the existing tool because the agent already knows how to perform that specific task.
207-
Just make sure the new tool follows the same API as the replaced tool or adapt the system prompt template to ensure all examples using the replaced tool are updated.
208-
209-
210205
### Use a collection of tools
211206

212207
You can leverage tool collections by using the ToolCollection object, with the slug of the collection you want to use.

src/smolagents/agents.py

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@
1818
from dataclasses import dataclass
1919
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
2020

21+
from rich import box
2122
from rich.console import Group
2223
from rich.panel import Panel
2324
from rich.rule import Rule
2425
from rich.syntax import Syntax
2526
from rich.text import Text
2627

27-
from .default_tools import FinalAnswerTool
28+
from .default_tools import FinalAnswerTool, TOOL_MAPPING
2829
from .e2b_executor import E2BExecutor
2930
from .local_python_executor import (
3031
BASE_BUILTIN_MODULES,
@@ -49,7 +50,6 @@
4950
from .tools import (
5051
DEFAULT_TOOL_DESCRIPTION_TEMPLATE,
5152
Tool,
52-
Toolbox,
5353
get_tool_description_with_args,
5454
)
5555
from .types import AgentAudio, AgentImage, handle_agent_output_types
@@ -107,18 +107,27 @@ class SystemPromptStep(AgentStep):
107107
system_prompt: str
108108

109109

110+
def get_tool_descriptions(
111+
tools: Dict[str, Tool], tool_description_template: str
112+
) -> str:
113+
return "\n".join(
114+
[
115+
get_tool_description_with_args(tool, tool_description_template)
116+
for tool in tools.values()
117+
]
118+
)
119+
120+
110121
def format_prompt_with_tools(
111-
toolbox: Toolbox, prompt_template: str, tool_description_template: str
122+
tools: Dict[str, Tool], prompt_template: str, tool_description_template: str
112123
) -> str:
113-
tool_descriptions = toolbox.show_tool_descriptions(tool_description_template)
124+
tool_descriptions = get_tool_descriptions(tools, tool_description_template)
114125
prompt = prompt_template.replace("{{tool_descriptions}}", tool_descriptions)
115-
116126
if "{{tool_names}}" in prompt:
117127
prompt = prompt.replace(
118128
"{{tool_names}}",
119-
", ".join([f"'{tool_name}'" for tool_name in toolbox.tools.keys()]),
129+
", ".join([f"'{tool.name}'" for tool in tools.values()]),
120130
)
121-
122131
return prompt
123132

124133

@@ -163,7 +172,7 @@ class MultiStepAgent:
163172

164173
def __init__(
165174
self,
166-
tools: Union[List[Tool], Toolbox],
175+
tools: List[Tool],
167176
model: Callable[[List[Dict[str, str]]], str],
168177
system_prompt: Optional[str] = None,
169178
tool_description_template: Optional[str] = None,
@@ -172,7 +181,7 @@ def __init__(
172181
add_base_tools: bool = False,
173182
verbose: bool = False,
174183
grammar: Optional[Dict[str, str]] = None,
175-
managed_agents: Optional[Dict] = None,
184+
managed_agents: Optional[List] = None,
176185
step_callbacks: Optional[List[Callable]] = None,
177186
planning_interval: Optional[int] = None,
178187
):
@@ -196,17 +205,18 @@ def __init__(
196205

197206
self.managed_agents = {}
198207
if managed_agents is not None:
208+
print("NOTNONE")
199209
self.managed_agents = {agent.name: agent for agent in managed_agents}
200210

201-
if isinstance(tools, Toolbox):
202-
self._toolbox = tools
203-
if add_base_tools:
204-
self._toolbox.add_base_tools(
205-
add_python_interpreter=(self.__class__ == ToolCallingAgent)
206-
)
207-
else:
208-
self._toolbox = Toolbox(tools, add_base_tools=add_base_tools)
209-
self._toolbox.add_tool(FinalAnswerTool())
211+
self.tools = {tool.name: tool for tool in tools}
212+
if add_base_tools:
213+
for tool_name, tool_class in TOOL_MAPPING.items():
214+
if (
215+
tool_name != "python_interpreter"
216+
or self.__class__.__name__ == "ToolCallingAgent"
217+
):
218+
self.tools[tool_name] = tool_class()
219+
self.tools["final_answer"] = FinalAnswerTool()
210220

211221
self.system_prompt = self.initialize_system_prompt()
212222
self.input_messages = None
@@ -217,14 +227,9 @@ def __init__(
217227
self.step_callbacks = step_callbacks if step_callbacks is not None else []
218228
self.step_callbacks.append(self.monitor.update_metrics)
219229

220-
@property
221-
def toolbox(self) -> Toolbox:
222-
"""Get the toolbox currently available to the agent"""
223-
return self._toolbox
224-
225230
def initialize_system_prompt(self):
226231
self.system_prompt = format_prompt_with_tools(
227-
self._toolbox,
232+
self.tools,
228233
self.system_prompt_template,
229234
self.tool_description_template,
230235
)
@@ -384,10 +389,10 @@ def execute_tool_call(
384389
This method replaces arguments with the actual values from the state if they refer to state variables.
385390
386391
Args:
387-
tool_name (`str`): Name of the Tool to execute (should be one from self.toolbox).
392+
tool_name (`str`): Name of the Tool to execute (should be one from self.tools).
388393
arguments (Dict[str, str]): Arguments passed to the Tool.
389394
"""
390-
available_tools = {**self.toolbox.tools, **self.managed_agents}
395+
available_tools = {**self.tools, **self.managed_agents}
391396
if tool_name not in available_tools:
392397
error_msg = f"Unknown tool {tool_name}, should be instead one of {list(available_tools.keys())}."
393398
raise AgentExecutionError(error_msg)
@@ -415,7 +420,7 @@ def execute_tool_call(
415420
raise AgentExecutionError(error_msg)
416421
return observation
417422
except Exception as e:
418-
if tool_name in self.toolbox.tools:
423+
if tool_name in self.tools:
419424
tool_description = get_tool_description_with_args(
420425
available_tools[tool_name]
421426
)
@@ -512,20 +517,26 @@ def stream_run(self, task: str):
512517
Runs the agent in streaming mode, yielding steps as they are executed: should be launched only in the `run` method.
513518
"""
514519
final_answer = None
515-
step_number = 0
516-
while final_answer is None and step_number < self.max_steps:
520+
self.step_number = 0
521+
while final_answer is None and self.step_number < self.max_steps:
517522
step_start_time = time.time()
518-
step_log = ActionStep(step=step_number, start_time=step_start_time)
523+
step_log = ActionStep(step=self.step_number, start_time=step_start_time)
519524
try:
520525
if (
521526
self.planning_interval is not None
522-
and step_number % self.planning_interval == 0
527+
and self.step_number % self.planning_interval == 0
523528
):
524529
self.planning_step(
525-
task, is_first_step=(step_number == 0), step=step_number
530+
task,
531+
is_first_step=(self.step_number == 0),
532+
step=self.step_number,
526533
)
527534
console.print(
528-
Rule(f"[bold]Step {step_number}", characters="━", style=YELLOW_HEX)
535+
Rule(
536+
f"[bold]Step {self.step_number}",
537+
characters="━",
538+
style=YELLOW_HEX,
539+
)
529540
)
530541

531542
# Run one step!
@@ -538,10 +549,10 @@ def stream_run(self, task: str):
538549
self.logs.append(step_log)
539550
for callback in self.step_callbacks:
540551
callback(step_log)
541-
step_number += 1
552+
self.step_number += 1
542553
yield step_log
543554

544-
if final_answer is None and step_number == self.max_steps:
555+
if final_answer is None and self.step_number == self.max_steps:
545556
error_message = "Reached max steps."
546557
final_step_log = ActionStep(error=AgentMaxStepsError(error_message))
547558
self.logs.append(final_step_log)
@@ -561,20 +572,26 @@ def direct_run(self, task: str):
561572
Runs the agent in direct mode, returning outputs only at the end: should be launched only in the `run` method.
562573
"""
563574
final_answer = None
564-
step_number = 0
565-
while final_answer is None and step_number < self.max_steps:
575+
self.step_number = 0
576+
while final_answer is None and self.step_number < self.max_steps:
566577
step_start_time = time.time()
567-
step_log = ActionStep(step=step_number, start_time=step_start_time)
578+
step_log = ActionStep(step=self.step_number, start_time=step_start_time)
568579
try:
569580
if (
570581
self.planning_interval is not None
571-
and step_number % self.planning_interval == 0
582+
and self.step_number % self.planning_interval == 0
572583
):
573584
self.planning_step(
574-
task, is_first_step=(step_number == 0), step=step_number
585+
task,
586+
is_first_step=(self.step_number == 0),
587+
step=self.step_number,
575588
)
576589
console.print(
577-
Rule(f"[bold]Step {step_number}", characters="━", style=YELLOW_HEX)
590+
Rule(
591+
f"[bold]Step {self.step_number}",
592+
characters="━",
593+
style=YELLOW_HEX,
594+
)
578595
)
579596

580597
# Run one step!
@@ -589,9 +606,9 @@ def direct_run(self, task: str):
589606
self.logs.append(step_log)
590607
for callback in self.step_callbacks:
591608
callback(step_log)
592-
step_number += 1
609+
self.step_number += 1
593610

594-
if final_answer is None and step_number == self.max_steps:
611+
if final_answer is None and self.step_number == self.max_steps:
595612
error_message = "Reached max steps."
596613
final_step_log = ActionStep(error=AgentMaxStepsError(error_message))
597614
self.logs.append(final_step_log)
@@ -637,8 +654,8 @@ def planning_step(self, task, is_first_step: bool, step: int):
637654
"role": MessageRole.USER,
638655
"content": USER_PROMPT_PLAN.format(
639656
task=task,
640-
tool_descriptions=self._toolbox.show_tool_descriptions(
641-
self.tool_description_template
657+
tool_descriptions=get_tool_descriptions(
658+
self.tools, self.tool_description_template
642659
),
643660
managed_agents_descriptions=(
644661
show_agents_descriptions(self.managed_agents)
@@ -692,8 +709,8 @@ def planning_step(self, task, is_first_step: bool, step: int):
692709
"role": MessageRole.USER,
693710
"content": USER_PROMPT_PLAN_UPDATE.format(
694711
task=task,
695-
tool_descriptions=self._toolbox.show_tool_descriptions(
696-
self.tool_description_template
712+
tool_descriptions=get_tool_descriptions(
713+
self.tools, self.tool_description_template
697714
),
698715
managed_agents_descriptions=(
699716
show_agents_descriptions(self.managed_agents)
@@ -761,7 +778,7 @@ def step(self, log_entry: ActionStep) -> Union[None, Any]:
761778
try:
762779
tool_name, tool_arguments, tool_call_id = self.model.get_tool_call(
763780
self.input_messages,
764-
available_tools=list(self.toolbox._tools.values()),
781+
available_tools=list(self.tools.values()),
765782
stop_sequences=["Observation:"],
766783
)
767784
except Exception as e:
@@ -856,7 +873,7 @@ def __init__(
856873
f"You passed both {use_e2b_executor=} and some managed agents. Managed agents is not yet supported with remote code execution."
857874
)
858875

859-
all_tools = {**self.toolbox.tools, **self.managed_agents}
876+
all_tools = {**self.tools, **self.managed_agents}
860877
if use_e2b_executor:
861878
self.python_executor = E2BExecutor(
862879
self.additional_authorized_imports, list(all_tools.values())
@@ -941,10 +958,10 @@ def step(self, log_entry: ActionStep) -> Union[None, Any]:
941958
lexer="python",
942959
theme="monokai",
943960
word_wrap=True,
944-
line_numbers=True,
945961
),
946962
title="[bold]Executing this code:",
947963
title_align="left",
964+
box=box.HORIZONTALS,
948965
)
949966
)
950967
observation = ""
@@ -1045,5 +1062,4 @@ def __call__(self, request, **kwargs):
10451062
"MultiStepAgent",
10461063
"CodeAgent",
10471064
"ToolCallingAgent",
1048-
"Toolbox",
10491065
]

src/smolagents/default_tools.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,15 @@ def decode(self, outputs):
322322
return self.pre_processor.batch_decode(outputs, skip_special_tokens=True)[0]
323323

324324

325+
TOOL_MAPPING = {
326+
tool_class.name: tool_class
327+
for tool_class in [
328+
PythonInterpreterTool,
329+
DuckDuckGoSearchTool,
330+
VisitWebpageTool,
331+
]
332+
}
333+
325334
__all__ = [
326335
"PythonInterpreterTool",
327336
"FinalAnswerTool",

src/smolagents/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ def generate(
157157
):
158158
raise NotImplementedError
159159

160+
def get_tool_call(
161+
self,
162+
messages: List[Dict[str, str]],
163+
available_tools: List[Tool],
164+
stop_sequences,
165+
):
166+
raise NotImplementedError
167+
160168
def __call__(
161169
self,
162170
messages: List[Dict[str, str]],

0 commit comments

Comments
 (0)