Skip to content

Commit e59e492

Browse files
authored
Merge pull request #273 from MacPaw/bug/create-speech-response-decoding
Bug/create speech response decoding
2 parents 0e8b8b1 + a10451a commit e59e492

File tree

7 files changed

+81
-4
lines changed

7 files changed

+81
-4
lines changed

Demo/DemoChat/Sources/SpeechStore.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public final class SpeechStore: ObservableObject {
4242
format: query.responseFormat?.rawValue ?? AudioSpeechQuery.AudioSpeechResponseFormat.mp3.rawValue)
4343
audioObjects.append(audioObject)
4444
} catch {
45-
print(error.localizedDescription)
45+
print("[SpeechStore] createSpeech error: \(error.localizedDescription)")
4646
}
4747
}
4848

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ let configuration = OpenAI.Configuration(token: "YOUR_TOKEN_HERE", organizationI
9898
let openAI = OpenAI(configuration: configuration)
9999
```
100100

101-
Once token you posses the token, and the instance is initialized you are ready to make requests.
101+
See `OpenAI.Configuration` for more values that can be passed on init for customization, like: `host`, `basePath`, `port`, `scheme` and `customHeaders`.
102+
103+
Once you posses the token, and the instance is initialized you are ready to make requests.
102104

103105
### Chats
104106

Sources/OpenAI/OpenAI+OpenAIAsync.swift

+33-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ extension OpenAI: OpenAIAsync {
7979
}
8080

8181
public func audioCreateSpeech(query: AudioSpeechQuery) async throws -> AudioSpeechResult {
82-
try await performRequestAsync(
82+
try await performSpeechRequestAsync(
8383
request: makeAudioCreateSpeechRequest(query: query)
8484
)
8585
}
@@ -214,4 +214,36 @@ extension OpenAI: OpenAIAsync {
214214
}
215215
}
216216
}
217+
218+
func performSpeechRequestAsync(request: any URLRequestBuildable) async throws -> AudioSpeechResult {
219+
let urlRequest = try request.build(configuration: configuration)
220+
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
221+
let (data, _) = try await session.data(for: urlRequest, delegate: nil)
222+
return .init(audio: data)
223+
} else {
224+
let dataTaskStore = URLSessionDataTaskStore()
225+
return try await withTaskCancellationHandler {
226+
return try await withCheckedThrowingContinuation { continuation in
227+
let dataTask = self.makeRawResponseDataTask(forRequest: urlRequest) { result in
228+
switch result {
229+
case .success(let success):
230+
continuation.resume(returning: .init(audio: success))
231+
case .failure(let failure):
232+
continuation.resume(throwing: failure)
233+
}
234+
}
235+
236+
dataTask.resume()
237+
238+
Task {
239+
await dataTaskStore.setDataTask(dataTask)
240+
}
241+
}
242+
} onCancel: {
243+
Task {
244+
await dataTaskStore.getDataTask()?.cancel()
245+
}
246+
}
247+
}
248+
}
217249
}

Sources/OpenAI/OpenAI+OpenAICombine.swift

+15-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ extension OpenAI: OpenAICombine {
7676
}
7777

7878
public func audioCreateSpeech(query: AudioSpeechQuery) -> AnyPublisher<AudioSpeechResult, Error> {
79-
performRequestCombine(
79+
performSpeechRequestCombine(
8080
request: makeAudioCreateSpeechRequest(query: query)
8181
)
8282
}
@@ -196,5 +196,19 @@ extension OpenAI: OpenAICombine {
196196
.eraseToAnyPublisher()
197197
}
198198
}
199+
200+
func performSpeechRequestCombine(request: any URLRequestBuildable) -> AnyPublisher<AudioSpeechResult, Error> {
201+
do {
202+
let request = try request.build(configuration: configuration)
203+
return session
204+
.dataTaskPublisher(for: request)
205+
.tryMap { (data, response) in
206+
return .init(audio: data)
207+
}.eraseToAnyPublisher()
208+
} catch {
209+
return Fail(outputType: AudioSpeechResult.self, failure: error)
210+
.eraseToAnyPublisher()
211+
}
212+
}
199213
}
200214
#endif

Sources/OpenAI/OpenAI.swift

+13
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,19 @@ extension OpenAI {
295295
}
296296
}
297297
}
298+
299+
func makeRawResponseDataTask(forRequest request: URLRequest, completion: @escaping (Result<Data, Error>) -> Void) -> URLSessionDataTaskProtocol {
300+
session.dataTask(with: request) { data, _, error in
301+
if let error = error {
302+
return completion(.failure(error))
303+
}
304+
guard let data = data else {
305+
return completion(.failure(OpenAIError.emptyData))
306+
}
307+
308+
completion(.success(data))
309+
}
310+
}
298311
}
299312

300313
extension OpenAI {

Tests/OpenAITests/OpenAITests.swift

+8
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,14 @@ class OpenAITests: XCTestCase {
294294

295295
XCTAssertEqual(query.speed, "\(4.0)")
296296
}
297+
298+
func testAudioCreateSpeech() async throws {
299+
let query = AudioSpeechQuery(model: .tts_1, input: "Hello, world!", voice: .alloy, speed: nil)
300+
let data = Data(repeating: 11, count: 11)
301+
urlSession.dataTask = .successful(with: data)
302+
let response = try await openAI.audioCreateSpeech(query: query)
303+
XCTAssertEqual(response.audio, data)
304+
}
297305

298306
func testAudioSpeechError() async throws {
299307
let query = AudioSpeechQuery(model: .tts_1, input: "Hello, world!", voice: .alloy, responseFormat: .mp3, speed: 1.0)

Tests/OpenAITests/OpenAITestsCombine.swift

+8
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ final class OpenAITestsCombine: XCTestCase {
7575
XCTAssertEqual(result, moderationsResult)
7676
}
7777

78+
func testAudioCreateSpeech() throws {
79+
let query = AudioSpeechQuery(model: .tts_1, input: "Hello, world!", voice: .alloy, speed: nil)
80+
let data = Data(repeating: 10, count: 10)
81+
urlSession.dataTask = .successful(with: data)
82+
let response = try awaitPublisher(openAI.audioCreateSpeech(query: query), timeout: 1)
83+
XCTAssertEqual(data, response.audio)
84+
}
85+
7886
func testAudioTranscriptions() throws {
7987
let data = Data()
8088
let query = AudioTranscriptionQuery(file: data, fileType: .m4a, model: .whisper_1)

0 commit comments

Comments
 (0)