Skip to content

Commit c2ce310

Browse files
authored
Pass entire transcript to Foundation Models session (#61)
1 parent 8a15c4d commit c2ce310

File tree

2 files changed

+121
-3
lines changed

2 files changed

+121
-3
lines changed

Sources/AnyLanguageModel/Models/SystemLanguageModel.swift

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
let fmSession = FoundationModels.LanguageModelSession(
7979
model: systemModel,
8080
tools: session.tools.toFoundationModels(),
81-
instructions: session.instructions?.toFoundationModels()
81+
transcript: session.transcript.toFoundationModels(instructions: session.instructions)
8282
)
8383

8484
let fmResponse = try await fmSession.respond(to: fmPrompt, options: fmOptions)
@@ -115,7 +115,7 @@
115115
let fmSession = FoundationModels.LanguageModelSession(
116116
model: systemModel,
117117
tools: session.tools.toFoundationModels(),
118-
instructions: session.instructions?.toFoundationModels()
118+
transcript: session.transcript.toFoundationModels(instructions: session.instructions)
119119
)
120120

121121
let stream = AsyncThrowingStream<LanguageModelSession.ResponseStream<Content>.Snapshot, any Error> {
@@ -475,4 +475,121 @@
475475
nil
476476
}
477477
}
478+
479+
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
480+
extension Transcript {
481+
fileprivate func toFoundationModels(instructions: AnyLanguageModel.Instructions?) -> FoundationModels.Transcript
482+
{
483+
var fmEntries: [FoundationModels.Transcript.Entry] = []
484+
485+
// Add instructions entry if provided and not already in transcript
486+
if let instructions = instructions {
487+
let hasInstructions =
488+
self.first.map { entry in
489+
if case .instructions = entry { return true } else { return false }
490+
} ?? false
491+
492+
if !hasInstructions {
493+
let fmInstructions = FoundationModels.Transcript.Instructions(
494+
segments: [.text(.init(content: instructions.description))],
495+
toolDefinitions: []
496+
)
497+
fmEntries.append(.instructions(fmInstructions))
498+
}
499+
}
500+
501+
// Convert each entry
502+
for entry in self {
503+
switch entry {
504+
case .instructions(let instr):
505+
let fmSegments = instr.segments.toFoundationModels()
506+
let fmToolDefinitions = instr.toolDefinitions.toFoundationModels()
507+
let fmInstructions = FoundationModels.Transcript.Instructions(
508+
segments: fmSegments,
509+
toolDefinitions: fmToolDefinitions
510+
)
511+
fmEntries.append(.instructions(fmInstructions))
512+
513+
case .prompt(let prompt):
514+
let fmSegments = prompt.segments.toFoundationModels()
515+
let fmPrompt = FoundationModels.Transcript.Prompt(
516+
segments: fmSegments
517+
)
518+
fmEntries.append(.prompt(fmPrompt))
519+
520+
case .response(let response):
521+
let fmSegments = response.segments.toFoundationModels()
522+
let fmResponse = FoundationModels.Transcript.Response(
523+
assetIDs: response.assetIDs,
524+
segments: fmSegments
525+
)
526+
fmEntries.append(.response(fmResponse))
527+
528+
case .toolCalls(let toolCalls):
529+
let fmCalls = toolCalls.compactMap { call -> FoundationModels.Transcript.ToolCall? in
530+
guard let fmArguments = try? FoundationModels.GeneratedContent(call.arguments) else {
531+
return nil
532+
}
533+
return FoundationModels.Transcript.ToolCall(
534+
id: call.id,
535+
toolName: call.toolName,
536+
arguments: fmArguments
537+
)
538+
}
539+
let fmToolCalls = FoundationModels.Transcript.ToolCalls(id: toolCalls.id, fmCalls)
540+
fmEntries.append(.toolCalls(fmToolCalls))
541+
542+
case .toolOutput(let toolOutput):
543+
let fmSegments = toolOutput.segments.toFoundationModels()
544+
let fmToolOutput = FoundationModels.Transcript.ToolOutput(
545+
id: toolOutput.id,
546+
toolName: toolOutput.toolName,
547+
segments: fmSegments
548+
)
549+
fmEntries.append(.toolOutput(fmToolOutput))
550+
}
551+
}
552+
553+
return FoundationModels.Transcript(entries: fmEntries)
554+
}
555+
}
556+
557+
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
558+
extension Array where Element == Transcript.Segment {
559+
fileprivate func toFoundationModels() -> [FoundationModels.Transcript.Segment] {
560+
compactMap { segment -> FoundationModels.Transcript.Segment? in
561+
switch segment {
562+
case .text(let textSegment):
563+
return .text(.init(id: textSegment.id, content: textSegment.content))
564+
case .structure(let structuredSegment):
565+
guard let fmContent = try? FoundationModels.GeneratedContent(structuredSegment.content) else {
566+
return nil
567+
}
568+
return .structure(
569+
.init(
570+
id: structuredSegment.id,
571+
source: structuredSegment.source,
572+
content: fmContent
573+
)
574+
)
575+
case .image:
576+
// FoundationModels Transcript does not support image segments
577+
return nil
578+
}
579+
}
580+
}
581+
}
582+
583+
@available(macOS 26.0, iOS 26.0, watchOS 26.0, tvOS 26.0, visionOS 26.0, *)
584+
extension Array where Element == Transcript.ToolDefinition {
585+
fileprivate func toFoundationModels() -> [FoundationModels.Transcript.ToolDefinition] {
586+
map { toolDef in
587+
FoundationModels.Transcript.ToolDefinition(
588+
name: toolDef.name,
589+
description: toolDef.description,
590+
parameters: FoundationModels.GenerationSchema(toolDef.parameters)
591+
)
592+
}
593+
}
594+
}
478595
#endif

Sources/AnyLanguageModel/Transcript.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,8 @@ public struct Transcript: Sendable, Equatable, Codable {
387387
/// A description of how and when to use the tool.
388388
public var description: String
389389

390-
private let parameters: GenerationSchema
390+
/// The schema describing the tool's parameters.
391+
internal let parameters: GenerationSchema
391392

392393
public init(name: String, description: String, parameters: GenerationSchema) {
393394
self.name = name

0 commit comments

Comments
 (0)