Intermediate

Tool Use and Function Calling Prompts

Lesson 3 of 4 Estimated Time 50 min

Tool Use and Function Calling Prompts

Introduction

A limitation of pure language models is that they can only generate text. They can’t actually search the internet, look up real-time information, or take actions. But many applications need exactly that: a model that can think about what to do AND actually do it.

This is where function calling (also called tool use) comes in. You define a set of tools the model can use, and it decides when and how to use them. This lesson teaches you how to design prompts that effectively guide models to use tools correctly.

Key Takeaway: Tool use requires clear tool descriptions and explicit reasoning steps in your prompts. The model must understand what each tool does, when to use it, and how to interpret the results. A poorly described tool is worse than no tool at all.

How Function Calling Works

Modern LLMs can be given descriptions of functions and then decide to call them:

1. User asks: "What's the weather in San Francisco?"

2. System prompt + tool definitions tell the model:
   "You have access to these tools: get_weather(city), web_search(query)"

3. Model reasons: "I need current weather. The get_weather tool is perfect."

4. Model outputs: {
     "tool": "get_weather",
     "parameters": {"city": "San Francisco"}
   }

5. Your app executes get_weather("San Francisco") → "72°F, sunny"

6. Model continues: "The weather in San Francisco is 72°F and sunny."

The key is that the model doesn’t execute the tool - it just decides to call it and your application runs the actual function.

Designing Tool Descriptions

The tool description is critical. It’s your chance to tell the model what the tool does and when to use it:

Bad Tool Description

{
  "name": "search",
  "description": "Search tool"
}

Why it’s bad:

  • What does it search? The web? A database?
  • When should I use this?
  • What format is the input?

Good Tool Description

{
  "name": "web_search",
  "description": "Search the public internet for current information. Use this when you need real-time data, recent news, or information not in your training data. Examples: weather, stock prices, breaking news, current events.",
  "parameters": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "The search query. Be specific and concise (1-10 words). Examples: 'TSLA stock price today', 'weather San Francisco', 'Python 3.12 release date'"
      },
      "max_results": {
        "type": "integer",
        "description": "Maximum number of results to return (1-10). Default is 5."
      }
    },
    "required": ["query"]
  }
}

Why it’s better:

  • Clear purpose and when to use it
  • Specific guidance on parameter format
  • Examples of good queries
  • Explains what counts as “current information”

Better Still: OpenAI Function Schema Example

def define_tools():
    """Define tools available to the model"""

    return [
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "Get the current weather in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city and state, e.g. 'San Francisco, CA'"
                        },
                        "unit": {
                            "type": "string",
                            "enum": ["celsius", "fahrenheit"],
                            "description": "Temperature unit preference"
                        }
                    },
                    "required": ["location"]
                }
            }
        },
        {
            "type": "function",
            "function": {
                "name": "get_current_time",
                "description": "Get the current date and time in a specified timezone",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "timezone": {
                            "type": "string",
                            "description": "IANA timezone, e.g. 'America/New_York', 'Europe/London'"
                        }
                    },
                    "required": ["timezone"]
                }
            }
        }
    ]

System Prompts for Tool Use

Your system prompt must teach the model how and when to use tools:

You are a helpful assistant with access to the following tools:
- web_search(query): Search the internet for current information
- calculator(expression): Perform mathematical calculations
- email_send(to, subject, body): Send an email

TOOL USE GUIDELINES:

When to Use Tools:
- Use web_search for: current events, real-time data, recent information
- Use calculator for: arithmetic, unit conversions, complex math
- Use email_send for: delivering messages (only with explicit permission)

When NOT to Use Tools:
- Don't search for general knowledge (use your training data)
- Don't search for things in your training data
- Don't use calculator for simple mental math (2+2)
- Never email without user permission

How to Use Tools:
1. Decide if you need a tool
2. If yes, call it with appropriate parameters
3. Wait for the result
4. Incorporate the result into your answer
5. Explain what you found

