openai

Servant bindings to OpenAI

Stackage Nightly 2026-10-16:2.5.4
Latest on Hackage:2.5.4

See all snapshots openai appears in

BSD-3-Clause licensed by Gabriella Gonzalez
Maintained by [email protected]
This version can be pinned in stack with:openai-2.5.4@sha256:90722cfa6763749cfa87c5617554e0078db7930ac15bb9620d1314198f0b82f4,6987

Module documentation for 2.5.4

  • OpenAI
    • OpenAI.V1
      • OpenAI.V1.Assistants
      • OpenAI.V1.Audio
        • OpenAI.V1.Audio.Speech
        • OpenAI.V1.Audio.Transcriptions
        • OpenAI.V1.Audio.Translations
      • OpenAI.V1.AutoOr
      • OpenAI.V1.Batches
      • OpenAI.V1.Chat
        • OpenAI.V1.Chat.Completions
          • OpenAI.V1.Chat.Completions.Stream
      • OpenAI.V1.ChatKit
      • OpenAI.V1.ChunkingStrategy
      • OpenAI.V1.DeletionStatus
      • OpenAI.V1.Embeddings
      • OpenAI.V1.Error
      • OpenAI.V1.Files
      • OpenAI.V1.FineTuning
        • OpenAI.V1.FineTuning.Jobs
      • OpenAI.V1.Images
        • OpenAI.V1.Images.Edits
        • OpenAI.V1.Images.Generations
        • OpenAI.V1.Images.Image
        • OpenAI.V1.Images.ResponseFormat
        • OpenAI.V1.Images.Variations
      • OpenAI.V1.ListOf
      • OpenAI.V1.Message
      • OpenAI.V1.Models
      • OpenAI.V1.Moderations
      • OpenAI.V1.Order
      • OpenAI.V1.ResponseFormat
      • OpenAI.V1.Responses
      • OpenAI.V1.Threads
        • OpenAI.V1.Threads.Messages
        • OpenAI.V1.Threads.Runs
          • OpenAI.V1.Threads.Runs.Steps
      • OpenAI.V1.Tool
      • OpenAI.V1.ToolCall
      • OpenAI.V1.ToolResources
      • OpenAI.V1.Uploads
      • OpenAI.V1.Usage
      • OpenAI.V1.VectorStores
        • OpenAI.V1.VectorStores.FileBatches
        • OpenAI.V1.VectorStores.FileCounts
        • OpenAI.V1.VectorStores.Files
        • OpenAI.V1.VectorStores.Status

openai

This provides a binding to OpenAI’s API using servant

Example usage

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns        #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE OverloadedLists       #-}

module Main where

import Data.Foldable (traverse_)
import OpenAI.V1
import OpenAI.V1.Chat.Completions

import qualified Data.Text as Text
import qualified Data.Text.IO as Text.IO
import qualified System.Environment as Environment

main :: IO ()
main = do
    key <- Environment.getEnv "OPENAI_KEY"

    clientEnv <- getClientEnv "https://api.openai.com"

    let Methods{ createChatCompletion } = makeMethods clientEnv (Text.pack key) Nothing Nothing

    text <- Text.IO.getLine

    ChatCompletionObject{ choices } <- createChatCompletion _CreateChatCompletion
        { messages = [ User{ content = [ Text{ text } ], name = Nothing } ]
        , model = "gpt-4o-mini"
        }

    let display Choice{ message } = Text.IO.putStrLn (messageToContent message)

    traverse_ display choices

