From e9a611d326eade31d2358f2831f5446906b99f4e Mon Sep 17 00:00:00 2001 From: james-333i Date: Thu, 3 Sep 2026 20:09:58 -0700 Subject: [PATCH 1/2] Drop the duplicated trailing user turn in Foundation Models sessions LanguageModelSession appends the prompt entry to its transcript before invoking the model. The Foundation Models adapter then passes that transcript to FoundationModels.LanguageModelSession and also sends the prompt through respond/streamResponse, which appends it again, so every FM-backed turn reaches the model with the final user message duplicated. The MLX adapter shows the intended contract: it renders the chat from session.transcript alone and treats the prompt parameter as a fallback. FM's API requires passing the prompt separately, so the fix on this adapter is to drop a trailing transcript prompt that matches the outgoing prompt before converting. Verified by decoding the rendered prompt of a custom LanguageModel backend on OS 27, where the doubled turn is directly observable; the same transcript construction applies to SystemLanguageModel on OS 26. Co-Authored-By: Claude Fable 5 --- .../Models/SystemLanguageModel.swift | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Sources/AnyLanguageModel/Models/SystemLanguageModel.swift b/Sources/AnyLanguageModel/Models/SystemLanguageModel.swift index ff2e8f25..334c4f4b 100644 --- a/Sources/AnyLanguageModel/Models/SystemLanguageModel.swift +++ b/Sources/AnyLanguageModel/Models/SystemLanguageModel.swift @@ -78,7 +78,7 @@ let fmSession = FoundationModels.LanguageModelSession( model: systemModel, tools: session.tools.toFoundationModels(), - transcript: session.transcript.toFoundationModels( + transcript: fmTranscriptDroppingDuplicatePrompt(session.transcript, prompt: prompt).toFoundationModels( instructions: session.instructions, toolDefinitions: session.tools .filter(\.includesSchemaInInstructions) @@ -159,7 +159,7 @@ let fmSession = FoundationModels.LanguageModelSession( model: systemModel, tools: session.tools.toFoundationModels(), - transcript: session.transcript.toFoundationModels( + transcript: fmTranscriptDroppingDuplicatePrompt(session.transcript, prompt: prompt).toFoundationModels( instructions: session.instructions, toolDefinitions: session.tools .filter(\.includesSchemaInInstructions) @@ -367,6 +367,22 @@ // MARK: - Helpers + @available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *) + func fmTranscriptDroppingDuplicatePrompt(_ transcript: Transcript, prompt: Prompt) -> Transcript { + guard let lastEntry = transcript.last, case .prompt(let lastPrompt) = lastEntry else { + return transcript + } + let lastText = lastPrompt.segments.compactMap { segment -> String? in + if case .text(let textSegment) = segment { return textSegment.content } + return nil + }.joined() + guard lastText == prompt.description else { + return transcript + } + return Transcript(entries: transcript.dropLast()) + } + + @available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *) extension Prompt { fileprivate func toFoundationModels() -> FoundationModels.Prompt { From 6b968f795433a04d5d13c1eddaa4bfecc87cd047 Mon Sep 17 00:00:00 2001 From: mattt Date: Fri, 4 Sep 2026 05:23:03 -0700 Subject: [PATCH 2/2] Fix formatting for swift-format lint --- Sources/AnyLanguageModel/Models/SystemLanguageModel.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/AnyLanguageModel/Models/SystemLanguageModel.swift b/Sources/AnyLanguageModel/Models/SystemLanguageModel.swift index 334c4f4b..10cf4b30 100644 --- a/Sources/AnyLanguageModel/Models/SystemLanguageModel.swift +++ b/Sources/AnyLanguageModel/Models/SystemLanguageModel.swift @@ -382,7 +382,6 @@ return Transcript(entries: transcript.dropLast()) } - @available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *) extension Prompt { fileprivate func toFoundationModels() -> FoundationModels.Prompt {