Skip to main content

Tool & Function Calling

Tool calls (also known as function calls) enable LLMs to access external tools and APIs. The LLM doesn't directly execute these tools but instead suggests which tool to invoke with specific parameters. Your application then calls the tool independently and returns the result to the LLM, which formats the response as an answer to the user's original question.

Knox Chat provides full compatibility with OpenAI's tool calling interface while standardizing it across different models and providers, ensuring consistent behavior regardless of which LLM you're using.

tip

Knox Chat supports both streaming and non-streaming tool calls, giving you flexibility in how you handle real-time responses.

Supported Models

Knox Chat supports tool calling across multiple providers. Here are some popular models that work well with tools:

  • Anthropic: anthropic/claude-opus-4,anthropic/claude-opus-4.1,anthropic/claude-sonnet-4.5,anthropic/claude-3.7-sonnet,anthropic/claude-3.5-sonnet, anthropic/claude-3.5-haiku
  • OpenAI: openai/gpt-4o, openai/gpt-4o-mini, openai/gpt-5
  • Google: google/gemini-2.5-flash, google/gemini-2.5-pro
  • And many more! Check the models page for the full list.

Quick Start Example

Let's start with a simple example that shows how to get the current time using a tool call:

curl -X POST https://knox.chat/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_KNOX_API_KEY" \
-d '{
"model": "anthropic/claude-3.5-haiku",
"messages": [
{
"role": "user",
"content": "What time is it in Tokyo?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "Get the current time in a specific timezone",
"parameters": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "The timezone (e.g., Asia/Tokyo, America/New_York)"
}
},
"required": ["timezone"]
}
}
}
],
"tool_choice": "auto"
}'

Expected Response

When the LLM decides to use a tool, you'll get a response like this:

{
"choices": [
{
"finish_reason": "tool_calls",
"index": 0,
"message": {
"content": "I'll get the current time in Tokyo for you.",
"role": "assistant",
"tool_calls": [
{
"id": "toolu_01SR862k3e4m1rZYzrMwEX35",
"type": "function",
"function": {
"name": "get_current_time",
"arguments": "{\"timezone\": \"Asia/Tokyo\"}"
}
}
]
}
}
],
"usage": {
"prompt_tokens": 120,
"completion_tokens": 45,
"total_tokens": 165
}
}
Key Points
  • The finish_reason is "tool_calls" when the model wants to use a tool
  • Each tool call has a unique id that you'll need to reference in your response
  • The arguments field contains a JSON string with the parameters

Complete Tool Calling Workflow

Here's a comprehensive example that shows the full workflow from tool definition to final response:

import json
import requests
from datetime import datetime
import pytz
from openai import OpenAI

# Initialize Knox Chat client
client = OpenAI(
base_url="https://knox.chat/v1",
api_key="YOUR_KNOX_API_KEY"
)

# Define your tool functions
def get_current_time(timezone):
"""Get the current time in a specific timezone"""
try:
tz = pytz.timezone(timezone)
current_time = datetime.now(tz)
return {
"timezone": timezone,
"current_time": current_time.strftime("%Y-%m-%d %H:%M:%S %Z"),
"day_of_week": current_time.strftime("%A")
}
except Exception as e:
return {"error": f"Invalid timezone: {timezone}"}

def search_web(query):
"""Search the web for information"""
# This is a mock implementation - replace with your preferred search API
return {
"query": query,
"results": [
{"title": "Sample Result", "url": "https://example.com", "snippet": "Sample content"}
]
}

# Tool definitions (OpenAI format)
tools = [
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "Get the current time in a specific timezone",
"parameters": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "The timezone (e.g., Asia/Tokyo, America/New_York, Europe/London)"
}
},
"required": ["timezone"]
}
}
},
{
"type": "function",
"function": {
"name": "search_web",
"description": "Search the web for current information",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
}
},
"required": ["query"]
}
}
}
]

# Tool mapping for execution
TOOL_MAPPING = {
"get_current_time": get_current_time,
"search_web": search_web
}

def execute_tool_calls(tool_calls):
"""Execute the requested tool calls and return results"""
tool_messages = []

for tool_call in tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)

# Execute the tool function
if tool_name in TOOL_MAPPING:
try:
tool_result = TOOL_MAPPING[tool_name](**tool_args)
tool_messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_name,
"content": json.dumps(tool_result)
})
except Exception as e:
tool_messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_name,
"content": json.dumps({"error": str(e)})
})
else:
tool_messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_name,
"content": json.dumps({"error": f"Unknown tool: {tool_name}"})
})

