Introduction & Overview

In the previous lessons, you learned how to create tool schemas and understand GPT-5's responses when it wants to use tools. You can now recognize when GPT-5 requests tool execution by checking for type: "function_call" items in the output array and extracting the necessary details from function call items. However, knowing what GPT-5 wants to do is only half the story — you still need to actually execute those tools and complete the conversation cycle.

In this lesson, you'll learn how to bridge that gap by executing the functions GPT-5 requests, capturing their results, and sending those results back to GPT-5 in the proper format. By the end of this lesson, you'll have a complete tool execution pipeline that can handle GPT-5's tool requests from start to finish, maintaining proper conversation flow throughout the entire process.

The Complete Tool Execution Flow

Before we dive into the implementation, let's understand the complete workflow we'll be building in this lesson. Here's the step-by-step process that transforms GPT-5 from a simple chatbot into a capable agent:

  1. Set up the foundation - Create function mappings, tool schemas, and initial conversation messages
  2. Send the initial request - Make the first API call to GPT-5 using responses.create with the user's question and available tools
  3. Detect tool use requests - Iterate through the output array to find items with type: "function_call"
  4. Extract tool information - Pull out the function name, arguments, and call ID from each function call item
  5. Execute the requested functions - Parse the JSON arguments and use our function mapping to call the actual Python functions with GPT-5's parameters
  6. Collect and format tool results - Structure all function outputs in the function_call_output format with matching call_id values
  7. Send results back to GPT-5 - Make a second API call with the complete conversation, including both the original function_call items AND their outputs
  8. Display the final response - Show GPT-5's natural language answer that incorporates the tool outputs

This complete cycle enables GPT-5 to seamlessly use tools as part of its reasoning process, transforming raw function outputs into conversational responses that directly answer user questions.

Setting Up the Foundation

Before diving into tool execution, we need to establish the foundation that connects GPT-5's tool requests to our actual Python functions. As we covered in previous lessons, this involves creating a mapping dictionary and preparing our tool schemas and initial messages. The critical component here is the function mapping dictionary — this serves as the bridge between the tool names GPT-5 uses and our actual Python functions.

This setup creates everything we need for tool execution: the tools dictionary enables dynamic function lookup, the system_prompt guides GPT-5's behavior, the tool_schemas provide technical specifications, and the messages array starts the conversation. The function mapping is particularly important because it allows our code to execute the correct function based on GPT-5's string-based tool requests.

Sending the Initial Request

With our foundation in place, we can now send the initial request to GPT-5. The Responses API uses different parameter names than traditional chat completions, so pay attention to the specific parameters we're using here.

This initial API call includes all the components necessary for GPT-5 to potentially request tool execution. The tools parameter gives GPT-5 the specifications it needs to decide whether to call functions, while the input contains the user's question that will drive that decision.

Detecting Tool Use Requests

After receiving GPT-5's response, we need to examine the output array to determine if tool execution is needed. Unlike checking a single stop_reason field, we iterate through the output items and look for function call requests.

The response.output array contains all output items from GPT-5, which can include text messages, function calls, and other content types. By iterating through this array and checking each item's type field for "function_call", we identify all the tools GPT-5 wants to execute. We collect these in a list because GPT-5 might request multiple function calls in a single response.

Once we've collected all function calls, we check if any were found and proceed accordingly:

