Skip to content

Releases: jamesrochabrun/SwiftOpenAI

SwiftOpenAI v4.2.0

07 Jun 22:31
Compare
Choose a tag to compare

What's Changed

⏺ Response API Streaming Support - Summary of Changes

Streaming Responses

The Response API supports streaming responses using Server-Sent Events (SSE). This allows you to receive partial responses as they are generated, enabling real-time UI updates and better user experience.

Stream Events

// The ResponseStreamEvent enum represents all possible streaming events
public enum ResponseStreamEvent: Decodable {
  case responseCreated(ResponseCreatedEvent)
  case responseInProgress(ResponseInProgressEvent)
  case responseCompleted(ResponseCompletedEvent)
  case responseFailed(ResponseFailedEvent)
  case outputItemAdded(OutputItemAddedEvent)
  case outputTextDelta(OutputTextDeltaEvent)
  case outputTextDone(OutputTextDoneEvent)
  case functionCallArgumentsDelta(FunctionCallArgumentsDeltaEvent)
  case reasoningSummaryTextDelta(ReasoningSummaryTextDeltaEvent)
  case error(ErrorEvent)
  // ... and many more event types
}

Basic Streaming Example

// Enable streaming by setting stream: true
let parameters = ModelResponseParameter(
    input: .string("Tell me a story"),
    model: .gpt4o,
    stream: true
)

// Create a stream
let stream = try await service.responseCreateStream(parameters)

// Process events as they arrive
for try await event in stream {
    switch event {
    case .outputTextDelta(let delta):
        // Append text chunk to your UI
        print(delta.delta, terminator: "")
        
    case .responseCompleted(let completed):
        // Response is complete
        print("\nResponse ID: \(completed.response.id)")
        
    case .error(let error):
        // Handle errors
        print("Error: \(error.message)")
        
    default:
        // Handle other events as needed
        break
    }
}

Streaming with Conversation State

// Maintain conversation continuity with previousResponseId
var previousResponseId: String? = nil
var messages: [(role: String, content: String)] = []

// First message
let firstParams = ModelResponseParameter(
    input: .string("Hello!"),
    model: .gpt4o,
    stream: true
)

let firstStream = try await service.responseCreateStream(firstParams)
var firstResponse = ""

for try await event in firstStream {
    switch event {
    case .outputTextDelta(let delta):
        firstResponse += delta.delta
        
    case .responseCompleted(let completed):
        previousResponseId = completed.response.id
        messages.append((role: "user", content: "Hello!"))
        messages.append((role: "assistant", content: firstResponse))
        
    default:
        break
    }
}

// Follow-up message with conversation context
var inputArray: [InputItem] = []

// Add conversation history
for message in messages {
    inputArray.append(.message(InputMessage(
        role: message.role,
        content: .text(message.content)
    )))
}

// Add new user message
inputArray.append(.message(InputMessage(
    role: "user",
    content: .text("How are you?")
)))

let followUpParams = ModelResponseParameter(
    input: .array(inputArray),
    model: .gpt4o,
    previousResponseId: previousResponseId,
    stream: true
)

let followUpStream = try await service.responseCreateStream(followUpParams)
// Process the follow-up stream...

Streaming with Tools and Function Calling

let parameters = ModelResponseParameter(
    input: .string("What's the weather in San Francisco?"),
    model: .gpt4o,
    tools: [
        Tool(
            type: "function",
            function: ChatCompletionParameters.ChatFunction(
                name: "get_weather",
                description: "Get current weather",
                parameters: JSONSchema(
                    type: .object,
                    properties: [
                        "location": JSONSchema(type: .string)
                    ],
                    required: ["location"]
                )
            )
        )
    ],
    stream: true
)

let stream = try await service.responseCreateStream(parameters)
var functionCallArguments = ""

for try await event in stream {
    switch event {
    case .functionCallArgumentsDelta(let delta):
        // Accumulate function call arguments
        functionCallArguments += delta.delta
        
    case .functionCallArgumentsDone(let done):
        // Function call is complete
        print("Function: \(done.name)")
        print("Arguments: \(functionCallArguments)")
        
    case .outputTextDelta(let delta):
        // Regular text output
        print(delta.delta, terminator: "")
        
    default:
        break
    }
}