return tool_messages

# Main conversation function
def chat_with_tools(user_message):
messages = [
{"role": "system", "content": "You are a helpful assistant with access to tools for getting current time and searching the web."},
{"role": "user", "content": user_message}
]

# First API call
response = client.chat.completions.create(
model="anthropic/claude-3.5-haiku",
messages=messages,
tools=tools,
tool_choice="auto"
)

assistant_message = response.choices[0].message
messages.append(assistant_message)

# Handle tool calls if present
if assistant_message.tool_calls:
print(f"🔧 Model requested {len(assistant_message.tool_calls)} tool call(s)")

# Execute tools and add results to conversation
tool_messages = execute_tool_calls(assistant_message.tool_calls)
messages.extend(tool_messages)

# Second API call with tool results
final_response = client.chat.completions.create(
model="anthropic/claude-3.5-haiku",
messages=messages,
tools=tools
)

return final_response.choices[0].message.content
else:
return assistant_message.content

# Example usage
if __name__ == "__main__":
result = chat_with_tools("What time is it in Tokyo and New York right now?")
print("🤖 Assistant:", result)

Streaming Tool Calls

Knox Chat supports streaming tool calls, allowing you to process tool call requests as they arrive:

import json
from openai import OpenAI

client = OpenAI(
base_url="https://knox.chat/v1",
api_key="YOUR_KNOX_API_KEY"
)

def handle_streaming_with_tools():
messages = [
{"role": "user", "content": "What's the weather like in Paris?"}
]

tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather information for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The city name"}
},
"required": ["city"]
}
}
}
]

# Stream the response
stream = client.chat.completions.create(
model="anthropic/claude-3.5-haiku",
messages=messages,
tools=tools,
tool_choice="auto",
stream=True
)

tool_calls = []
current_tool_call = None

for chunk in stream:
if chunk.choices[0].delta.tool_calls:
for tool_call_delta in chunk.choices[0].delta.tool_calls:
if tool_call_delta.id:
# New tool call starting
current_tool_call = {
"id": tool_call_delta.id,
"type": "function",
"function": {
"name": tool_call_delta.function.name or "",
"arguments": tool_call_delta.function.arguments or ""
}
}
tool_calls.append(current_tool_call)
elif current_tool_call:
# Continue building the current tool call
if tool_call_delta.function.arguments:
current_tool_call["function"]["arguments"] += tool_call_delta.function.arguments

# Handle regular content
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)

print("\n")

# Process any tool calls that were collected
if tool_calls:
print(f"🔧 Collected {len(tool_calls)} tool call(s)")
for tool_call in tool_calls:
print(f"Tool: {tool_call['function']['name']}")
print(f"Arguments: {tool_call['function']['arguments']}")

# Run the streaming example
handle_streaming_with_tools()

Tool Choice Options

Knox Chat supports different tool choice strategies:

Let the model decide when to use tools:

{
"tool_choice": "auto"
}

"none"

Disable tool calling for this request:

{
"tool_choice": "none"
}

"required"

Force the model to call at least one tool:

{
"tool_choice": "required"
}

Specific Tool

Force the model to call a specific tool:

{
"tool_choice": {
"type": "function",
"function": {
"name": "get_current_time"
}
}
}

Advanced Examples

Multi-Step Agent with Tool Calling

Here's an example of a more sophisticated agent that can handle multiple tool calls and complex workflows:

import json
from openai import OpenAI
from typing import List, Dict, Any

class ToolAgent:
def __init__(self, api_key: str):
self.client = OpenAI(
base_url="https://knox.chat/v1",
api_key=api_key
)
self.tools = []
self.tool_mapping = {}

def add_tool(self, tool_spec: Dict, tool_func: callable):
"""Add a tool to the agent"""
self.tools.append(tool_spec)
self.tool_mapping[tool_spec["function"]["name"]] = tool_func

def execute_tool_calls(self, tool_calls) -> List[Dict]:
"""Execute multiple tool calls and return results"""
results = []
for tool_call in tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)

if tool_name in self.tool_mapping:
try:
result = self.tool_mapping[tool_name](**tool_args)
results.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_name,
"content": json.dumps(result)
})
except Exception as e:
results.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": tool_name,
"content": json.dumps({"error": str(e)})
})
return results

