---
title: Tool Calling Methods
description: Reference documentation for the three tool calling methods supported by Kollabor CLI
category: reference
status: stable
last_updated: 2026-01-26
version: 1.0.0
tags:
  - level: intermediate
  - component: llm
  - status: stable
related:
  - ./llm-integration/llm-service.md
  - ./architecture-overview.md
  - ../analysis/tool-system.md
---

# Tool Calling Methods

## Overview

Kollabor CLI supports three distinct methods for LLM tool calling:

| Method | Type | Description |
|--------|------|-------------|
| **OpenAI Native** | Native | OpenAI function calling format via `/v1/chat/completions` |
| **Anthropic Native** | Native | Anthropic tool use format via `/v1/messages` |
| **XML Custom** | Custom | XML tag-based format embedded in response text |

The term **"native tool calling"** refers to OpenAI and Anthropic formats where tools are passed as structured API parameters and returned as structured response objects.

The term **"XML custom"** refers to the fallback format where tool calls are embedded as XML tags within the response text content.

---

## Method Comparison

```
                    ┌─────────────────────────────────────────────────────────┐
                    │              Tool Calling Methods                       │
                    └─────────────────────────────────────────────────────────┘
                                          │
            ┌─────────────────────────────┼─────────────────────────────┐
            │                             │                             │
            ▼                             ▼                             ▼
    ┌───────────────┐           ┌───────────────┐           ┌───────────────┐
    │  OpenAI       │           │  Anthropic    │           │  XML Custom   │
    │  Native       │           │  Native       │           │               │
    └───────────────┘           └───────────────┘           └───────────────┘
            │                             │                             │
            │                             │                             │
    • /v1/chat/completions       • /v1/messages              • Embedded in text
    • "parameters" key           • "input_schema" key        • Regex parsing
    • "tool_calls" response      • "tool_use" blocks         • No API support needed
    • role="tool" results        • "tool_result" blocks      • Always available
```

### Feature Matrix

| Feature | OpenAI Native | Anthropic Native | XML Custom |
|---------|--------------|------------------|------------|
| API parameter passing | Yes | Yes | No |
| Structured responses | Yes | Yes | No (text parsing) |
| Streaming support | Yes | Yes | Yes |
| Works without API tool support | No | No | Yes |
| Tool result tracking | `tool_call_id` | `tool_use_id` | None |
| Provider detection | Automatic | Automatic | Always enabled |

---

## 1. OpenAI Native Format

### When Used

- Provider is `openai` or OpenAI-compatible (LM Studio, Ollama, vLLM)
- Profile configuration specifies OpenAI endpoint
- API supports function calling

### Request Format

Tools are passed as structured API parameters:

```python
# Tool definition format
{
    "type": "function",
    "function": {
        "name": "file_read",
        "description": "Read content from a file",
        "parameters": {
            "type": "object",
            "properties": {
                "filepath": {"type": "string", "description": "Path to file"},
                "offset": {"type": "integer", "description": "Line offset"},
                "limit": {"type": "integer", "description": "Number of lines"}
            },
            "required": ["filepath"]
        }
    }
}
```

### Response Format

Tool calls are returned as structured objects:

```python
# Response with tool calls
{
    "role": "assistant",
    "content": "Let me read that file for you.",
    "tool_calls": [
        {
            "id": "call_abc123",
            "type": "function",
            "function": {
                "name": "file_read",
                "arguments": "{\"filepath\": \"README.md\"}"
            }
        }
    ]
}
```

### Tool Result Format

Results are sent back with the tool call ID:

```python
{
    "role": "tool",
    "tool_call_id": "call_abc123",
    "content": "File contents here..."
}
```

### API Endpoint

```
POST /v1/chat/completions
```

### Implementation

**File**: `kollabor/llm/api_adapters/openai_adapter.py`

```python
class OpenAIAdapter(BaseAPIAdapter):
    @property
    def tool_format(self) -> ToolCallingFormat:
        return ToolCallingFormat.OPENAI

    def format_tool_definitions(self, tools):
        # Wraps tools in {"type": "function", "function": {...}}
        ...
```

---

## 2. Anthropic Native Format

### When Used