When we find function call items, we extract the essential information from each one: the function name (which function to call), the call_id (a unique identifier we'll need to match results back to requests), and the arguments (provided as a JSON string). The call is essential because the arguments come as a JSON string, not a Python dictionary, so we must parse them before we can use them to call our functions.

Executing the Requested Functions

With the function information extracted and arguments parsed, we can now execute the actual Python functions using our mapping dictionary. This is where the tool names from GPT-5's requests get translated into actual function calls.

The critical step here is parsing the arguments with json.loads(function.arguments) before unpacking them with **args. Without this parsing step, we'd be trying to unpack a JSON string instead of a dictionary, which would cause an error. Using a try-except block provides comprehensive error handling that catches not only missing tools but also any runtime errors that might occur during function execution. For example, if a function receives invalid parameters (like trying to divide by zero), the exception will be caught and converted into an error message that can be sent back to GPT-5.

Formatting Tool Results

After executing each tool (or encountering an error), we must format the results in the specific structure that GPT-5 expects. The Responses API requires a particular format for function outputs that properly links each result back to its original request.

Each function output must have the type "function_call_output", the call_id must exactly match the call_id from the original function call item, and the output must be a JSON string (not a plain string or dictionary). We use json.dumps() to convert the result into proper JSON format.

It's critical that you provide a function output for every function call that GPT-5 makes. If you skip providing an output for any call_id, your next API call to GPT-5 will fail with an error because GPT-5 expects to receive results for all the functions it requested. This is why we handle both successful executions and errors in the same way — GPT-5 needs to know what happened with each function request, whether it succeeded or failed.

Reconstructing Function Call Items

However, formatting the outputs is only half of what we need to send back to GPT-5. The Responses API requires both the original function calls AND their outputs to be present in the conversation. This means we need to reconstruct the function call items in the proper format before adding them to our messages array.

You might wonder why we can't simply use messages.extend(function_calls) instead of reconstructing each item. The reason is that the items in function_calls are SDK model objects, not plain JSON dictionaries. The Responses API requires a strictly shaped, JSON-serializable payload with specific fields. By reconstructing each item, we ensure we send only the allowed fields (type, name, arguments, call_id) in the exact wire format the API expects, avoiding serialization or validation errors that could occur from SDK-internal fields.

This two-step process is essential: first, we reconstruct the original function call items with all their details, then we add both the function calls and their outputs to the messages array using extend(). This maintains the proper conversation structure that GPT-5 expects, showing both what was requested and what the results were.

Getting GPT-5's Final Response

Once we've executed all requested functions and added both the function calls and their results to the messages array, we can send the updated conversation back to GPT-5 to get the final response that incorporates the tool results.

This second API call uses the same parameters as the first, but now the input array contains the complete conversation history, including both the original function call items and their corresponding outputs. GPT-5 can now provide a comprehensive answer that incorporates the tool execution results, transforming raw calculation outputs into natural, conversational responses.

The output_text property provides a convenient way to extract just the text content from GPT-5's response, filtering out any non-text items in the output array.

The complete execution flow produces the following output, showing both the tool execution and GPT-5's final response:

This output demonstrates the complete tool execution cycle: our code executes the requested function with the provided parameters, captures the numerical result, and then GPT-5 transforms that raw output into a natural, conversational response that directly answers the user's original question.

Handling Non-Tool Responses

Not every user request will require tool usage. When GPT-5 can answer directly without needing to execute functions, the function_calls list will be empty after we iterate through the output array.

By checking if the function_calls list contains any items, we can determine whether GPT-5 needs tools or can answer directly. When the list is empty (meaning no function call items were found in the output array), we simply display GPT-5's response using response.output_text, which extracts the text content from the output array. For example, if a user asks, "What is the capital of France?", GPT-5 would respond directly without requesting any function calls, since no mathematical calculations are needed. This ensures your application handles both tool-requiring and non-tool scenarios gracefully, maintaining proper conversation flow in all cases.

Visualizing the Complete Conversation History

Throughout the tool execution cycle, maintaining proper conversation history enables GPT-5 to understand context and provide coherent responses. Let's examine how the complete conversation flow develops through each step of the process.

GPT-5's message structure is simpler than some other systems — each item in the messages array has a type field that indicates whether it's a regular message, a function_call, or a function_call_output. Regular user and assistant messages have role and content fields, function calls have name, arguments, and call_id fields, and function outputs have call_id and output fields.

This debugging output reveals the complete conversation structure that enables GPT-5's tool-using capabilities:

Summary & Practice Preparation

You now understand the complete tool execution workflow that transforms GPT-5 from a text-only assistant into an agent capable of performing actions and calculations. The process involves iterating through the output array to find function calls, parsing JSON arguments before execution, collecting and formatting all results with matching call IDs, reconstructing original function call items, and maintaining conversation history throughout the entire cycle.

The key components work together to create a robust system: function mapping enables dynamic execution, JSON parsing ensures arguments are properly formatted, proper result collection ensures all tools are handled, correct message formatting with both function calls and outputs allows GPT-5 to understand results, and conversation history maintains context across the entire interaction. This foundation allows GPT-5 to act as an intelligent agent that can reason about when to use tools and incorporate their results into natural responses.

In the upcoming practice exercises, you'll implement this complete workflow yourself, working with different types of tools and handling various scenarios, including multiple tool uses and error handling. Happy coding!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal