Skip to content

Commit 50561f8

Browse files
committed
Pass config to tool constructor
1 parent 39f6eb3 commit 50561f8

File tree

17 files changed

+289
-39
lines changed

17 files changed

+289
-39
lines changed

agents.yaml.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ agents:
4444
deepwiki:
4545
url: "https://mcp.deepwiki.com/mcp"
4646

47-
# Tools this agent can use (names from tools section in config)
47+
# Tools: list of names, or dicts with "name" and optional kwargs (passed to tool at runtime)
4848
tools:
4949
- "web_search_tool"
50-
- "extract_page_content_tool"
50+
- {"name": "extract_page_content_tool", "content_limit": 2000}
5151
- "create_report_tool"
5252
- "clarification_tool"
5353
- "generate_plan_tool"

config.yaml.example

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,8 @@ agents:
9999
deepwiki:
100100
url: "https://mcp.deepwiki.com/mcp"
101101

102-
# Tools this agent can use (must be registered in tool registry)
103-
# Recommended: use snake_case format (e.g., "web_search_tool")
104-
# PascalCase format (e.g., "WebSearchTool") is supported for backward compatibility
102+
# Tools: names, or dicts with "name" and optional kwargs (e.g. search settings per tool)
103+
# Example: {"name": "web_search_tool", "max_results": 15, "max_searches": 6}
105104
tools:
106105
- "web_search_tool"
107106
- "extract_page_content_tool"

docs/en/framework/configuration.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,29 @@ tools:
9494

9595
**Using Tools in Agents:**
9696

97+
Each item in the `tools` list can be:
98+
99+
- **String** – tool name (resolved from the `tools:` section or `ToolRegistry`)
100+
- **Object** – dict with required `"name"` and optional parameters passed to the tool at runtime as kwargs (e.g. search settings for search tools)
101+
97102
```yaml
98103
agents:
99104
my_agent:
100105
base_class: "SGRToolCallingAgent"
101106
tools:
102-
- "web_search_tool" # From ToolRegistry
103-
- "reasoning_tool" # From tools section
104-
- "custom_tool" # From tools section
107+
- "web_search_tool"
108+
- "reasoning_tool"
109+
# Per-tool config: name + kwargs (e.g. search settings)
110+
- name: "extract_page_content_tool"
111+
content_limit: 2000
112+
- name: "web_search_tool"
113+
max_results: 15
114+
max_searches: 6
115+
# tavily_api_key, max_searches, etc. can be set here instead of in global search:
105116
```
106117

118+
Search-related settings (`tavily_api_key`, `tavily_api_base_url`, `max_results`, `content_limit`, `max_searches`) can be set globally in `search:` or per-tool in the tool object. Tool kwargs override agent-level `search` for that tool.
119+
107120
!!! note "Tool Resolution Order"
108121
When resolving tools, the system checks in this order:
109122
1. Tools defined in `tools:` section (by name)

examples/sgr_deep_research/agents.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ async def _prepare_tools(self) -> Type[NextStepToolStub]:
6161
tools -= {
6262
ClarificationTool,
6363
}
64-
if self._context.searches_used >= self.config.search.max_searches:
64+
max_searches = self.tool_configs.get(WebSearchTool.tool_name, {}).get("max_searches") or (
65+
self.config.search.max_searches if self.config.search else 4
66+
)
67+
if self._context.searches_used >= max_searches:
6568
tools -= {
6669
WebSearchTool,
6770
}
@@ -102,7 +105,10 @@ async def _prepare_tools(self) -> list[ChatCompletionFunctionToolParam]:
102105
tools -= {
103106
ClarificationTool,
104107
}
105-
if self._context.searches_used >= self.config.search.max_searches:
108+
max_searches = self.tool_configs.get(WebSearchTool.tool_name, {}).get("max_searches") or (
109+
self.config.search.max_searches if self.config.search else 4
110+
)
111+
if self._context.searches_used >= max_searches:
106112
tools -= {
107113
WebSearchTool,
108114
}
@@ -144,7 +150,10 @@ async def _prepare_tools(self) -> list[ChatCompletionFunctionToolParam]:
144150
tools -= {
145151
ClarificationTool,
146152
}
147-
if self._context.searches_used >= self.config.search.max_searches:
153+
max_searches = self.tool_configs.get(WebSearchTool.tool_name, {}).get("max_searches") or (
154+
self.config.search.max_searches if self.config.search else 4
155+
)
156+
if self._context.searches_used >= max_searches:
148157
tools -= {
149158
WebSearchTool,
150159
}