- Provider is `anthropic`
- Profile uses Anthropic API (`api.anthropic.com`)
- API supports tool use

### Request Format

Tools use `input_schema` instead of `parameters`:

```python
# Tool definition format
{
    "name": "file_read",
    "description": "Read content from a file",
    "input_schema": {
        "type": "object",
        "properties": {
            "filepath": {"type": "string", "description": "Path to file"},
            "offset": {"type": "integer", "description": "Line offset"},
            "limit": {"type": "integer", "description": "Number of lines"}
        },
        "required": ["filepath"]
    }
}
```

### Response Format

Tool calls are `tool_use` content blocks:

```python
# Response with tool use
{
    "role": "assistant",
    "content": [
        {"type": "text", "text": "Let me read that file for you."},
        {
            "type": "tool_use",
            "id": "toolu_abc123",
            "name": "file_read",
            "input": {"filepath": "README.md"}
        }
    ],
    "stop_reason": "tool_use"
}
```

### Tool Result Format

Results are `tool_result` content blocks in user messages:

```python
{
    "role": "user",
    "content": [
        {
            "type": "tool_result",
            "tool_use_id": "toolu_abc123",
            "content": "File contents here..."
        }
    ]
}
```

### Key Differences from OpenAI

1. **System message**: Separate field, not in messages array
2. **Schema key**: Uses `input_schema` instead of `parameters`
3. **Content structure**: Array of typed blocks, not single string
4. **Tool results**: Embedded in user messages as `tool_result` blocks
5. **tool_choice**: Different format (`{"type": "auto"}` vs `"auto"`)

### API Endpoint

```
POST /v1/messages
```

### Implementation

**File**: `kollabor/llm/api_adapters/anthropic_adapter.py`

```python
class AnthropicAdapter(BaseAPIAdapter):
    @property
    def tool_format(self) -> ToolCallingFormat:
        return ToolCallingFormat.ANTHROPIC

    def format_tool_definitions(self, tools):
        # Renames "parameters" to "input_schema"
        ...
```

---

## 3. XML Custom Format

### When Used

- Fallback when native tool calling unavailable
- LLMs that don't support API-level tools
- Always enabled for compatibility (parsing is always active)
- Works alongside native formats (checked after native parsing)

### Important: Tool Discovery Limitation

**XML format does NOT automatically expose MCP tools to the LLM.**

Unlike native tool calling where tools are passed as API parameters, XML parsing only works if the LLM already knows about the tools. This means:

- **Native enabled**: LLM receives tool definitions in API request, knows what tools exist
- **Native disabled/unavailable**: LLM has no way to discover available MCP tools

For XML-only LLMs to use MCP tools, you must manually document available tools in:
- System prompt
- Agent definition files
- Custom prompt files

```markdown
## Available MCP Tools

You have access to these tools via <tool> tags:

- search_nodes: Search the knowledge graph
  <tool name="search_nodes" query="your query">content</tool>

- create_entity: Create a new entity
  <tool name="create_entity" name="Entity Name" type="concept">description</tool>
```

**Built-in tools (terminal, file operations)** work with XML format because they're documented in the default system prompt.

### Format Types

The XML format supports multiple tool call syntaxes:

#### Terminal Commands

```xml
<terminal>ls -la</terminal>
```

#### MCP Tool Calls (Attribute Format)

```xml
<tool name="search_nodes" query="python" limit="10">
    Additional content
</tool>
```

#### MCP Tool Calls (JSON Format)

```xml
<tool_call>
{"name": "search_nodes", "arguments": {"query": "python", "limit": 10}}
</tool_call>
```

#### MCP Tool Calls (Simple Format)

```xml
<tool_call>search_nodes</tool_call>
```

#### File Operations

```xml
<read>
  <file>README.md</file>
</read>

<edit>
  <file>src/main.py</file>
  <find>old code</find>
  <replace>new code</replace>
</edit>

<create>
  <file>new_file.py</file>
  <content>
def hello():
    print("world")
  </content>
</create>
```

#### Agent File Generation (@@@ Blocks)

```
@@@FILE path/to/file.md
Content here (can contain XML examples without conflicts)
@@@END
```

### Supported File Operations

