View Source ExOpenAI.Codegen.ResponseConverter (ex_openai.ex v2.0.0-beta2)

Converts raw JSON API responses into Elixir structs based on the OpenAPI response schema and the generated component modules.

At a high level this module:

  • Takes the decoded JSON response (string-keyed maps/lists from Jason)
  • Uses the response Schema to find the top-level response component
  • Uses the generated typespecs (@type t() :: ...) to understand field types
  • Builds the top-level response struct (e.g. Response, CreateChatCompletionResponse)
  • Recursively converts nested maps into component structs where the schema and typespecs provide enough information (including anyOf/oneOf unions)
  • Deep-atomizes keys for remaining nested maps/lists to keep ergonomics high

This keeps the runtime behavior in sync with the generated typespecs under ExOpenAI.Components.*, including complex unions like OutputItem.t/0.

Summary

Functions

Converts an API response into the expected struct type based on the response schema.

Extracts the type definition for a specific field from a typespec AST.

Parses a value based on its typespec.

Functions

Link to this function

convert_response(response, response_schema)

View Source
@spec convert_response(
  {:ok, any()} | {:error, any()},
  ExOpenAI.Codegen.DocsParser.Schema.t() | nil
) ::
  {:ok, any()} | {:error, any()}

Converts an API response into the expected struct type based on the response schema.

Takes the response and the expected response schema, and converts the response into the expected top-level struct type if they match. Nested maps and lists are not converted into their own component structs; instead, their keys are recursively converted to atoms for easier access.

Parameters

  • response - The API response tuple {:ok, map} or {:error, any}
  • response_schema - The Schema struct describing the expected response type

Behavior Details

  • Handles responses with different patterns, including those with "response" and "type" keys
  • Processes reference values directly (when is_reference(ref) is true)
  • Converts responses to top-level component structs when the response schema is known
  • For oneOf and anyOf response schemas, selects the best matching component based on key overlap
  • Nested maps and lists are recursively converted to atom-keyed maps/lists, but are not wrapped into their own component structs
  • Returns the original response when no conversion is needed or possible
  • Preserves error tuples, passing them through unchanged

Return Values

  • {:ok, converted_value} for successful conversions
  • Original error tuples are passed through unchanged
Link to this function

get_field_type_from_ast(typespec_ast, field_name)

View Source
@spec get_field_type_from_ast(list(), atom()) :: any() | nil

Extracts the type definition for a specific field from a typespec AST.

Takes a typespec AST (typically from Code.Typespec.fetch_types/1) and a field name, then searches through the AST structure to find the type definition for that field.

Parameters

  • typespec_ast - The typespec AST structure, typically a list containing type definitions
  • field_name - The atom name of the field to look up

Returns

  • The type AST for the field if found
  • nil if the field is not found in the typespec

Examples

iex> ast = [type: {:t, {:type, 1, :map, [...]}, []}]
iex> get_field_type_from_ast(ast, :id)
{:remote_type, 1, [{:atom, 0, String}, {:atom, 0, :t}, []]}
Link to this function

parse_remote_type(type_spec, value)

View Source

Parses a value based on its typespec.

Handles primitive and enum-like fields, as well as remote component types and unions. For component modules, it can recursively convert nested maps to the appropriate structs based on the component's typespec and the shape of the data. Nested maps and lists are atomized recursively when no more specific type information is available.

Parameters

  • type_spec - The typespec AST for the field
  • value - The actual value to parse

Returns

  • The parsed/converted value

Examples

iex> parse_remote_type({:type, 1, :boolean, []}, true)
true

iex> parse_remote_type({:remote_type, 1, [{:atom, 0, ExOpenAI.Components.Reasoning}, {:atom, 0, :t}, []]}, %{"effort" => nil})
%ExOpenAI.Components.Reasoning{effort: nil}