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:
- Tool – A function your application exposes to the AI (e.g., get_weather, search_database)
- Tool Call – The AI's request to execute a specific function with specific arguments
- tool_choice – A parameter controlling how the AI selects which tools to use
- Parallel Calls – When multiple tools execute simultaneously
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:
- Always use the exact
idfrom the model'stool_callsresponse - Store the tool call ID immediately when you receive it
- Never generate your own IDs
# 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:
- Verify exact spelling and case sensitivity
- Check that the function exists in your tools array
- Use consistent naming conventions
# 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:
- Always JSON-encode your tool response content
- Ensure the content is a string, not a dictionary or list
- Include all relevant information in the response
# 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:
- Make your parameter descriptions extremely clear and specific
- Use
enumfor limited options - Set
requiredarray correctly - Consider adding
strictmode if available
# 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:
- Set
tool_choiceto"auto" - Improve your tool descriptions to be more specific about when to use them
- Ensure your tool definitions are properly formatted JSON
- Try simplifying the user query
# 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