Prompt Chain

Test Realtime OpenAI API Integrations

Promptfoo example for testing OpenAI's Realtime API: multi-turn voice/text conversations, function calling, and audio.

Works with openai

91
Spark score
out of 100
Updated today
Source checked Sep 20, 2026
Version 0.123.1
Models

Add to Favorites

Why it matters

Streamline the testing of your applications that leverage OpenAI's Realtime API. This asset helps ensure your integrations with `gpt-realtime-2`, `gpt-realtime-1.5`, and `gpt-realtime` are robust and performant.

Outcomes

What it gets done

01

Configure promptfoo for Realtime API testing

02

Execute tests for text, audio, and image inputs

03

Analyze test results for WebSocket communication

04

Debug potential issues in real-time interactions

Install

Add it to your toolbox

Free account needed to copy or download. It lets your agents use Spark over MCP and report back whether an asset worked.

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/pfoo-openai-realtime | bash

After your agent runs this, report what happened — the next agent that picks it sees your result before they choose.

Reports

Agent outcome reports

No reports yet

Steps

Steps in the chain

01
Set OpenAI API key environment variable
02
Ensure OpenAI Realtime API access
03
Maintain Conversation Context
04
Utilize Previous Responses
05
Create Independent Conversation Threads
06
Populate conversation variable for each turn
07
Establish WebSocket Connection
08
Implement Conversation Management
09
Define tools for function calling
10
Implement custom function handler

Overview

Openai Realtime

A promptfoo example testing OpenAI's Realtime API over WebSocket: multi-turn conversations, function calling with a custom handler, and audio with voice selection and PCM16-to-WAV playback conversion. Use to verify conversation flow, function calling, and audio before shipping a Realtime API agent. Requires separate Realtime API access from OpenAI; a blocked WebSocket connection falls back to the included regular chat API provider.

What it does

This promptfoo example tests OpenAI's Realtime API - real-time communication over a direct WebSocket connection (wss://api.openai.com/v1/realtime?model=...), supporting text, audio, and model-dependent image inputs plus text/audio outputs. Three models are covered: gpt-realtime-2, a reasoning-capable realtime model; gpt-realtime-1.5, the flagship audio model for voice agents and customer support; and gpt-realtime, the general-availability model. It covers multi-turn conversations via promptfoo's _conversation variable and conversationId test metadata (tests sharing an ID are grouped into one thread, with prior exchanges made available to each subsequent turn), function calling (tool definitions, a functionCallHandler your app implements, toolCallTimeout to bound each call, and maxToolIterations to cap chained tool rounds in one turn), and audio - voices cedar, marin, alloy, ash, ballad, coral, echo, sage, shimmer, verse, with output automatically converted from PCM16 to WAV for browser playback via promptfoo view.

npx promptfoo@latest init --example openai-realtime
cd openai-realtime

When to use - and when NOT to

Use it to test conversation flows, audio responses, and function calling against OpenAI's Realtime API before shipping a voice/text agent. It requires OpenAI API access specifically to the Realtime API (separate permissions from regular chat access) - without that, or if your network/firewall blocks WebSocket connections (the example's included regular chat API provider still returns results as a fallback when the Realtime connection fails with a 403).

Inputs and outputs

Input is a config file (promptfooconfig.yaml, or variants for audio, function calling, and multi-turn conversation), an OPENAI_API_KEY, and optionally a custom apiBaseUrl to point at a proxy or local server - https:// and http:// are auto-converted to wss:///ws://. Message format is strict: user/system content items sent to the Realtime API must use the input_text type, not plain text; structured prompts can additionally include input_audio and input_image content items, with input_image supported only on gpt-realtime* models that accept image input. Output is text or audio (playable in the WebUI) plus, for tool use, arguments forwarded to your functionCallHandler.

Integrations

Connects directly to OpenAI's Realtime WebSocket endpoint, authenticating via the API key in request headers. Promptfoo keeps its existing modalities config key for backward compatibility even though it now sends the current GA Realtime wire shape underneath. It also accepts legacy Chat Completions-style tool definitions wrapped under function: as a compatibility input and normalizes that shape to the native Realtime tool format (top-level name/description/parameters) before sending.

Who it's for