Canceling a Stream

// Streams can be canceled using Swift's task cancellation
let streamTask = Task {
    let stream = try await service.responseCreateStream(parameters)
    
    for try await event in stream {
        // Check if task is cancelled
        if Task.isCancelled {
            break
        }
        
        // Process events...
    }
}

// Cancel the stream when needed
streamTask.cancel()

Complete Streaming Implementation Example

@MainActor
@Observable
class ResponseStreamProvider {
    var messages: [Message] = []
    var isStreaming = false
    var error: String?
    
    private let service: OpenAIService
    private var previousResponseId: String?
    private var streamTask: Task<Void, Never>?
    
    init(service: OpenAIService) {
        self.service = service
    }
    
    func sendMessage(_ text: String) {
        streamTask?.cancel()
        
        // Add user message
        messages.append(Message(role: .user, content: text))
        
        // Start streaming
        streamTask = Task {
            await streamResponse(for: text)
        }
    }
    
    private func streamResponse(for userInput: String) async {
        isStreaming = true
        error = nil
        
        // Create streaming message placeholder
        let streamingMessage = Message(role: .assistant, content: "", isStreaming: true)
        messages.append(streamingMessage)
        
        do {
            // Build conversation history
            var inputArray: [InputItem] = []
            for message in messages.dropLast(2) {
                inputArray.append(.message(InputMessage(
                    role: message.role.rawValue,
                    content: .text(message.content)
                )))
            }
            inputArray.append(.message(InputMessage(
                role: "user",
                content: .text(userInput)
            )))
            
            let parameters = ModelResponseParameter(
                input: .array(inputArray),
                model: .gpt4o,
                previousResponseId: previousResponseId,
                stream: true
            )
            
            let stream = try await service.responseCreateStream(parameters)
            var accumulatedText = ""
            
            for try await event in stream {
                guard !Task.isCancelled else { break }
                
                switch event {
                case .outputTextDelta(let delta):
                    accumulatedText += delta.delta
                    updateStreamingMessage(with: accumulatedText)
                    
                case .responseCompleted(let completed):
                    previousResponseId = completed.response.id
                    finalizeStreamingMessage(with: accumulatedText, responseId: completed.response.id)
                    
                case .error(let errorEvent):
                    throw APIError.requestFailed(description: errorEvent.message)
                    
                default:
                    break
                }
            }
        } catch {
            self.error = error.localizedDescription
            messages.removeLast() // Remove streaming message on error
        }
        
        isStreaming = false
    }
    
    private func updateStreamingMessage(with content: String) {
        if let index = messages.lastIndex(where: { $0.isStreaming }) {
            messages[index].content = content
        }
    }
    
    private func finalizeStreamingMessage(with content: String, responseId: String) {
        if let index = messages.lastIndex(where: { $0.isStreaming }) {
            messages[index].content = content
            messages[index].isStreaming = false
            messages[index].responseId = responseId
        }
    }
}

New Contributors

Full Changelog: v4.1.1...v4.2.0

SwiftOpenAI v4.1.1

13 May 06:47
Compare
Choose a tag to compare

Update Read me for Anthropic OpenAI compatibility

Anthropic

Anthropic provides OpenAI compatibility, for more, visit the documentation

To use Claude models with SwiftOpenAI you can.

let anthropicApiKey = ""
let openAIService = OpenAIServiceFactory.service(apiKey: anthropicApiKey, 
                     overrideBaseURL: "https://api.anthropic.com", 
                     overrideVersion: "v1")

Now you can create the completio parameters like this:

let parameters = ChatCompletionParameters(
   messages: [.init(
   role: .user,
   content: "Are you Claude?")],
   model: .custom("claude-3-7-sonnet-20250219"))

What else

New Contributors

Full Changelog: v4.1.0...v4.1.1

SwiftOpenAI v4.1.0

25 Apr 21:53
Compare
Choose a tag to compare

This library supports latest OpenAI Image generation

  • Parameters Create
/// 'Create Image':
/// https://platform.openai.com/docs/api-reference/images/create
public struct CreateImageParameters: Encodable {
   
