-
Foundation Models 프레임워크로 에이전틱 앱 경험 빌드하기
Foundation Models 프레임워크 프리미티브로 동적 컨텍스트와 에이전틱 워크플로를 위한 인텔리전스 기능을 한 단계 더 발전시키는 방법을 알아보세요. 공유 컨텍스트를 설계하고, 개인정보 보호 경계를 설정하며, 키 값 캐싱을 관리하는 방법을 안내합니다. 로컬 모델과 서버 모델 간의 원활한 핸드오프를 오케스트레이션하는 방법을 살펴보세요.
챕터
- 0:00 - Introduction
- 2:47 - The example app and agents
- 3:47 - Declaring a dynamic profile
- 4:45 - Dynamic instructions
- 5:36 - Configuring models per phase
- 7:21 - Transcript management and history transforms
- 8:50 - Custom modifiers
- 9:39 - Lifecycle modifiers and session properties
- 12:52 - Orchestration: baton-pass
- 14:06 - Orchestration: phone-a-friend and skills
- 15:18 - Tool calling mode
- 17:12 - Transcript error handling
- 18:27 - Performance, accuracy, and evaluations
- 21:24 - Next steps
리소스
-
비디오 검색…
-
-
5:04 - DynamicInstructions
// DynamicInstructions struct BrainstormFacilitator: DynamicInstructions { var orchestrator: CraftOrchestrator var body: some DynamicInstructions { Instructions { "You are a warm and friendly expert crafting brainstorm facilitator." } // Tools GenerateProjectTitle() // Conditionally include Origami knowledge if orchestrator.techniques.contains(.origami) { OrigamiExpert() } } } -
6:41 - DynamicProfile
// DynamicProfile struct CraftProfile: LanguageModelSession.DynamicProfile { var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .brainstorming: Profile { BrainstormFacilitator(orchestrator: orchestrator) } .model(orchestrator.pccLanguageModel) .temperature(1) case .planning: Profile { TutorialAuthor(orchestrator: orchestrator) } .model(orchestrator.pccLanguageModel) .reasoningLevel(.deep) case .reviewing: Profile { CraftCoach() } .model(orchestrator.systemLanguageModel) } } } -
6:43 - Initialize your session with your dynamic profile
// Initialize your session with your dynamic profile let session = LanguageModelSession(profile: CraftProfile(orchestrator: orchestrator)) -
8:33 - Transcript management
// Transcript management struct CraftProfile: LanguageModelSession.DynamicProfile { var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .reviewing: Profile { CraftCoach() } .model(orchestrator.systemLanguageModel) .historyTransform { history in // Update the history for your profile guard let latestResponseIndex = lastResponseEntryIndex(history) else { return history } let filteredHistory = history[0..<latestResponseIndex].filter { entry in isToolCallsOrToolOutput(entry) } return filteredHistory + history[latestResponseIndex...] } } } } -
9:15 - Custom modifiers
// Custom modifiers struct DroppingToolCallsProfileModifier: LanguageModelSession.DynamicProfileModifier { func body(content: Content) -> some DynamicProfile { content .historyTransform { history in guard let latestResponseIndex = lastResponseEntryIndex(history) else { return history } let filteredHistory = history[0..<latestResponseIndex].filter { entry in isToolCallsOrToolOutput(entry) } return filteredHistory + history[latestResponseIndex...] } } } extension LanguageModelSession.DynamicProfile { func droppingCompletedToolCalls() -> some DynamicProfile { self.modifier(DroppingToolCallsProfileModifier()) } } -
9:27 - History management modifiers
// History management modifiers import FoundationModelsUtilities struct CraftProfile: LanguageModelSession.DynamicProfile { var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .reviewing: Profile { CraftCoach() } // Keep the most recent 10 entries // after dropping finished tool calls .rollingWindow(size: .entries(10)) .droppingCompletedToolCalls() } } } -
10:48 - Lifecycle modifiers
// Lifecycle modifiers struct CraftProfile: LanguageModelSession.DynamicProfile { @SessionProperty(\.history) var history var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .planning: Profile { TutorialAuthor(orchestrator: orchestrator) } .model(orchestrator.pccLanguageModel) .reasoningLevel(.deep) .onResponse { // Update history if history.count > 50, let responseIndex = lastResponseIndex(history) { history = history[responseIndex...] } } } } } -
11:40 - Declare a custom session property
// Session properties — declaration extension SessionPropertyValues { @SessionPropertyEntry var summary: String? } -
12:24 - Read and write session properties in a profile
// Session properties struct CraftProfile: LanguageModelSession.DynamicProfile { @SessionProperty(\.history) var history @SessionProperty(\.summary) var summary var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .planning: Profile { TutorialAuthor(orchestrator: orchestrator) if let summary { Instructions { "Summary: \(summary)" } } } .onResponse { if history.count > 50, let responseIndex = lastResponse(history.prefix(40)) { summary = try await summarize(history[0..<responseIndex]) history = history[responseIndex...] } } } } } -
13:02 - Orchestration: baton-pass
// Baton-pass struct CraftProfile: LanguageModelSession.DynamicProfile { var orchestrator: CraftOrchestrator var body: some DynamicProfile { switch orchestrator.mode { case .brainstorm: Profile { BrainstormInstructions() BatonPassTool() } .onToolCall { orchestrator.mode = .tutorial } .model(orchestrator.serverModel) case .tutorial: Profile { TutorialInstructions() BatonPassTool() } .onToolCall { orchestrator.mode = .brainstorm } .model(orchestrator.systemModel) } } } -
14:14 - Orchestration: phone-a-friend
// Phone-a-friend struct CraftProfile: LanguageModelSession.DynamicProfile { var body: some DynamicProfile { Profile { BrainstormInstructions() PhoneFriendTool( name: "generate_title", description: "Generate a creative project title", profile: TitleProfile() ) } } } struct PhoneFriendTool<P: LanguageModelSession.DynamicProfile>: Tool { func call(arguments: GeneratedContent) async throws -> String { let session = LanguageModelSession(profile: profile()) let response = try await session.respond(to: arguments) return response.content } } -
15:15 - The skills pattern
// The skills pattern struct CraftingSkills: LanguageModelSession.DynamicInstructions { var activations: SkillActivations var body: some DynamicInstructions { Skills(activations: activations) { Skill( name: "origami_folds", description: "Details about specific types of folds", prompt: """ Valley Fold: Paper is folded toward you, creating a V-shaped crease Mountain Fold: Paper is folded away from you, creating an inverted V ... """ ) Skill(...) Skill(...) } } } -
15:31 - Tool calling mode
// Tool calling mode public struct ToolCallingMode: Sendable { public static let allowed: ToolCallingMode public static let disallowed: ToolCallingMode public static let required: ToolCallingMode } // Pass tool calling mode as a profile modifier struct OrigamiExpert: LanguageModelSession.DynamicProfile { var body: some LanguageModelSession.DynamicProfile { Profile { Instructions("You are an origami expert") QueryOrigamiDatabaseTool() ShowDirectionsTool() } .toolCallingMode(.required) } } // Or pass it as a generation option let response = try await session.respond( to: "Write out the instructions for folding a paper crane.", options: GenerationOptions(toolCallingMode: .required) ) -
16:47 - Escaping a tool call loop
// Escaping a tool call loop struct OrigamiExpert: LanguageModelSession.DynamicProfile { let state: OrigamiAppState var body: some LanguageModelSession.DynamicProfile { Profile { Instructions("Answer questions about how to fold origami") QueryOrigamiDatabaseTool() } .toolCallingMode(state.queriedDatabase ? .disallowed : .required) .onToolCall { state.queriedDatabase = true } } } -
16:57 - Define a tool that throws an error
// Define a tool that throws an error var output: String? @Generable struct Arguments { var answer: String } func call(arguments: Arguments) async throws -> Never { output = arguments.answer throw CancellationError() } } -
17:28 - Set the transcript error handling policy
// Specify transcript behavior on a profile struct OrigamiExpert: LanguageModelSession.DynamicProfile { let state: OrigamiAppState var body: some LanguageModelSession.DynamicProfile { Profile { Instructions("Answer questions about how to fold origami") QueryOrigamiDatabaseTool() } .transcriptErrorHandlingPolicy(.preserveTranscript) } } // Or specify it on a session let session = LanguageModelSession() session.transcriptErrorHandlingPolicy = .preserveTranscript // Policy options extension LanguageModelSession { public struct TranscriptErrorHandlingPolicy: Sendable { // Roll the transcript back to its previous state public static let revertTranscript: TranscriptErrorHandlingPolicy // Keep the transcript in state following an error public static let preserveTranscript: TranscriptErrorHandlingPolicy } } -
17:51 - Transcript mutation
// Transcript mutation public final class LanguageModelSession: Sendable { public var transcriptErrorHandlingPolicy: TranscriptErrorHandlingPolicy { get set } // Transcript is now settable public var transcript: Transcript { get set } // But you must not modify it during a response! public var isResponding: Bool { get } }
-
-
- 0:00 - Introduction
Erik Hornberger and Oliver O'Neill introduce Dynamic Profiles and the problems they solve, context management and model boundaries, plus the new open-source Foundation Models framework utilities package. Agenda: dynamic profiles, orchestration patterns, performance and accuracy.
- 2:47 - The example app and agents
Introduces the Origami craft app with three phases (brainstorming, planning, reviewing) that share context but have different priorities. Each phase becomes an agent: a configuration with its own model, instructions, and tools.
- 3:47 - Declaring a dynamic profile
A DynamicProfile declares individual Profiles representing a configuration or agent. Build the brainstorming profile from an Observable orchestrator, instructions, and tools, conditionally adding capabilities for origami projects.
- 4:45 - Dynamic instructions
DynamicInstructions groups related instructions and tools into a single reusable, composable component. Nesting one inside another concatenates their instructions and tools, such as an OrigamiExpert reused wherever needed.
- 5:36 - Configuring models per phase
Assign different models and options per profile: Private Cloud Compute with temperature and deep reasoningLevel for brainstorming and planning, and SystemLanguageModel for reviewing. The profile body is re-evaluated on each prompt, swapping the session's persona by mode.
- 7:21 - Transcript management and history transforms
Trim or redact the transcript to stay within context limits, keep the model focused, or protect privacy. historyTransform applies stateless, per-request transforms over the history window (such as dropping tool calls) without mutating the session.
- 8:50 - Custom modifiers
Custom modifiers hide transform complexity — a type conforming to DynamicProfileModifier, exposed through a DynamicProfile extension for reuse across profiles, alongside the ready-made history-management modifiers in the utilities package.
- 9:39 - Lifecycle modifiers and session properties
onResponse and other lifecycle modifiers run imperative code at session boundaries to update UI, profile state, or history. Session properties (the built-in history, plus custom @SessionPropertyEntry values) share state across tools and profiles, for example storing a conversation summary.
- 12:52 - Orchestration: baton-pass
A collaboration pattern: multiple profiles share the full transcript, and a tool toggles which profile is active. The profile that receives the baton produces the final response.
- 14:06 - Orchestration: phone-a-friend and skills
A consultation pattern: a tool spawns a short-lived child session with an isolated transcript, and the parent profile always gives the final answer. Also notes the Skills pattern in the utilities package for procedural context loading.
- 15:18 - Tool calling mode
Control when tools run via allowed, disallowed, or required (as a profile modifier or generation option). When required, the model loops, so ensure an exit condition by conditionalizing the mode or using a final-answer tool that throws to break out.
- 17:12 - Transcript error handling
By default a thrown tool error or cancellation reverts the transcript; the new transcriptErrorHandlingPolicy (.revertTranscript or .preserveTranscript) keeps it. With preserve, the now-mutable transcript is yours to fix, only when isResponding is false.
- 18:27 - Performance, accuracy, and evaluations
Transcript mutations can invalidate key-value caches and raise latency; appending preserves them. Rewriting history can also confuse the model, so measure with the Foundation Models Instrument and quantify changes with the Evaluations framework.
- 21:24 - Next steps
Where to go next — try the sample app, explore the Foundation Models framework utilities, and measure performance with Private Cloud Compute and the revamped Xcode instrument.