apple/foundation-models-utilities: a chat completions client and context helpers for the Foundation Models framework
Emerging and experimental patterns for building with the Foundation Models framework
At a glance
- What is it?
- Apple's utilities package adds a ChatCompletionsLanguageModel, history compression modifiers and just-in-time Skills to the Foundation Models framework. It is an early-stage Swift package for developers who already build against Apple's on-device model APIs.
- Who is it for?
- Adopt this package if you already build against the Foundation Models framework and want to point a LanguageModelSession at a hosted chat completions server, or you need a transcript that does not outgrow the context window. Do not adopt it as a general-purpose LLM client for Linux or for non-Apple platforms, and do not treat it as a replacement for the framework itself: the README frames it as extra utilities on top.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 36 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What apple/foundation-models-utilities adds to the Foundation Models framework
The Foundation Models framework is Apple's API for working with LLMs on Apple platforms. This package sits on top of it. The README describes three additions: custom skills, context management helpers, and a chat completions client that connects to a hosted model of your choice. The supported platforms are listed as Apple platforms and select Linux distributions such as Ubuntu.
The audience is narrow by design. You need an existing project that already imports the Foundation Models framework and creates a LanguageModelSession. If you have never used that framework, this package gives you nothing to build on, because every example in the README assumes a session already exists. The problem it addresses is practical rather than conceptual: sessions accumulate transcripts, and transcripts eventually exceed the context window; and a session bound to one model cannot talk to a self-hosted server. Those two gaps are what the utilities fill.
The repository is a Swift package with a Sources/ directory, a Tests/ directory, a Package.swift manifest, and a skills/ directory that the README says can teach a coding agent how to use the package. There is no homepage listed, and no GitHub Releases were retrieved, so the version to pin is whatever tag you find in the repository itself.
How ChatCompletionsLanguageModel connects a session to a hosted server
The mechanism is a language model implementation that speaks the chat completions REST API. You construct it with a model name and a URL, hand it to a LanguageModelSession, and then call respond(to:) as you would with any other model. The README's example uses the name "minimax-m2.5" and the URL http://localhost/v1:8000, which is worth reading carefully: the package does not ship a server, it points at one you run or rent.
Capabilities are declared at construction time rather than discovered. The README notes that some local LLM servers do not support guided generation, and shows a supportsGuidedGeneration flag set to false for that case. This is the honest part of the design and also the fragile part. You are responsible for knowing what the server behind the URL can do. Pass a flag that overstates the server and the failure will surface at generation time, not at initialization.
The README argues the client is useful for integrating with the ecosystem of open source utilities built around the chat completions protocol. That framing is accurate: the value is interoperability with servers that already expose that protocol, not any new inference capability.
Installing the package in Xcode or Swift Package Manager
On Apple platforms, the README gives the Xcode route: from the menu bar choose File > Add Package Dependencies, then enter the package URL https://github.com/apple/foundation-models-utilities. Xcode downloads the package assets.
For Swift Package Manager, which the README says works on all supported platforms, the dependency and product are declared in Package.swift. Note the product name is FoundationModelsUtilities while the package name is foundation-models-utilities; getting that pair wrong is the most likely first error.
let package = Package(
name: "YourApp",
dependencies: [
.package(url: "https://github.com/apple/foundation-models-utilities", from: "1.0.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "FoundationModelsUtilities", package: "foundation-models-utilities")
]
)
]
)The README's first real use is a session backed by a chat completions server. You build the model, build the session, and await a response. The printed content is the model's answer, assuming the server at that URL is reachable and speaks the protocol.
let model = ChatCompletionsLanguageModel(
name: "minimax-m2.5",
url: URL(string: "http://localhost/v1:8000")!,
)
let session = LanguageModelSession(model: model)
let response = try await session.respond(to: "How many folds does it take to make a paper crane?")
print(response.content)If guided generation is unsupported on your server, the README shows the same initializer with supportsGuidedGeneration: false added. Start with the plain version, and add that flag only when your server requires it.
History management: composing modifiers instead of picking one
The package ships profile modifiers that compress a session transcript so it does not outgrow the model's context window. The README names three strategies: dropping completed tool calls, a rolling window, and summarizing previous interactions into a single entry. It states plainly that there is no one-size-fits-all solution and encourages composing them.
Composition order matters, and the README is explicit that modifiers apply outside-in. In the example, the profile first drops completed tool calls, then applies a rolling window, and summarization runs only if the rolling window of 10 entries exceeds 5000 tokens. That conditional is the interesting detail: summarization is not a fixed step in the chain, it is gated on a token threshold, so a short conversation never pays for it.
struct MyProfile: LanguageModelSession.DynamicProfile {
let status: Status
var body: some DynamicProfile {
Profile {
Instructions("A conversation between a user and a helpful assistant.")
ToggleDarkModeTool()
}
.summarizeHistory(entryThreshold: 10, model: status.summarizerModel)
.rollingWindow(entries: 10)
.droppingCompletedToolCalls()
}
}Read that chain twice before copying it. The summarizer needs its own model, supplied here through a status value, and that model is a separate dependency from the one answering the user. The README does not document what happens when the summarizer itself fails, or whether a summarization pass can be rolled back. Those gaps are worth testing before you rely on the chain in production.
Skills and why the initializer you choose changes the transcript
A Skill adds directions for a specific task into a session transcript on a just-in-time basis. The README's stated motivation is avoiding context pollution and improving time-to-first-token: instructions for calendaring should not sit in the prompt while the user is asking about writing style.
Skills is a DynamicInstructions type built with a result builder, and it requires a SkillActivations instance that tracks which skills are active. Because SkillActivations conforms to Observable and RandomAccessCollection, the README notes you can drive UI updates from it. That is a small but real integration point: activation state is inspectable, not hidden inside the model layer.
The design decision worth understanding is the two initializers. A Skill built with a prompt string has its content added to the transcript as part of a matching tool output, and the README says this avoids invalidating the key-value cache. A Skill built with an instructions string has its content inserted at the end of the first instructions entry. The README observes that models are typically trained to obey instructions with high priority, but that this often costs key-value cache invalidation. In both cases the model activates a skill by generating a tool call, so activation is model-driven rather than something you toggle directly.
@Observable
class Assistant {
var activations = SkillActivations()
}
struct MyProfile: LanguageModelSession.DynamicProfile {
let assistant: Assistant
var body: some DynamicProfile {
Profile {
Instructions("A conversation between a user and a helpful assistant.")
ToggleDarkModeTool()
Skills(activations: assistant.activations) {
Skill(
name: "style-guide",
description: "Applies the project's writing style guide",
prompt: """
# Style Guide
## Keep phrasing literal
Idioms and figurative phrases can add color, but they slow
down readers who are scanning, learning the language, or
translating the text. Prefer literal phrasing that names
what you mean, and reserve metaphor for places where it
genuinely earns its keep.
...(continued)
"""
)
Skill(
name: "calendaring",
description: "Read and modify the user's calendar",
instructions: """
Unless specified otherwise, all work meetings
should start 5 minutes after the hour
"""
)
}
}
}
}The trade-off is a real one and the README does not resolve it for you. Prompt-based skills protect the cache but place content later in the transcript, where instruction priority is weaker. Instruction-based skills get higher priority but may invalidate the cache. If your latency budget is tight, that is a decision you have to make per skill, not once for the project.
Where this package is the wrong tool
The clearest limitation is scope. This is a utilities package for the Foundation Models framework, not a standalone LLM toolkit. If your target is a server-side Swift service that talks to an inference endpoint and has no interest in LanguageModelSession, the chat completions client here is a thin layer you would be adopting along with the rest of the framework's assumptions. A plain HTTP client may be simpler.
The README lists supported platforms as Apple platforms and select Linux distributions such as Ubuntu, with the phrase "select" doing a lot of work. It does not enumerate which distributions or which Swift versions. Verify your toolchain before you plan around it.
Capability declaration is another soft spot. The supportsGuidedGeneration flag is a manual assertion about a remote server. Nothing in the README describes capability negotiation or a discovery endpoint. If you point the client at a server whose behavior changes under you, the package will not detect it.
Finally, the history modifiers are heuristics. A rolling window drops entries by count, and summarization replaces interactions with a single entry. Both are lossy by construction. For a session where every prior turn matters, for example a multi-step tool workflow, dropping completed tool calls may remove state the model still needs. The README presents composition as the answer and does not claim the strategies are safe for every transcript shape.
Alternatives and the difference in approach
The natural alternative is to use the Foundation Models framework directly and write the missing pieces yourself. The difference is where the work sits. The framework gives you LanguageModelSession and the on-device model path; this package gives you a session profile system, a set of composable history modifiers, and a model implementation that targets a remote chat completions endpoint. If you only need the on-device model and short conversations, the framework alone is enough and you avoid an extra dependency.
A second alternative is to skip the session abstraction and call a chat completions server over HTTP from Swift, managing message arrays yourself. That gives you full control over what stays in the request, and it removes the dependency entirely. What you lose is the integration: Skills activation, the profile builder, and the transcript modifiers all operate on LanguageModelSession types, so none of them carry over to a hand-rolled client.
The README does not compare the package to any other library, so treat this as a structural comparison rather than a recommendation from the maintainers. The deciding question is whether your app already lives inside the Foundation Models framework. If it does, the utilities slot in. If it does not, adopting them means adopting the framework's session model as well.
Maintenance, licensing and what to check before you depend on it
The repository is not archived, and the last push was on 2026-08-12. No releases were retrieved, so there is no published changelog to read for upgrade guidance. The README points issue reporting at the Apple Developer Forums rather than a GitHub issue tracker, which changes how you should expect to get answers: forum threads, not tracked issues with milestones.
The licence is Apache-2.0, per the LICENSE.txt file in the repository root. That is a permissive licence with an explicit patent grant, which matters if you are shipping a product. It also means you carry the usual obligations around attribution and notice retention. This is a description of the licence file, not legal advice; read LICENSE.txt and your own counsel's guidance if the terms affect your distribution model.
Upgrade cost is hard to estimate from what is available. The README documents a from: "1.0.0" dependency, and the package has no retrieved releases, so pinning to a specific tag and reading the diff between tags is the only reliable upgrade path. The API surface you would be exposed to is small: one model type, a profile builder, three history modifiers, and the Skills types. That limits blast radius, but the Skills initializer choice and the modifier order are behavioral, not just compile-time, so a version bump can change transcripts without breaking your build.
Editorial conclusion
Adopt this package if you already build against the Foundation Models framework and want to point a LanguageModelSession at a hosted chat completions server, or you need a transcript that does not outgrow the context window. Do not adopt it as a general-purpose LLM client for Linux or for non-Apple platforms, and do not treat it as a replacement for the framework itself: the README frames it as extra utilities on top. Before committing, verify three things in your own build: that the product name FoundationModelsUtilities resolves from the tagged release you pin, that your server's capabilities match the flags you pass to ChatCompletionsLanguageModel, and that your chosen modifier order produces the transcript you expect, since the README states that modifiers apply outside-in and that no single strategy fits every application.
Frequently asked questions
What are examples of foundation models?
The README does not list foundation models in general. Its concrete example names a model called "minimax-m2.5" when constructing a ChatCompletionsLanguageModel pointed at a local server, and it says the client can communicate with any server that uses the chat completions REST API.
Which companies have foundational models?
The README does not cover model vendors or companies. It only describes how to connect a LanguageModelSession to a hosted model of your choice through the chat completions REST API.
How do foundation models make money?
This is outside the scope of the repository. The README covers package installation, the ChatCompletionsLanguageModel client, history management modifiers and Skills, and says nothing about commercial models or pricing.
How does a foundation model work?
The README does not explain model internals. It describes how a session interacts with one: the model activates a Skill by generating a tool call, and a profile's modifiers can drop completed tool calls, apply a rolling window, or summarize previous interactions into a single entry.
Community notes