examples/sgr_deep_research_without_reporting/agents.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ async def _prepare_tools(self) -> Type[NextStepToolStub]:
6161
tools -= {
6262
ClarificationTool,
6363
}
64-
if self._context.searches_used >= self.config.search.max_searches:
64+
max_searches = self.tool_configs.get(WebSearchTool.tool_name, {}).get("max_searches") or (
65+
self.config.search.max_searches if self.config.search else 4
66+
)
67+
if self._context.searches_used >= max_searches:
6568
tools -= {
6669
WebSearchTool,
6770
}
@@ -103,7 +106,10 @@ async def _prepare_tools(self) -> list[ChatCompletionFunctionToolParam]:
103106
tools -= {
104107
ClarificationTool,
105108
}
106-
if self._context.searches_used >= self.config.search.max_searches:
109+
max_searches = self.tool_configs.get(WebSearchTool.tool_name, {}).get("max_searches") or (
110+
self.config.search.max_searches if self.config.search else 4
111+
)
112+
if self._context.searches_used >= max_searches:
107113
tools -= {
108114
WebSearchTool,
109115
}
@@ -146,7 +152,10 @@ async def _prepare_tools(self) -> list[ChatCompletionFunctionToolParam]:
146152
tools -= {
147153
ClarificationTool,
148154
}
149-
if self._context.searches_used >= self.config.search.max_searches:
155+
max_searches = self.tool_configs.get(WebSearchTool.tool_name, {}).get("max_searches") or (
156+
self.config.search.max_searches if self.config.search else 4
157+
)
158+
if self._context.searches_used >= max_searches:
150159
tools -= {
151160
WebSearchTool,
152161
}

sgr_agent_core/agent_definition.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
from pathlib import Path
77
from typing import Any, Self, Union
88

9+
# Element of AgentDefinition.tools: name (str), class (type), or dict with "name" + kwargs
10+
ToolItem = Union[str, type[Any], dict[str, Any]]
11+
912
import yaml
1013
from fastmcp.mcp_config import MCPConfig
1114
from pydantic import BaseModel, Field, FilePath, ImportString, computed_field, field_validator, model_validator
@@ -175,7 +178,26 @@ class AgentDefinition(AgentConfig):
175178
name: str = Field(description="Unique agent name/ID")
176179
# ToDo: not sure how to type this properly and avoid circular imports
177180
base_class: type[Any] | ImportString | str = Field(description="Agent class name")
178-
tools: list[type[Any] | str] = Field(default_factory=list, description="List of tool names to include")
181+
tools: list[ToolItem] = Field(
182+
default_factory=list,
183+
description="List of tool names, classes, or dicts with 'name' and optional kwargs for the tool",
184+
)
185+
186+
@field_validator("tools", mode="before")
187+
@classmethod
188+
def tools_dict_must_have_name(cls, v: Any) -> Any:
189+
"""Ensure each dict item in tools has a 'name' key."""
190+
if not isinstance(v, list):
191+
return v
192+
result = []
193+
for item in v:
194+
if isinstance(item, dict):
195+
if "name" not in item:
196+
raise ValueError("Tool dict must have 'name' key")
197+
result.append(item)
198+
else:
199+
result.append(item)
200+
return result
179201

180202
@field_validator("base_class", mode="before")
181203
def base_class_import_points_to_file(cls, v: Any) -> Any:
@@ -213,8 +235,7 @@ def default_config_override_validator(cls, data):
213235
def necessary_fields_validator(self) -> Self:
214236
if self.llm.api_key is None:
215237
raise ValueError(f"LLM API key is not provided for agent '{self.name}'")
216-
if self.search and self.search.tavily_api_key is None:
217-
raise ValueError(f"Search API key is not provided for agent '{self.name}'")
238+
# Search API key can be provided via config.search or per-tool in tools array (kwargs)
218239
if not self.tools:
219240
raise ValueError(f"Tools are not provided for agent '{self.name}'")
220241
return self
@@ -229,7 +250,10 @@ def base_class_is_agent(cls, v: Any) -> type[Any]:
229250

