Skip to content

Tool Calling Quick Start

Get started with Captain's tool calling in 5 minutes.

What is Tool Calling?

Tool calling (function calling) lets your AI use external functions like:

  • 🧮 Perform calculations
  • 🌐 Call APIs
  • 💾 Query databases
  • 📁 Read files
  • 🔧 Execute custom code

Key Point: Tools execute on your side (client), not Captain's servers. You maintain full control.

Quick Example

Python

from openai import OpenAI

client = OpenAI(
    base_url="https://api.runcaptain.com/v1",
    api_key="your_api_key",
    default_headers={"X-Organization-ID": "your_org_id"}
)

# Define your tool
tools = [{
    "type": "function",
    "function": {
        "name": "calculate",
        "description": "Perform arithmetic",
        "parameters": {
            "type": "object",
            "properties": {
                "operation": {"type": "string", "enum": ["add", "multiply"]},
                "a": {"type": "number"},
                "b": {"type": "number"}
            },
            "required": ["operation", "a", "b"]
        },
        "strict": True  # Required!
    }
}]

# Make request
response = client.chat.completions.create(
    model="captain-voyager-latest",
    messages=[{"role": "user", "content": "What is 50 times 3?"}],
    tools=tools
)

# Check if model wants to use tool
if response.choices[0].finish_reason == "tool_calls":
    import json
    tool_call = response.choices[0].message.tool_calls[0]
    args = json.loads(tool_call.function.arguments)

    # Execute tool on your side
    if args["operation"] == "multiply":
        result = args["a"] * args["b"]
        print(f"Result: {result}")

TypeScript (Vercel AI SDK)

import { createOpenAI } from '@ai-sdk/openai';
import { generateText } from 'ai';
import { z } from 'zod';

const captain = createOpenAI({
  apiKey: process.env.CAPTAIN_API_KEY!,
  baseURL: 'https://api.runcaptain.com/v1',
  headers: { 'X-Organization-ID': process.env.CAPTAIN_ORG_ID! }
});

const result = await generateText({
  model: captain.chat('captain-voyager-latest'),
  messages: [
    { role: 'user', content: 'What is 50 times 3?' }
  ],
  tools: {
    calculate: {
      description: 'Perform arithmetic',
      parameters: z.object({
        operation: z.enum(['add', 'multiply']),
        a: z.number(),
        b: z.number()
      }),
      execute: async ({ operation, a, b }) => {
        // Execute on your side
        return operation === 'multiply' ? a * b : a + b;
      }
    }
  },
  maxSteps: 5  // Allow multiple tool calls
});

console.log(result.text);

How It Works

  1. You define tools with names, descriptions, and parameters
  2. Send request to Captain with tools
  3. Captain returns tool call request (if needed)
  4. You execute the tool in your environment
  5. Continue conversation with results (optional)
graph LR
    A[Your App] -->|1. Request with tools| B[Captain API]
    B -->|2. Tool call needed| A
    A -->|3. Execute tool| C[Your Function]
    C -->|4. Return result| A

Tool Definition Format

Every tool needs:

{
    "type": "function",           # Always "function"
    "function": {
        "name": "tool_name",      # Unique name
        "description": "...",     # Clear description
        "parameters": {...},      # JSON Schema
        "strict": True            # REQUIRED!
    }
}

Common Use Cases

API Call Tool

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string"}
            },
            "required": ["city"]
        },
        "strict": True
    }
}]

Database Query Tool

tools = [{
    "type": "function",
    "function": {
        "name": "query_users",
        "description": "Query user database",
        "parameters": {
            "type": "object",
            "properties": {
                "user_id": {"type": "string"}
            },
            "required": ["user_id"]
        },
        "strict": True
    }
}]

File Operation Tool

tools = [{
    "type": "function",
    "function": {
        "name": "read_file",
        "description": "Read file contents",
        "parameters": {
            "type": "object",
            "properties": {
                "filename": {"type": "string"}
            },
            "required": ["filename"]
        },
        "strict": True
    }
}]

Best Practices

✅ Do This

# Clear, specific descriptions
"description": "Calculate the sum of two numbers. Use for addition only."

# Strict parameter types
"parameters": {
    "type": "object",
    "properties": {
        "amount": {"type": "number"},
        "currency": {"type": "string", "enum": ["USD", "EUR"]}
    },
    "required": ["amount", "currency"]
}

# Validate inputs before execution
def execute_tool(name, args):
    if name not in ALLOWED_TOOLS:
        raise ValueError("Tool not allowed")
    return ALLOWED_TOOLS[name](**args)

❌ Don't Do This

# Vague description
"description": "Does stuff"

# Missing types
"parameters": {
    "type": "object",
    "properties": {
        "data": {}  # What type?
    }
}

# No input validation
def execute_tool(name, args):
    return eval(f"{name}(**{args})")  # Dangerous!

Framework Support

Framework Status Multi-Turn Best For
OpenAI Python SDK Manual Python apps
OpenAI Node.js SDK Manual Node.js apps
Vercel AI SDK Auto (maxSteps) Best DX
LangChain Via agents Complex workflows

Recommendation: Use Vercel AI SDK for automatic multi-turn handling.

Troubleshooting

Tool Not Being Called

# Make description more explicit
"description": "ALWAYS use this tool for math. Never calculate manually."

# Strengthen system prompt
messages = [
    {
        "role": "system",
        "content": "You MUST use provided tools. Don't do calculations yourself."
    },
    {"role": "user", "content": "What is 5 + 3?"}
]

Empty Tool Arguments

# Check for missing args
args = json.loads(tool_call.function.arguments)
if not args.get("required_param"):
    # Provide default or skip
    args["required_param"] = "default"

Tool Execution Errors

# Wrap in try-catch
try:
    result = execute_tool(name, args)
except Exception as e:
    result = {"error": str(e)}

Next Steps

Get Help