Responses API (JSON)

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns        #-}
{-# LANGUAGE OverloadedStrings     #-}

import qualified Data.Text as Text
import qualified OpenAI.V1 as V1
import qualified OpenAI.V1.Responses as Responses

main :: IO ()
main = do
    key <- System.Environment.getEnv "OPENAI_KEY"

    env <- V1.getClientEnv "https://api.openai.com"
    let V1.Methods{ createResponse } = V1.makeMethods env (Text.pack key) Nothing Nothing

    let req = Responses._CreateResponse
            { Responses.model = "gpt-5"
            , Responses.input = Just (Responses.Input
                [ Responses.Item_Input_Message
                    { Responses.role = Responses.User
                    , Responses.content = [ Responses.Input_Text{ Responses.text = "Say hello in one sentence." } ]
                    , Responses.status = Nothing
                    }
                ])
            }

    res <- createResponse req
    print res

Responses API (Structured Outputs)

{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns        #-}
{-# LANGUAGE OverloadedLists       #-}
{-# LANGUAGE OverloadedStrings     #-}

import qualified Data.Aeson as Aeson
import qualified Data.Text as Text
import qualified OpenAI.V1 as V1
import qualified OpenAI.V1.Responses as Responses

main :: IO ()
main = do
    key <- System.Environment.getEnv "OPENAI_KEY"

    env <- V1.getClientEnv "https://api.openai.com"
    let V1.Methods{ createResponse } = V1.makeMethods env (Text.pack key) Nothing Nothing

    let schemaObject = Aeson.object
            [ "type" Aeson..= ("object" :: Text.Text)
            , "properties" Aeson..= Aeson.object
                [ "greeting" Aeson..= Aeson.object [ "type" Aeson..= ("string" :: Text.Text) ] ]
            , "required" Aeson..= (["greeting"] :: [Text.Text])
            , "additionalProperties" Aeson..= False
            ]

    let req = Responses._CreateResponse
            { Responses.model = "gpt-5"
            , Responses.input = Just (Responses.Input
                [ Responses.Item_Input_Message
                    { Responses.role = Responses.User
                    , Responses.content = [ Responses.Input_Text{ Responses.text = "Say hello." } ]
                    , Responses.status = Nothing
                    }
                ])
            , Responses.text = Just Responses._TextConfig
                { Responses.format = Responses.TextFormat_JSON_Schema
                    { Responses.name = "greeting"
                    , Responses.description = Just "Greeting payload"
                    , Responses.schema = Just schemaObject
                    , Responses.strict = Just True
                    }
                }
            }

    res <- createResponse req
    print res

Setup

Using Nix with Flakes (Recommended)

This project uses Nix with flakes for development environment setup.

  1. Ensure you have Nix with flakes enabled
  2. Copy the sample environment file and configure your OpenAI API key:
# Copy the sample environment file
cp .envrc.sample .envrc
  1. Edit the .envrc file and replace the placeholder API key with your actual key

  2. Use direnv to automatically load the development environment:

# Install direnv if you haven't already
# macOS: brew install direnv
# Linux: your-package-manager install direnv

# Enable direnv hook in your shell
eval "$(direnv hook bash)" # or zsh, fish, etc.

# Clone the repository and enter the directory
git clone https://github.com/MercuryTechnologies/openai.git
cd openai

# Allow direnv (this will automatically load the environment)
direnv allow

Manual Setup

Without Nix:

# Clone the repository
git clone https://github.com/MercuryTechnologies/openai.git
cd openai

# Build with cabal
cabal build

Environment Variables

Set your OpenAI API key as an environment variable:

# Option 1: Set directly in your shell
export OPENAI_KEY="your-openai-api-key"

# Option 2: Using .envrc with direnv (recommended)
(umask 077; cp .envrc.sample .envrc)
# Edit .envrc to add your API key
direnv allow

The API key is needed for running the test suite and example program.

Testing

Run the test suite:

cabal test

The test suite is in the tasty/ directory with test data located in tasty/data/.

Running the Example

# Make sure your API key is set (either via .envrc or export)
# If using direnv with proper .envrc setup, this happens automatically

# Build and run the example
cabal run openai-example

Changes

2.5.4:

  • Add ReasoningEffort_None support to chat completions, serialized as "none".

2.5.3:

  • Fix streaming Responses and chat-completions requests so caller-injected headers added via ClientEnv.middleware are preserved on the wire.

2.5.2:

  • Add ResponseWithMetadata wrapper and lookupHeader helper for accessing raw HTTP response headers from API calls.
  • Add createResponseWithMetadata for non-streaming responses with headers.
  • Add createResponseStreamWithMetadata and createResponseStreamTypedWithMetadata for streaming responses with headers (delivered via callback before events flow).
  • Existing createResponse, createResponseStream, and createResponseStreamTyped remain unchanged.

2.5.1:

  • Add 28 missing ResponseStreamEvent constructors covering all event types from the OpenAPI spec (lifecycle, reasoning, refusal, audio, image generation, MCP, and custom tool call events).
  • Add UnknownEvent fallback constructor that gracefully handles unrecognized event types instead of crashing the parser.

2.5.0:

  • BREAKING (streaming imports): move chat-completion streaming payload types (ChatCompletionChunk, ChunkChoice, Delta, ChatCompletionStreamEvent) from OpenAI.V1.Chat.Completions to OpenAI.V1.Chat.Completions.Stream to avoid selector ambiguity on choices/usage in non-streaming imports.

2.4.0:

  • Add Structured Outputs support to the Responses API (TextConfig, TextFormat, TextFormat_JSON_Schema).
  • Extend Responses supplementary request/response fields, including conversation, prompt, tool_resources, truncation, max_output_tokens, max_tool_calls, top_logprobs, safety_identifier, prompt_cache_key, and user.
  • Add verbosity support on Responses text config via Verbosity and text.verbosity.
  • Add ReasoningEffort_None for Responses reasoning effort.

2.3.0:

  • Add ChatCompletionStreamOptions support for including usage in chat completion streams.
  • Simplify Responses tool JSON encoding/decoding.
  • Add support for /v1/chatkit/*.
  • Add missing arguments in makeMethods in the create chat example.

2.2.1:

2.2.0:

  • Add structured reasoning support to OpenAI.V1.Responses, including the Reasoning, ReasoningEffort, and ReasoningSummary types, plus the ServiceTier alias.
  • Extend CreateResponse and ResponseObject with reasoning and service_tier fields to round-trip reasoning configuration.

2.1.0:

  • Add Item_Input_Reasoning constructor and JSON round-trip test so reasoning traces from tool calls can be echoed back via the Responses API.

2.0.0:

  • BREAKING CHANGE: Renamed Item_InputMessage to Item_Input_Message in OpenAI.V1.Responses for consistency with new constructors

    The InputItem data type in the Responses API has been updated to use a consistent naming scheme. If you are using the Responses API, you will need to update your code:

    -- Before (v1.2.0):
    Item_InputMessage { role = ..., content = ..., status = ... }
    
    -- After (v2.0.0):
    Item_Input_Message { role = ..., content = ..., status = ... }
    
  • Add support for function tool calls in Responses API

    • New InputItem constructors: Item_Input_Function_Call, Item_Input_Function_Call_Output, Item_Input_Item_Reference
    • Add flattened tool JSON representation for Responses API compatibility
    • Export status constants: statusIncomplete, statusCompleted
    • Export tool choice constants: toolChoiceNoneText, toolChoiceAutoText, toolChoiceRequiredText
  • Code quality improvements:

    • Optimize isFunctionField using HashSet for O(1) lookups
    • Simplify unflattenChoice with guards
    • Extract magic strings as named constants

1.2.0:

1.1.1:

1.1.0:

1.0.1:

  • Include README
  • Include usage example

1.0.0:

  • Initial release