| Tag | Description | Parameters |
|-----|-------------|------------|
| `<read>` | Read file content | `<file>` |
| `<edit>` | Replace content | `<file>`, `<find>`, `<replace>` |
| `<create>` | Create new file | `<file>`, `<content>` |
| `<create_overwrite>` | Create/overwrite file | `<file>`, `<content>` |
| `<delete>` | Delete file | `<file>` |
| `<move>` | Move/rename file | `<from>`, `<to>` |
| `<copy>` | Copy file | `<from>`, `<to>` |
| `<copy_overwrite>` | Copy with overwrite | `<from>`, `<to>` |
| `<append>` | Append to file | `<file>`, `<content>` |
| `<insert_after>` | Insert after pattern | `<file>`, `<pattern>`, `<content>` |
| `<insert_before>` | Insert before pattern | `<file>`, `<pattern>`, `<content>` |
| `<mkdir>` | Create directory | `<path>` |
| `<rmdir>` | Remove directory | `<path>` |
| `<grep>` | Search for pattern | `<pattern>`, `<path>` |

### Implementation

**File**: `kollabor/llm/response_parser.py`

```python
class ResponseParser:
    def __init__(self):
        # Terminal commands
        self.terminal_pattern = re.compile(
            r'<terminal>(.*?)</terminal>',
            re.DOTALL | re.IGNORECASE
        )

        # MCP tool calls with attributes
        self.tool_pattern = re.compile(
            r'<tool\s+([^>]*?)>(.*?)</tool>',
            re.DOTALL | re.IGNORECASE
        )

        # Native-style tool_call tags
        self.tool_call_pattern = re.compile(
            r'<tool_call>(.*?)</tool_call>',
            re.DOTALL | re.IGNORECASE
        )
```

---

## Adapter System Architecture

### Generic Tool Schema

Tools are defined in a provider-agnostic format internally:

```python
{
    "name": "tool_name",
    "description": "Tool description",
    "parameters": {
        "type": "object",
        "properties": {...},
        "required": [...]
    }
}
```

### Automatic Conversion

The adapter system automatically converts to provider-specific formats:

```
Generic Schema
     │
     ├──▶ OpenAI Adapter ──▶ {"type": "function", "function": {..., "parameters": {...}}}
     │
     └──▶ Anthropic Adapter ──▶ {"name": ..., "input_schema": {...}}
```

### Base Adapter Interface

**File**: `kollabor/llm/api_adapters/base.py`

```python
class ToolCallingFormat(Enum):
    OPENAI = "openai"      # /v1/chat/completions, parameters, tool_calls
    ANTHROPIC = "anthropic"  # /v1/messages, input_schema, tool_use

class BaseAPIAdapter(ABC):
    @abstractmethod
    def format_tool_definitions(self, tools) -> List[Dict]:
        """Convert generic tools to provider format."""
        pass

    @abstractmethod
    def format_tool_result(self, tool_id, result, is_error) -> Dict:
        """Format tool result for conversation continuation."""
        pass

    @abstractmethod
    def parse_response(self, raw_response) -> AdapterResponse:
        """Parse response and extract tool calls."""
        pass
```

---

## Tool Execution Flow

### Native Tool Calling Flow

```
┌─────────────────────────────────────────────────────────────────────────┐
│                     Native Tool Calling Flow                            │
└─────────────────────────────────────────────────────────────────────────┘

1. Request Phase:
   User Input ──▶ LLMService ──▶ APICommunicationService
                                        │
                                        ▼
                              ┌───────────────────┐
                              │ Generic Tool      │
                              │ Definitions       │
                              └───────────────────┘
                                        │
                                        ▼
                              ┌───────────────────┐
                              │ Provider Adapter  │
                              │ (OpenAI/Anthropic)│
                              └───────────────────┘
                                        │
                                        ▼
                              ┌───────────────────┐
                              │ Formatted Request │
                              │ + Tools           │
                              └───────────────────┘
                                        │
                                        ▼
                                   LLM API

2. Response Phase:
   LLM API ──▶ Provider Adapter ──▶ AdapterResponse
                                          │
                                          ▼
                                 ┌─────────────────┐
                                 │ tool_calls:     │
                                 │ - tool_id       │
                                 │ - tool_name     │
                                 │ - arguments     │
                                 └─────────────────┘
                                          │
                                          ▼
                                   ToolExecutor
                                          │
                                          ▼
                                   Execute Tool
                                          │
                                          ▼
                               Format Result + Continue
```

