Skip to content

Commit

Permalink
Simplify managed agents
Browse files Browse the repository at this point in the history
  • Loading branch information
aymeric-roucher committed Feb 3, 2025
1 parent 42f95d8 commit aafa5ae
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 109 deletions.
32 changes: 11 additions & 21 deletions docs/source/en/examples/multiagents.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ rendered properly in your Markdown viewer.

In this notebook we will make a **multi-agent web browser: an agentic system with several agents collaborating to solve problems using the web!**

It will be a simple hierarchy, using a `ManagedAgent` object to wrap the managed web search agent:
It will be a simple hierarchy:

```
+----------------+
Expand All @@ -28,15 +28,12 @@ It will be a simple hierarchy, using a `ManagedAgent` object to wrap the managed
|
_______________|______________
| |
Code interpreter +--------------------------------+
tool | Managed agent |
| +------------------+ |
| | Web Search agent | |
| +------------------+ |
| | | |
| Web Search tool | |
| Visit webpage tool |
+--------------------------------+
Code Interpreter +------------------+
tool | Web Search agent |
+------------------+
| |
Web Search tool |
Visit webpage tool
```
Let's set up this system.

Expand Down Expand Up @@ -127,7 +124,6 @@ from smolagents import (
CodeAgent,
ToolCallingAgent,
HfApiModel,
ManagedAgent,
DuckDuckGoSearchTool,
LiteLLMModel,
)
Expand All @@ -138,20 +134,14 @@ web_agent = ToolCallingAgent(
tools=[DuckDuckGoSearchTool(), visit_webpage],
model=model,
max_steps=10,
)
```

We then wrap this agent into a `ManagedAgent` that will make it callable by its manager agent.

```py
managed_web_agent = ManagedAgent(
agent=web_agent,
name="search",
description="Runs web searches for you. Give it your query as an argument.",
)
```

Finally we create a manager agent, and upon initialization we pass our managed agent to it in its `managed_agents` argument.
Note that we gave this agent attributes `name` and `description`, mandatory attributes to make this agent callable by its manager agent.

Then we create a manager agent, and upon initialization we pass our managed agent to it in its `managed_agents` argument.

Since this agent is the one tasked with the planning and thinking, advanced reasoning will be beneficial, so a `CodeAgent` will be the best choice.

Expand All @@ -161,7 +151,7 @@ Also, we want to ask a question that involves the current year and does addition
manager_agent = CodeAgent(
tools=[],
model=model,
managed_agents=[managed_web_agent],
managed_agents=[web_agent],
additional_authorized_imports=["time", "numpy", "pandas"],
)
```
Expand Down
2 changes: 1 addition & 1 deletion docs/source/en/reference/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Both require arguments `model` and list of tools `tools` at initialization.

### ManagedAgent

[[autodoc]] ManagedAgent
_This class is deprecated since 1.8.0: now you simply need to pass attributes `name` and `description` to a normal agent to make it callable by a manager agent._

### stream_to_gradio

Expand Down
11 changes: 4 additions & 7 deletions docs/source/en/tutorials/inspect_runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,24 @@ Then you can run your agents!
from smolagents import (
CodeAgent,
ToolCallingAgent,
ManagedAgent,
DuckDuckGoSearchTool,
VisitWebpageTool,
HfApiModel,
)

model = HfApiModel()

