Overview
Tools are the building blocks that enable agents to interact with external systems, execute code, generate images, or perform any custom operation. Qwen-Agent provides a flexible tool system based on the BaseTool class.
Quick Start
Here’s a simple custom tool example from the codebase (examples/assistant_add_custom_tool.py:30):
from qwen_agent.tools.base import BaseTool, register_tool
import json
@register_tool ( 'my_image_gen' )
class MyImageGen ( BaseTool ):
description = 'AI painting service that generates images from text descriptions.'
parameters = [{
'name' : 'prompt' ,
'type' : 'string' ,
'description' : 'Detailed description of the desired image content, in English' ,
'required' : True ,
}]
def call ( self , params : str , ** kwargs ) -> str :
import json5
prompt = json5.loads(params)[ 'prompt' ]
# Your implementation here
return json.dumps(
{ 'image_url' : f 'https://image.pollinations.ai/prompt/ { prompt } ' },
ensure_ascii = False ,
)
The BaseTool class (qwen_agent/tools/base.py:109) defines the interface all tools must implement:
class BaseTool ( ABC ):
name: str = '' # Tool identifier
description: str = '' # What the tool does
parameters: Union[List[ dict ], dict ] = [] # Tool parameters schema
@abstractmethod
def call ( self , params : Union[ str , dict ], ** kwargs ) -> Union[ str , list , dict ]:
"""Execute the tool"""
raise NotImplementedError
Inherit from BaseTool and use the @register_tool decorator:
from qwen_agent.tools.base import BaseTool, register_tool
@register_tool ( 'weather_query' )
class WeatherTool ( BaseTool ):
description = 'Get current weather for a city'
parameters = [{
'name' : 'city' ,
'type' : 'string' ,
'description' : 'City name' ,
'required' : True
}, {
'name' : 'units' ,
'type' : 'string' ,
'description' : 'Temperature units (celsius/fahrenheit)' ,
'required' : False
}]
Step 2: Implement the call Method
def call ( self , params : str , ** kwargs ) -> str :
# Parse parameters
params_dict = self ._verify_json_format_args(params)
city = params_dict[ 'city' ]
units = params_dict.get( 'units' , 'celsius' )
# Your logic here
weather_data = fetch_weather(city, units)
# Return result as string or JSON
return json.dumps(weather_data, ensure_ascii = False )
from qwen_agent.agents import Assistant
agent = Assistant(
llm = { 'model' : 'qwen-max' },
function_list = [ 'weather_query' ] # Use your registered tool
)
messages = [{ 'role' : 'user' , 'content' : 'What is the weather in Beijing?' }]
for response in agent.run(messages):
print (response)
Qwen-Agent supports two parameter schema formats:
parameters = [{
'name' : 'param_name' ,
'type' : 'string' ,
'description' : 'Parameter description' ,
'required' : True
}]
parameters = {
'type' : 'object' ,
'properties' : {
'location' : {
'type' : 'string' ,
'description' : 'The city and state, e.g. San Francisco, CA'
},
'unit' : {
'type' : 'string' ,
'enum' : [ 'celsius' , 'fahrenheit' ]
}
},
'required' : [ 'location' ]
}
The OpenAI JSON Schema format provides better validation and is recommended for complex tools. It must conform to the OpenAI function calling specification.
Real-World Examples
import json
import sqlite3
from qwen_agent.tools.base import BaseTool, register_tool
@register_tool ( 'database_query' )
class DatabaseQuery ( BaseTool ):
description = 'Execute SQL queries on the database'
parameters = {
'type' : 'object' ,
'properties' : {
'query' : {
'type' : 'string' ,
'description' : 'SQL query to execute'
}
},
'required' : [ 'query' ]
}
def __init__ ( self , cfg = None ):
super (). __init__ (cfg)
self .db_path = cfg.get( 'db_path' , 'database.db' ) if cfg else 'database.db'
def call ( self , params : str , ** kwargs ) -> str :
params_dict = self ._verify_json_format_args(params)
query = params_dict[ 'query' ]
try :
conn = sqlite3.connect( self .db_path)
cursor = conn.cursor()
cursor.execute(query)
results = cursor.fetchall()
conn.close()
return json.dumps({
'success' : True ,
'data' : results
})
except Exception as e:
return json.dumps({
'success' : False ,
'error' : str (e)
})
import requests
from qwen_agent.tools.base import BaseTool, register_tool
@register_tool ( 'github_search' )
class GitHubSearch ( BaseTool ):
description = 'Search GitHub repositories'
parameters = [{
'name' : 'query' ,
'type' : 'string' ,
'description' : 'Search query' ,
'required' : True
}, {
'name' : 'language' ,
'type' : 'string' ,
'description' : 'Programming language filter' ,
'required' : False
}]
def call ( self , params : str , ** kwargs ) -> str :
import json5
params_dict = json5.loads(params)
query = params_dict[ 'query' ]
language = params_dict.get( 'language' , '' )
search_query = f " { query } language: { language } " if language else query
response = requests.get(
'https://api.github.com/search/repositories' ,
params = { 'q' : search_query, 'sort' : 'stars' , 'order' : 'desc' },
headers = { 'Accept' : 'application/vnd.github.v3+json' }
)
if response.status_code == 200 :
data = response.json()
repos = [{
'name' : repo[ 'full_name' ],
'stars' : repo[ 'stargazers_count' ],
'url' : repo[ 'html_url' ]
} for repo in data[ 'items' ][: 5 ]]
return json.dumps(repos, ensure_ascii = False )
else :
return f "Error: { response.status_code } "
import os
from qwen_agent.tools.base import BaseToolWithFileAccess, register_tool
@register_tool ( 'csv_analyzer' )
class CSVAnalyzer ( BaseToolWithFileAccess ):
description = 'Analyze CSV files and return statistics'
parameters = [{
'name' : 'file_path' ,
'type' : 'string' ,
'description' : 'Path to CSV file' ,
'required' : True
}]
def call ( self , params : str , files = None , ** kwargs ) -> str :
# BaseToolWithFileAccess automatically handles file downloads
super ().call(params, files = files)
params_dict = self ._verify_json_format_args(params)
file_path = params_dict[ 'file_path' ]
# Process file in self.work_dir
local_path = os.path.join( self .work_dir, os.path.basename(file_path))
import pandas as pd
df = pd.read_csv(local_path)
stats = {
'rows' : len (df),
'columns' : len (df.columns),
'column_names' : df.columns.tolist(),
'summary' : df.describe().to_dict()
}
return json.dumps(stats, ensure_ascii = False , indent = 2 )
Pass configuration when initializing tools:
String Name
Dict Configuration
Tool Instance
# Use default configuration
agent = Assistant(
llm = { 'model' : 'qwen-max' },
function_list = [ 'code_interpreter' ]
)
Qwen-Agent includes several powerful built-in tools:
code_interpreter : Execute Python code in a sandboxed environment
image_gen : Generate images (integration point)
doc_parser : Parse and extract content from documents
See the Code Interpreter guide for detailed usage.
Helper Methods
The BaseTool class provides useful helper methods (qwen_agent/tools/base.py:140):
Validate and parse tool parameters:
def call ( self , params : str , ** kwargs ) -> str :
# Automatically validates against self.parameters schema
params_dict = self ._verify_json_format_args(params)
# Use params_dict...
function Property
Get OpenAI-compatible function definition:
tool = MyTool()
function_def = tool.function
# Returns: {'name': '...', 'description': '...', 'parameters': ...}
Best Practices
Tool Design Guidelines
Keep tools focused on a single responsibility
Provide clear, detailed descriptions for better LLM understanding
Use descriptive parameter names and descriptions
Return structured JSON for complex data
Handle errors gracefully and return informative error messages
Security Considerations
Validate all inputs thoroughly
Use BaseToolWithFileAccess for tools that need file operations
Be cautious with tools that execute code or make system calls
Consider rate limiting for API-based tools
Never expose sensitive credentials in tool responses
Troubleshooting
# Make sure to import the file where @register_tool is called
import my_tools # This registers the tool
agent = Assistant(
function_list = [ 'my_custom_tool' ] # Now it's available
)
Parameter Validation Errors
# Use json5 for more flexible parsing
import json5
def call ( self , params : str , ** kwargs ):
params_dict = json5.loads(params) # Handles trailing commas, comments, etc.
Next Steps