Format for Tool Calls:
Use this exact format when calling a tool:
TOOL_CALL: {
  "tool": "tool_name",
  "parameters": {
    "param1": "value1",
    "param2": "value2"
  }
}

Tool Call Examples:
TOOL_CALL: {
  "tool": "web_search",
  "parameters": {"query": "current Bitcoin price"}
}

TOOL_CALL: {
  "tool": "calculator",
  "parameters": {"expression": "500 * 0.15 + 50"}
}

Error Handling:
If a tool call fails, say "Let me try a different approach" and
suggest an alternative (not always another tool).

Multi-Tool Orchestration

When you have multiple tools, the model must choose the right one(s):

def design_multi_tool_system_prompt(tools: list) -> str:
    """Create system prompt that guides multi-tool orchestration"""

    tool_descriptions = "\n".join([
        f"- {tool['name']}: {tool['description']}"
        for tool in tools
    ])

    return f"""You have access to these tools:
{tool_descriptions}

DECISION RULES FOR CHOOSING TOOLS:

1. Read the user's request carefully
2. Identify what information or action is needed
3. Choose the MINIMAL set of tools needed
   - Don't call multiple tools if one will do
   - Some tasks need sequential tools (call 1, then call 2)
4. Call tools in the right order
5. Use results from one tool as input to another

EXAMPLE: "What's the weather in NYC and how do I dress for it?"

Analysis:
- Need: Current weather in NYC + advice on clothing
- Tools needed: get_weather (required), no other tools needed
- Clothing advice comes from your knowledge, not a tool

Don't over-tool. Use tools only when necessary.

ANOTHER EXAMPLE: "Calculate a 20% tip on $47.50 and email it to Sarah"

Analysis:
- Need: Calculate tip (calculator), send email (email_send)
- Order: Calculate FIRST, then email with result
- Permission: Must have explicit permission before emailing

Call tools in sequence:
TOOL_CALL: {{...calculator...}}
[Wait for result]
Tip is $9.50

TOOL_CALL: {{...email_send...}}
"""

Handling Tool Errors and Failures

Tools can fail. Your system prompt must teach graceful error handling:

TOOL ERROR HANDLING:

If a tool returns an error or unexpected result:

1. DON'T pretend the tool worked
2. DON'T make up a result
3. DO acknowledge the error
4. DO suggest an alternative

Examples:

Error: "web_search failed - no internet connection"
Response: "I'm unable to search right now, but based on my
knowledge [provide what you know]. For real-time data,
please try again in a moment."

Error: "calculator returned overflow - expression too complex"
Response: "That calculation is too complex for the calculator.
Let me break it down into smaller steps..."

Error: "email_send failed - invalid email address"
Response: "That email address doesn't look valid. Can you
double-check the spelling? Make sure it's 'name@domain.com'"

Structured Output from Tool Use

Often you’ll want tool results in a specific format:

from dataclasses import dataclass
import json

@dataclass
class ToolCall:
    """Represents a tool call"""
    tool_name: str
    parameters: dict
    result: str = None
    error: str = None

def execute_tool(tool_call: ToolCall) -> ToolCall:
    """Execute a tool and return results"""

    tools = {
        'get_weather': get_weather_fn,
        'calculate': calculate_fn,
        'search': search_fn
    }

    if tool_call.tool_name not in tools:
        tool_call.error = f"Unknown tool: {tool_call.tool_name}"
        return tool_call

    try:
        result = tools[tool_call.tool_name](**tool_call.parameters)
        tool_call.result = json.dumps(result)
    except Exception as e:
        tool_call.error = str(e)

    return tool_call


def parse_tool_call_from_model(model_output: str) -> ToolCall:
    """Extract tool call from model's response"""

    # Model output contains: TOOL_CALL: {...json...}
    import re

    match = re.search(r'TOOL_CALL:\s*(\{.*?\})', model_output, re.DOTALL)

    if not match:
        return None

    json_str = match.group(1)

    try:
        tool_dict = json.loads(json_str)
        return ToolCall(
            tool_name=tool_dict.get('tool'),
            parameters=tool_dict.get('parameters', {})
        )
    except json.JSONDecodeError:
        return None


