# Creating Custom Function Tools for ChatDev Agents: A Complete Implementation Guide

> Implement custom function tools for ChatDev agents easily. Learn to add Python files or use the POST API to register functions and generate JSON schemas for enhanced agent capabilities.

- Repository: [OpenBMB/ChatDev](https://github.com/OpenBMB/ChatDev)
- Tags: how-to-guide
- Published: 2026-04-01

---

**You can create custom function tools for ChatDev agents by adding Python files to the function calling directory or using the POST /api/tools/local endpoint, which automatically registers callable functions via FunctionManager and generates OpenAI-compatible JSON schemas through FunctionCatalog.**

ChatDev enables agents to invoke external capabilities through **function-calling tools**—ordinary Python functions that are dynamically discovered and exposed via a FastAPI interface. According to the OpenBMB/ChatDev source code, this runtime-extensible architecture allows developers to add new capabilities by simply dropping `.py` files into a designated folder, with immediate availability to all agents without server restarts.

## How Function Discovery Works in ChatDev

The foundation of ChatDev's tool system rests on dynamic module loading. When the system initializes, it scans a configurable directory for Python files, imports them as modules, and registers any callable functions for agent use.

### The FunctionManager Class

Located in [`utils/function_manager.py`](https://github.com/OpenBMB/ChatDev/blob/main/utils/function_manager.py), the `FunctionManager` class handles the discovery and registration pipeline. It resolves the tools directory using the environment variable `MAC_FUNCTIONS_DIR` (falling back to `functions/function_calling`):

```python
_FUNCTION_CALLING_ENV = "MAC_FUNCTIONS_DIR"
_DEFAULT_FUNCTION_CALLING_DIR = Path("functions") / "function_calling"
FUNCTION_CALLING_DIR = _resolve_dir(_DEFAULT_FUNCTION_CALLING_DIR,
                                   _FUNCTION_CALLING_ENV).resolve()

```

The manager walks the directory tree using `rglob("*.py")`, skips private files (those starting with `_` or named [`__init__.py`](https://github.com/OpenBMB/ChatDev/blob/main/__init__.py)), and imports each module with a unique identifier:

```python
def load_functions(self) -> None:
    for file in self.functions_dir.rglob("*.py"):
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)
        for name, obj in inspect.getmembers(module, inspect.isfunction):
            if name.startswith("_"): continue
            self.functions[name] = obj

```

The manager caches instances per directory via `_function_managers` and provides access through `get_function_manager()`.

## Building OpenAI-Compatible Tool Schemas

Once functions are loaded, `FunctionCatalog` (from [`utils/function_catalog.py`](https://github.com/OpenBMB/ChatDev/blob/main/utils/function_catalog.py)) inspects them to build metadata required for LLM function calling. This includes generating JSON Schema definitions from Python type hints and docstrings.

### Metadata Extraction and Type Inspection

The catalog uses `inspect.signature()` to analyze parameters and `_resolve_annotations()` to handle complex types including `Annotated`, `Union`, `Literal`, and custom enums:

```python
def _build_function_metadata(name: str, fn: Any, functions_dir: Path) -> FunctionMetadata:
    signature = inspect.signature(fn)
    annotations = _resolve_annotations(fn)
    description = _extract_description(fn)
    schema = _build_parameters_schema(signature, annotations)

```

Key implementation details:
- **Descriptions** are extracted from the first paragraph of the function docstring, trimmed to 600 characters
- **Parameters** follow strict JSON-Schema rules via `_build_parameters_schema`
- The catalog lazy-loads on first access and caches results in `self._loaded`

Refresh the catalog after adding new files:

```python
catalog = get_function_catalog()
catalog.refresh()

```

## HTTP API for Tool Management

The FastAPI routes in [`server/routes/tools.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/routes/tools.py) expose two critical endpoints for tool management, enabling both inspection and runtime extension of the toolset.

### Listing Available Tools (GET /api/tools/local)

The GET endpoint retrieves all registered tools with their schemas:

```python
@router.get("/api/tools/local")
def list_local_tools():
    catalog = get_function_catalog()
    metadata = catalog.list_metadata()
    tools = [
        {
            "name": name,
            "description": meta.description,
            "parameters": meta.parameters_schema,
            "module": meta.module_name,
            "file_path": meta.file_path,
        }
        for name, meta in metadata.items()
    ]
    return {
        "success": True,
        "count": len(tools),
        "tools": tools,
        "load_error": str(catalog.load_error) if catalog.load_error else None,
    }

```

This returns a JSON array containing each tool's name, description, parameter schema, and source file location.

### Creating New Tools via API (POST /api/tools/local)

The POST endpoint enables runtime tool creation without filesystem access:

```python
@router.post("/api/tools/local")
def create_local_tool(payload: LocalToolCreateRequest):
    filename = payload.filename.strip()
    if not re.match(r"^[A-Za-z0-9_-]+(\.py)?$", filename):
        raise HTTPException(400, "filename must be alphanumeric")
    
    tools_dir = Path(FUNCTION_CALLING_DIR).resolve()
    target_path = (tools_dir / (filename if filename.endswith(".py") else f"{filename}.py")).resolve()
    target_path.relative_to(tools_dir)  # Security check

    
    target_path.write_text(payload.content, encoding="utf-8")
    
    catalog = get_function_catalog()
    catalog.refresh()

```

**Security features:**
- Filename validation using regex (`^[A-Za-z0-9_-]+(\.py)?$`)
- Path traversal prevention via `relative_to()` check
- Automatic catalog refresh after file creation

## Implementing a Custom Tool: Step-by-Step Example

To create a functional tool, define a Python function with type hints and a descriptive docstring in the `functions/function_calling/` directory:

```python

# functions/function_calling/hello_world.py

def hello_world(name: str) -> str:
    """
    Greet the user with a friendly message.

    Parameters
    ----------
    name: str
        The name of the person to greet.

    Returns
    -------
    str
        A greeting sentence.
    """
    return f"Hello, {name}! 👋"

```

After saving, the function appears in API listings with a generated schema marking `name` as a required string parameter.

### Uploading Tools via HTTP

Alternatively, upload tools programmatically:

```bash
curl -X POST http://localhost:8000/api/tools/local \
  -H "Content-Type: application/json" \
  -d '{
        "filename": "adder.py",
        "content": "def adder(a: int, b: int) -> int:\n    \"\"\"Return a+b.\"\"\"\n    return a + b",
        "overwrite": true
      }'

```

A successful response includes `load_error: null`, confirming the tool is immediately available.

## How Agents Execute Custom Functions

When an LLM requests a function call, the agent executor ([`runtime/node/executor/agent_executor.py`](https://github.com/OpenBMB/ChatDev/blob/main/runtime/node/executor/agent_executor.py)) retrieves and executes the function:

```python
func = runtime.context.function_manager.get_function("adder")
result = func(a=3, b=5)  # Returns 8

```

The executor handles parameter validation and returns results to the conversation context, enabling seamless integration between agent reasoning and external capabilities.

## Summary

- **FunctionManager** ([`utils/function_manager.py`](https://github.com/OpenBMB/ChatDev/blob/main/utils/function_manager.py)) dynamically loads Python files from `MAC_FUNCTIONS_DIR`, registering public functions for agent use
- **FunctionCatalog** ([`utils/function_catalog.py`](https://github.com/OpenBMB/ChatDev/blob/main/utils/function_catalog.py)) inspects registered functions to build OpenAI-compatible JSON schemas from type hints and docstrings
- **API endpoints** ([`server/routes/tools.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/routes/tools.py)) provide HTTP interfaces for listing tools (GET) and creating new ones (POST) with automatic validation and security checks
- **Runtime execution** occurs through `context.function_manager.get_function()` in the agent executor, enabling immediate invocation of newly added tools
- **Configuration** defaults to `functions/function_calling/` but is overrideable via the `MAC_FUNCTIONS_DIR` environment variable

## Frequently Asked Questions

### Where should I place custom tool files in ChatDev?

Place Python files in the directory specified by the `MAC_FUNCTIONS_DIR` environment variable, or in the default location `functions/function_calling/`. The `FunctionManager` automatically discovers all `.py` files in this directory tree during startup and catalog refreshes, excluding private files that start with an underscore.

### What Python types are supported for tool parameters?

ChatDev's `FunctionCatalog` supports standard JSON-Schema convertible types including `int`, `str`, `float`, `bool`, `list`, and `dict`. It also handles advanced typing constructs via `_annotation_to_schema()`, including `Annotated`, `Union`, `Literal`, `Optional`, custom enums, and nested collections. All parameters require type hints for proper schema generation.

### How do I update an existing tool without restarting the server?

Overwrite the Python file and trigger a catalog refresh. If using the filesystem, modify the file in `FUNCTION_CALLING_DIR` and call `get_function_catalog().refresh()`. When using the API, set `overwrite: true` in your POST request to `/api/tools/local`—the endpoint automatically refreshes the catalog after writing the file, making the updated tool immediately available to agents.

### Can I restrict which functions are exposed to agents?

Yes. The `FunctionManager` automatically excludes any function starting with an underscore (`_`) or defined in files starting with an underscore. To hide specific functions from the catalog, prefix them with an underscore in their definition. For additional security, modify [`server/routes/tools.py`](https://github.com/OpenBMB/ChatDev/blob/main/server/routes/tools.py) to implement authentication middleware or validate tool contents before allowing the POST endpoint to write files.