Skip to content

Commit 61ea093

Browse files
committed
fix: Support for providers without native tool calling
1 parent 146d247 commit 61ea093

File tree

6 files changed

+153
-10
lines changed

6 files changed

+153
-10
lines changed

Sidekick/Localizable.xcstrings

+50
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@
5050
}
5151
}
5252
},
53+
"A new endpoint has been selected, which has been identified as capable of native tool calling. Would you like to turn on native tool calling?" : {
54+
"localizations" : {
55+
"zh-Hans" : {
56+
"stringUnit" : {
57+
"state" : "translated",
58+
"value" : "已选择一个新的端点,该端点已被确认支持原生工具调用功能。您是否要启用原生工具调用?"
59+
}
60+
}
61+
}
62+
},
63+
"A new endpoint has been selected, which might not be capable of native tool calling. Would you like to turn off native tool calling?" : {
64+
"localizations" : {
65+
"zh-Hans" : {
66+
"stringUnit" : {
67+
"state" : "translated",
68+
"value" : "已选择新端点,该端点可能不支持原生工具调用功能。是否要关闭原生工具调用?"
69+
}
70+
}
71+
}
72+
},
5373
"A new remote model has been selected, which has been identified as having vision capabilities. Would you like to turn on vision for this model?" : {
5474
"localizations" : {
5575
"zh-Hans" : {
@@ -670,6 +690,16 @@
670690
}
671691
}
672692
},
693+
"Controls whether native tool calling is available for the remote model. Turn it on only when the inference provider supports native tool calling for the selected model." : {
694+
"localizations" : {
695+
"zh-Hans" : {
696+
"stringUnit" : {
697+
"state" : "translated",
698+
"value" : "控制远程模型是否支持原生工具调用功能。仅在推理服务提供商确认所选模型支持原生工具调用时,才开启此选项。"
699+
}
700+
}
701+
}
702+
},
673703
"Controls whether Sidekick launches automatically at login." : {
674704
"localizations" : {
675705
"zh-Hans" : {
@@ -2407,6 +2437,16 @@
24072437
}
24082438
}
24092439
},
2440+
"Native Tool Calling" : {
2441+
"localizations" : {
2442+
"zh-Hans" : {
2443+
"stringUnit" : {
2444+
"state" : "translated",
2445+
"value" : "原生工具调用"
2446+
}
2447+
}
2448+
}
2449+
},
24102450
"Needed to access an API for inference" : {
24112451
"localizations" : {
24122452
"zh-Hans" : {
@@ -2827,6 +2867,16 @@
28272867
}
28282868
}
28292869
},
2870+
"Provider Changed" : {
2871+
"localizations" : {
2872+
"zh-Hans" : {
2873+
"stringUnit" : {
2874+
"state" : "translated",
2875+
"value" : "新端点"
2876+
}
2877+
}
2878+
}
2879+
},
28302880
"Reasoning Process" : {
28312881
"localizations" : {
28322882
"zh-Hans" : {

Sidekick/Logic/Inference/llama.cpp/Types/ChatParameters.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ struct ChatParameters: Codable {
9393
) -> String {
9494
// Declare variable for params
9595
var params: any Codable = self
96-
// If modelType is .worker, encode without tools
97-
if modelType == .worker {
96+
// If modelType is .worker, or if native tools disabled in settings, encode without tools
97+
if modelType == .worker || !InferenceSettings.hasNativeToolCalling {
9898
// Omit 'tools'
9999
struct EncodableWithoutTools: Codable {
100100
let model: String

Sidekick/Logic/Settings/InferenceSettings.swift

+31-1
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ Current date & time: \(Date.now.formatted(date: .long, time: .shortened))
253253
}
254254

255255
/// A `Bool` representing whether the LLM has vision
256-
static var serverModelHasVision: Bool {
256+
public static var serverModelHasVision: Bool {
257257
get {
258258
// Set default
259259
if !UserDefaults.standard.exists(key: "serverModelHasVision") {
@@ -268,7 +268,37 @@ Current date & time: \(Date.now.formatted(date: .long, time: .shortened))
268268
UserDefaults.standard.set(newValue, forKey: "serverModelHasVision")
269269
}
270270
}
271+
272+
/// A `Bool` representing whether the inference provider supports tool calling natively
273+
public static var hasNativeToolCalling: Bool {
274+
get {
275+
// Set default
276+
if !UserDefaults.standard.exists(key: "hasNativeToolCalling") {
277+
// Use default
278+
Self.hasNativeToolCalling = Self.providerSupportsToolCalling() ?? false
279+
}
280+
return UserDefaults.standard.bool(
281+
forKey: "hasNativeToolCalling"
282+
)
283+
}
284+
set {
285+
UserDefaults.standard.set(newValue, forKey: "hasNativeToolCalling")
286+
}
287+
}
271288

289+
/// A function to check if the provider selected supports tool calling
290+
public static func providerSupportsToolCalling() -> Bool? {
291+
// Check inference provider
292+
for provider in Provider.popularProviders {
293+
// If matches, use provider value
294+
if Self.endpoint == provider.endpointUrl.absoluteString {
295+
return provider.supportsToolCalling
296+
}
297+
}
298+
// Default to nil
299+
return nil
300+
}
301+
272302
/// A `String` representing the name of the remote worker model
273303
public static var serverWorkerModelName: String {
274304
get {

Sidekick/Types/Model/Provider.swift

+16-7
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@ public struct Provider: Identifiable {
1616
public var name: String
1717
/// A `URL` for the provider's OpenAI compatible endpoint
1818
public var endpointUrl: URL
19+
/// A `Bool` for whether the provider supports OpenAI style tool calling
20+
public var supportsToolCalling: Bool = false
1921

2022
/// A list of popular providers
2123
public static let popularProviders: [Provider] = [
2224
Provider(
2325
name: "Aliyun Bailian (China)",
24-
endpointUrl: URL(string: "https://dashscope.aliyuncs.com/compatible-mode/v1")!
26+
endpointUrl: URL(string: "https://dashscope.aliyuncs.com/compatible-mode/v1")!,
27+
supportsToolCalling: true
2528
),
2629
Provider(
2730
name: "Anthropic",
28-
endpointUrl: URL(string: "https://api.anthropic.com/v1")!
31+
endpointUrl: URL(string: "https://api.anthropic.com/v1")!,
32+
supportsToolCalling: true
2933
),
3034
Provider(
3135
name: "DeepSeek",
@@ -37,27 +41,32 @@ public struct Provider: Identifiable {
3741
),
3842
Provider(
3943
name: "Groq",
40-
endpointUrl: URL(string: "https://api.groq.com/openai/v1")!
44+
endpointUrl: URL(string: "https://api.groq.com/openai/v1")!,
45+
supportsToolCalling: true
4146
),
4247
Provider(
4348
name: "LM Studio",
44-
endpointUrl: URL(string: "http://localhost:1234/v1")!
49+
endpointUrl: URL(string: "http://localhost:1234/v1")!,
50+
supportsToolCalling: true
4551
),
4652
Provider(
4753
name: "Mistral",
4854
endpointUrl: URL(string: "https://api.mistral.ai/v1")!
4955
),
5056
Provider(
5157
name: "Ollama",
52-
endpointUrl: URL(string: "http://localhost:11434/v1")!
58+
endpointUrl: URL(string: "http://localhost:11434/v1")!,
59+
supportsToolCalling: true
5360
),
5461
Provider(
5562
name: "OpenAI",
56-
endpointUrl: URL(string: "https://api.openai.com/v1")!
63+
endpointUrl: URL(string: "https://api.openai.com/v1")!,
64+
supportsToolCalling: true
5765
),
5866
Provider(
5967
name: "OpenRouter",
60-
endpointUrl: URL(string: "https://openrouter.ai/api/v1")!
68+
endpointUrl: URL(string: "https://openrouter.ai/api/v1")!,
69+
supportsToolCalling: true
6170
)
6271
]
6372
}

Sidekick/Views/Settings/Model/ServerModelSettingsView.swift

+54
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ struct ServerModelSettingsView: View {
1515
@State private var inferenceApiKey: String = InferenceSettings.inferenceApiKey
1616

1717
@AppStorage("remoteModelName") private var serverModelName: String = InferenceSettings.serverModelName
18+
1819
@AppStorage("serverModelHasVision") private var serverModelHasVision: Bool = InferenceSettings.serverModelHasVision
20+
@AppStorage("hasNativeToolCalling") private var hasNativeToolCalling: Bool = InferenceSettings.hasNativeToolCalling
21+
1922
@AppStorage("serverWorkerModelName") private var serverWorkerModelName: String = ""
2023

2124
var popularEndpointsTip: PopularEndpointsTip = .init()
@@ -40,6 +43,7 @@ struct ServerModelSettingsView: View {
4043
modelType: .regular
4144
)
4245
serverModelHasVisionToggle
46+
hasNativeToolCallingToggle
4347
ServerModelNameEditor(
4448
serverModelName: $serverWorkerModelName,
4549
modelType: .worker
@@ -126,6 +130,14 @@ struct ServerModelSettingsView: View {
126130
}
127131
.padding(.top, 10)
128132
}
133+
.onChange(of: self.serverEndpoint) {
134+
// Return if invalid or blank
135+
if !self.endpointUrlIsValid || self.serverEndpoint.isEmpty {
136+
return
137+
}
138+
// Else, run check
139+
self.checkProviderForToolCalling()
140+
}
129141
}
130142

131143
var inferenceApiKeyEditor: some View {
@@ -166,4 +178,46 @@ struct ServerModelSettingsView: View {
166178
}
167179
}
168180

181+
var hasNativeToolCallingToggle: some View {
182+
HStack(alignment: .top) {
183+
VStack(alignment: .leading) {
184+
Text("Native Tool Calling")
185+
.font(.title3)
186+
.bold()
187+
Text("Controls whether native tool calling is available for the remote model. Turn it on only when the inference provider supports native tool calling for the selected model.")
188+
.font(.caption)
189+
}
190+
Spacer()
191+
Toggle(
192+
"",
193+
isOn: $hasNativeToolCalling.animation(.linear)
194+
)
195+
.disabled(serverEndpoint.isEmpty || !endpointUrlIsValid)
196+
}
197+
}
198+
199+
private func checkProviderForToolCalling() {
200+
// Check provider, defaulting to false
201+
let providerSupportsToolCalling = InferenceSettings.providerSupportsToolCalling() ?? false
202+
// If no change, exit
203+
if providerSupportsToolCalling == self.hasNativeToolCalling {
204+
return
205+
}
206+
// Get message
207+
let message: String = providerSupportsToolCalling ? String(localized: "A new endpoint has been selected, which has been identified as capable of native tool calling. Would you like to turn on native tool calling?") : String(
208+
localized: "A new endpoint has been selected, which might not be capable of native tool calling. Would you like to turn off native tool calling?"
209+
)
210+
// Confirm with user
211+
if Dialogs.dichotomy(
212+
title: String(localized: "Provider Changed"),
213+
message: message,
214+
option1: String(localized: "Yes"),
215+
option2: String(localized: "No")
216+
) {
217+
withAnimation(.linear) {
218+
self.hasNativeToolCalling = providerSupportsToolCalling
219+
}
220+
}
221+
}
222+
169223
}

0 commit comments

Comments
 (0)