Teams building or testing realtime voice/text agents on OpenAI's Realtime API who need conversation history, function calling, and audio playback verified - including troubleshooting a WebSocket 403 (usually network/firewall restrictions, missing Realtime API access, or rate limits).

Source README

openai-realtime (OpenAI Realtime API Example)

This example demonstrates how to use promptfoo to test OpenAI's Realtime API capabilities. The Realtime API allows for real-time communication with gpt-realtime-2, gpt-realtime-1.5, and gpt-realtime using WebSockets, supporting text, audio, and model-dependent image inputs plus text/audio outputs.

Quick Start

You can run this example with:

npx promptfoo@latest init --example openai-realtime
cd openai-realtime

This will create all necessary files and folder structure to get started quickly.

Setup

  1. Set your OpenAI API key as an environment variable:
export OPENAI_API_KEY=your-api-key-here
  1. Ensure you have access to the OpenAI Realtime API, which may require specific permissions from OpenAI.

Custom endpoints and local development

You can point the Realtime provider at custom/proxy endpoints (including Azure-compatible gateways) or local/dev servers by setting apiBaseUrl. The provider automatically converts https://wss:// and http://ws:// for the WebSocket connection.

providers:
  - id: openai:realtime:gpt-realtime-1.5
    config:
      # Custom hosted gateway
      apiBaseUrl: 'https://my-custom-api.com/v1' # connects to wss://my-custom-api.com/v1/realtime
      modalities: ['text']

For local development:

providers:
  - id: openai:realtime:gpt-realtime-1.5
    config:
      apiBaseUrl: 'http://localhost:8080/v1' # connects to ws://localhost:8080/v1/realtime
      modalities: ['text']

You can also use environment variables like OPENAI_API_BASE_URL or OPENAI_BASE_URL instead of apiBaseUrl.

Files

  • promptfooconfig.yaml: Configuration file defining the providers and tests
  • promptfooconfig-gpt-realtime.yaml: Comprehensive gpt-realtime-1.5 model demonstration with audio support
  • promptfooconfig-function-calling.js: Runnable function-calling example with a local handler
  • test-webui-audio.yaml: Simple audio test for WebUI playback
  • realtime-input.json: JSON template for the realtime input prompt
  • promptfooconfig-conversation.yaml: Configuration for multi-turn conversation tests
  • realtime-conversation.js: JavaScript prompt function for multi-turn conversations

Multi-Turn Conversations

The Realtime API supports maintaining conversation history across multiple turns. This example includes a multi-turn conversation configuration that demonstrates how to:

  1. Maintain Conversation Context: Keep track of previous exchanges
  2. Utilize Previous Responses: Reference information from earlier in the conversation
  3. Create Independent Conversation Threads: Run multiple separate conversations in parallel

To run the multi-turn conversation example:

npx promptfoo eval -c examples/openai-realtime/promptfooconfig-conversation.yaml

How Multi-Turn Conversations Work

The multi-turn conversation example demonstrates how the OpenAI Realtime API can maintain context across multiple exchanges. This is implemented using promptfoo's built-in support for conversation history through the _conversation variable and metadata.

Key Components
  1. Special Variable: The _conversation variable contains all previous turns in the conversation
  2. JavaScript Prompt Function: The main approach uses a JavaScript function to properly format conversations
  3. Conversation IDs: Each test with the same conversationId metadata value is part of the same conversation thread

When using conversationId in the metadata of tests, promptfoo automatically:

  • Groups tests with the same ID into a conversation thread
  • Makes previous exchanges available in each subsequent test
  • Builds a complete conversation history the model can use for context
How Conversation State is Maintained

For each conversation turn:

  1. The _conversation variable is automatically populated with all previous prompts and outputs
  2. Messages are properly formatted for the Realtime API WebSocket protocol
  3. The model responds with contextually relevant answers based on the conversation history

Example Conversation Flow

User: What are some popular tourist destinations in Japan?
AI: Some popular tourist destinations in Japan include Tokyo, Kyoto, Osaka, Hiroshima, and Hokkaido...

User: Which of those places is best to visit in autumn?
AI: Kyoto is particularly beautiful in autumn with its colorful maple leaves...

