OpenAI.Responses.Helpers (openai_responses v0.3.2)

Helper functions for working with OpenAI Responses.

This module provides utility functions for common tasks when working with the OpenAI Responses API, including:

  • Extracting text and data from responses
  • Creating structured messages with images
  • Handling response status and errors

See the individual function documentation for usage examples.

Summary

Functions

Calculates the estimated cost from a raw OpenAI API response map.

Calculates the estimated cost of an OpenAI API call based on token usage.

Creates a structured message with text and optional images.

Checks if a response contains a refusal.

Extracts the output text from a response.

Gets the refusal message if one exists.

Extracts the status from a response.

Gets token usage information from a response.

Functions

calculate_cost(raw_response)

@spec calculate_cost(map()) ::
  {:ok, Decimal.t()}
  | {:error,
     :unknown_model_name | :invalid_response_format | :invalid_usage_format}

Calculates the estimated cost from a raw OpenAI API response map.

Extracts the model name and usage information from the response map and calls calculate_cost/2.

Parameters

  • raw_response (map) - The raw map returned by the OpenAI API client (e.g., Req). Expected format: %{ "model" => string, "usage" => map, ... }

Returns

  • {:ok, Decimal.t()} - The calculated cost as a Decimal.
  • {:error, :unknown_model_name} - If the model name is not found in the pricing table.
  • {:error, :invalid_response_format} - If the raw_response map doesn't contain "model" or "usage".
  • {:error, :invalid_usage_format} - If the nested "usage" map has an unexpected format.

Examples

iex> raw_response = %{
...>   "model" => "gpt-4o-mini",
...>   "usage" => %{ "input_tokens" => 500, "input_tokens_details" => %{"cached_tokens" => 100}, "output_tokens" => 1000 },
...>   "output" => [...]
...> }
iex> OpenAI.Responses.Helpers.calculate_cost(raw_response)
{:ok, #Decimal<0.0006675>}

iex> raw_response = %{"model" => "unknown-model", "usage" => %{ "input_tokens" => 100, "input_tokens_details" => %{"cached_tokens" => 0}, "output_tokens" => 50 }}
iex> OpenAI.Responses.Helpers.calculate_cost(raw_response)
{:error, :unknown_model_name}

iex> raw_response = %{"model" => "gpt-4o", "usage" => %{"input_tokens" => 10}} # Incomplete usage
iex> OpenAI.Responses.Helpers.calculate_cost(raw_response)
{:error, :invalid_usage_format}

calculate_cost(model_name, token_usage)

@spec calculate_cost(String.t(), map()) ::
  {:ok, Decimal.t()} | {:error, :unknown_model_name | :invalid_usage_format}

Calculates the estimated cost of an OpenAI API call based on token usage.

Uses the provided model name and token usage map to calculate the cost. Handles model aliases (e.g., gpt-4o maps to gpt-4o-2024-08-06). Applies discounts for cached input tokens where applicable.

Costs are based on the pricing defined in @model_pricing (per 1 million tokens), which uses the full, dated model names as keys.

Parameters

  • model_name (string) - The name of the model used (e.g., "gpt-4o", "gpt-4o-mini-2024-07-18").
  • token_usage (map) - The token usage map, typically from response["usage"]. Expected format: %{ "input_tokens" => integer, "input_tokens_details" => %{"cached_tokens" => integer}, "output_tokens" => integer, ... }

Returns

  • {:ok, Decimal.t()} - The calculated cost as a Decimal.
  • {:error, :unknown_model_name} - If the model name is not found in the pricing table.
  • {:error, :invalid_usage_format} - If the token_usage map has an unexpected format.

Examples

iex> usage = %{ "input_tokens" => 1000, "input_tokens_details" => %{"cached_tokens" => 500}, "output_tokens" => 2000 }
iex> OpenAI.Responses.Helpers.calculate_cost("gpt-4o", usage) # Using alias
{:ok, #Decimal<0.021875>}

iex> usage = %{ "input_tokens" => 1000, "input_tokens_details" => %{"cached_tokens" => 0}, "output_tokens" => 2000 }
iex> OpenAI.Responses.Helpers.calculate_cost("gpt-4o-2024-08-06", usage) # Using full name
{:ok, #Decimal<0.0225>}

iex> usage = %{ "input_tokens" => 1000, "input_tokens_details" => %{"cached_tokens" => 500}, "output_tokens" => 2000 }
iex> OpenAI.Responses.Helpers.calculate_cost("gpt-4o-audio-preview-2024-12-17", usage) # Model without cached input discount
{:ok, #Decimal<0.0225>}

iex> usage = %{ "input_tokens" => 100, "input_tokens_details" => %{"cached_tokens" => 0}, "output_tokens" => 50 }
iex> OpenAI.Responses.Helpers.calculate_cost("unknown-model", usage)
{:error, :unknown_model_name}

create_message_with_images(text, images \\ nil, opts \\ [])

Creates a structured message with text and optional images.

Uses the ImageHelpers module to create a properly formatted message with text and images for the OpenAI Responses API. See OpenAI.Responses.Helpers.ImageHelpers.create_message_with_images/3 for full documentation.

Examples

# Simple text with one image URL
iex> input_message = OpenAI.Responses.Helpers.create_message_with_images("What is in this image?", "https://example.com/image.jpg")
iex> {:ok, response} = OpenAI.Responses.create("gpt-4o", [input_message])

# With a local file
iex> input_message = OpenAI.Responses.Helpers.create_message_with_images("Describe this", "/path/to/image.jpg")

# With multiple images and high detail
iex> input_message = OpenAI.Responses.Helpers.create_message_with_images(
...>   "Compare these",
...>   ["image1.jpg", "image2.jpg"],
...>   detail: "high"
...> )

has_refusal?(response)

@spec has_refusal?(map()) :: boolean()

Checks if a response contains a refusal.

Parameters

  • response - The response from OpenAI.Responses.create/3

Returns

  • true if the response contains a refusal, false otherwise

output_text(response)

@spec output_text(map()) :: String.t()

Extracts the output text from a response.

This function navigates the response structure to find and return the generated text. If the response contains multiple outputs or messages, it concatenates them with newlines.

Parameters

  • response - The response from OpenAI.Responses.create/3

Returns

  • The extracted text as a string
  • An empty string if no text output is found

Examples

{:ok, response} = OpenAI.Responses.create("gpt-4o", "Write a haiku about programming")
text = OpenAI.Responses.Helpers.output_text(response)
# => "Fingers tap keyboard

Logic blooms in silent code Bugs hide in plain sight"

refusal_message(response)

@spec refusal_message(map()) :: String.t() | nil

Gets the refusal message if one exists.

Parameters

  • response - The response from OpenAI.Responses.create/3

Returns

  • The refusal message as a string
  • nil if no refusal is found

status(response)

@spec status(map()) :: String.t() | nil

Extracts the status from a response.

Parameters

  • response - The response from OpenAI.Responses.create/3

Returns

  • The status as a string (e.g., "completed", "in_progress")
  • nil if no status is found

token_usage(response)

@spec token_usage(map()) :: map() | nil

Gets token usage information from a response.

Parameters

  • response - The response from OpenAI.Responses.create/3

Returns

  • A map with token usage information
  • nil if no usage information is found

Examples

{:ok, response} = OpenAI.Responses.create("gpt-4o", "Write a haiku about programming")
usage = OpenAI.Responses.Helpers.token_usage(response)
# => %{"input_tokens" => 8, "output_tokens" => 17, "total_tokens" => 25}