Skip to content

Commit 95bb974

Browse files
committed
Use new options in more places
To fix parsing Gemini responses
1 parent 035fdc4 commit 95bb974

10 files changed

+91
-17
lines changed

Demo/DemoChat/Sources/ChatStore.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -175,25 +175,25 @@ public final class ChatStore: ObservableObject {
175175
let chatQuery = ChatQuery(
176176
messages: conversation.messages.map { message in
177177
ChatQuery.ChatCompletionMessageParam(role: message.role, content: message.content)!
178-
}, model: model,
178+
},
179+
model: model,
179180
tools: functions
180181
)
181182

182183
if stream {
183184
try await completeConversationStreaming(
184185
conversationIndex: conversationIndex,
185-
model: model,
186186
query: chatQuery
187187
)
188188
} else {
189-
try await completeConversation(conversationIndex: conversationIndex, model: model, query: chatQuery)
189+
try await completeConversation(conversationIndex: conversationIndex, query: chatQuery)
190190
}
191191
} catch {
192192
conversationErrors[conversationId] = error
193193
}
194194
}
195195

196-
private func completeConversation(conversationIndex: Int, model: Model, query: ChatQuery) async throws {
196+
private func completeConversation(conversationIndex: Int, query: ChatQuery) async throws {
197197
let chatResult: ChatResult = try await openAIClient.chats(query: query)
198198
chatResult.choices
199199
.map {
@@ -212,7 +212,7 @@ public final class ChatStore: ObservableObject {
212212
}
213213
}
214214

215-
private func completeConversationStreaming(conversationIndex: Int, model: Model, query: ChatQuery) async throws {
215+
private func completeConversationStreaming(conversationIndex: Int, query: ChatQuery) async throws {
216216
let chatsStream: AsyncThrowingStream<ChatStreamResult, Error> = openAIClient.chatsStream(query: query)
217217

218218
var functionCalls = [Int: (name: String?, arguments: String)]()

Sources/OpenAI/OpenAI+OpenAIAsync.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,11 @@ extension OpenAI: OpenAIAsync {
221221
do {
222222
return try decoder.decode(ResultType.self, from: interceptedData ?? data)
223223
} catch {
224-
throw (try? decoder.decode(APIErrorResponse.self, from: interceptedData ?? data)) ?? error
224+
if let decoded = JSONResponseErrorDecoder(decoder: decoder).decodeErrorResponse(data: interceptedData ?? data) {
225+
throw decoded
226+
} else {
227+
throw error
228+
}
225229
}
226230
} else {
227231
let dataTaskStore = URLSessionDataTaskStore()

Sources/OpenAI/OpenAI.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,11 @@ extension OpenAI {
406406
do {
407407
completion(.success(try decoder.decode(ResultType.self, from: data)))
408408
} catch {
409-
completion(.failure((try? decoder.decode(APIErrorResponse.self, from: data)) ?? error))
409+
if let decoded = JSONResponseErrorDecoder(decoder: decoder).decodeErrorResponse(data: data) {
410+
completion(.failure(decoded))
411+
} else {
412+
completion(.failure(error))
413+
}
410414
}
411415
}
412416
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// JSONResponseErrorDecoder.swift
3+
// OpenAI
4+
//
5+
// Created by Oleksii Nezhyborets on 31.03.2025.
6+
//
7+
8+
import Foundation
9+
10+
struct JSONResponseErrorDecoder {
11+
let decoder: JSONDecoder
12+
13+
func decodeErrorResponse(data: Data) -> (any ErrorResponse)? {
14+
if let decoded = try? decoder.decode(APIErrorResponse.self, from: data) {
15+
return decoded
16+
} else if let decoded = try? decoder.decode([GeminiAPIErrorResponse].self, from: data) {
17+
return decoded[0]
18+
} else {
19+
return nil
20+
}
21+
}
22+
}

Sources/OpenAI/Private/Streaming/AudioSpeechStreamInterpreter.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ final class AudioSpeechStreamInterpreter: @unchecked Sendable, StreamInterpreter
2828
func processData(_ data: Data) {
2929
executionSerializer.dispatch {
3030
let decoder = JSONDecoder()
31-
if let decoded = try? decoder.decode(APIErrorResponse.self, from: data) {
31+
if let decoded = JSONResponseErrorDecoder(decoder: decoder).decodeErrorResponse(data: data) {
3232
self.onError?(decoded)
3333
}
3434

Sources/OpenAI/Private/Streaming/ServerSentEventsStreamInterpreter.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class ServerSentEventsStreamInterpreter <ResultType: Codable & Sendable>:
3737

3838
func processData(_ data: Data) {
3939
let decoder = JSONDecoder()
40-
if let decoded = try? decoder.decode(APIErrorResponse.self, from: data) {
40+
if let decoded = JSONResponseErrorDecoder(decoder: decoder).decodeErrorResponse(data: data) {
4141
onError?(decoded)
4242
return
4343
}
@@ -97,7 +97,7 @@ final class ServerSentEventsStreamInterpreter <ResultType: Codable & Sendable>:
9797
let object = try decoder.decode(ResultType.self, from: jsonData)
9898
onEventDispatched?(object)
9999
} catch {
100-
if let decoded = try? decoder.decode(APIErrorResponse.self, from: jsonData) {
100+
if let decoded = JSONResponseErrorDecoder(decoder: decoder).decodeErrorResponse(data: jsonData) {
101101
onError?(decoded)
102102
return
103103
} else if index == jsonObjects.count - 1 {

Sources/OpenAI/Public/Errors/APIError.swift

+9-5
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,17 @@ extension APIError: LocalizedError {
6363
}
6464
}
6565

66-
public struct APIErrorResponse: Error, Decodable, Equatable {
66+
public struct APIErrorResponse: ErrorResponse {
6767
public let error: APIError
68-
}
69-
70-
extension APIErrorResponse: LocalizedError {
7168

7269
public var errorDescription: String? {
73-
return error.errorDescription
70+
error.errorDescription
7471
}
7572
}
73+
74+
public protocol ErrorResponse: Error, Decodable, Equatable, LocalizedError {
75+
associatedtype Err: Error, Decodable, Equatable, LocalizedError
76+
77+
var error: Err { get }
78+
var errorDescription: String? { get }
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// GeminiAPIError.swift
3+
// OpenAI
4+
//
5+
// Created by Oleksii Nezhyborets on 31.03.2025.
6+
//
7+
8+
import Foundation
9+
10+
public struct GeminiAPIErrorResponse: ErrorResponse {
11+
public let error: GeminiAPIError
12+
13+
public var errorDescription: String? {
14+
error.errorDescription
15+
}
16+
}
17+
18+
public struct GeminiAPIError: Error, Decodable, Equatable, LocalizedError {
19+
public let code: Int
20+
public let message: String
21+
public let status: String
22+
23+
public var errorDescription: String? {
24+
message
25+
}
26+
}

Sources/OpenAI/Public/Models/ChatResult.swift

+14
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,20 @@ public struct ChatResult: Codable, Equatable, Sendable {
213213
case usage
214214
case citations
215215
}
216+
217+
public init(from decoder: any Decoder) throws {
218+
let container = try decoder.container(keyedBy: CodingKeys.self)
219+
let parsingOptions = decoder.userInfo[.parsingOptions] as? ParsingOptions ?? []
220+
self.id = try container.decodeString(forKey: .id, parsingOptions: parsingOptions)
221+
self.object = try container.decodeString(forKey: .object, parsingOptions: parsingOptions)
222+
self.created = try container.decode(Int.self, forKey: .created)
223+
self.model = try container.decodeString(forKey: .model, parsingOptions: parsingOptions)
224+
self.choices = try container.decode([ChatResult.Choice].self, forKey: .choices)
225+
self.serviceTier = try container.decodeIfPresent(String.self, forKey: .serviceTier)
226+
self.systemFingerprint = try container.decodeString(forKey: .systemFingerprint, parsingOptions: parsingOptions)
227+
self.usage = try container.decodeIfPresent(ChatResult.CompletionUsage.self, forKey: .usage)
228+
self.citations = try container.decodeIfPresent([String].self, forKey: .citations)
229+
}
216230
}
217231

218232
extension ChatQuery.ChatCompletionMessageParam {

Sources/OpenAI/Public/Models/ChatStreamResult.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ public struct ChatStreamResult: Codable, Equatable, Sendable {
154154
let parsingOptions = decoder.userInfo[.parsingOptions] as? ParsingOptions ?? []
155155

156156
self.id = try container.decodeString(forKey: .id, parsingOptions: parsingOptions)
157-
self.object = try container.decode(String.self, forKey: .object)
157+
self.object = try container.decodeString(forKey: .object, parsingOptions: parsingOptions)
158158
self.created = try container.decode(TimeInterval.self, forKey: .created)
159-
self.model = try container.decode(String.self, forKey: .model)
159+
self.model = try container.decodeString(forKey: .model, parsingOptions: parsingOptions)
160160
self.citations = try container.decodeIfPresent([String].self, forKey: .citations)
161161
self.choices = try container.decode([ChatStreamResult.Choice].self, forKey: .choices)
162162
self.systemFingerprint = try container.decodeString(forKey: .systemFingerprint, parsingOptions: parsingOptions)

0 commit comments

Comments
 (0)