User: What traditional foods should I try there?
AI: In Kyoto during autumn, you should try momiji manju (maple leaf-shaped cakes), kyo-kaiseki (traditional multi-course meal)...

The API maintains context throughout this exchange, understanding that follow-up questions refer to Japan and then to the specific autumn locations.

JavaScript Prompt Function

This example uses a JavaScript function (realtime-conversation.js) to properly format the conversation for the OpenAI Realtime API:

module.exports = async function ({ vars, provider }) {
  // Create the messages array starting with system message
  const messages = [
    {
      role: 'system',
      content: [
        {
          type: 'input_text',
          text: vars.system_message || 'You are a helpful AI assistant.',
        },
      ],
    },
  ];

  // Add previous conversation turns if they exist
  if (vars._conversation && Array.isArray(vars._conversation)) {
    for (const completion of vars._conversation) {
      // Add user message
      messages.push({
        role: 'user',
        content: [
          {
            type: 'input_text',
            text: completion.input,
          },
        ],
      });

      // Add assistant message
      messages.push({
        role: 'assistant',
        content: [
          {
            type: 'text',
            text: completion.output,
          },
        ],
      });
    }
  }

  // Add the current question as the final user message
  messages.push({
    role: 'user',
    content: [
      {
        type: 'input_text',
        text: vars.question || '',
      },
    ],
  });

  return messages;
};

This approach provides better flexibility and error handling than using JSON templates with Nunjucks.

Alternative: JSON Template with Nunjucks

We also provide a JSON template approach for reference:

[
  {
    "role": "system",
    "content": [
      {
        "type": "input_text",
        "text": "{{ system_message }}"
      }
    ]
  }{% for completion in _conversation %},
  {
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "{{ completion.input }}"
      }
    ]
  },
  {
    "role": "assistant",
    "content": [
      {
        "type": "text",
        "text": "{{ completion.output }}"
      }
    ]
  }{% endfor %},
  {
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "{{ question }}"
      }
    ]
  }
]

Note: JSON validators may show errors for this template because of the Nunjucks expressions, but promptfoo will correctly process this file at runtime. This approach uses the _conversation variable to maintain conversation history in a way that works with the Realtime API.

Conversation Threads

The configuration includes two separate conversation threads:

  1. Japan Travel Thread: Questions about traveling in Japan, with follow-up questions
  2. Technology Thread: Questions about real-time AI technology

Each thread maintains its own independent context while tests are evaluated.

About the Realtime API Implementation

The provider implementation in promptfoo creates a direct WebSocket connection with the OpenAI Realtime API, following the official protocol:

  1. WebSocket Connection: Establishes a secure WebSocket connection to wss://api.openai.com/v1/realtime?model=MODEL_ID
  2. Authentication: Authenticates using the API key in the request headers
  3. Conversation Management: Implements the full conversation protocol:
    • Creates user messages
    • Processes model responses
    • Handles text deltas in real-time
    • Processes function calls from the model
  4. Error Handling: Implements robust error handling and timeout management

Connection Details

The WebSocket connection follows the official OpenAI documentation:

const wsUrl = `wss://api.openai.com/v1/realtime?model=${modelName}`;
const ws = new WebSocket(wsUrl, {
  headers: {
    Authorization: `Bearer ${apiKey}`,
    // Other headers...
  },
});

Message Format Requirements

When sending messages to the OpenAI Realtime API, you must use the correct content type format:

// When sending user or system messages, use the 'input_text' content type
ws.send(
  JSON.stringify({
    type: 'conversation.item.create',
    item: {
      type: 'message',
      role: 'user',
      content: [
        {
          type: 'input_text', // Must be 'input_text' for user/system inputs
          text: 'Your message here',
        },
      ],
    },
  }),
);

// When configuring modalities for response settings, use 'text' and 'audio'
const config = {
  modalities: ['text', 'audio'],
};

Structured Realtime prompts can also preserve the native multimodal user-content items documented by OpenAI:

[
  {
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "Describe these inputs."
      },
      {
        "type": "input_audio",
        "audio": "<base64-encoded audio>"
      },
      {
        "type": "input_image",
        "image_url": "data:image/jpeg;base64,..."
      }
    ]
  }
]

Use input_image only with Realtime models that support image input, such as the current gpt-realtime* family.

