@@ -37,6 +37,7 @@ public struct CoreAILanguageModel: LanguageModel {
3737 private let vocabSize : Int ?
3838 private let supportsToolCalling : Bool
3939 private let supportsReasoning : Bool
40+ private let additionalEosTokenIds : [ Int32 ]
4041
4142 // MARK: - Protocol Requirements
4243
@@ -56,7 +57,8 @@ public struct CoreAILanguageModel: LanguageModel {
5657 tokenizer: tokenizer,
5758 modelIdentifier: modelIdentifier,
5859 samplingConfig: samplingConfig,
59- vocabSize: vocabSize
60+ vocabSize: vocabSize,
61+ additionalEosTokenIds: additionalEosTokenIds
6062 )
6163 }
6264
@@ -97,13 +99,15 @@ public struct CoreAILanguageModel: LanguageModel {
9799 tokenizer: any Tokenizer ,
98100 modelIdentifier: String = " coreai-model " ,
99101 samplingConfig: SamplingConfiguration = . greedy,
100- vocabSize: Int ? = nil
102+ vocabSize: Int ? = nil ,
103+ additionalEosTokenIds: [ Int32 ] = [ ]
101104 ) {
102105 self . engine = engine
103106 self . tokenizer = tokenizer
104107 self . modelIdentifier = modelIdentifier
105108 self . samplingConfig = samplingConfig
106109 self . vocabSize = vocabSize
110+ self . additionalEosTokenIds = additionalEosTokenIds
107111 self . supportsToolCalling = CoreAIExecutor . detectToolCallMarkers ( using: tokenizer) != nil
108112 self . supportsReasoning =
109113 tokenizer. convertTokenToId ( " <think> " ) != nil
@@ -121,6 +125,7 @@ public struct CoreAILanguageModel: LanguageModel {
121125 fileprivate let modelIdentifier : String
122126 fileprivate let samplingConfig : SamplingConfiguration
123127 fileprivate let vocabSize : Int ?
128+ fileprivate let additionalEosTokenIds : [ Int32 ]
124129
125130 public static func == ( lhs: Configuration , rhs: Configuration ) -> Bool {
126131 lhs. modelIdentifier == rhs. modelIdentifier
@@ -140,6 +145,9 @@ public struct CoreAILanguageModel: LanguageModel {
140145 private let modelIdentifier : String
141146 private let samplingConfig : SamplingConfiguration
142147 private let vocabSize : Int ?
148+ /// All EOS-like token IDs: the main `eosTokenId` plus any additional
149+ /// stop tokens from tokenizer_config.json (e.g. Gemma's `<end_of_turn>`).
150+ private let eosTokenIds : Set < Int32 >
143151 /// Open / close marker pair the model uses for chain-of-thought
144152 /// blocks, discovered from the tokenizer's known token ids at init
145153 /// (see `detectThinkingMarkers`). For models that don't emit
@@ -162,6 +170,14 @@ public struct CoreAILanguageModel: LanguageModel {
162170 self . vocabSize = configuration. vocabSize
163171 self . thinkingMarkers = Self . detectThinkingMarkers ( using: configuration. tokenizer)
164172 self . toolCallMarkers = Self . detectToolCallMarkers ( using: configuration. tokenizer)
173+
174+ // Build the full set of EOS-like token IDs
175+ var eos = Set < Int32 > ( )
176+ if let id = configuration. tokenizer. eosTokenId {
177+ eos. insert ( Int32 ( id) )
178+ }
179+ eos. formUnion ( configuration. additionalEosTokenIds)
180+ self . eosTokenIds = eos
165181 }
166182
167183 /// Probes the tokenizer for known reasoning marker pairs. Each
@@ -328,7 +344,8 @@ public struct CoreAILanguageModel: LanguageModel {
328344 inferenceOptions: InferenceOptions ( maxTokens: maxTokens)
329345 )
330346
331- let eosTokenId = tokenizer. eosTokenId
347+ // Use pre-computed set of all EOS-like tokens (main + additional)
348+ let eosTokens = eosTokenIds
332349 // Incremental-decode buffer. After a clean emit, one token is
333350 // retained as context for the next step (see below). During a
334351 // multi-byte sequence that hasn't decoded cleanly yet, multiple
@@ -359,7 +376,7 @@ public struct CoreAILanguageModel: LanguageModel {
359376
360377 for try await output in tokenStream {
361378 let token = output. tokenId
362- if let eos = eosTokenId , Int ( token) == eos {
379+ if eosTokens . contains ( token) {
363380 tokenStream. setStopReason ( . eos)
364381 break
365382 }
@@ -523,7 +540,10 @@ public struct CoreAILanguageModel: LanguageModel {
523540 }
524541
525542 let strategy = ConstrainedDecodingStrategy ( jsonSchema: jsonSchema, vocabSize: vocabSize)
526- let stopSequences = StopSequences ( for: tokenizer)
543+ let stopSequences = StopSequences (
544+ for: tokenizer,
545+ additionalEosTokenIds: Array ( eosTokenIds)
546+ )
527547
528548 let stream = try await strategy. decode (
529549 from: . tokens( promptTokens) ,
0 commit comments