230251
def __str__(self) -> str:
231252
base_class_name = self.base_class.__name__ if isinstance(self.base_class, type) else self.base_class
232-
tool_names = [t.__name__ if isinstance(t, type) else t for t in self.tools]
253+
tool_names = [
254+
t.get("name", t) if isinstance(t, dict) else (t.__name__ if isinstance(t, type) else t)
255+
for t in self.tools
256+
]
233257
return (
234258
f"AgentDefinition(name='{self.name}', "
235259
f"base_class={base_class_name}, "

sgr_agent_core/agent_factory.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Agent Factory for dynamic agent creation from definitions."""
22

33
import logging
4-
from typing import Type, TypeVar
4+
from typing import Any, Type, TypeVar
55

66
import httpx
77
from openai import AsyncOpenAI
@@ -123,6 +123,35 @@ def _resolve_tools(cls, tool_names: list[str | type], config: GlobalConfig) -> l
123123
"""
124124
return [cls._resolve_tool(tool_name, config) for tool_name in tool_names]
125125

126+
@classmethod
127+
def _resolve_tools_with_configs(
128+
cls, tools_spec: list[Any], config: GlobalConfig
129+
) -> tuple[list[type[BaseTool]], dict[str, dict[str, Any]]]:
130+
"""Resolve tools from spec (str, type, or dict with 'name' + kwargs).
131+
132+
Args:
133+
tools_spec: List of tool names, classes, or dicts with 'name' and optional kwargs
134+
config: Global configuration containing tool definitions
135+
136+
Returns:
137+
Tuple of (list of tool classes, dict of tool_name -> kwargs for each tool)
138+
"""
139+
toolkit: list[type[BaseTool]] = []
140+
tool_configs: dict[str, dict[str, Any]] = {}
141+
for item in tools_spec:
142+
if isinstance(item, dict):
143+
name = item["name"]
144+
kwargs = {k: v for k, v in item.items() if k not in ("name", "type", "base_class")}
145+
tool_class = cls._resolve_tool(name, config)
146+
toolkit.append(tool_class)
147+
tool_configs[tool_class.tool_name] = kwargs
148+
else:
149+
tool_class = cls._resolve_tool(item, config)
150+
toolkit.append(tool_class)
151+
if tool_class.tool_name not in tool_configs:
152+
tool_configs[tool_class.tool_name] = {}
153+
return toolkit, tool_configs
154+
126155
@classmethod
127156
async def create(cls, agent_def: AgentDefinition, task_messages: list[ChatCompletionMessageParam]) -> Agent:
128157
"""Create an agent instance from a definition.
@@ -165,8 +194,8 @@ async def create(cls, agent_def: AgentDefinition, task_messages: list[ChatComple
165194
logger.error(error_msg)
166195
raise ValueError(error_msg)
167196
mcp_tools: list = await MCP2ToolConverter.build_tools_from_mcp(agent_def.mcp)
168-
config = GlobalConfig()
169-
tools = cls._resolve_tools(agent_def.tools, config)
197+
global_config = GlobalConfig()
198+
tools, tool_configs = cls._resolve_tools_with_configs(agent_def.tools, global_config)
170199
tools.extend(mcp_tools)
171200

172201
try:
@@ -180,6 +209,7 @@ async def create(cls, agent_def: AgentDefinition, task_messages: list[ChatComple
180209
task_messages=task_messages,
181210
def_name=agent_def.name,
182211
toolkit=tools,
212+
tool_configs=tool_configs,
183213
openai_client=cls._create_client(agent_def.llm),
184214
agent_config=agent_def,
185215
**agent_kwargs,

sgr_agent_core/agents/sgr_agent.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ async def _select_action_phase(self, reasoning: NextStepToolStub) -> BaseTool:
8383
return tool
8484

8585
async def _action_phase(self, tool: BaseTool) -> str:
86-
result = await tool(self._context, self.config)
86+
result = await tool(
87+
self._context, self.config, **self.tool_configs.get(tool.tool_name, {})
88+
)
8789
self.conversation.append(
8890
{"role": "tool", "content": result, "tool_call_id": f"{self._context.iteration}-action"}
8991
)

sgr_agent_core/agents/sgr_tool_calling_agent.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ async def _select_action_phase(self, reasoning: ReasoningTool) -> BaseTool:
124124
return tool
125125

126126
async def _action_phase(self, tool: BaseTool) -> str:
127-
result = await tool(self._context, self.config)
127+
result = await tool(
128+
self._context, self.config, **self.tool_configs.get(tool.tool_name, {})
129+
)
128130
self.conversation.append(
129131
{"role": "tool", "content": result, "tool_call_id": f"{self._context.iteration}-action"}
130132
)

sgr_agent_core/agents/tool_calling_agent.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ async def _select_action_phase(self, reasoning=None) -> BaseTool:
7474
return tool
7575

7676
async def _action_phase(self, tool: BaseTool) -> str:
77-
result = await tool(self._context, self.config)
77+
result = await tool(
78+
self._context, self.config, **self.tool_configs.get(tool.tool_name, {})
79+
)
7880
self.conversation.append(
7981
{"role": "tool", "content": result, "tool_call_id": f"{self._context.iteration}-action"}
8082
)

0 commit comments

Comments
 (0)