   /// A text description of the desired image(s).
   /// The maximum length is 32000 characters for `gpt-image-1`, 1000 characters for `dall-e-2` and 4000 characters for `dall-e-3`.
   public let prompt: String
   
   // MARK: - Optional properties
   
   /// Allows to set transparency for the background of the generated image(s).
   /// This parameter is only supported for `gpt-image-1`.
   /// Must be one of `transparent`, `opaque` or `auto` (default value).
   /// When `auto` is used, the model will automatically determine the best background for the image.
   /// If `transparent`, the output format needs to support transparency, so it should be set to either `png` (default value) or `webp`.
   public let background: Background?
   
   /// The model to use for image generation. One of `dall-e-2`, `dall-e-3`, or `gpt-image-1`.
   /// Defaults to `dall-e-2` unless a parameter specific to `gpt-image-1` is used.
   public let model: Model?
   
   /// Control the content-moderation level for images generated by `gpt-image-1`.
   /// Must be either low for less restrictive filtering or auto (default value).
   public let moderation: Moderation?
   
   /// The number of images to generate. Must be between 1 and 10. For `dall-e-3`, only `n=1` is supported.
   /// Defaults to `1`
   public let n: Int?
   
   /// The compression level (0-100%) for the generated images.
   /// This parameter is only supported for `gpt-image-1` with the `webp` or `jpeg` output formats, and defaults to 100.
   public let outputCompression: Int?
   
   /// The format in which the generated images are returned.
   /// This parameter is only supported for `gpt-image-1`.
   /// Must be one of `png`, `jpeg`, or `webp`.
   public let outputFormat: OutputFormat?
   
   /// The quality of the image that will be generated.
   /// - `auto` (default value) will automatically select the best quality for the given model.
   /// - `high`, `medium` and `low` are supported for gpt-image-1.
   /// - `hd` and `standard` are supported for dall-e-3.
   /// - `standard` is the only option for dall-e-2.
   public let quality: Quality?
   
   /// The format in which generated images with dall-e-2 and dall-e-3 are returned.
   /// Must be one of `url` or `b64_json`.
   /// URLs are only valid for 60 minutes after the image has been generated.
   /// This parameter isn't supported for `gpt-image-1` which will always return base64-encoded images.
   public let responseFormat: ResponseFormat?
   
   /// The size of the generated images.
   /// - For gpt-image-1, one of `1024x1024`, `1536x1024` (landscape), `1024x1536` (portrait), or `auto` (default value)
   /// - For dall-e-3, one of `1024x1024`, `1792x1024`, or `1024x1792`
   /// - For dall-e-2, one of `256x256`, `512x512`, or `1024x1024`
   public let size: String?
   
   /// The style of the generated images.
   /// This parameter is only supported for `dall-e-3`.
   /// Must be one of `vivid` or `natural`.
   /// Vivid causes the model to lean towards generating hyper-real and dramatic images.
   /// Natural causes the model to produce more natural, less hyper-real looking images.
   public let style: Style?
   
   /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
   public let user: String?
}
  • Parameters Edit
/// Creates an edited or extended image given one or more source images and a prompt.
/// This endpoint only supports `gpt-image-1` and `dall-e-2`.
public struct CreateImageEditParameters: Encodable {
   
   /// The image(s) to edit.
   /// For `gpt-image-1`, each image should be a `png`, `webp`, or `jpg` file less than 25MB.
   /// For `dall-e-2`, you can only provide one image, and it should be a square `png` file less than 4MB.
   let image: [Data]
   
   /// A text description of the desired image(s).
   /// The maximum length is 1000 characters for `dall-e-2`, and 32000 characters for `gpt-image-1`.
   let prompt: String
   
   /// An additional image whose fully transparent areas indicate where `image` should be edited.
   /// If there are multiple images provided, the mask will be applied on the first image.
   /// Must be a valid PNG file, less than 4MB, and have the same dimensions as `image`.
   let mask: Data?
   
   /// The model to use for image generation. Only `dall-e-2` and `gpt-image-1` are supported.
   /// Defaults to `dall-e-2` unless a parameter specific to `gpt-image-1` is used.
   let model: String?
   