# Usage
model_output = """Based on your request, let me search for the information.

TOOL_CALL: {
  "tool": "web_search",
  "parameters": {"query": "Python async best practices 2024"}
}"""

tool_call = parse_tool_call_from_model(model_output)
result = execute_tool(tool_call)

print(f"Tool: {result.tool_name}")
print(f"Result: {result.result}")
if result.error:
    print(f"Error: {result.error}")

JSON Schema for Structured Outputs

For predictable tool calls, use JSON Schema:

def create_tool_with_schema(name: str, description: str, schema: dict):
    """Create a tool definition with JSON Schema"""

    return {
        "type": "function",
        "function": {
            "name": name,
            "description": description,
            "parameters": {
                "type": "object",
                "properties": schema['properties'],
                "required": schema.get('required', [])
            }
        }
    }

# Define tools with schemas
search_tool = create_tool_with_schema(
    name="web_search",
    description="Search the internet for information",
    schema={
        "properties": {
            "query": {
                "type": "string",
                "description": "What to search for"
            },
            "max_results": {
                "type": "integer",
                "description": "Number of results (1-10)"
            }
        },
        "required": ["query"]
    }
)

calculator_tool = create_tool_with_schema(
    name="calculator",
    description="Perform mathematical calculations",
    schema={
        "properties": {
            "expression": {
                "type": "string",
                "description": "Math expression, e.g., '2 + 2' or '100 * 0.15'"
            }
        },
        "required": ["expression"]
    }
)

contact_tool = create_tool_with_schema(
    name="get_contact",
    description": "Look up a contact by name",
    schema={
        "properties": {
            "name": {
                "type": "string",
                "description": "Person's name"
            }
        },
        "required": ["name"]
    }
)

A Complete Tool-Using System Prompt

Here’s a realistic example for an AI assistant with multiple tools:

You are an intelligent research assistant with access to tools
for searching, calculating, and retrieving information.

AVAILABLE TOOLS:

1. web_search(query, max_results)
   - Searches the internet for current information
   - Use for: recent news, current prices, real-time data
   - Query tips: be specific, 1-10 words

2. calculator(expression)
   - Performs mathematical calculations
   - Use for: arithmetic, unit conversions, complex math
   - Don't use for simple mental math (2+2)

3. get_contact(name)
   - Looks up contact information by name
   - Returns: email, phone, department
   - Use for: finding how to reach someone

4. send_email(to, subject, body)
   - Sends an email to a recipient
   - REQUIRES explicit user permission before calling
   - Don't assume permission

DECISION FRAMEWORK:

Step 1: Understand what the user needs
Step 2: Decide which tools are necessary (usually just one)
Step 3: Call tools with appropriate parameters
Step 4: Interpret results and explain to user
Step 5: Only escalate if tools can't help

TOOL CALL FORMAT:

TOOL_CALL: {
  "tool": "tool_name",
  "parameters": {
    "param1": "value",
    "param2": "value"
  }
}

GUIDELINES:

- Use minimal tools (fewer is better)
- Always explain what you're doing: "Let me search for that..."
- If a tool fails, don't make up results. Offer alternatives.
- Web search is for current information, not general knowledge
- Always get permission before sending emails
- Tool results may be incomplete; use your knowledge too

ERROR HANDLING:

If a tool fails:
1. Acknowledge the failure
2. Don't fabricate results
3. Offer what you can do:
   - "I can help based on my training data"
   - "Let me try a different search"
   - "I can help you manually"

EXAMPLES OF GOOD TOOL USE:

User: "How much is Bitcoin worth?"
Analysis: Need current price → web_search is right tool
TOOL_CALL: {"tool": "web_search", "parameters": {"query": "Bitcoin price today"}}
[Returns: Bitcoin at $42,500]
Response: "Bitcoin is trading at $42,500 according to current markets."

