-
Notifications
You must be signed in to change notification settings - Fork 100
Expand file tree
/
Copy pathToolCallFormat.swift
More file actions
170 lines (143 loc) · 6 KB
/
ToolCallFormat.swift
File metadata and controls
170 lines (143 loc) · 6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright © 2025 Apple Inc.
import Foundation
// MARK: - ToolCallParser Protocol
/// Protocol for parsing tool call content from model output.
///
/// Different models use different formats for tool calls. This protocol provides
/// a common interface for parsing tool calls from model output text.
///
/// Reference: https://github.com/ml-explore/mlx-lm/tree/main/mlx_lm/tool_parsers
public protocol ToolCallParser: Sendable {
/// The start tag that indicates a tool call is beginning.
/// Returns `nil` for inline formats that don't use wrapper tags.
var startTag: String? { get }
/// The end tag that indicates a tool call has ended.
/// Returns `nil` for inline formats that don't use wrapper tags.
var endTag: String? { get }
/// Parse the content into a `ToolCall`.
/// - Parameters:
/// - content: The text content to parse (may include tags)
/// - tools: Optional tool schemas for type-aware parsing
/// - Returns: A `ToolCall` if parsing succeeds, `nil` otherwise
func parse(content: String, tools: [[String: any Sendable]]?) -> ToolCall?
/// Parse remaining buffered content at end-of-sequence.
///
/// Called when generation ends to extract any tool calls still in the buffer.
/// The default implementation splits on `startTag` (if present) and parses
/// each segment individually.
func parseEOS(_ toolCallBuffer: String, tools: [[String: any Sendable]]?) -> [ToolCall]
}
extension ToolCallParser {
public func parseEOS(_ toolCallBuffer: String, tools: [[String: any Sendable]]?) -> [ToolCall] {
if let startTag {
return
toolCallBuffer
.components(separatedBy: startTag)
.filter { !$0.isEmpty }
.compactMap { parse(content: $0, tools: tools) }
} else {
guard let toolCall = parse(content: toolCallBuffer, tools: tools) else {
return []
}
return [toolCall]
}
}
}
// MARK: - ToolCallFormat Enum
/// Supported tool call formats for different language models.
///
/// This enum defines the various tool call formats used by different LLM families.
/// Each format has its own syntax for encoding function names and arguments.
///
/// The raw string values can be used for JSON serialization or CLI parameters.
///
/// Reference: https://github.com/ml-explore/mlx-lm/tree/main/mlx_lm/tool_parsers
public enum ToolCallFormat: String, Sendable, Codable, CaseIterable {
/// Default JSON format used by Llama, Qwen, and most models.
/// Example: `<tool_call>{"name": "func", "arguments": {...}}</tool_call>`
case json
/// LFM2/LFM2.5 Pythonic format with model-specific tags.
/// Example: `<|tool_call_start|>[func(arg='value')]<|tool_call_end|>`
case lfm2
/// XML function format used by Nemotron, Qwen3 Coder, Qwen3.5, and similar models.
/// Example: `<tool_call><function=name><parameter=key>value</parameter></function></tool_call>`
case xmlFunction = "xml_function"
/// GLM4 format with arg_key/arg_value tags.
/// Example: `func<arg_key>k</arg_key><arg_value>v</arg_value>`
case glm4
/// Gemma function call format.
/// Example: `call:name{key:value,k:<escape>str<escape>}`
case gemma
/// Kimi K2 format with functions prefix.
/// Example: `functions.name:0<|tool_call_argument_begin|>{"key": "value"}`
case kimiK2 = "kimi_k2"
/// MiniMax M2 format with invoke/parameter tags.
/// Example: `<invoke name="f"><parameter name="k">v</parameter></invoke>`
case minimaxM2 = "minimax_m2"
/// Mistral V11+ format with [TOOL_CALLS] and [ARGS] delimiters.
/// Example: `[TOOL_CALLS]get_weather [ARGS]{"location": "Tokyo"}`
case mistral
// MARK: - Factory Methods
/// Create the appropriate parser for this format.
/// - Returns: A parser instance configured for this format
public func createParser() -> any ToolCallParser {
switch self {
case .json:
return JSONToolCallParser(startTag: "<tool_call>", endTag: "</tool_call>")
case .lfm2:
return PythonicToolCallParser(
startTag: "<|tool_call_start|>", endTag: "<|tool_call_end|>")
case .xmlFunction:
return XMLFunctionParser(startTag: "<tool_call>", endTag: "</tool_call>")
case .glm4:
return GLM4ToolCallParser()
case .gemma:
return GemmaFunctionParser()
case .kimiK2:
return KimiK2ToolCallParser()
case .minimaxM2:
return MiniMaxM2ToolCallParser()
case .mistral:
return MistralToolCallParser()
}
}
/// Infer the tool call format based on model type from config.json.
///
/// This method maps known model types to their corresponding tool call formats,
/// enabling automatic format detection when loading models.
///
/// - Parameter modelType: The `model_type` value from config.json
/// - Returns: The appropriate `ToolCallFormat`, or `nil` to use the default format
public static func infer(from modelType: String) -> ToolCallFormat? {
let type = modelType.lowercased()
// LFM2 family (lfm2, lfm2_moe, lfm2_5, lfm25, etc.)
if type.hasPrefix("lfm2") {
return .lfm2
}
// GLM4 family (glm4, glm4_moe, glm4_moe_lite, etc.)
if type.hasPrefix("glm4") {
return .glm4
}
// Gemma
if type == "gemma" {
return .gemma
}
// Nemotron family (nemotron_h, etc.)
if type.hasPrefix("nemotron") {
return .xmlFunction
}
// Qwen3.5 family (qwen3_5, qwen3_5_moe, etc.)
if type.hasPrefix("qwen3_5") {
return .xmlFunction
}
// Qwen3-Next family (qwen3_next, etc.)
if type.hasPrefix("qwen3_next") {
return .xmlFunction
}
// Mistral3 family (mistral3, mistral3_text, etc.)
if type.hasPrefix("mistral3") {
return .mistral
}
return nil
}
}