   /// The number of images to generate. Must be between 1 and 10.
   /// Defaults to 1.
   let n: Int?
   
   /// The quality of the image that will be generated.
   /// `high`, `medium` and `low` are only supported for `gpt-image-1`.
   /// `dall-e-2` only supports `standard` quality.
   /// Defaults to `auto`.
   let quality: String?
   
   /// The format in which the generated images are returned.
   /// Must be one of `url` or `b64_json`.
   /// URLs are only valid for 60 minutes after the image has been generated.
   /// This parameter is only supported for `dall-e-2`, as `gpt-image-1` will always return base64-encoded images.
   let responseFormat: String?
   
   /// The size of the generated images.
   /// Must be one of `1024x1024`, `1536x1024` (landscape), `1024x1536` (portrait), or `auto` (default value) for `gpt-image-1`,
   /// and one of `256x256`, `512x512`, or `1024x1024` for `dall-e-2`.
   let size: String?
   
   /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
   let user: String?
}
  • Parameters Variations
/// Creates a variation of a given image.
/// This endpoint only supports `dall-e-2`.
public struct CreateImageVariationParameters: Encodable {
   
   /// The image to use as the basis for the variation(s).
   /// Must be a valid PNG file, less than 4MB, and square.
   let image: Data
   
   /// The model to use for image generation. Only `dall-e-2` is supported at this time.
   /// Defaults to `dall-e-2`.
   let model: String?
   
   /// The number of images to generate. Must be between 1 and 10.
   /// Defaults to 1.
   let n: Int?
   
   /// The format in which the generated images are returned.
   /// Must be one of `url` or `b64_json`.
   /// URLs are only valid for 60 minutes after the image has been generated.
   /// Defaults to `url`.
   let responseFormat: String?
   
   /// The size of the generated images.
   /// Must be one of `256x256`, `512x512`, or `1024x1024`.
   /// Defaults to `1024x1024`.
   let size: String?
   
   /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
   let user: String?
}
  • Request example
import SwiftOpenAI

let service = OpenAIServiceFactory.service(apiKey: "<YOUR_KEY>")

// ❶ Describe the image you want
let prompt = "A watercolor dragon-unicorn hybrid flying above snowy mountains"

// ❷ Build parameters with the brand-new types (commit 880a15c)
let params = CreateImageParameters(
    prompt: prompt,
    model:  .gptImage1,      // .dallE3 / .dallE2 also valid
    n:      1,               // 1-10  (only 1 for DALL-E 3)
    quality: .high,          // .hd / .standard for DALL-E 3
    size:   "1024x1024"      // use "1792x1024" or "1024x1792" for wide / tall
)

do {
    // ❸ Fire the request – returns a `CreateImageResponse`
    let result = try await service.createImages(parameters: params)
    let url    = result.data?.first?.url          // or `b64Json` for base-64
    print("Image URL:", url ?? "none")
} catch {
    print("Generation failed:", error)
}

For a sample app example go to the Examples/SwiftOpenAIExample project on this repo.

SwiftOpenAI v4.0.7

14 Apr 06:36
3e81448
Compare
Choose a tag to compare

What's Changed

Full Changelog: v4.0.6...v4.0.7

SwiftOpenAI v4.0.6

17 Mar 07:08
3f5e195
Compare
Choose a tag to compare

Adding convenient property in ResponseModel addressing #129

   /// Convenience property that aggregates all text output from output_text items in the output array.
   /// Similar to the outputText property in Python and JavaScript SDKs.
   public var outputText: String? {
      let outputTextItems = output.compactMap { outputItem -> String? in
         switch outputItem {
         case .message(let message):
            return message.content.compactMap { contentItem -> String? in
               switch contentItem {
               case .outputText(let outputText):
                  return outputText.text
               }
            }.joined()
         default:
            return nil
         }
      }
      
      return outputTextItems.isEmpty ? nil : outputTextItems.joined()
   }

SwiftOpenAI v4.0.5

16 Mar 07:24
0a2a810
Compare
Choose a tag to compare

Support for NON Stream Response API

Response

OpenAI's most advanced interface for generating model responses. Supports text and image inputs, and text outputs. Create stateful interactions with the model, using the output of previous responses as input. Extend the model's capabilities with built-in tools for file search, web search, computer use, and more. Allow the model access to external systems and data using function calling.

