Skip to content

Commit 047861d

Browse files
authored
DeepSeek R1 usage improvements (#101)
1 parent f68d564 commit 047861d

14 files changed

+432
-48
lines changed

README.md

+306-19
Large diffs are not rendered by default.

Sources/AIProxy/DeepSeek/DeepSeekDirectService.swift

+10-4
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,17 @@ open class DeepSeekDirectService: DeepSeekService, DirectService {
2323
/// - Parameters:
2424
/// - body: The request body to send to DeepSeek. See this reference:
2525
/// https://api-docs.deepseek.com/api/create-chat-completion
26+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
2627
/// - Returns: The chat response. See this reference:
2728
/// https://api-docs.deepseek.com/api/create-chat-completion#responses
2829
public func chatCompletionRequest(
29-
body: DeepSeekChatCompletionRequestBody
30+
body: DeepSeekChatCompletionRequestBody,
31+
secondsToWait: Int
3032
) async throws -> DeepSeekChatCompletionResponseBody {
3133
var body = body
3234
body.stream = false
3335
body.streamOptions = nil
34-
let request = try AIProxyURLRequest.createDirect(
36+
var request = try AIProxyURLRequest.createDirect(
3537
baseURL: "https://api.deepseek.com",
3638
path: "/chat/completions",
3739
body: try body.serialize(),
@@ -42,6 +44,7 @@ open class DeepSeekDirectService: DeepSeekService, DirectService {
4244
"Accept": "application/json"
4345
]
4446
)
47+
request.timeoutInterval = TimeInterval(secondsToWait)
4548
return try await self.makeRequestAndDeserializeResponse(request)
4649
}
4750

@@ -50,15 +53,17 @@ open class DeepSeekDirectService: DeepSeekService, DirectService {
5053
/// - Parameters:
5154
/// - body: The request body to send to DeepSeek. See this reference:
5255
/// https://api-docs.deepseek.com/api/create-chat-completion
56+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
5357
/// - Returns: An async sequence of completion chunks. See the 'Streaming' tab here:
5458
/// https://api-docs.deepseek.com/api/create-chat-completion#responses
5559
public func streamingChatCompletionRequest(
56-
body: DeepSeekChatCompletionRequestBody
60+
body: DeepSeekChatCompletionRequestBody,
61+
secondsToWait: Int
5762
) async throws -> AsyncCompactMapSequence<AsyncLineSequence<URLSession.AsyncBytes>, DeepSeekChatCompletionChunk> {
5863
var body = body
5964
body.stream = true
6065
body.streamOptions = .init(includeUsage: true)
61-
let request = try AIProxyURLRequest.createDirect(
66+
var request = try AIProxyURLRequest.createDirect(
6267
baseURL: "https://api.deepseek.com",
6368
path: "/chat/completions",
6469
body: try body.serialize(),
@@ -69,6 +74,7 @@ open class DeepSeekDirectService: DeepSeekService, DirectService {
6974
"Accept": "application/json"
7075
]
7176
)
77+
request.timeoutInterval = TimeInterval(secondsToWait)
7278
return try await self.makeRequestAndDeserializeStreamingChunks(request)
7379
}
7480
}

Sources/AIProxy/DeepSeek/DeepSeekProxiedService.swift

+11-5
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,17 @@ open class DeepSeekProxiedService: DeepSeekService, ProxiedService {
2929
/// - Parameters:
3030
/// - body: The request body to send to DeepSeek, protected through AIProxy. See this reference:
3131
/// https://api-docs.deepseek.com/api/create-chat-completion
32+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
3233
/// - Returns: The chat response. See this reference:
3334
/// https://api-docs.deepseek.com/api/create-chat-completion#responses
3435
public func chatCompletionRequest(
35-
body: DeepSeekChatCompletionRequestBody
36+
body: DeepSeekChatCompletionRequestBody,
37+
secondsToWait: Int
3638
) async throws -> DeepSeekChatCompletionResponseBody {
3739
var body = body
3840
body.stream = false
3941
body.streamOptions = nil
40-
let request = try await AIProxyURLRequest.create(
42+
var request = try await AIProxyURLRequest.create(
4143
partialKey: self.partialKey,
4244
serviceURL: self.serviceURL,
4345
clientID: self.clientID,
@@ -49,23 +51,26 @@ open class DeepSeekProxiedService: DeepSeekService, ProxiedService {
4951
"Accept": "application/json"
5052
]
5153
)
54+
request.timeoutInterval = TimeInterval(secondsToWait)
5255
return try await self.makeRequestAndDeserializeResponse(request)
5356
}
5457

5558
/// Initiates a streaming chat completion request to /chat/completions.
5659
///
5760
/// - Parameters:
58-
/// - body: The request body to send to DeepSeek. See this reference:
61+
/// - body: The request body to send to DeepSeek, protected through AIProxy. See this reference:
5962
/// https://api-docs.deepseek.com/api/create-chat-completion
63+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
6064
/// - Returns: An async sequence of completion chunks. See the 'Streaming' tab here:
6165
/// https://api-docs.deepseek.com/api/create-chat-completion#responses
6266
public func streamingChatCompletionRequest(
63-
body: DeepSeekChatCompletionRequestBody
67+
body: DeepSeekChatCompletionRequestBody,
68+
secondsToWait: Int
6469
) async throws -> AsyncCompactMapSequence<AsyncLineSequence<URLSession.AsyncBytes>, DeepSeekChatCompletionChunk> {
6570
var body = body
6671
body.stream = true
6772
body.streamOptions = .init(includeUsage: true)
68-
let request = try await AIProxyURLRequest.create(
73+
var request = try await AIProxyURLRequest.create(
6974
partialKey: self.partialKey,
7075
serviceURL: self.serviceURL,
7176
clientID: self.clientID,
@@ -77,6 +82,7 @@ open class DeepSeekProxiedService: DeepSeekService, ProxiedService {
7782
"Accept": "application/json"
7883
]
7984
)
85+
request.timeoutInterval = TimeInterval(secondsToWait)
8086
return try await self.makeRequestAndDeserializeStreamingChunks(request)
8187
}
8288
}

Sources/AIProxy/DeepSeek/DeepSeekService.swift

+20-2
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,38 @@ public protocol DeepSeekService {
1414
/// - Parameters:
1515
/// - body: The request body to send to DeepSeek. See this reference:
1616
/// https://api-docs.deepseek.com/api/create-chat-completion
17+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
1718
/// - Returns: The chat response. See this reference:
1819
/// https://api-docs.deepseek.com/api/create-chat-completion#responses
1920
func chatCompletionRequest(
20-
body: DeepSeekChatCompletionRequestBody
21+
body: DeepSeekChatCompletionRequestBody,
22+
secondsToWait: Int
2123
) async throws -> DeepSeekChatCompletionResponseBody
2224

2325
/// Initiates a streaming chat completion request to /chat/completions.
2426
///
2527
/// - Parameters:
2628
/// - body: The request body to send to DeepSeek. See this reference:
2729
/// https://api-docs.deepseek.com/api/create-chat-completion
30+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
2831
/// - Returns: An async sequence of completion chunks. See the 'Streaming' tab here:
2932
/// https://api-docs.deepseek.com/api/create-chat-completion#responses
3033
func streamingChatCompletionRequest(
31-
body: DeepSeekChatCompletionRequestBody
34+
body: DeepSeekChatCompletionRequestBody,
35+
secondsToWait: Int
3236
) async throws -> AsyncCompactMapSequence<AsyncLineSequence<URLSession.AsyncBytes>, DeepSeekChatCompletionChunk>
3337
}
38+
39+
extension DeepSeekService {
40+
public func chatCompletionRequest(
41+
body: DeepSeekChatCompletionRequestBody
42+
) async throws -> DeepSeekChatCompletionResponseBody {
43+
return try await self.chatCompletionRequest(body: body, secondsToWait: 60)
44+
}
45+
46+
public func streamingChatCompletionRequest(
47+
body: DeepSeekChatCompletionRequestBody
48+
) async throws -> AsyncCompactMapSequence<AsyncLineSequence<URLSession.AsyncBytes>, DeepSeekChatCompletionChunk> {
49+
return try await self.streamingChatCompletionRequest(body: body, secondsToWait: 60)
50+
}
51+
}

Sources/AIProxy/FireworksAI/FireworksAIProxiedService.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ open class FireworksAIProxiedService: FireworksAIService, ProxiedService {
3131
/// - body: The request body to send to FireworksAI. See these references:
3232
/// https://fireworks.ai/models/fireworks/deepseek-r1
3333
/// https://api-docs.deepseek.com/api/create-chat-completion
34-
/// - secondsToWait: The number of seconds to wait before timing out
34+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
3535
/// - Returns: The chat response. See this reference:
3636
/// https://api-docs.deepseek.com/api/create-chat-completion#responses
3737
public func deepSeekR1Request(
@@ -63,7 +63,7 @@ open class FireworksAIProxiedService: FireworksAIService, ProxiedService {
6363
/// - body: The request body to send to FireworksAI. See these references:
6464
/// https://fireworks.ai/models/fireworks/deepseek-r1
6565
/// https://api-docs.deepseek.com/api/create-chat-completion
66-
/// - secondsToWait: The number of seconds to wait before timing out
66+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
6767
/// - Returns: An async sequence of completion chunks. See the 'Streaming' tab here:
6868
/// https://api-docs.deepseek.com/api/create-chat-completion#responses
6969
public func streamingDeepSeekR1Request(

Sources/AIProxy/OpenRouter/OpenRouterChatCompletionChunk.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ extension OpenRouterChatCompletionChunk {
3939
extension OpenRouterChatCompletionChunk.Choice {
4040
public struct Delta: Codable {
4141
public let role: String
42-
public let content: String
42+
43+
/// Output content. For reasoning models, these chunks arrive after `reasoning` has finished.
44+
public let content: String?
45+
46+
/// Reasoning content. For reasoning models, these chunks arrive before `content`.
47+
public let reasoning: String?
4348
}
4449
}

Sources/AIProxy/OpenRouter/OpenRouterChatCompletionRequestBody.swift

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ public struct OpenRouterChatCompletionRequestBody: Encodable {
2525
/// Defaults to 0
2626
public let frequencyPenalty: Double?
2727

28+
/// Include reasoning content in the response. Useful to understand how a reasoning model arrives at its response.
29+
public let includeReasoning: Bool?
30+
2831
/// Modify the likelihood of specified tokens appearing in the completion.
2932
/// Accepts an object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token.
3033
public let logitBias: [String: Double]?
@@ -157,6 +160,7 @@ public struct OpenRouterChatCompletionRequestBody: Encodable {
157160

158161
// optional
159162
case frequencyPenalty = "frequency_penalty"
163+
case includeReasoning = "include_reasoning"
160164
case logitBias = "logit_bias"
161165
case logprobs
162166
case maxTokens = "max_tokens"
@@ -192,6 +196,7 @@ public struct OpenRouterChatCompletionRequestBody: Encodable {
192196
public init(
193197
messages: [OpenRouterChatCompletionRequestBody.Message],
194198
frequencyPenalty: Double? = nil,
199+
includeReasoning: Bool? = nil,
195200
logitBias: [String : Double]? = nil,
196201
logprobs: Bool? = nil,
197202
maxTokens: Int? = nil,
@@ -222,6 +227,7 @@ public struct OpenRouterChatCompletionRequestBody: Encodable {
222227
) {
223228
self.messages = messages
224229
self.frequencyPenalty = frequencyPenalty
230+
self.includeReasoning = includeReasoning
225231
self.logitBias = logitBias
226232
self.logprobs = logprobs
227233
self.maxTokens = maxTokens

Sources/AIProxy/OpenRouter/OpenRouterChatCompletionResponseBody.swift

+4
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ extension OpenRouterChatCompletionResponseBody.Choice {
7878
/// The contents of the message.
7979
public let content: String?
8080

81+
/// Reasoning models such as R1 will populate this field with the reasoning used to arrive at `content`
82+
public let reasoning: String?
83+
8184
/// The role of the author of this message.
8285
public let role: String
8386

@@ -86,6 +89,7 @@ extension OpenRouterChatCompletionResponseBody.Choice {
8689

8790
private enum CodingKeys: String, CodingKey {
8891
case content
92+
case reasoning
8993
case role
9094
case toolCalls = "tool_calls"
9195
}

Sources/AIProxy/OpenRouter/OpenRouterDirectService.swift

+10-4
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,18 @@ open class OpenRouterDirectService: OpenRouterService, DirectService {
2121
/// - Parameters:
2222
/// - body: The request body to send to OpenRouter through AIProxy. See this reference:
2323
/// https://openrouter.ai/docs/requests
24+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
2425
///
2526
/// - Returns: The response body from OpenRouter. See this reference:
2627
/// https://openrouter.ai/docs/responses
2728
public func chatCompletionRequest(
28-
body: OpenRouterChatCompletionRequestBody
29+
body: OpenRouterChatCompletionRequestBody,
30+
secondsToWait: Int
2931
) async throws -> OpenRouterChatCompletionResponseBody {
3032
var body = body
3133
body.stream = false
3234
body.streamOptions = nil
33-
let request = try AIProxyURLRequest.createDirect(
35+
var request = try AIProxyURLRequest.createDirect(
3436
baseURL: "https://openrouter.ai",
3537
path: "/api/v1/chat/completions",
3638
body: body.serialize(),
@@ -40,6 +42,7 @@ open class OpenRouterDirectService: OpenRouterService, DirectService {
4042
"Authorization": "Bearer \(self.unprotectedAPIKey)"
4143
]
4244
)
45+
request.timeoutInterval = TimeInterval(secondsToWait)
4346
return try await self.makeRequestAndDeserializeResponse(request)
4447
}
4548

@@ -48,16 +51,18 @@ open class OpenRouterDirectService: OpenRouterService, DirectService {
4851
/// - Parameters:
4952
/// - body: The request body to send to OpenRouter through AIProxy. See this reference:
5053
/// https://openrouter.ai/docs/requests
54+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
5155
///
5256
/// - Returns: The response body from OpenRouter. See this reference:
5357
/// https://openrouter.ai/docs/responses
5458
public func streamingChatCompletionRequest(
55-
body: OpenRouterChatCompletionRequestBody
59+
body: OpenRouterChatCompletionRequestBody,
60+
secondsToWait: Int
5661
) async throws -> AsyncCompactMapSequence<AsyncLineSequence<URLSession.AsyncBytes>, OpenRouterChatCompletionChunk> {
5762
var body = body
5863
body.stream = true
5964
body.streamOptions = .init(includeUsage: true)
60-
let request = try AIProxyURLRequest.createDirect(
65+
var request = try AIProxyURLRequest.createDirect(
6166
baseURL: "https://openrouter.ai",
6267
path: "/api/v1/chat/completions",
6368
body: try body.serialize(),
@@ -67,6 +72,7 @@ open class OpenRouterDirectService: OpenRouterService, DirectService {
6772
"Authorization": "Bearer \(self.unprotectedAPIKey)"
6873
]
6974
)
75+
request.timeoutInterval = TimeInterval(secondsToWait)
7076
return try await self.makeRequestAndDeserializeStreamingChunks(request)
7177
}
7278
}

Sources/AIProxy/OpenRouter/OpenRouterProxiedService.swift

+10-4
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,18 @@ open class OpenRouterProxiedService: OpenRouterService, ProxiedService {
2929
/// - Parameters:
3030
/// - body: The request body to send to OpenRouter through AIProxy. See this reference:
3131
/// https://openrouter.ai/docs/requests
32+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
3233
///
3334
/// - Returns: The response body from OpenRouter. See this reference:
3435
/// https://openrouter.ai/docs/responses
3536
public func chatCompletionRequest(
36-
body: OpenRouterChatCompletionRequestBody
37+
body: OpenRouterChatCompletionRequestBody,
38+
secondsToWait: Int
3739
) async throws -> OpenRouterChatCompletionResponseBody {
3840
var body = body
3941
body.stream = false
4042
body.streamOptions = nil
41-
let request = try await AIProxyURLRequest.create(
43+
var request = try await AIProxyURLRequest.create(
4244
partialKey: self.partialKey,
4345
serviceURL: self.serviceURL,
4446
clientID: self.clientID,
@@ -47,6 +49,7 @@ open class OpenRouterProxiedService: OpenRouterService, ProxiedService {
4749
verb: .post,
4850
contentType: "application/json"
4951
)
52+
request.timeoutInterval = TimeInterval(secondsToWait)
5053
return try await self.makeRequestAndDeserializeResponse(request)
5154
}
5255

@@ -55,16 +58,18 @@ open class OpenRouterProxiedService: OpenRouterService, ProxiedService {
5558
/// - Parameters:
5659
/// - body: The request body to send to OpenRouter through AIProxy. See this reference:
5760
/// https://openrouter.ai/docs/requests
61+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
5862
///
5963
/// - Returns: The response body from OpenRouter. See this reference:
6064
/// https://openrouter.ai/docs/responses
6165
public func streamingChatCompletionRequest(
62-
body: OpenRouterChatCompletionRequestBody
66+
body: OpenRouterChatCompletionRequestBody,
67+
secondsToWait: Int
6368
) async throws -> AsyncCompactMapSequence<AsyncLineSequence<URLSession.AsyncBytes>, OpenRouterChatCompletionChunk> {
6469
var body = body
6570
body.stream = true
6671
body.streamOptions = .init(includeUsage: true)
67-
let request = try await AIProxyURLRequest.create(
72+
var request = try await AIProxyURLRequest.create(
6873
partialKey: self.partialKey,
6974
serviceURL: self.serviceURL,
7075
clientID: self.clientID,
@@ -73,6 +78,7 @@ open class OpenRouterProxiedService: OpenRouterService, ProxiedService {
7378
verb: .post,
7479
contentType: "application/json"
7580
)
81+
request.timeoutInterval = TimeInterval(secondsToWait)
7682
return try await self.makeRequestAndDeserializeStreamingChunks(request)
7783
}
7884
}

Sources/AIProxy/OpenRouter/OpenRouterService.swift

+20-2
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,40 @@ public protocol OpenRouterService {
1414
/// - Parameters:
1515
/// - body: The request body to send to OpenRouter through AIProxy. See this reference:
1616
/// https://openrouter.ai/docs/requests
17+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
1718
///
1819
/// - Returns: The response body from OpenRouter. See this reference:
1920
/// https://openrouter.ai/docs/responses
2021
func chatCompletionRequest(
21-
body: OpenRouterChatCompletionRequestBody
22+
body: OpenRouterChatCompletionRequestBody,
23+
secondsToWait: Int
2224
) async throws -> OpenRouterChatCompletionResponseBody
2325

2426
/// Initiates a streaming chat completion request to /api/v1/chat/completions.
2527
///
2628
/// - Parameters:
2729
/// - body: The request body to send to OpenRouter through AIProxy. See this reference:
2830
/// https://openrouter.ai/docs/requests
31+
/// - secondsToWait: The amount of time to wait before `URLError.timedOut` is raised
2932
///
3033
/// - Returns: The response body from OpenRouter. See this reference:
3134
/// https://openrouter.ai/docs/responses
3235
func streamingChatCompletionRequest(
33-
body: OpenRouterChatCompletionRequestBody
36+
body: OpenRouterChatCompletionRequestBody,
37+
secondsToWait: Int
3438
) async throws -> AsyncCompactMapSequence<AsyncLineSequence<URLSession.AsyncBytes>, OpenRouterChatCompletionChunk>
3539
}
40+
41+
extension OpenRouterService {
42+
public func chatCompletionRequest(
43+
body: OpenRouterChatCompletionRequestBody
44+
) async throws -> OpenRouterChatCompletionResponseBody {
45+
return try await self.chatCompletionRequest(body: body, secondsToWait: 60)
46+
}
47+
48+
public func streamingChatCompletionRequest(
49+
body: OpenRouterChatCompletionRequestBody
50+
) async throws -> AsyncCompactMapSequence<AsyncLineSequence<URLSession.AsyncBytes>, OpenRouterChatCompletionChunk> {
51+
return try await self.streamingChatCompletionRequest(body: body, secondsToWait: 60)
52+
}
53+
}

0 commit comments

Comments
 (0)