Skip to content

Commit a78d9d7

Browse files
committed
Add ChatGPTConfiguration
1 parent 1708830 commit a78d9d7

File tree

7 files changed

+199
-73
lines changed

7 files changed

+199
-73
lines changed

Core/Sources/ChatPlugin/AskChatGPT.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ public func askChatGPT(
77
question: String,
88
temperature: Double? = nil
99
) async throws -> String? {
10-
let service = ChatGPTService(systemPrompt: systemPrompt, temperature: temperature)
10+
let service = ChatGPTService(
11+
systemPrompt: systemPrompt,
12+
configuration: OverridingUserPreferenceChatGPTConfiguration(
13+
overriding: .init(temperature: temperature)
14+
)
15+
)
1116
return try await service.sendAndWait(content: question)
1217
}
18+

Core/Sources/HostApp/AccountSettings/AzureView.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ struct AzureView: View {
4444
isTesting = true
4545
defer { isTesting = false }
4646
do {
47-
let reply = try await ChatGPTService(designatedProvider: .azureOpenAI)
47+
let reply =
48+
try await ChatGPTService(
49+
configuration: OverridingUserPreferenceChatGPTConfiguration(
50+
overriding: .init(featureProvider: .azureOpenAI)
51+
)
52+
)
4853
.sendAndWait(content: "Hello", summary: nil)
4954
toast(Text("ChatGPT replied: \(reply ?? "N/A")"), .info)
5055
} catch {

Core/Sources/HostApp/AccountSettings/OpenAIView.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct OpenAIView: View {
1212
@AppStorage(\.openAIBaseURL) var openAIBaseURL: String
1313
init() {}
1414
}
15-
15+
1616
let apiKeyURL = URL(string: "https://platform.openai.com/account/api-keys")!
1717
let modelURL = URL(
1818
string: "https://platform.openai.com/docs/models/model-endpoint-compatibility"
@@ -49,7 +49,12 @@ struct OpenAIView: View {
4949
isTesting = true
5050
defer { isTesting = false }
5151
do {
52-
let reply = try await ChatGPTService(designatedProvider: .openAI)
52+
let reply =
53+
try await ChatGPTService(
54+
configuration: OverridingUserPreferenceChatGPTConfiguration(
55+
overriding: .init(featureProvider: .openAI)
56+
)
57+
)
5358
.sendAndWait(content: "Hello", summary: nil)
5459
toast(Text("ChatGPT replied: \(reply ?? "N/A")"), .info)
5560
} catch {

Core/Sources/PromptToCodeService/OpenAIPromptToCodeAPI.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,12 @@ final class OpenAIPromptToCodeAPI: PromptToCodeAPI {
147147
###
148148
"""
149149

150-
let chatGPTService = ChatGPTService(systemPrompt: systemPrompt, temperature: 0.3)
150+
let chatGPTService = ChatGPTService(
151+
systemPrompt: systemPrompt,
152+
configuration: OverridingUserPreferenceChatGPTConfiguration(
153+
overriding: .init(temperature: 0)
154+
)
155+
)
151156
service = chatGPTService
152157
if let firstMessage {
153158
await chatGPTService.mutateHistory { history in

Tool/Sources/LangChain/ChatModel/OpenAIChat.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ public struct OpenAIChat: ChatModel {
1818
stops: [String],
1919
callbackManagers: [ChainCallbackManager]
2020
) async throws -> String {
21-
let service = ChatGPTService(temperature: temperature, stop: stops)
21+
let service = ChatGPTService(configuration: OverridingUserPreferenceChatGPTConfiguration(
22+
overriding: .init(
23+
temperature: temperature,
24+
stop: stops
25+
)
26+
))
2227
await service.mutateHistory { history in
2328
for message in prompt {
2429
let role: OpenAIService.ChatMessage.Role = {
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import Foundation
2+
import Preferences
3+
4+
public protocol ChatGPTConfiguration {
5+
var featureProvider: ChatFeatureProvider { get }
6+
var temperature: Double { get }
7+
var model: String { get }
8+
var endpoint: String { get }
9+
var apiKey: String { get }
10+
var stop: [String] { get }
11+
var maxToken: Int { get }
12+
}
13+
14+
extension ChatGPTConfiguration {
15+
func endpoint(for provider: ChatFeatureProvider) -> String {
16+
switch provider {
17+
case .openAI:
18+
let baseURL = UserDefaults.shared.value(for: \.openAIBaseURL)
19+
if baseURL.isEmpty { return "https://api.openai.com/v1/chat/completions" }
20+
return "\(baseURL)/v1/chat/completions"
21+
case .azureOpenAI:
22+
let baseURL = UserDefaults.shared.value(for: \.azureOpenAIBaseURL)
23+
let deployment = UserDefaults.shared.value(for: \.azureChatGPTDeployment)
24+
let version = "2023-05-15"
25+
if baseURL.isEmpty { return "" }
26+
return "\(baseURL)/openai/deployments/\(deployment)/chat/completions?api-version=\(version)"
27+
}
28+
}
29+
30+
func apiKey(for provider: ChatFeatureProvider) -> String {
31+
switch provider {
32+
case .openAI:
33+
return UserDefaults.shared.value(for: \.openAIAPIKey)
34+
case .azureOpenAI:
35+
return UserDefaults.shared.value(for: \.azureOpenAIAPIKey)
36+
}
37+
}
38+
}
39+
40+
public struct UserPreferenceChatGPTConfiguration: ChatGPTConfiguration {
41+
public var featureProvider: ChatFeatureProvider {
42+
UserDefaults.shared.value(for: \.chatFeatureProvider)
43+
}
44+
45+
public var temperature: Double {
46+
min(max(0, UserDefaults.shared.value(for: \.chatGPTTemperature)), 2)
47+
}
48+
49+
public var model: String {
50+
let value = UserDefaults.shared.value(for: \.chatGPTModel)
51+
if value.isEmpty { return "gpt-3.5-turbo" }
52+
return value
53+
}
54+
55+
public var endpoint: String {
56+
endpoint(for: featureProvider)
57+
}
58+
59+
public var apiKey: String {
60+
apiKey(for: featureProvider)
61+
}
62+
63+
public var maxToken: Int {
64+
UserDefaults.shared.value(for: \.chatGPTMaxToken)
65+
}
66+
67+
public var stop: [String] {
68+
[""]
69+
}
70+
71+
public init() {}
72+
}
73+
74+
public class OverridingUserPreferenceChatGPTConfiguration: ChatGPTConfiguration {
75+
public struct Overriding {
76+
var featureProvider: ChatFeatureProvider?
77+
var temperature: Double?
78+
var model: String?
79+
var endPoint: String?
80+
var apiKey: String?
81+
var stop: [String]?
82+
var maxToken: Int?
83+
84+
public init(
85+
temperature: Double? = nil,
86+
model: String? = nil,
87+
stop: [String]? = nil,
88+
maxToken: Int? = nil,
89+
featureProvider: ChatFeatureProvider? = nil,
90+
endPoint: String? = nil,
91+
apiKey: String? = nil
92+
) {
93+
self.temperature = temperature
94+
self.model = model
95+
self.stop = stop
96+
self.maxToken = maxToken
97+
self.featureProvider = featureProvider
98+
self.endPoint = endPoint
99+
self.apiKey = apiKey
100+
}
101+
}
102+
103+
private let userPreference = UserPreferenceChatGPTConfiguration()
104+
public var overriding = Overriding()
105+
106+
public init(overriding: Overriding = .init()) {
107+
self.overriding = overriding
108+
}
109+
110+
public var featureProvider: ChatFeatureProvider {
111+
overriding.featureProvider ?? userPreference.featureProvider
112+
}
113+
114+
public var temperature: Double {
115+
overriding.temperature ?? userPreference.temperature
116+
}
117+
118+
public var model: String {
119+
overriding.model ?? userPreference.model
120+
}
121+
122+
public var endpoint: String {
123+
overriding.endPoint
124+
?? overriding.featureProvider.map(endpoint(for:))
125+
?? userPreference.endpoint
126+
}
127+
128+
public var apiKey: String {
129+
overriding.apiKey
130+
?? overriding.featureProvider.map(apiKey(for:))
131+
?? userPreference.apiKey
132+
}
133+
134+
public var stop: [String] {
135+
overriding.stop ?? userPreference.stop
136+
}
137+
138+
public var maxToken: Int {
139+
overriding.maxToken ?? userPreference.maxToken
140+
}
141+
}
142+

Tool/Sources/OpenAIService/ChatGPTService.swift

Lines changed: 25 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -56,77 +56,32 @@ public struct ChatGPTError: Error, Codable, LocalizedError {
5656

5757
public actor ChatGPTService: ChatGPTServiceType {
5858
public var systemPrompt: String
59-
60-
public var defaultTemperature: Double {
61-
min(max(0, UserDefaults.shared.value(for: \.chatGPTTemperature)), 2)
62-
}
63-
64-
var temperature: Double?
65-
66-
public var model: String {
67-
let value = UserDefaults.shared.value(for: \.chatGPTModel)
68-
if value.isEmpty { return "gpt-3.5-turbo" }
69-
return value
70-
}
71-
72-
var designatedProvider: ChatFeatureProvider?
73-
74-
public var endpoint: String {
75-
switch designatedProvider ?? UserDefaults.shared.value(for: \.chatFeatureProvider) {
76-
case .openAI:
77-
let baseURL = UserDefaults.shared.value(for: \.openAIBaseURL)
78-
if baseURL.isEmpty { return "https://api.openai.com/v1/chat/completions" }
79-
return "\(baseURL)/v1/chat/completions"
80-
case .azureOpenAI:
81-
let baseURL = UserDefaults.shared.value(for: \.azureOpenAIBaseURL)
82-
let deployment = UserDefaults.shared.value(for: \.azureChatGPTDeployment)
83-
let version = "2023-05-15"
84-
if baseURL.isEmpty { return "" }
85-
return "\(baseURL)/openai/deployments/\(deployment)/chat/completions?api-version=\(version)"
86-
}
87-
}
88-
89-
public var apiKey: String {
90-
switch designatedProvider ?? UserDefaults.shared.value(for: \.chatFeatureProvider) {
91-
case .openAI:
92-
return UserDefaults.shared.value(for: \.openAIAPIKey)
93-
case .azureOpenAI:
94-
return UserDefaults.shared.value(for: \.azureOpenAIAPIKey)
95-
}
96-
}
97-
98-
public var maxToken: Int {
99-
UserDefaults.shared.value(for: \.chatGPTMaxToken)
100-
}
101-
10259
public var history: [ChatMessage] = [] {
10360
didSet { objectWillChange.send() }
10461
}
10562

106-
var stop: [String]
63+
public var configuration: ChatGPTConfiguration
64+
10765
var uuidGenerator: () -> String = { UUID().uuidString }
10866
var cancelTask: Cancellable?
10967
var buildCompletionStreamAPI: CompletionStreamAPIBuilder = OpenAICompletionStreamAPI.init
11068
var buildCompletionAPI: CompletionAPIBuilder = OpenAICompletionAPI.init
11169

11270
public init(
11371
systemPrompt: String = "",
114-
temperature: Double? = nil,
115-
stop: [String] = [],
116-
designatedProvider: ChatFeatureProvider? = nil
72+
configuration: ChatGPTConfiguration = UserPreferenceChatGPTConfiguration()
11773
) {
11874
self.systemPrompt = systemPrompt
119-
self.temperature = temperature
120-
self.stop = stop
121-
self.designatedProvider = designatedProvider
75+
self.configuration = configuration
12276
}
12377

12478
public func send(
12579
content: String,
12680
summary: String? = nil
12781
) async throws -> AsyncThrowingStream<String, Error> {
128-
guard let url = URL(string: endpoint) else { throw ChatGPTServiceError.endpointIncorrect }
129-
82+
guard let url = URL(string: configuration.endpoint)
83+
else { throw ChatGPTServiceError.endpointIncorrect }
84+
13085
if !content.isEmpty || summary != nil {
13186
let newMessage = ChatMessage(
13287
id: uuidGenerator(),
@@ -140,17 +95,20 @@ public actor ChatGPTService: ChatGPTServiceType {
14095
let (messages, remainingTokens) = combineHistoryWithSystemPrompt()
14196

14297
let requestBody = CompletionRequestBody(
143-
model: model,
98+
model: configuration.model,
14499
messages: messages,
145-
temperature: temperature ?? defaultTemperature,
100+
temperature: configuration.temperature,
146101
stream: true,
147-
stop: stop.isEmpty ? nil : stop,
148-
max_tokens: maxTokenForReply(model: model, remainingTokens: remainingTokens)
102+
stop: configuration.stop.isEmpty ? nil : configuration.stop,
103+
max_tokens: maxTokenForReply(
104+
model: configuration.model,
105+
remainingTokens: remainingTokens
106+
)
149107
)
150108

151109
let api = buildCompletionStreamAPI(
152-
apiKey,
153-
designatedProvider ?? UserDefaults.shared.value(for: \.chatFeatureProvider),
110+
configuration.apiKey,
111+
configuration.featureProvider,
154112
url,
155113
requestBody
156114
)
@@ -181,7 +139,7 @@ public actor ChatGPTService: ChatGPTServiceType {
181139
if let content = delta.content {
182140
continuation.yield(content)
183141
}
184-
142+
185143
try await Task.sleep(nanoseconds: 3_500_000)
186144
}
187145

@@ -205,8 +163,8 @@ public actor ChatGPTService: ChatGPTServiceType {
205163
content: String,
206164
summary: String? = nil
207165
) async throws -> String? {
208-
guard let url = URL(string: endpoint) else { throw ChatGPTServiceError.endpointIncorrect }
209-
166+
guard let url = URL(string: configuration.endpoint) else { throw ChatGPTServiceError.endpointIncorrect }
167+
210168
if !content.isEmpty || summary != nil {
211169
let newMessage = ChatMessage(
212170
id: uuidGenerator(),
@@ -220,17 +178,17 @@ public actor ChatGPTService: ChatGPTServiceType {
220178
let (messages, remainingTokens) = combineHistoryWithSystemPrompt()
221179

222180
let requestBody = CompletionRequestBody(
223-
model: model,
181+
model: configuration.model,
224182
messages: messages,
225-
temperature: temperature ?? defaultTemperature,
183+
temperature: configuration.temperature,
226184
stream: true,
227-
stop: stop.isEmpty ? nil : stop,
228-
max_tokens: maxTokenForReply(model: model, remainingTokens: remainingTokens)
185+
stop: configuration.stop.isEmpty ? nil : configuration.stop,
186+
max_tokens: maxTokenForReply(model: configuration.model, remainingTokens: remainingTokens)
229187
)
230188

231189
let api = buildCompletionAPI(
232-
apiKey,
233-
designatedProvider ?? UserDefaults.shared.value(for: \.chatFeatureProvider),
190+
configuration.apiKey,
191+
configuration.featureProvider,
234192
url,
235193
requestBody
236194
)

0 commit comments

Comments
 (0)