Function Calling allows AI models to interact with external tools and APIs. When something goes wrong, the debugging process can feel overwhelming for beginners. This guide walks you through every step, from basic concepts to advanced troubleshooting, using practical examples with the HolySheep AI API.

What is Function Calling?

Think of Function Calling as giving your AI a "toolkit." Instead of just generating text, the model can request specific actions—like searching a database, making calculations, or fetching real-time data. The AI sends back a structured request (called a "tool call") that your application executes, then returns the results to continue the conversation.

Key terminology you'll encounter:

Understanding the tool_choice Parameter

The tool_choice parameter determines which tools the model can use and how it selects them. There are three main options:

Option 1: auto (Default)

The model decides whether to use tools based on the query. This is the most flexible setting for general use.

import requests

url = "https://api.holysheep.ai/v1/chat/completions"
headers = {
    "Authorization": f"Bearer {YOUR_HOLYSHEEP_API_KEY}",
    "Content-Type": "application/json"
}

payload = {
    "model": "deepseek-v3-0324",
    "messages": [
        {"role": "user", "content": "What's the weather in Tokyo?"}
    ],
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "Fetch current weather for a city",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city": {"type": "string", "description": "City name"}
                    },
                    "required": ["city"]
                }
            }
        }
    ],
    "tool_choice": "auto"  # Model decides if tool is needed
}

response = requests.post(url, headers=headers, json=payload)
print(response.json())

Option 2: none

The model will not call any tools. Useful when you want pure text generation without function execution.

Option 3: forced (or {"type": "function", "function": {"name": "specific_function"})

You force the model to use a specific tool. This is powerful for controlling agent workflows.

# Force the model to use a specific tool
payload = {
    "model": "deepseek-v3-0324",
    "messages": [
        {"role": "user", "content": "Explain this SQL query"}
    ],
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "validate_sql",
                "description": "Check SQL syntax and security",
                "parameters": {"type": "object", "properties": {}}
            }
        },
        {
            "type": "function",
            "function": {
                "name": "format_sql",
                "description": "Pretty-print SQL code",
                "parameters": {"type": "object", "properties": {}}
            }
        }
    ],
    # Force validate_sql - model MUST use this tool
    "tool_choice": {
        "type": "function",
        "function": {"name": "validate_sql"}
    }
}

Setting Up Your First Function Calling Request

Let's build a complete working example step by step. We'll create a simple calculator tool that the AI can call.

Step 1: Define Your Tool

Every tool needs a name, description, and parameter schema. The description is critical—it's how the AI decides when to use the tool. Be specific and clear.

# Complete tool definition example
weather_tool = {
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Returns the current weather for a specified city. "
                      "Use this when users ask about weather conditions, "
                      "temperature, or forecasts for a location.",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "The city name (e.g., 'London', 'New York')"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit preference",
                    "default": "celsius"
                }
            },
            "required": ["city"]
        }
    }
}

Step 2: Make the API Request

Send your request to the HolySheep AI endpoint. Notice the base URL uses https://api.holysheep.ai/v1—this is your gateway to Function Calling capabilities.

import requests
import json

url = "https://api.holysheep.ai/v1/chat/completions"
headers = {
    "Authorization": f"Bearer {YOUR_HOLYSHEEP_API_KEY}",
    "Content-Type": "application/json"
}

payload = {
    "model": "deepseek-v3-0324",  # Cost-effective model at $0.42/MTok
    "messages": [
        {"role": "user", "content": "Should I bring an umbrella in San Francisco tomorrow?"}
    ],
    "tools": [weather_tool],
    "tool_choice": "auto"
}

response = requests.post(url, headers=headers, json=payload)
result = response.json()

print(json.dumps(result, indent=2))

Step 3: Parse the Response

A tool call response looks different from regular chat completions. Here's how to identify and extract it:

def parse_response(response_data):
    """Handle both regular and tool-call responses"""
    
    if "choices" not in response_data:
        print(f"Error: {response_data}")
        return None
    
    choice = response_data["choices"][0]
    message = choice["message"]
    
    # Check if model wants to call a tool
    if "tool_calls" in message:
        print("🛠️  Tool call requested!")
        for tool_call in message["tool_calls"]:
            function_name = tool_call["function"]["name"]
            arguments = tool_call["function"]["arguments"]
            call_id = tool_call["id"]
            
            print(f"Function: {function_name}")
            print(f"Arguments: {arguments}")
            print(f"Call ID: {call_id}")
        
        return message["tool_calls"]
    else:
        # No tool call - regular text response
        print("💬 Text response:")
        print(message["content"])
        return None

Use the parser

tool_calls = parse_response(result)

If tools were called, execute them and continue

if tool_calls: for call in tool_calls: args = json.loads(call["function"]["arguments"]) # Simulate tool execution result_from_tool = execute_weather_tool(args["city"], args.get("unit", "celsius")) # Send result back to model messages = [ {"role": "user", "content": "Should I bring an umbrella in San Francisco tomorrow?"}, { "role": "assistant", "tool_calls": tool_calls }, { "role": "tool", "tool_call_id": call["id"], "content": json.dumps(result_from_tool) } ]

Common Errors and Fixes