Related guides:

Parameters

/// [Creates a model response.](https://platform.openai.com/docs/api-reference/responses/create)
public struct ModelResponseParameter: Codable {

   /// Text, image, or file inputs to the model, used to generate a response.
   /// A text input to the model, equivalent to a text input with the user role.
   /// A list of one or many input items to the model, containing different content types.
   public var input: InputType

   /// Model ID used to generate the response, like gpt-4o or o1. OpenAI offers a wide range of models with
   /// different capabilities, performance characteristics, and price points.
   /// Refer to the model guide to browse and compare available models.
   public var model: String

   /// Specify additional output data to include in the model response. Currently supported values are:
   /// file_search_call.results : Include the search results of the file search tool call.
   /// message.input_image.image_url : Include image urls from the input message.
   /// computer_call_output.output.image_url : Include image urls from the computer call output.
   public var include: [String]?

   /// Inserts a system (or developer) message as the first item in the model's context.
   /// When using along with previous_response_id, the instructions from a previous response will be not be
   /// carried over to the next response. This makes it simple to swap out system (or developer) messages in new responses.
   public var instructions: String?

   /// An upper bound for the number of tokens that can be generated for a response, including visible output tokens
   /// and reasoning tokens.
   public var maxOutputTokens: Int?

   /// Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information
   /// about the object in a structured format, and querying for objects via API or the dashboard.
   /// Keys are strings with a maximum length of 64 characters. Values are strings with a maximum length of 512 characters.
   public var metadata: [String: String]?

   /// Whether to allow the model to run tool calls in parallel.
   /// Defaults to true
   public var parallelToolCalls: Bool?

   /// The unique ID of the previous response to the model. Use this to create multi-turn conversations.
   /// Learn more about conversation state.
   public var previousResponseId: String?

   /// o-series models only
   /// Configuration options for reasoning models.
   public var reasoning: Reasoning?

   /// Whether to store the generated model response for later retrieval via API.
   /// Defaults to true
   public var store: Bool?

   /// If set to true, the model response data will be streamed to the client as it is generated using server-sent events.
   public var stream: Bool?

   /// What sampling temperature to use, between 0 and 2.
   /// Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.
   /// We generally recommend altering this or top_p but not both.
   /// Defaults to 1
   public var temperature: Double?

   /// Configuration options for a text response from the model. Can be plain text or structured JSON data.
   public var text: TextConfiguration?

   /// How the model should select which tool (or tools) to use when generating a response.
   /// See the tools parameter to see how to specify which tools the model can call.
   public var toolChoice: ToolChoiceMode?

   /// An array of tools the model may call while generating a response. You can specify which tool to use by setting the tool_choice parameter.
   public var tools: [Tool]?

   /// An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass.
   /// So 0.1 means only the tokens comprising the top 10% probability mass are considered.
   /// We generally recommend altering this or temperature but not both.
   /// Defaults to 1
   public var topP: Double?

   /// The truncation strategy to use for the model response.
   /// Defaults to disabled
   public var truncation: String?

   /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
   public var user: String?
}

The Response object

/// The Response object returned when retrieving a model response
public struct ResponseModel: Decodable {

   /// Unix timestamp (in seconds) of when this Response was created.
   public let createdAt: Int

   /// An error object returned when the model fails to generate a Response.
   public let error: ErrorObject?

   /// Unique identifier for this Response.
   public let id: String

   /// Details about why the response is incomplete.
   public let incompleteDetails: IncompleteDetails?

   /// Inserts a system (or developer) message as the first item in the model's context.
   public let instructions: String?

   /// An upper bound for the number of tokens that can be generated for a response, including visible output tokens
   /// and reasoning tokens.
   public let maxOutputTokens: Int?

   /// Set of 16 key-value pairs that can be attached to an object.
   public let metadata: [String: String]

   /// Model ID used to generate the response, like gpt-4o or o1.
   public let model: String

   /// The object type of this resource - always set to response.
   public let object: String

   /// An array of content items generated by the model.
   public let output: [OutputItem]