def chat(self, user_message: str, max_iterations: int = 5) -> str:
"""Chat with automatic tool calling"""
messages = [
{"role": "system", "content": "You are a helpful assistant with access to various tools. Use them when needed to provide accurate and helpful responses."},
{"role": "user", "content": user_message}
]

for iteration in range(max_iterations):
response = self.client.chat.completions.create(
model="anthropic/claude-3.5-haiku",
messages=messages,
tools=self.tools,
tool_choice="auto"
)

assistant_message = response.choices[0].message
messages.append(assistant_message)

# Check if tools were called
if assistant_message.tool_calls:
print(f"🔧 Iteration {iteration + 1}: Calling {len(assistant_message.tool_calls)} tool(s)")

# Execute tools and add results
tool_results = self.execute_tool_calls(assistant_message.tool_calls)
messages.extend(tool_results)

# Continue the loop for another iteration
continue
else:
# No more tools needed, return final response
return assistant_message.content

return "Maximum iterations reached. The assistant may need more steps to complete the task."

# Example usage
def get_weather(city: str, units: str = "celsius"):
"""Mock weather function"""
return {
"city": city,
"temperature": "22°C" if units == "celsius" else "72°F",
"condition": "Sunny",
"humidity": "65%"
}

def calculate_distance(origin: str, destination: str):
"""Mock distance calculation"""
return {
"origin": origin,
"destination": destination,
"distance": "500 km",
"travel_time": "5 hours"
}

# Create agent and add tools
agent = ToolAgent("YOUR_KNOX_API_KEY")

agent.add_tool({
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather information for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The city name"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"}
},
"required": ["city"]
}
}
}, get_weather)

agent.add_tool({
"type": "function",
"function": {
"name": "calculate_distance",
"description": "Calculate distance between two cities",
"parameters": {
"type": "object",
"properties": {
"origin": {"type": "string", "description": "Origin city"},
"destination": {"type": "string", "description": "Destination city"}
},
"required": ["origin", "destination"]
}
}
}, calculate_distance)

# Use the agent
result = agent.chat("I'm planning a trip from New York to Boston. Can you tell me the distance and what the weather is like in both cities?")
print("🤖 Final result:", result)

Best Practices

1. Tool Design

  • Clear descriptions: Make tool descriptions specific and actionable
  • Proper parameters: Use appropriate JSON Schema types and constraints
  • Error handling: Always handle tool execution errors gracefully
  • Validation: Validate tool parameters before execution

2. Performance Optimization

  • Parallel execution: Execute multiple tools concurrently when possible
  • Caching: Cache tool results for repeated requests
  • Timeouts: Set reasonable timeouts for tool execution
  • Rate limiting: Respect API rate limits for external tools

3. Security Considerations

  • Input validation: Always validate tool inputs
  • Permissions: Implement proper authorization for sensitive tools
  • Sandboxing: Consider sandboxing tool execution
  • Logging: Log tool usage for monitoring and debugging

4. Error Handling

def safe_tool_execution(tool_func, **kwargs):
try:
result = tool_func(**kwargs)
return {"success": True, "data": result}
except ValueError as e:
return {"success": False, "error": f"Invalid input: {str(e)}"}
except Exception as e:
return {"success": False, "error": f"Tool execution failed: {str(e)}"}

Troubleshooting

Common Issues

  1. Tool calls not triggered

    • Ensure your tool descriptions are clear and specific
    • Check that tool_choice is set to "auto" or appropriate value
    • Verify the model supports tool calling
  2. Invalid JSON in arguments

    • Add proper JSON Schema validation
    • Handle parsing errors gracefully
    • Provide clear parameter descriptions
  3. Streaming issues

    • Ensure you're handling partial tool call data correctly
    • Buffer tool call arguments until complete
    • Check for proper stream termination

Testing Your Tools

Use this simple test to verify your tool calling setup:

curl -X POST https://knox.chat/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_KNOX_API_KEY" \
-d '{
"model": "anthropic/claude-3.5-haiku",
"messages": [{"role": "user", "content": "Test my tools"}],
"tools": [
{
"type": "function",
"function": {
"name": "test_tool",
"description": "A simple test tool",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string", "description": "Test message"}
},
"required": ["message"]
}
}
}
],
"tool_choice": {
"type": "function",
"function": {"name": "test_tool"}
}
}' | jq '.choices[0].message.tool_calls'