# How to Define Custom Tools for Qwen-Agent: A Complete Developer Guide

> Define custom tools for Qwen-Agent by subclassing BaseTool and implementing the call method. This guide shows developers how to register or pass custom tool instances to the Assistant for enhanced functionality.

- Repository: [Qwen/Qwen-Agent](https://github.com/qwenlm/Qwen-Agent)
- Tags: how-to-guide
- Published: 2026-03-09

---

**Define custom tools for Qwen-Agent by subclassing `BaseTool`, implementing the `call` method, and either registering the class with the `@register_tool` decorator or passing an instance directly to the `Assistant`'s `function_list` parameter.**

Qwen-Agent extends large language model capabilities through a structured tool-calling framework that converts natural language requests into executable function invocations. To integrate proprietary APIs or custom business logic, developers must implement tools that conform to the framework's abstract base class and schema validation requirements. This guide explores the exact implementation patterns found in the Qwen-Agent repository, referencing specific source files and method signatures.

## Understanding the Tool Architecture

The Qwen-Agent tool system relies on three core components that handle discovery, validation, and execution. Understanding these building blocks ensures your custom tools integrate seamlessly with the agent's function-calling loop.

### The BaseTool Abstract Class

The **`BaseTool`** class in [`qwen_agent/tools/base.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/qwen_agent/tools/base.py) (lines 9-27) serves as the foundation for all tools. It defines the required interface that every custom tool must implement, specifically the `call(self, params, **kwargs)` method that executes the tool's logic. The class also manages metadata through the `function` property, which generates an OpenAI-compatible function schema from the tool's `name`, `description`, and `parameters` attributes.

### The register_tool Decorator

The **`@register_tool`** decorator, located in [`qwen_agent/tools/base.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/qwen_agent/tools/base.py) (lines 44-60), inserts tool classes into a global `TOOL_REGISTRY` dictionary. This registration enables the `Assistant` class to instantiate tools dynamically by string name rather than requiring direct object references. The decorator accepts a unique identifier string that becomes the tool's canonical name throughout the system.

### Assistant Integration and Validation

When initializing an **`Assistant`** (defined in [`qwen_agent/agent.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/qwen_agent/agent.py)), the `function_list` parameter accepts a mixed list containing either registered tool names (strings) or pre-instantiated `BaseTool` objects. During execution, the framework validates incoming JSON arguments using **`BaseTool._verify_json_format_args`** (lines 40-63 in [`base.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/base.py)) before invoking the tool's `call` method, ensuring type safety and required parameter presence.

## Step-by-Step Implementation Guide

Follow these concrete steps to create functional custom tools that Qwen-Agent can discover and execute automatically.

### 1. Subclass BaseTool and Define Metadata

Create a Python class inheriting from `BaseTool` and specify the required class attributes. The `parameters` field must follow JSON Schema conventions to enable the LLM to generate valid arguments.

```python
from qwen_agent.tools.base import BaseTool, register_tool
import json5

@register_tool('image_generator')
class ImageGenerator(BaseTool):
    description = 'Generate images from text prompts using an external API.'
    parameters = [{
        'name': 'prompt',
        'type': 'string',
        'description': 'Detailed description of the image to generate.',
        'required': True,
    }]

```

### 2. Implement the call Method

The `call` method receives a JSON string (`params`) containing the function arguments and must return a string representing the tool's output. Parse the JSON using `json5.loads()` for robust handling of unquoted keys or trailing commas.

```python
    def call(self, params: str, **kwargs) -> str:
        # Extract arguments from JSON string

        args = json5.loads(params)
        prompt = args['prompt']
        
        # Execute tool logic (example: URL construction)

        import urllib.parse
        encoded = urllib.parse.quote(prompt)
        image_url = f'https://image.pollinations.ai/prompt/{encoded}'
        
        return json.dumps({'image_url': image_url}, ensure_ascii=False)

```

### 3. Register or Instantiate the Tool

You have two integration options depending on your architectural needs:

**Option A: Registration by Name** (Best for reusable tools)
Use the `@register_tool('identifier')` decorator as shown above, then reference the tool by string name in the `Assistant` configuration.

**Option B: Direct Instance Pass** (Best for configured instances)
Skip the decorator and pass an instantiated object directly to `function_list`:

```python
from qwen_agent.agents import Assistant

# Instance approach allows per-agent configuration

custom_tool = ImageGenerator()
bot = Assistant(
    llm={'model': 'qwen-max'},
    function_list=[custom_tool, 'code_interpreter']  # Mix instances and registered names

)

```

### 4. Configure the Assistant

Add your tool to the `Assistant` initialization via the `function_list` parameter. This example from [`examples/assistant_add_custom_tool.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/examples/assistant_add_custom_tool.py) (lines 56-66) demonstrates combining custom and built-in tools:

```python
def init_agent_service():
    llm_cfg = {'model': 'qwen-max'}
    system = ("According to the user's request, you first draw a picture "
              "and then automatically run code to download the picture.")
    
    bot = Assistant(
        llm=llm_cfg,
        name='AI painting',
        description='AI painting service',
        system_message=system,
        function_list=[
            'image_generator',   # Custom registered tool

            'code_interpreter',  # Built-in tool

        ],
        files=['doc.pdf'],
    )
    return bot

```

## Practical Code Examples

These complete implementations demonstrate both registration patterns validated by the Qwen-Agent test suite.

### Registered Tool Pattern

This implementation from [`examples/assistant_add_custom_tool.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/examples/assistant_add_custom_tool.py) shows the decorator-based approach with full parameter schema:

```python
import json, json5, urllib.parse
from qwen_agent.tools.base import BaseTool, register_tool

@register_tool('my_image_gen')
class MyImageGen(BaseTool):
    """Generate an image URL from a textual prompt."""
    description = 'AI painting service – input a description, get an image URL.'
    parameters = [{
        'name': 'prompt',
        'type': 'string',
        'description': 'English description of the desired image.',
        'required': True,
    }]

    def call(self, params: str, **kwargs) -> str:
        prompt = json5.loads(params)['prompt']
        prompt = urllib.parse.quote(prompt)
        return json.dumps(
            {'image_url': f'https://image.pollinations.ai/prompt/{prompt}'},
            ensure_ascii=False,
        )

```

### Instance-Based Pattern

For scenarios requiring runtime configuration, instantiate the tool directly as shown in [`tests/agents/test_custom_tool_object.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/tests/agents/test_custom_tool_object.py):

```python
class MyImageGen(BaseTool):
    name = 'my_image_gen'
    description = 'Generate images from prompts.'
    parameters = [{'name': 'prompt', 'type': 'string', 'required': True}]
    
    def call(self, params: str, **kwargs) -> str:
        # Tool implementation here

        return json.dumps({'status': 'generated'})

def init_agent_service():
    llm_cfg = {'model': 'qwen-max'}
    system = "You must draw a picture with my_image_gen."
    
    # Pass instance directly instead of string name

    tools = [MyImageGen(), 'code_interpreter']
    bot = Assistant(
        llm=llm_cfg, 
        system_message=system, 
        function_list=tools
    )
    return bot

```

### Minimal Working Example

A self-contained script demonstrating a text transformation tool:

```python
import json, json5
from qwen_agent.agents import Assistant
from qwen_agent.tools.base import BaseTool, register_tool

@register_tool('uppercase')
class UpperCaseTool(BaseTool):
    """Convert text to uppercase."""
    description = 'Transforms provided text to all caps.'
    parameters = [{
        'name': 'text',
        'type': 'string',
        'description': 'Input text to transform.',
        'required': True,
    }]

    def call(self, params: str, **kwargs) -> str:
        txt = json5.loads(params)['text']
        return json.dumps({'result': txt.upper()}, ensure_ascii=False)

def main():
    bot = Assistant(
        llm={'model': 'qwen-max'},
        system_message="Use the uppercase tool for text transformation requests.",
        function_list=['uppercase'],
    )
    
    messages = [{'role': 'user', 'content': 'Convert "hello world" to uppercase'}]
    for response in bot.run(messages=messages):
        print(response)

if __name__ == '__main__':
    main()

```

## Summary

- **Subclass `BaseTool`** from [`qwen_agent/tools/base.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/qwen_agent/tools/base.py) and implement the `call(self, params, **kwargs)` method to define executable logic.

- **Define JSON Schema** in the `parameters` class attribute to instruct the LLM on valid argument structures; the `function` property automatically converts this to OpenAI-compatible format.

- **Register tools** using `@register_tool('name')` for global discoverability by string name, or pass **tool instances** directly to `function_list` for agent-specific configurations.

- **Validation occurs automatically** via `BaseTool._verify_json_format_args` before each invocation, ensuring arguments match the declared schema.

- **Integration happens at `Assistant` initialization** through the `function_list` parameter, which accepts mixed lists of registered names and live instances.

## Frequently Asked Questions

### What is the difference between registering a tool and passing an instance?

**Registering a tool** with `@register_tool` adds the class to the global `TOOL_REGISTRY`, allowing you to reference it by string name across multiple agents. **Passing an instance** provides immediate object integration without global registration, useful for dependency injection or agent-specific tool configurations. Both approaches support the same execution flow and validation logic.

### How does Qwen-Agent validate tool arguments?

The framework validates JSON arguments using **`BaseTool._verify_json_format_args`** before executing the `call` method. This internal method checks that required parameters exist and conform to the types specified in the tool's `parameters` schema, preventing malformed data from reaching your business logic.

### Can I use custom tools without the register_tool decorator?

Yes, instantiation works without registration. Create an instance of your `BaseTool` subclass and pass it directly to the `Assistant` constructor's `function_list` parameter. This pattern appears in [`tests/agents/test_custom_tool_object.py`](https://github.com/QwenLM/Qwen-Agent/blob/main/tests/agents/test_custom_tool_object.py) and is ideal when you need to configure tool dependencies or state at initialization time.

### Where are tool schemas defined for the LLM?

Tool schemas are defined in the **`parameters`** class attribute of your `BaseTool` subclass. This attribute accepts a list of dictionaries or a JSON Schema object describing each argument's name, type, description, and required status. The `Assistant` class consumes these schemas through each tool's `function` property to build the OpenAI-compatible function definitions sent to the LLM.