### XML Tool Calling Flow

```
┌─────────────────────────────────────────────────────────────────────────┐
│                      XML Tool Calling Flow                              │
└─────────────────────────────────────────────────────────────────────────┘

1. Request Phase:
   User Input ──▶ LLMService ──▶ APICommunicationService
                                        │
                                        ▼
                                   LLM API
                            (No tool definitions sent)

2. Response Phase:
   LLM API Response (text with XML tags)
           │
           ▼
   ┌────────────────────────────┐
   │ ResponseParser             │
   │ - Regex pattern matching   │
   │ - Extract <terminal>       │
   │ - Extract <tool>           │
   │ - Extract <tool_call>      │
   │ - Extract file operations  │
   └────────────────────────────┘
           │
           ▼
   ┌────────────────────────────┐
   │ Extracted Tools:           │
   │ - terminal_commands: []    │
   │ - tool_calls: []           │
   │ - file_operations: []      │
   └────────────────────────────┘
           │
           ▼
       ToolExecutor
           │
           ▼
       Execute Tool
           │
           ▼
   Add Results to Conversation
   (as text, not structured)
```

---

## Unified Tool Execution

Regardless of how tools are detected (native or XML), they are executed through the same `ToolExecutor`:

**File**: `kollabor/llm/tool_executor.py`

```python
class ToolExecutor:
    async def execute_tool(self, tool_data: Dict) -> ToolExecutionResult:
        tool_type = tool_data.get("type", "unknown")

        if tool_type == "terminal":
            return await self._execute_terminal_command(tool_data)
        elif tool_type == "mcp_tool":
            return await self._execute_mcp_tool(tool_data)
        elif tool_type.startswith("file_"):
            return await self._execute_file_operation(tool_data)
```

### ToolExecutionResult

```python
@dataclass
class ToolExecutionResult:
    tool_id: str           # Unique identifier
    tool_type: str         # terminal, mcp_tool, file_*
    success: bool          # Execution success
    output: str            # Tool output/result
    error: str             # Error message if failed
    execution_time: float  # Seconds
    metadata: Dict         # Additional data
    timestamp: float       # Unix timestamp
```

---

## Configuration

### Profile Configuration

Profiles determine which native format to use:

```json
{
    "profiles": {
        "claude": {
            "provider": "anthropic",
            "model": "claude-sonnet-4-20250514",
            "api_url": "https://api.anthropic.com"
        },
        "gpt4": {
            "provider": "openai",
            "model": "gpt-4",
            "api_url": "https://api.openai.com"
        },
        "local": {
            "provider": "openai",
            "model": "qwen/qwen3-4b",
            "api_url": "http://localhost:1234"
        }
    }
}
```

### Tool Accumulation Mode

```json
{
    "core": {
        "llm": {
            "use_explicit_tool_accumulation": false
        }
    }
}
```

---

## Fallback Behavior

The system uses both native and XML parsing for maximum compatibility:

```python
# In llm_service.py after receiving response

# 1. Check for native tool calls first
if api_service.has_pending_tool_calls():
    native_results = await self._execute_native_tool_calls()

# 2. Also check for XML-based tools (fallback)
parsed_response = response_parser.parse_response(response_text)
xml_tools = response_parser.get_all_tools(parsed_response)
if xml_tools:
    xml_results = await tool_executor.execute_all_tools(xml_tools)
```

This dual-checking ensures:
- Native tools work with modern APIs
- XML tools work with any LLM (if LLM knows about them)
- Backwards compatibility is maintained

### Critical Distinction: Parsing vs Discovery

| Aspect | Native | XML |
|--------|--------|-----|
| **Parsing** | Always enabled | Always enabled |
| **Tool Discovery** | Automatic (API params) | Manual (system prompt) |
| **MCP Tools** | Auto-exposed | Must document |
| **Built-in Tools** | Auto-exposed | Documented in default prompt |

**XML parsing is always active**, but it only works when the LLM outputs XML tags. The LLM won't output XML tags for tools it doesn't know exist.