Error 1: "Invalid tool_call id" or Mismatched Tool IDs

Symptom: You receive a 400 or 422 error when submitting tool results, or the model ignores your tool outputs.

Cause: The tool_call_id in your follow-up message doesn't match the ID returned by the model's tool call.

Fix:

# WRONG - This will fail!
wrong_message = {
    "role": "tool",
    "tool_call_id": "custom_id_123",  # ❌ Wrong!
    "content": "sunny, 22°C"
}

CORRECT - Use the ID from model response

correct_message = { "role": "tool", "tool_call_id": tool_call["id"], # ✅ Use exact ID from response "content": "sunny, 22°C" }

Error 2: tool_choice References Non-Existent Function

Symptom: 400 Bad Request error with message about function not found.

Cause: The function name in tool_choice doesn't exactly match a function in your tools array.

Fix:

# WRONG - Function name mismatch
tools = [
    {"type": "function", "function": {"name": "get_weather", ...}}  # lowercase
]
payload = {
    ...
    "tool_choice": {"type": "function", "function": {"name": "Get_Weather"}}  # ❌ Capital letters!
}

CORRECT - Exact match

tools = [ {"type": "function", "function": {"name": "get_weather", ...}} # lowercase ] payload = { ... "tool_choice": {"type": "function", "function": {"name": "get_weather"}} # ✅ Exact match }

Error 3: Missing Required Parameters in Tool Response

Symptom: The model ignores your tool output or asks for information you already provided.

Cause: Tool responses must be strings (JSON encoded). If you pass objects directly, they may not serialize correctly.

Fix:

# WRONG - Dictionary instead of string
{
    "role": "tool",
    "tool_call_id": call["id"],
    "content": {"status": "success", "temp": 22}  # ❌ Not a string!
}

CORRECT - JSON encoded string

{ "role": "tool", "tool_call_id": call["id"], "content": json.dumps({"status": "success", "temp": 22}) # ✅ String }

Error 4: Tool Arguments Not Matching Schema

Symptom: Your tool receives unexpected or missing arguments, or you get validation errors.

Cause: The AI generates arguments that don't match your parameter schema (wrong types, missing required fields, extra fields not defined).

Fix:

# Better schema with clear constraints
better_tool = {
    "type": "function",
    "function": {
        "name": "search_products",
        "description": "Search product catalog by category or keyword",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search query. Use specific terms for best results. "
                                 "Examples: 'wireless headphones', 'running shoes size 10'"
                },
                "max_price": {
                    "type": "number",
                    "description": "Maximum price in USD. Leave empty for no limit.",
                    "minimum": 0
                },
                "category": {
                    "type": "string",
                    "enum": ["electronics", "clothing", "home", "sports"],
                    "description": "Product category filter"
                }
            },
            "required": ["query"]  # Only query is mandatory
        }
    }
}

Error 5: Tool Calls Not Appearing in Response

Symptom: Model returns text instead of making a tool call.

Cause: Several possible issues—wrong tool_choice setting, unclear descriptions, or the model doesn't think it needs the tool.

Fix:

# Debug checklist
def validate_tool_setup(tools, tool_choice="auto"):
    """Check for common setup issues"""
    issues = []
    
    if not tools:
        issues.append("No tools defined")
    
    for tool in tools:
        func = tool.get("function", {})
        
        if not func.get("name"):
            issues.append("Tool missing function name")
        
        if not func.get("description"):
            issues.append(f"Tool '{func.get('name')}' missing description")
        
        params = func.get("parameters", {})
        if params.get("type") != "object":
            issues.append(f"Tool '{func.get('name')}' parameters must be type 'object'")
    
    if issues:
        print("⚠️  Issues found:")
        for issue in issues:
            print(f"  - {issue}")
        return False
    
    print("✅ Tool setup looks valid")
    return True

Debugging Workflow: Systematic Approach

When Function Calling fails, follow this troubleshooting sequence:

Step 1: Verify Your Request Structure

import json

def debug_request(payload):
    """Print request structure for debugging"""
    print("=== REQUEST DEBUG ===")
    print(f"Model: {payload.get('model')}")
    print(f"tool_choice: {payload.get('tool_choice')}")
    print(f"Messages: {len(payload.get('messages', []))}")
    print(f"Tools: {len(payload.get('tools', []))}")
    
    for i, tool in enumerate(payload.get('tools', [])):
        print(f"  Tool {i+1}: {tool['function']['name']}")
    
    print("=== END DEBUG ===\n")

Step 2: Check the Raw Response

def analyze_response(response):
    """Analyze API response for debugging"""
    if not response.ok:
        print(f"❌ HTTP Error: {response.status_code}")
        print(f"Response: {response.text}")
        return
    
    data = response.json()
    
    if "error" in data:
        print(f"❌ API Error: {data['error']}")
        return
    
    choice = data.get("choices", [{}])[0]
    message = choice.get("message", {})
    
    if "tool_calls" in message:
        print(f"✅ Tool calls made: {len(message['tool_calls'])}")
        for tc in message["tool_calls"]:
            print(f"  - {tc['function']['name']}({tc['function']['arguments']})")
    else:
        print("⚠️  No tool calls in response")
        print(f"Content: {message