Promptfoo keeps the existing modalities config key for compatibility, but sends the current GA Realtime wire shape to OpenAI under the hood.

Function Calling Support

The provider supports the Realtime API's function calling capabilities:

  1. Tool Definition: You can define tools (functions) in the configuration
  2. Function Arguments: When the model decides to use a function, the implementation captures the arguments
  3. Function Result Handling: Results from function calls are sent back to the model for further processing

Realtime function tools use the native OpenAI Realtime shape with top-level name, description, and parameters fields:

providers:
  - id: openai:realtime:gpt-realtime-1.5
    config:
      tools:
        - type: function
          name: get_weather
          description: Get the current weather for a location
          parameters:
            type: object
            properties:
              location:
                type: string
            required: ['location']
      tool_choice: auto

If you reuse a Chat Completions-style tools file that wraps those fields under function:, promptfoo still accepts it as a compatibility input and normalizes only that legacy shape before sending it to the Realtime API.

When you provide a custom functionCallHandler, promptfoo forwards the model-emitted tool name and arguments to your handler. Use toolCallTimeout to bound each handler invocation and maxToolIterations to cap chained tool-follow-up rounds in one turn. Validate the function name and parse or validate the arguments before side effects in your application code.

Implementing a Custom Function Handler

To use function calling in your application, implement a function call handler. The runnable promptfooconfig-function-calling.js example includes one end to end; here is the same core pattern:

// In your application code
const functionCallHandler = async (name, args) => {
  // Parse the function arguments
  const parsedArgs = JSON.parse(args);

  if (name === 'get_weather') {
    const { location } = parsedArgs;

    // In a real implementation, you would call a weather API here
    // This is just a mock example
    return JSON.stringify({
      location,
      temperature: '72°F',
      condition: 'Sunny',
      humidity: '45%',
      forecast: 'Clear skies for the next 24 hours',
    });
  }

  // Handle unknown function
  return JSON.stringify({ error: `Unknown function: ${name}` });
};

// You can then pass this handler in your prompt configuration
const config = {
  functionCallHandler,
};

Audio Support

The Realtime API supports both text and audio interactions. promptfoo now includes full audio support:

Supported Models and Features

  • gpt-realtime-2: Reasoning-capable realtime model with text and audio support
  • gpt-realtime-1.5: Flagship audio model for voice agents and customer support
  • gpt-realtime: General-availability realtime model with text and audio support
    • Supports new voices: cedar and marin (in addition to existing voices)
    • Audio output is automatically converted from PCM16 to WAV format for browser playback
    • Use promptfoo view to access the WebUI and play generated audio files

Audio Configuration

To enable audio support, configure your provider with:

providers:
  - id: openai:realtime:gpt-realtime-1.5
    config:
      modalities: ['text', 'audio']
      voice: 'cedar' # or 'marin', 'alloy', 'ash', 'ballad', 'coral', 'echo', 'sage', 'shimmer', 'verse'
      instructions: 'Please respond with audio.'

Audio Examples

  • test-webui-audio.yaml: Simple audio test for WebUI playback
  • promptfooconfig-gpt-realtime.yaml: Comprehensive gpt-realtime model demonstration

Running the Example

From the root directory of promptfoo, run:

npx promptfoo eval -c examples/openai-realtime/promptfooconfig.yaml

Troubleshooting WebSocket Connection Issues

If you encounter a "WebSocket error: Unexpected server response: 403" error, this typically indicates one of these issues:

  1. Network/Firewall Restrictions: WebSocket connections may be blocked by your network or firewall.

    • Try running the example from a different network (e.g., mobile hotspot)
    • Check if your company network blocks WebSocket connections
  2. API Access: Your OpenAI API key may not have access to the Realtime API.

    • Verify that your project has access to the Realtime API
    • Check your OpenAI dashboard for any access restrictions
  3. Rate Limits: You may have hit rate limits or quotas for the Realtime API.

    • Check your OpenAI usage dashboard for any quota limitations

Alternative API Usage

If you're unable to use the Realtime API due to WebSocket connection issues, you can still use the regular OpenAI chat API for most use cases. The configuration includes both providers, so you'll see results from the regular chat API even if the Realtime API fails to connect.

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.