← All courses ← Course home

Lesson 05 of 05

Build and measure the assembler

Wire budget, rank, and Profile into one function. Log leftover tokens. Refuse a pack that does not fit.

Agent brief (llms.md)

Environment setup

Same Xcode 27 app. Create the session with a Dynamic Profile. Confirm LanguageModelSession(profile:) in the installed SDK.

BudgetCount
RankPack
ProfileOne active
RespondMeasure
@MainActor
final class ContextAssembler {
    let model: SystemLanguageModel
    var session: LanguageModelSession?

    func ask(question: String, slices: [ContextSlice], tools: [any Tool]) async throws -> String {
        let budget = try await makeBudget(
            model: model,
            question: question,
            instructions: "Answer using only the packed context."
        )
        let packed = pack(slices: slices, leftover: budget.leftover)
        let used = packed.reduce(0) { $0 + $1.tokens }
        precondition(used <= budget.leftover)
        session = LanguageModelSession(profile: PackedProfile(pack: packed, tools: tools))
        let response = try await session!.respond(to: question)
        print("leftoverAfterPack:", budget.leftover - used, "slices:", packed.count)
        return String(describing: response)
    }
}

Print leftover tokens, slice ids, and response.usage if the SDK gives it. Compare a packed run with an unpacked dump on the same question. Packed should be smaller and, on a good pack, no worse on a short yes/no check.

If leftover is negative before packing, cut instructions first. If packing yields zero slices and the question needed files, that is a retrieval miss, not a packer win. Send the user to Search private files on the phone.

Sources: WWDC26 241, WWDC26 242, Foundation Models.

Done. You can fill a token budget, declare it as one Dynamic Profile, and measure what you left on the table.

Key concepts

  • ContextAssembler wires budget, rank, and Profile into one ask() function.
  • The pipeline is budget, rank, one active Profile, respond, measure.
  • Create LanguageModelSession(profile:) then session.respond(to:).
  • Log leftover tokens, slice ids, and usage if available.

Takeaways

  • Refuse an overfull pack before you call the model.
  • Compare packed versus unpacked on the same question.
  • Zero slices when files were needed is a retrieval miss, not a packer win.