   /// Whether to allow the model to run tool calls in parallel.
   public let parallelToolCalls: Bool

   /// The unique ID of the previous response to the model. Use this to create multi-turn conversations.
   public let previousResponseId: String?

   /// Configuration options for reasoning models.
   public let reasoning: Reasoning?

   /// The status of the response generation. One of completed, failed, in_progress, or incomplete.
   public let status: String

   /// What sampling temperature to use, between 0 and 2.
   public let temperature: Double?

   /// Configuration options for a text response from the model.
   public let text: TextConfiguration

   /// How the model should select which tool (or tools) to use when generating a response.
   public let toolChoice: ToolChoiceMode

   /// An array of tools the model may call while generating a response.
   public let tools: [Tool]

   /// An alternative to sampling with temperature, called nucleus sampling.
   public let topP: Double?

   /// The truncation strategy to use for the model response.
   public let truncation: String?

   /// Represents token usage details.
   public let usage: Usage?

   /// A unique identifier representing your end-user.
   public let user: String?
}

Usage

Simple text input

let prompt = "What is the capital of France?"
let parameters = ModelResponseParameter(input: .string(prompt), model: .gpt4o)
let response = try await service.responseCreate(parameters)

Text input with reasoning

let prompt = "How much wood would a woodchuck chuck?"
let parameters = ModelResponseParameter(
    input: .string(prompt),
    model: .o3Mini,
    reasoning: Reasoning(effort: "high")
)
let response = try await service.responseCreate(parameters)

Image input

let textPrompt = "What is in this image?"
let imageUrl = "https://example.com/path/to/image.jpg"
let imageContent = ContentItem.imageUrl(ImageUrlContent(imageUrl: imageUrl))
let textContent = ContentItem.text(TextContent(text: textPrompt))
let message = InputItem(role: "user", content: [textContent, imageContent])
let parameters = ModelResponseParameter(input: .array([message]), model: .gpt4o)
let response = try await service.responseCreate(parameters)

Using tools (web search)

let prompt = "What was a positive news story from today?"
let parameters = ModelResponseParameter(
    input: .string(prompt),
    model: .gpt4o,
    tools: [Tool(type: "web_search_preview", function: nil)]
)
let response = try await service.responseCreate(parameters)

Using tools (file search)

let prompt = "What are the key points in the document?"
let parameters = ModelResponseParameter(
    input: .string(prompt),
    model: .gpt4o,
    tools: [
        Tool(
            type: "file_search",
            function: ChatCompletionParameters.ChatFunction(
                name: "file_search",
                strict: false,
                description: "Search through files",
                parameters: JSONSchema(
                    type: .ob...
Read more

SwiftOpenAI v4.0.4

10 Mar 22:09
75b8f09
Compare
Choose a tag to compare

What's Changed

Full Changelog: v4.0.3...v4.0.4

SwiftOpenAI v4.0.3

12 Feb 05:43
8bb0ffc
Compare
Choose a tag to compare

What's Changed

Following suggestions from API's that handles many providers, SwiftOpenAI makes now all properties for completions optional. This will make this library more friendly to different providers.

_> Decodables should all have optional properties. Why? We don't want to fail decoding in live apps if the provider changes something out from under us (which can happen purposefully due to deprecations, or by accident due to regressions). If we use non-optionals in decodable definitions, then a provider removing a field, changing the type of a field, or removing an enum case would cause decoding to fail.

You may think this isn't too bad, since the JSONDecoder throws anyway, and therefore client code will already be wrapped in a do/catch. However, we always want to give the best chance that decodable succeeds for the properties that the client actually uses. That is, if the provider changes out the enum case of a property unused by the client, we want the client application to continue functioning correctly, not to throw an error and enter the catch branch of the client's call site._

Full Changelog: v4.0.2...v4.0.3

SwiftOpenAI v4.0.2

04 Feb 05:25
15cd189
Compare
Choose a tag to compare

What's Changed

Full Changelog: v4.0.1...v4.0.2

SwiftOpenAI v4.0.1

02 Feb 07:12
e589864
Compare
Choose a tag to compare

SwiftOpenAI v4.0.0

What's Changed

Full Changelog: v4.0.0...v4.0.1