Empowering AgentScope Agents with Custom Tools is the key to turning a bare LLM wrapper into a practical, production-ready agent. Out of the box, a language model can reason over text — but it cannot execute a shell command, call a REST API, or query a live database. Tools bridge that gap, giving agents the ability to act on the world rather than just describe it. This guide walks you through AgentScope’s tool system end-to-end: what a tool is, how the framework wires them together, and how to build a realistic tool from scratch.
Before diving in, make sure you have a working AgentScope installation. If you are starting fresh, the package requires Python 3.10 or higher:
pip install agentscope
Most model backends also require an API key. Set it as an environment variable before running any agent code:
export DASHSCOPE_API_KEY="your-key-here"
Why Extend AgentScope with Custom Tools?
Large language models are powerful reasoners but fundamentally stateless and sandboxed — they cannot reach outside the conversation window on their own. If you want an agent to retrieve real-time data, manipulate files, or integrate with third-party services, you need tools.
AgentScope v1.0.0 introduced a fully asynchronous, first-class tools API that replaces the older prompt-based approach. The new design is cleaner: you write a plain Python function, register it with a Toolkit, and hand the toolkit to a ReActAgent. The agent then decides — on its own, by reasoning over the model’s output — when to call a tool, which one, and with what arguments.
This pattern mirrors how humans use software. If you are familiar with how autonomous systems work conceptually, the What Is an AI Agent? From LLMs to Autonomous Systems article covers the theoretical grounding behind this reasoning-and-action loop. For AgentScope specifically, the key insight is that the framework does the heavy lifting: you only need to define what a tool does, not how the agent decides to use it.
Custom tools unlock:
- Live data access — weather, stock prices, search results
- System integration — shell commands, file I/O, database queries
- External APIs — CRM updates, GitHub operations, Slack messages
- Computation — math solvers, code execution, data transformation
Anatomy of a Tool: Functions and Decorators
In AgentScope, a tool function is simply a Python function — synchronous or asynchronous — that performs one well-defined action. There is no magic decorator required to define a tool. The framework treats any registered Python callable as a tool.
What does matter:
- Type annotations — AgentScope uses them to generate the JSON schema that the model receives. Always annotate your parameters and return type.
- Docstrings — The docstring becomes the tool’s description in the schema. Write it clearly; the model reads it to decide whether to call the tool.
- Single responsibility — One tool should do one thing. Compound tools are harder for models to reason about correctly.
Here is a minimal synchronous tool:
def add_numbers(a: float, b: float) -> float:
"""Add two numbers together and return the result."""
return a + b
And an asynchronous variant (both are supported):
import asyncio
async def fetch_page_title(url: str) -> str:
"""Fetch the HTML title of a given URL."""
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
html = await response.text()
# Simplified title extraction
start = html.find("<title>") + 7
end = html.find("</title>")
return html[start:end].strip() if start > 6 else "No title found"
The async variant is preferred for I/O-bound operations because AgentScope’s agent execution is itself asynchronous — your tool runs inside an event loop.
Step-by-Step: Building a Web Search Tool
Let’s build something practical: a tool that performs a web search and returns a summary of the top results. We will use the DuckDuckGo Instant Answer API because it requires no API key and is safe for testing.
1. Install the HTTP dependency
pip install aiohttp
2. Write the tool function
import aiohttp
import json
async def web_search(query: str, max_results: int = 3) -> str:
"""
Search the web using DuckDuckGo and return a summary of top results.
Args:
query: The search query string.
max_results: Maximum number of results to return (default 3).
Returns:
A formatted string containing search result titles and abstracts.
"""
url = "https://api.duckduckgo.com/"
params = {
"q": query,
"format": "json",
"no_html": "1",
"skip_disambig": "1",
}
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params) as response:
data = await response.json(content_type=None)
results = []
# DuckDuckGo returns an AbstractText for the top result
if data.get("AbstractText"):
results.append(f"Summary: {data['AbstractText']}")
# Related topics as additional results
for topic in data.get("RelatedTopics", [])[:max_results]:
if isinstance(topic, dict) and topic.get("Text"):
results.append(f"- {topic['Text']}")
if not results:
return f"No results found for query: {query}"
return "\n".join(results)
A few things to notice:
- The docstring is explicit.
Args:andReturns:sections help the model understand what values to pass and what to expect back. max_resultshas a default value, making it optional. The model can omit it when it is not relevant.- The function returns a plain string. AgentScope tools can return any JSON-serializable type, but strings work universally across model backends.
3. Register the tool in a Toolkit
from agentscope.tool import Toolkit
toolkit = Toolkit()
toolkit.register_tool_function(web_search)
register_tool_function() inspects the function’s annotations and docstring to build the schema. After registration, the toolkit knows about web_search and can expose it to any agent it is attached to.
You can register multiple tools in one toolkit:
toolkit.register_tool_function(add_numbers)
toolkit.register_tool_function(web_search)
toolkit.register_tool_function(fetch_page_title)
Integrating and Calling Tools Within an Agent
With the toolkit ready, the final step is attaching it to a ReActAgent. The ReAct (Reasoning + Acting) pattern lets the agent interleave thinking steps with tool calls until it can produce a final answer.
Full working example
import asyncio
import aiohttp
from agentscope.tool import Toolkit
from agentscope.agent import ReActAgent
# --- Tool definition ---
async def web_search(query: str, max_results: int = 3) -> str:
"""
Search the web using DuckDuckGo and return a summary of top results.
Args:
query: The search query string.
max_results: Maximum number of results to return (default 3).
Returns:
A formatted string containing search result titles and abstracts.
"""
url = "https://api.duckduckgo.com/"
params = {
"q": query,
"format": "json",
"no_html": "1",
"skip_disambig": "1",
}
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params) as response:
data = await response.json(content_type=None)
results = []
if data.get("AbstractText"):
results.append(f"Summary: {data['AbstractText']}")
for topic in data.get("RelatedTopics", [])[:max_results]:
if isinstance(topic, dict) and topic.get("Text"):
results.append(f"- {topic['Text']}")
return "\n".join(results) if results else f"No results found for: {query}"
async def main():
# --- Toolkit setup ---
toolkit = Toolkit()
toolkit.register_tool_function(web_search)
# --- Agent initialization ---
# Replace the model config below with your actual model configuration.
# AgentScope supports DashScope, OpenAI, Anthropic, and other backends.
agent = ReActAgent(
name="ResearchBot",
model={
"model_type": "dashscope_chat",
"model_name": "qwen-max",
"api_key": "YOUR_DASHSCOPE_API_KEY", # or use env var
},
toolkit=toolkit,
)
# --- Run the agent ---
response = await agent("What is LangChain and what is it used for?")
print(response)
if __name__ == "__main__":
asyncio.run(main())
What happens at runtime
When you await agent(...), the following loop runs:
- Think — The agent sends the user message plus the tool schema to the model.
- Act — If the model outputs a tool call, the agent executes the corresponding Python function.
- Observe — The function’s return value is appended to the conversation as an observation.
- Repeat — Steps 1–3 repeat until the model decides it has enough information.
- Answer — The agent returns a final text response to the caller.
You never need to manage this loop manually. The ReActAgent handles it internally.
Combining tools for complex workflows
You can register as many tools as needed. For example, an agent doing research could have web_search, fetch_page_title, and a summarize_text tool all in the same toolkit. The model will orchestrate them in sequence as needed — searching first, then fetching details, then summarizing.
This composability is where AgentScope really shines compared to single-tool setups. For comparison on how other frameworks handle multi-step tool use, see CrewAI vs AutoGen: Which Multi-Agent Framework Should You Use?, which covers how different architectures approach the same coordination problem.
If you are interested in how the concept of tool-augmented language models evolved theoretically, Toolformer Explained: Teaching LLMs to Use Tools provides excellent background on the research that underpins frameworks like AgentScope.
Frequently Asked Questions
Can I use synchronous functions as tools, or do they all need to be async?
Both are supported. AgentScope accepts standard synchronous functions via register_tool_function() and will wrap them appropriately. That said, async functions are preferred for anything involving I/O (HTTP requests, file reads, database calls) because the agent execution loop is asynchronous — blocking calls in sync tools can stall the event loop.
How does the agent know when to call a tool versus answer directly?
The model decides based on the tool schema (function name, parameter types, docstring description) and the user’s message. If the query can be answered from the model’s training knowledge, it may answer directly. If it requires live data or a side effect, a well-written docstring guides the model to call the tool. This is why descriptive docstrings are critical — they are the model’s primary signal.
What happens if a tool raises an exception?
By default, AgentScope catches exceptions thrown by tool functions and returns the error message as an observation string. The agent then receives that error as context and can decide how to proceed — retry with different arguments, try a different tool, or inform the user. You can add explicit error handling inside your tool function to return structured error strings instead of letting exceptions propagate.
Are there built-in tools I can use instead of writing everything from scratch?
AgentScope includes some built-in tools, but the framework has moved away from older helpers like WebBrowser in favor of MCP-based implementations. The recommended approach for production use is to check the latest AgentScope documentation for officially supported MCP integrations. For custom or domain-specific logic, writing your own functions is always the cleanest path.
Can one toolkit be shared across multiple agents?
Yes. A Toolkit instance is just a Python object. You can pass the same toolkit to multiple ReActAgent instances if they share a common set of capabilities. Each agent independently decides how and when to use the registered tools based on its own conversation context, so sharing a toolkit does not create any cross-agent state issues.