Skip to content
Back to skills

1640 Tool Calling Eb0d2525

ASecurity

Tool calling (also known as function calling) allows language models to request execution of predefined functions during a conversation. This enables models to interact with external systems, retrieve real-time data, or perform actions on behalf of users.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
ai-agentspythongobashazureapidatabasedocumentation

Works with

  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1640-tool-calling_eb0d2525 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1640 Tool Calling Eb0d2525?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1640 Tool Calling Eb0d2525
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1640-tool-calling-eb0d2525/badge)](https://www.skillsdirectory.com/skills/tools-only-1640-tool-calling-eb0d2525)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Tool/Function Calling

## Overview

Tool calling (also known as function calling) allows language models to request execution of predefined functions during a conversation. This enables models to interact with external systems, retrieve real-time data, or perform actions on behalf of users.

Esperanto provides a **unified tool calling interface** that works consistently across all supported providers. Define your tools once, and use them with OpenAI, Anthropic, Google, Groq, Mistral, and more.

## Quick Start

```python
import json
from esperanto import AIFactory
from esperanto.common_types import Tool, ToolFunction

# Define a tool
weather_tool = Tool(
    type="function",
    function=ToolFunction(
        name="get_weather",
        description="Get the current weather for a location",
        parameters={
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City name, e.g., 'San Francisco, CA'"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit"
                }
            },
            "required": ["location"]
        }
    )
)

# Create a model with tools
model = AIFactory.create_language(
    provider="openai",
    model_name="gpt-4o",
    config={"tools": [weather_tool]}
)

# Send a message that might trigger tool use
messages = [{"role": "user", "content": "What's the weather in Tokyo?"}]
response = model.chat_complete(messages)

# Check if the model wants to call a tool
if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]
    print(f"Tool: {tool_call.function.name}")
    print(f"Arguments: {tool_call.function.arguments}")
```

## Tool Definition

### Tool Types

Esperanto provides typed classes for tool definitions:

```python
from esperanto.common_types import Tool, ToolFunction

# ToolFunction defines the function signature
function = ToolFunction(
    name="search_database",           # Function name (required)
    description="Search the database", # What the function does (required)
    parameters={                       # JSON Schema for parameters
        "type": "object",
        "properties": {
            "query": {"type": "string"},
            "limit": {"type": "integer", "default": 10}
        },
        "required": ["query"]
    },
    strict=True  # OpenAI-only: Enable strict mode for guaranteed schema adherence
)

# Tool wraps the function
tool = Tool(
    type="function",  # Currently only "function" is supported
    function=function
)
```

### Parameters Schema

The `parameters` field uses [JSON Schema](https://json-schema.org/) to define the function's input:

```python
parameters={
    "type": "object",
    "properties": {
        # String parameter
        "name": {
            "type": "string",
            "description": "The user's name"
        },
        # Number with constraints
        "age": {
            "type": "integer",
            "minimum": 0,
            "maximum": 150
        },
        # Enum (fixed choices)
        "status": {
            "type": "string",
            "enum": ["active", "inactive", "pending"]
        },
        # Array
        "tags": {
            "type": "array",
            "items": {"type": "string"}
        },
        # Nested object
        "address": {
            "type": "object",
            "properties": {
                "street": {"type": "string"},
                "city": {"type": "string"}
            }
        }
    },
    "required": ["name", "status"]  # Required fields
}
```

## Using Tools

### Passing Tools

Tools can be configured at two levels:

```python
# 1. Instance level - tools available for all calls
model = AIFactory.create_language(
    provider="openai",
    model_name="gpt-4o",
    config={"tools": [weather_tool, search_tool]}
)
response = model.chat_complete(messages)

# 2. Call level - override for specific request
response = model.chat_complete(
    messages,
    tools=[different_tool]  # Overrides instance tools
)
```

### Tool Choice

Control how the model uses tools:

```python
# "auto" (default) - Model decides whether to use tools
response = model.chat_complete(messages, tool_choice="auto")

# "required" - Model must call at least one tool
response = model.chat_complete(messages, tool_choice="required")

# "none" - Model cannot use tools (even if provided)
response = model.chat_complete(messages, tool_choice="none")

# Specific tool - Force calling a particular tool
response = model.chat_complete(
    messages,
    tool_choice={"type": "function", "function": {"name": "get_weather"}}
)
```

### Parallel Tool Calls

Some providers allow models to request multiple tool calls in a single response:

```python
# Allow parallel tool calls (default for most providers)
response = model.chat_complete(messages, parallel_tool_calls=True)

# Force single tool call per response
response = model.chat_complete(messages, parallel_tool_calls=False)
```

## Processing Tool Calls

### Reading Tool Call Responses

When a model decides to call a tool, the response contains `tool_calls`:

```python
response = model.chat_complete(messages, tools=tools)

message = response.choices[0].message

if message.tool_calls:
    for tool_call in message.tool_calls:
        # Unique ID for this tool call
        print(f"ID: {tool_call.id}")

        # Function name
        print(f"Function: {tool_call.function.name}")

        # Arguments as JSON string
        print(f"Arguments (raw): {tool_call.function.arguments}")

        # Parse arguments
        args = json.loads(tool_call.function.arguments)
        print(f"Arguments (parsed): {args}")
else:
    # Model responded with text instead
    print(f"Response: {message.content}")
```

### ToolCall Structure

```python
from esperanto.common_types import ToolCall, FunctionCall

# ToolCall has these fields:
tool_call = ToolCall(
    id="call_abc123",           # Unique identifier
    type="function",            # Always "function" currently
    function=FunctionCall(
        name="get_weather",     # Function to call
        arguments='{"location": "Tokyo"}'  # JSON string of arguments
    ),
    index=0                     # Optional: index for streaming
)
```

## Multi-Turn Conversations

To continue a conversation after tool calls, send the tool results back to the model:

```python
import json

# Initial request
messages = [{"role": "user", "content": "What's the weather in Tokyo and London?"}]
response = model.chat_complete(messages, tools=tools)

# Model requests tool calls
assistant_message = response.choices[0].message
if assistant_message.tool_calls:
    # Add assistant's message (with tool_calls) to history
    messages.append({
        "role": "assistant",
        "tool_calls": [
            {
                "id": tc.id,
                "type": tc.type,
                "function": {
                    "name": tc.function.name,
                    "arguments": tc.function.arguments
                }
            }
            for tc in assistant_message.tool_calls
        ]
    })

    # Execute each tool and add results
    for tool_call in assistant_message.tool_calls:
        # Execute your tool (implement this based on your needs)
        result = execute_tool(
            tool_call.function.name,
            json.loads(tool_call.function.arguments)
        )

        # Add tool result to messages
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)  # Must be a string
        })

    # Get final response with tool results
    final_response = model.chat_complete(messages, tools=tools)
    print(final_response.content)
```

### Complete Multi-Turn Example

```python
import json
from esperanto import AIFactory
from esperanto.common_types import Tool, ToolFunction

# Define tools
tools = [
    Tool(
        type="function",
        function=ToolFunction(
            name="get_weather",
            description="Get weather for a city",
            parameters={
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        )
    )
]

# Simulated tool execution
def execute_tool(name: str, args: dict) -> dict:
    if name == "get_weather":
        # In reality, call a weather API
        return {"temperature": 22, "condition": "sunny", "city": args["city"]}
    return {"error": f"Unknown tool: {name}"}

# Create model
model = AIFactory.create_language("openai", "gpt-4o")

# Conversation loop
messages = [{"role": "user", "content": "What's the weather in Paris?"}]

while True:
    response = model.chat_complete(messages, tools=tools)
    message = response.choices[0].message

    if not message.tool_calls:
        # No more tool calls - print final response
        print(f"Assistant: {message.content}")
        break

    # Add assistant message with tool calls
    messages.append({
        "role": "assistant",
        "tool_calls": [
            {
                "id": tc.id,
                "type": tc.type,
                "function": {
                    "name": tc.function.name,
                    "arguments": tc.function.arguments
                }
            }
            for tc in message.tool_calls
        ]
    })

    # Execute tools and add results
    for tool_call in message.tool_calls:
        args = json.loads(tool_call.function.arguments)
        result = execute_tool(tool_call.function.name, args)

        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)
        })
```

## Validation

Esperanto can validate tool call arguments against the JSON schema:

```python
from esperanto.common_types import ToolCallValidationError

try:
    response = model.chat_complete(
        messages,
        tools=tools,
        validate_tool_calls=True  # Enable validation
    )
except ToolCallValidationError as e:
    print(f"Tool '{e.tool_name}' validation failed:")
    for error in e.errors:
        print(f"  - {error}")
```

Validation requires the `jsonschema` package:

```bash
pip install esperanto[validation]
# or
pip install jsonschema
```

### Manual Validation

You can also validate tool calls manually:

```python
from esperanto.common_types import validate_tool_call, validate_tool_calls, find_tool_by_name

# Validate a single tool call
tool = find_tool_by_name(tools, tool_call.function.name)
if tool:
    validate_tool_call(tool_call, tool)

# Validate all tool calls in a response
if message.tool_calls:
    validate_tool_calls(message.tool_calls, tools)
```

## Streaming with Tools

Tool calls can also be received via streaming:

```python
# Streaming with tools
for chunk in model.chat_complete(messages, tools=tools, stream=True):
    delta = chunk.choices[0].delta

    if delta.content:
        print(delta.content, end="", flush=True)

    # Tool calls come in chunks during streaming
    if delta.tool_calls:
        for tc in delta.tool_calls:
            print(f"Tool call chunk: {tc}")
```

**Note**: When streaming, tool calls arrive incrementally. You'll need to accumulate the chunks to get complete tool call data. The `index` field on tool calls helps track which chunks belong to which tool call.

**Important**: The `validate_tool_calls` parameter is **not supported with streaming**. Tool call validation requires the complete response to validate arguments against the tool schema. If you pass `validate_tool_calls=True` with `stream=True`, a warning will be emitted and validation will be skipped. To validate tool calls when streaming, collect all chunks first, then use the manual validation utilities.

## Provider Support

| Provider | Tools | tool_choice | Streaming | Parallel Calls |
|----------|-------|-------------|-----------|----------------|
| OpenAI | Yes | Yes | Yes | Yes |
| Anthropic | Yes | Yes | Yes | Yes |
| Google | Yes | Yes | Yes | Varies |
| Groq | Yes | Yes | Yes | Yes |
| Mistral | Yes | Yes | Yes | Yes |
| Azure | Yes | Yes | Yes | Yes |
| Vertex AI | Yes | Yes | Yes | Varies |
| DeepSeek | Yes | Yes | Yes | Varies |
| xAI | Yes | Yes | Yes | Varies |
| OpenRouter | Yes* | Yes* | Yes* | Yes* |
| Ollama | Yes** | Yes** | Yes** | Varies |

\* Depends on underlying model
\** Model-dependent; not all Ollama models support tools

### Provider-Specific Notes

#### OpenAI
- Full support for all tool calling features
- Supports `strict` mode for guaranteed schema adherence
- Reasoning models (o1, o3) have limited tool support

#### Anthropic
- Tools are converted to Anthropic's `input_schema` format automatically
- `tool_choice="required"` maps to `{"type": "any"}`
- `parallel_tool_calls=False` maps to `disable_parallel_tool_use`

#### Google (Gemini)
- Tool IDs are generated by Esperanto (Google doesn't provide them)
- `tool_choice` maps to `function_calling_config.mode`

#### Vertex AI
- Similar to Google but uses the Vertex AI SDK
- Requires Google Cloud authentication

## Examples

See the [examples/tool_calling/](../../examples/tool_calling/) directory for complete examples:

- [basic_tool.py](../../examples/tool_calling/basic_tool.py) - Simple single-tool example
- [multiple_tools.py](../../examples/tool_calling/multiple_tools.py) - Working with multiple tools
- [multi_turn.py](../../examples/tool_calling/multi_turn.py) - Multi-turn conversations with tool results
- [streaming_tools.py](../../examples/tool_calling/streaming_tools.py) - Streaming with tool calls
- [provider_comparison.py](../../examples/tool_calling/provider_comparison.py) - Same code across providers

## Best Practices

### 1. Write Clear Descriptions

Models use descriptions to decide when and how to call tools:

```python
# Good - Clear and specific
ToolFunction(
    name="search_products",
    description="Search the product catalog by name, category, or price range. "
                "Returns up to 10 matching products with name, price, and availability.",
    parameters={...}
)

# Bad - Vague
ToolFunction(
    name="search",
    description="Searches stuff",
    parameters={...}
)
```

### 2. Use Specific Parameter Types

```python
# Good - Specific types with constraints
parameters={
    "type": "object",
    "properties": {
        "price_min": {"type": "number", "minimum": 0},
        "price_max": {"type": "number", "minimum": 0},
        "category": {"type": "string", "enum": ["electronics", "clothing", "food"]}
    }
}

# Bad - Too permissive
parameters={
    "type": "object",
    "properties": {
        "filters": {"type": "object"}  # Too vague
    }
}
```

### 3. Handle Errors Gracefully

```python
def execute_tool(name: str, args: dict) -> str:
    try:
        if name == "get_weather":
            return json.dumps(fetch_weather(args["city"]))
    except Exception as e:
        # Return error as tool result - model can handle it
        return json.dumps({"error": str(e)})
```

### 4. Limit Tool Count

Providing too many tools can confuse the model. Group related functionality:

```python
# Instead of many narrow tools:
# get_user_name, get_user_email, get_user_address, ...

# Use one comprehensive tool:
Tool(
    type="function",
    function=ToolFunction(
        name="get_user_info",
        description="Get user information",
        parameters={
            "type": "object",
            "properties": {
                "user_id": {"type": "string"},
                "fields": {
                    "type": "array",
                    "items": {"type": "string", "enum": ["name", "email", "address", "phone"]}
                }
            }
        }
    )
)
```

## See Also

- [Language Models (LLM)](../capabilities/llm.md) - Core LLM documentation
- [Provider Guides](../providers/README.md) - Provider-specific setup and features
- [Examples](../../examples/tool_calling/) - Complete working examples

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.