Skip to main content
Lesson 4 of 14 · Function Calling with Grok

Define Tools in JSON

Lesson 4 of 14 Updated July 29, 2026 Developer
On this page
  1. From theory to practice
  2. Request structure with tools
  3. Implementation key figures
  4. Handle the tool_call response
  5. Execute and return the result
  6. Define multiple tools
  7. Check finish_reason
  8. Key takeaways

From theory to practice

You now understand the concept of function calling and the structure of JSON schemas. It’s time to move to concrete implementation. In this lesson, you will learn how to build a complete request with tool definitions and handle the model’s response.

Request structure with tools

Here is a complete request to the Grok API with a defined tool:

from openai import OpenAI

client = OpenAI(
    api_key="your-api-key",
    base_url="https://api.x.ai/v1"
)

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "default": "celsius"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

response = client.chat.completions.create(
    model="grok-4.5",
    messages=[
        {"role": "user", "content": "What's the weather like in Paris?"}
    ],
    tools=tools
)

Note the nested structure: the tools object contains an array, where each element has a type and a function object with the details.

Implementation key figures

200
Max tools per request
6
Supported JSON types
3
Compatible SDKs
1
Request = entire flow

Handle the tool_call response

When the model decides to call a function, the response contains a tool_calls array:

message = response.choices[0].message

if message.tool_calls:
    for tool_call in message.tool_calls:
        print(f"Function: {tool_call.function.name}")
        print(f"Arguments: {tool_call.function.arguments}")
        print(f"ID: {tool_call.id}")

The tool_call.function.arguments field is a JSON string (not a Python object). You must parse it:

import json

args = json.loads(tool_call.function.arguments)
city = args["city"]
unit = args.get("unit", "celsius")

Execute and return the result

Once the function is executed, you send the result back to the model in a second call:

import json

# 1. Execute the function
weather_data = get_weather(city="Paris", unit="celsius")

# 2. Build the messages with the result
messages = [
    {"role": "user", "content": "What's the weather like in Paris?"},
    message,  # The model response with tool_calls
    {
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(weather_data)
    }
]

# 3. Call the model again with the result
final_response = client.chat.completions.create(
    model="grok-4.5",
    messages=messages,
    tools=tools
)

print(final_response.choices[0].message.content)

The message with role tool must contain:

  • tool_call_id: the unique identifier of the tool_call (to link it to the request)
  • content: the result as a JSON string

Define multiple tools

You can declare as many tools as needed in the tools array:

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a city",
            "parameters": { ... }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "search_restaurants",
            "description": "Search for restaurants by city and cuisine type",
            "parameters": { ... }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "book_table",
            "description": "Book a table at a restaurant",
            "parameters": { ... }
        }
    }
]

The model will automatically choose the relevant tool(s) based on the user request.

Check finish_reason

The finish_reason field in the response indicates whether the model finished its response or is waiting for a tool result:

finish_reason = response.choices[0].finish_reason

if finish_reason == "tool_calls":
    # The model is waiting for the result of one or more tools
    handle_tool_calls(response)
elif finish_reason == "stop":
    # The model has finished its response
    print(response.choices[0].message.content)

Key takeaways

  • The request contains messages and tools (definitions)
  • tool_call arguments are a JSON string to parse
  • Send the result in a message with role tool including the tool_call_id
  • Check finish_reason to know if the model is waiting for a result
  • You can declare multiple tools—the model chooses automatically