For MCP tools to work with XML-only LLMs, you must:
1. Document available MCP tools in system prompt
2. Include example XML syntax for each tool
3. Describe parameters and expected behavior

---

## Question Gate Protocol

Both native and XML tool calls integrate with the Question Gate:

```xml
<question>Which payment system are you referring to - Stripe or PayPal?</question>
<terminal>ls payment/</terminal>
<read><file>payment/config.py</file></read>
```

When `<question>` is detected:
1. Tools are suspended in `pending_tools`
2. Question is displayed to user
3. User responds
4. Suspended tools execute with user's context

---

## MCP Tool Exposure Matrix

Understanding when LLMs can access MCP tools:

| Scenario | LLM Knows Tools? | Can Call Tools? | Notes |
|----------|-----------------|-----------------|-------|
| Native ON + MCP connected | Yes (API params) | Yes | Full support |
| Native OFF + MCP connected | No | Only if documented | Must add to system prompt |
| Native ON + no MCP | No MCP tools | Built-in only | File ops, terminal work |
| Native OFF + no MCP | Documented only | If in prompt | XML for built-ins |

### Tool Exposure Flow

```
                   ┌─────────────────────────────────────────┐
                   │         MCP Server Connected            │
                   │  tool_registry: {search_nodes, ...}     │
                   └─────────────────────────────────────────┘
                                      │
                                      ▼
           ┌──────────────────────────┴──────────────────────────┐
           │                                                      │
           ▼                                                      ▼
   ┌───────────────────┐                              ┌───────────────────┐
   │  Native Enabled   │                              │  Native Disabled  │
   └───────────────────┘                              └───────────────────┘
           │                                                      │
           ▼                                                      ▼
   ┌───────────────────┐                              ┌───────────────────┐
   │ get_tool_defs_    │                              │  No automatic     │
   │ for_api()         │                              │  tool exposure    │
   │                   │                              │                   │
   │ Returns:          │                              │  LLM only knows   │
   │ - MCP tools       │                              │  tools documented │
   │ - Built-in tools  │                              │  in system prompt │
   └───────────────────┘                              └───────────────────┘
           │                                                      │
           ▼                                                      ▼
   ┌───────────────────┐                              ┌───────────────────┐
   │ API Request:      │                              │ API Request:      │
   │ tools=[...]       │                              │ tools=[] (empty)  │
   └───────────────────┘                              └───────────────────┘
           │                                                      │
           ▼                                                      ▼
   LLM can call any                                   LLM can only call
   registered tool                                    tools it "knows about"
```

---

## Best Practices

### For LLM Providers

1. **Use Native Format When Available**: More reliable, structured responses
2. **Fallback to XML**: For providers without native tool support
3. **Configure Profiles Correctly**: Set `provider` field to enable correct adapter
4. **Document Tools for XML-only LLMs**: Add MCP tool documentation to system prompt

### For Tool Development

1. **Use Generic Schema**: Define tools in provider-agnostic format
2. **Let Adapters Convert**: Don't hard-code provider-specific formats
3. **Handle Both Paths**: Test with native and XML parsing

### For Debugging

1. **Check `stop_reason`**: `tool_use` or `tool_calls` indicates native calls
2. **Enable Debug Logging**: See which path detected tools
3. **Review Raw Response**: Verify XML tags if native fails

---

## Related Documentation

- [LLM Service Architecture](./llm-integration/llm-service.md)
- [Tool System Analysis](../analysis/tool-system.md)
- [Native Tool Calling Spec](../specs/started/native-tool-calling-spec.md)
- [Architecture Overview](./architecture-overview.md)

---

## Glossary

| Term | Definition |
|------|------------|
| **Native Tool Calling** | Tool calls via structured API parameters (OpenAI/Anthropic) |
| **XML Custom Format** | Tool calls via XML tags embedded in response text |
| **Adapter** | Component that converts between generic and provider-specific formats |
| **Tool Schema** | JSON Schema definition of tool parameters |
| **Tool Result** | Output from executing a tool, formatted for conversation |
| **Question Gate** | Protocol that suspends tools when LLM asks clarifying question |

---

**Last Updated**: 2026-01-26
**Status**: Stable
**Version**: 1.0.0