agent = ToolCallingAgent(
search_agent = ToolCallingAgent(
tools=[DuckDuckGoSearchTool(), VisitWebpageTool()],
model=model,
)
managed_agent = ManagedAgent(
agent=agent,
name="managed_agent",
name="search_agent",
description="This is an agent that can do web search.",
)

manager_agent = CodeAgent(
tools=[],
model=model,
managed_agents=[managed_agent],
managed_agents=[search_agent],
)
manager_agent.run(
"If the US keeps its 2024 growth rate, how many years will it take for the GDP to double?"
Expand Down
2 changes: 1 addition & 1 deletion docs/source/zh/reference/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Both require arguments `model` and list of tools `tools` at initialization.

### ManagedAgent

[[autodoc]] ManagedAgent
_This class is deprecated since 1.8.0: now you just need to pass name and description attributes to an agent to use it as a ManagedAgent._

### stream_to_gradio

Expand Down
11 changes: 4 additions & 7 deletions examples/inspect_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
CodeAgent,
DuckDuckGoSearchTool,
HfApiModel,
ManagedAgent,
ToolCallingAgent,
VisitWebpageTool,
)
Expand All @@ -23,18 +22,16 @@
# Then we run the agentic part!
model = HfApiModel()

agent = ToolCallingAgent(
search_agent = ToolCallingAgent(
tools=[DuckDuckGoSearchTool(), VisitWebpageTool()],
model=model,
)
managed_agent = ManagedAgent(
agent=agent,
name="managed_agent",
name="search_agent",
description="This is an agent that can do web search.",
)

manager_agent = CodeAgent(
tools=[],
model=model,
managed_agents=[managed_agent],
managed_agents=[search_agent],
)
manager_agent.run("If the US keeps it 2024 growth rate, how many years would it take for the GDP to double?")
91 changes: 34 additions & 57 deletions src/smolagents/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
from .monitoring import Monitor
from .prompts import (
CODE_SYSTEM_PROMPT,
MANAGED_AGENT_PROMPT,
PLAN_UPDATE_FINAL_PLAN_REDACTION,
SYSTEM_PROMPT_FACTS,
SYSTEM_PROMPT_FACTS_UPDATE,
Expand Down Expand Up @@ -140,6 +139,9 @@ class MultiStepAgent:
managed_agents (`list`, *optional*): Managed agents that the agent can call.
step_callbacks (`list[Callable]`, *optional*): Callbacks that will be called at each step.
planning_interval (`int`, *optional*): Interval at which the agent will run a planning step.
name (`str`, *optional*): Necessary for a managed agent only - the name by which this agent can be called.
description (`str`, *optional*): Necessary for a managed agent only - the description of this agent.
managed_agent_prompt (`str`, *optional*): Custom prompt for the managed agent. Defaults to None.
"""

def __init__(
Expand All @@ -156,6 +158,9 @@ def __init__(
managed_agents: Optional[List] = None,
step_callbacks: Optional[List[Callable]] = None,
planning_interval: Optional[int] = None,
name: Optional[str] = None,
description: Optional[str] = None,
managed_agent_prompt: Optional[str] = None,
):
if system_prompt is None:
system_prompt = CODE_SYSTEM_PROMPT
Expand All @@ -172,9 +177,16 @@ def __init__(
self.grammar = grammar
self.planning_interval = planning_interval
self.state = {}
self.name = name
self.description = description
self.managed_agent_prompt = managed_agent_prompt

self.managed_agents = {}
if managed_agents is not None:
for managed_agent in managed_agents:
assert managed_agent.name and managed_agent.description, (
"All managed agents need both a name and a description!"
)
self.managed_agents = {agent.name: agent for agent in managed_agents}

for tool in tools:
Expand Down Expand Up @@ -638,6 +650,26 @@ def replay(self, detailed: bool = False):
"""
self.memory.replay(self.logger, detailed=detailed)

def __call__(self, request, provide_run_summary=False, **kwargs):
"""Adds additional prompting for the managed agent, and runs it."""
full_task = self.managed_agent_prompt.format(name=self.name, task=request)
if self.additional_prompting:
full_task = full_task.replace("\n{additional_prompting}", self.additional_prompting).strip()
else:
full_task = full_task.replace("\n{additional_prompting}", "").strip()
output = self.agent.run(full_task, **kwargs)
if provide_run_summary:
answer = f"Here is the final answer from your managed agent '{self.name}':\n"
answer += str(output)
answer += f"\n\nFor more detail, find below a summary of this agent's work:\nSUMMARY OF WORK FROM AGENT '{self.name}':\n"
for message in self.agent.write_memory_to_messages(summary_mode=True):
content = message["content"]
answer += "\n" + truncate_content(str(content)) + "\n---"
answer += f"\nEND OF SUMMARY OF WORK FROM AGENT '{self.name}'."
return answer
else:
return output


class ToolCallingAgent(MultiStepAgent):
"""
Expand Down Expand Up @@ -928,59 +960,4 @@ def step(self, memory_step: ActionStep) -> Union[None, Any]:
return output if is_final_answer else None


class ManagedAgent:
"""
ManagedAgent class that manages an agent and provides additional prompting and run summaries.
Args:
agent (`object`): The agent to be managed.
name (`str`): The name of the managed agent.
description (`str`): A description of the managed agent.
additional_prompting (`Optional[str]`, *optional*): Additional prompting for the managed agent. Defaults to None.
provide_run_summary (`bool`, *optional*): Whether to provide a run summary after the agent completes its task. Defaults to False.
managed_agent_prompt (`Optional[str]`, *optional*): Custom prompt for the managed agent. Defaults to None.
"""

def __init__(
self,
agent,
name,
description,
additional_prompting: Optional[str] = None,
provide_run_summary: bool = False,
managed_agent_prompt: Optional[str] = None,
):
self.agent = agent
self.name = name
self.description = description
self.additional_prompting = additional_prompting
self.provide_run_summary = provide_run_summary
self.managed_agent_prompt = managed_agent_prompt if managed_agent_prompt else MANAGED_AGENT_PROMPT

def write_full_task(self, task):
"""Adds additional prompting for the managed agent, like 'add more detail in your answer'."""
full_task = self.managed_agent_prompt.format(name=self.name, task=task)
if self.additional_prompting:
full_task = full_task.replace("\n{additional_prompting}", self.additional_prompting).strip()
else:
full_task = full_task.replace("\n{additional_prompting}", "").strip()
return full_task

def __call__(self, request, **kwargs):
full_task = self.write_full_task(request)
output = self.agent.run(full_task, **kwargs)
if self.provide_run_summary:
answer = f"Here is the final answer from your managed agent '{self.name}':\n"
answer += str(output)
answer += f"\n\nFor more detail, find below a summary of this agent's work:\nSUMMARY OF WORK FROM AGENT '{self.name}':\n"
for message in self.agent.write_memory_to_messages(summary_mode=True):
content = message["content"]
answer += "\n" + truncate_content(str(content)) + "\n---"
answer += f"\nEND OF SUMMARY OF WORK FROM AGENT '{self.name}'."
return answer
else:
return output


__all__ = ["ManagedAgent", "MultiStepAgent", "CodeAgent", "ToolCallingAgent", "AgentMemory"]
__all__ = ["MultiStepAgent", "CodeAgent", "ToolCallingAgent", "AgentMemory"]
23 changes: 8 additions & 15 deletions tests/test_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from smolagents.agents import (
AgentMaxStepsError,
CodeAgent,
ManagedAgent,
MultiStepAgent,
ToolCall,
ToolCallingAgent,
Expand Down Expand Up @@ -465,22 +464,20 @@ def test_function_persistence_across_steps(self):
assert res[0] == 0.5

def test_init_managed_agent(self):
agent = CodeAgent(tools=[], model=fake_code_functiondef)
managed_agent = ManagedAgent(agent, name="managed_agent", description="Empty")
assert managed_agent.name == "managed_agent"
assert managed_agent.description == "Empty"
agent = CodeAgent(tools=[], model=fake_code_functiondef, name="managed_agent", description="Empty")
assert agent.name == "managed_agent"
assert agent.description == "Empty"

def test_agent_description_gets_correctly_inserted_in_system_prompt(self):
agent = CodeAgent(tools=[], model=fake_code_functiondef)
managed_agent = ManagedAgent(agent, name="managed_agent", description="Empty")
managed_agent = CodeAgent(tools=[], model=fake_code_functiondef, name="managed_agent", description="Empty")
manager_agent = CodeAgent(
tools=[],
model=fake_code_functiondef,
managed_agents=[managed_agent],
)
assert "You can also give requests to team members." not in agent.system_prompt
assert "You can also give requests to team members." not in managed_agent.system_prompt
print("ok1")
assert "{{managed_agents_descriptions}}" not in agent.system_prompt
assert "{{managed_agents_descriptions}}" not in managed_agent.system_prompt
assert "You can also give requests to team members." in manager_agent.system_prompt

def test_code_agent_missing_import_triggers_advice_in_error_log(self):
Expand Down Expand Up @@ -587,18 +584,14 @@ def __call__(
tools=[],
model=managed_model,
max_steps=10,
)

managed_web_agent = ManagedAgent(
agent=web_agent,
name="search_agent",
description="Runs web searches for you. Give it your request as an argument. Make the request as detailed as needed, you can ask for thorough reports",
)

manager_code_agent = CodeAgent(
tools=[],
model=manager_model,
managed_agents=[managed_web_agent],
managed_agents=[web_agent],
additional_authorized_imports=["time", "numpy", "pandas"],
)

Expand All @@ -608,7 +601,7 @@ def __call__(
manager_toolcalling_agent = ToolCallingAgent(
tools=[],
model=manager_model,
managed_agents=[managed_web_agent],
managed_agents=[web_agent],
)

report = manager_toolcalling_agent.run("Fake question.")
Expand Down

0 comments on commit aafa5ae

Please sign in to comment.