User: "What's 23% of $567.89?"
Analysis: Simple math, but large numbers → calculator
TOOL_CALL: {"tool": "calculator", "parameters": {"expression": "567.89 * 0.23"}}
[Returns: 130.61]
Response: "23% of $567.89 is $130.61"

User: "Send an email to Sarah saying hello"
Analysis: Need email address first → get_contact, then send_email
TOOL_CALL: {"tool": "get_contact", "parameters": {"name": "Sarah"}}
[Returns: sarah@company.com]
Now: "I found Sarah's email. Should I send the email? I'll send it now."
TOOL_CALL: {"tool": "send_email", "parameters": {"to": "sarah@company.com", "subject": "Hello", "body": "Hello Sarah!"}}

Testing Tool Use

Make sure the model actually uses tools correctly:

def test_tool_usage():
    """Test that model calls tools appropriately"""

    test_cases = [
        {
            'prompt': "What's the current price of gold?",
            'should_call_tools': ['web_search'],
            'should_not_call': ['calculator'],
            'reason': 'Needs current real-time data'
        },
        {
            'prompt': "Calculate 15% of $200",
            'should_call_tools': ['calculator'],
            'should_not_call': [],
            'reason': 'Needs calculation'
        },
        {
            'prompt': "What is photosynthesis?",
            'should_call_tools': [],
            'should_not_call': ['web_search', 'calculator'],
            'reason': 'General knowledge, no tools needed'
        },
        {
            'prompt': "Email my boss that I'll be late",
            'should_call_tools': [],  # Should ASK first, not email
            'should_not_call': ['send_email'],
            'reason': 'Must ask user for confirmation'
        }
    ]

    for test in test_cases:
        print(f"\nTest: {test['prompt']}")
        print(f"Reason: {test['reason']}")

        response = model.generate(
            system_prompt=TOOL_USING_SYSTEM_PROMPT,
            user_prompt=test['prompt']
        )

        # Parse tool calls from response
        tool_calls = parse_all_tool_calls(response)

        # Verify expectations
        called_tools = [tc.tool_name for tc in tool_calls]

        should_have = set(test['should_call_tools'])
        should_not_have = set(test['should_not_call'])

        has_should_have = should_have.issubset(called_tools)
        has_should_not_have = bool(should_not_have & set(called_tools))

        status = '✓' if (has_should_have and not has_should_not_have) else '✗'

        print(f"Status: {status}")
        print(f"Called: {called_tools}")
        if should_have:
            print(f"Should have called: {should_have}")
        if should_not_have and (should_not_have & set(called_tools)):
            print(f"Should NOT have called: {should_not_have}")

Exercise: Build a 3-Tool Agent

Design an AI assistant with three tools. Create:

  1. System prompt that teaches when and how to use tools
  2. Tool definitions (with JSON Schema) for:
    • A search tool
    • A calculator tool
    • A tool of your choice (email, lookup, API call, etc.)
  3. 5-7 test cases showing:
    • When to use each tool
    • When NOT to use any tool
    • What happens when tools fail
  4. Example conversations showing tool use:
    • Single tool call
    • Sequential tool calls
    • Minimal tool use (don’t use tools unnecessarily)

Deliverables:

  • Complete system prompt (~400 words)
  • Tool definitions in JSON Schema format
  • Test cases with expected behavior
  • Example conversation scripts (3-4)
  • Explanation of your tool selection logic

Summary

In this lesson, you’ve learned:

  • How function calling/tool use works at a high level
  • How to write clear tool descriptions so models understand them
  • System prompts that teach tool decision-making
  • Multi-tool orchestration and sequential tool calling
  • Error handling for when tools fail
  • Structured output and JSON Schema for predictable tool calls
  • How to test that models use tools appropriately

Next, you’ll learn about dynamic system prompts that adapt to user context.