From c3d5f46cadd39fe2912bc2c5063aa8937f7245b6 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 28 Oct 2025 03:17:56 -0700 Subject: [PATCH 1/4] Add API compatibilit tests --- ...PICompatibilityAnyLanguageModelTests.swift | 31 +++++++++++++++++++ ...PICompatibilityFoundationModelsTests.swift | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 Tests/AnyLanguageModelTests/APICompatibilityAnyLanguageModelTests.swift create mode 100644 Tests/AnyLanguageModelTests/APICompatibilityFoundationModelsTests.swift diff --git a/Tests/AnyLanguageModelTests/APICompatibilityAnyLanguageModelTests.swift b/Tests/AnyLanguageModelTests/APICompatibilityAnyLanguageModelTests.swift new file mode 100644 index 00000000..a3d62967 --- /dev/null +++ b/Tests/AnyLanguageModelTests/APICompatibilityAnyLanguageModelTests.swift @@ -0,0 +1,31 @@ +import Testing + +#if canImport(FoundationModels) + import AnyLanguageModel + + @available(macOS 26.0, *) + @Test("AnyLanguageModel Drop-In Compatibility", .enabled(if: SystemLanguageModel.default.isAvailable)) + func anyLanguageModelCompatibility() async throws { + let model = SystemLanguageModel.default + let session = LanguageModelSession( + model: model, + instructions: Instructions("You are a helpful assistant.") + ) + + let options = GenerationOptions(temperature: 0.7) + let response = try await session.respond(options: options) { + Prompt("Say 'Hello'") + } + #expect(!response.content.isEmpty) + + let stream = session.streamResponse { + Prompt("Count to 3") + } + var hasSnapshots = false + for try await _ in stream { + hasSnapshots = true + break + } + #expect(hasSnapshots) + } +#endif diff --git a/Tests/AnyLanguageModelTests/APICompatibilityFoundationModelsTests.swift b/Tests/AnyLanguageModelTests/APICompatibilityFoundationModelsTests.swift new file mode 100644 index 00000000..6e446e79 --- /dev/null +++ b/Tests/AnyLanguageModelTests/APICompatibilityFoundationModelsTests.swift @@ -0,0 +1,31 @@ +import Testing + +#if canImport(FoundationModels) + import FoundationModels + + @available(macOS 26.0, *) + @Test("FoundationModels Drop-In Compatibility", .enabled(if: SystemLanguageModel.default.isAvailable)) + func foundationModelsCompatibility() async throws { + let model = SystemLanguageModel.default + let session = LanguageModelSession( + model: model, + instructions: Instructions("You are a helpful assistant.") + ) + + let options = GenerationOptions(temperature: 0.7) + let response = try await session.respond(options: options) { + Prompt("Say 'Hello'") + } + #expect(!response.content.isEmpty) + + let stream = session.streamResponse { + Prompt("Count to 3") + } + var hasSnapshots = false + for try await _ in stream { + hasSnapshots = true + break + } + #expect(hasSnapshots) + } +#endif From bb970ac36c0d1426c7367b1b6506c13eb125ad64 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 28 Oct 2025 03:22:42 -0700 Subject: [PATCH 2/4] Update README --- README.md | 52 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 04feec75..ea08a2ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AnyLanguageModel -A Swift package that provides an API-compatible, drop-in replacement for +A Swift package that provides an API-compatible replacement for [Apple's Foundation Models framework](https://developer.apple.com/documentation/FoundationModels) with support for custom language model providers. @@ -63,12 +63,45 @@ dependencies: [ ## Usage +AnyLanguageModel is a drop-in replacement for Apple's Foundation Models framework. +All you need to do is change your import statement: + +```diff +- import FoundationModels ++ import AnyLanguageModel + +struct WeatherTool: Tool { + let name = "getWeather" + let description = "Retrieve the latest weather information for a city" + + @Generable + struct Arguments { + @Guide(description: "The city to fetch the weather for") + var city: String + } + + func call(arguments: Arguments) async throws -> String { + "The weather in \(arguments.city) is sunny and 72°F / 23°C" + } +} + +let model = SystemLanguageModel.default +let session = LanguageModelSession(model: model, tools: [WeatherTool()]) + +let response = try await session.respond { + Prompt("What's the weather in Cupertino?") +} +print(response.content) +``` + +Here's an example using all of the available language model providers: + ```swift import AnyLanguageModel // Core functionality (always available) var models: [(any LanguageModel)] = [ - SystemLanguageModel(), // Apple Foundation Models + SystemLanguageModel.default, OllamaLanguageModel(model: "qwen3") // `ollama pull qwen3:0.6b` AnthropicLanguageModel( apiKey: ProcessInfo.processInfo.environment["ANTHROPIC_API_KEY"]!, @@ -93,21 +126,6 @@ models.append(MLXLanguageModel(modelId: "mlx-community/Qwen3-0.6B-4bit")) models.append(LlamaLanguageModel(modelPath: "/path/to/model.gguf")) #endif -struct WeatherTool: Tool { - let name = "getWeather" - let description = "Retrieve the latest weather information for a city" - - @Generable - struct Arguments { - @Guide(description: "The city to fetch the weather for") - var city: String - } - - func call(arguments: Arguments) async throws -> String { - "The weather in \(arguments.city) is sunny and 72°F / 23°C" - } -} - for model in models { let session = LanguageModelSession(model: model, tools: [WeatherTool()]) let response = try await session.respond(to: "What's the weather in Cupertino?") From 71909516aa078b84c4b0ac0d9b3df108f6ecd927 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 28 Oct 2025 03:26:08 -0700 Subject: [PATCH 3/4] Move import diff to seperate code block --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ea08a2ae..0ec9b9f9 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,9 @@ All you need to do is change your import statement: ```diff - import FoundationModels + import AnyLanguageModel +``` +```swift struct WeatherTool: Tool { let name = "getWeather" let description = "Retrieve the latest weather information for a city" From e31b558fdd892ac9bb1129077303083b19301664 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Tue, 28 Oct 2025 03:26:38 -0700 Subject: [PATCH 4/4] What's the weather -> How's the weather --- README.md | 4 ++-- Tests/AnyLanguageModelTests/AnthropicLanguageModelTests.swift | 2 +- Tests/AnyLanguageModelTests/MLXLanguageModelTests.swift | 2 +- Tests/AnyLanguageModelTests/OllamaLanguageModelTests.swift | 2 +- Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0ec9b9f9..1b37471b 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ let model = SystemLanguageModel.default let session = LanguageModelSession(model: model, tools: [WeatherTool()]) let response = try await session.respond { - Prompt("What's the weather in Cupertino?") + Prompt("How's the weather in Cupertino?") } print(response.content) ``` @@ -130,7 +130,7 @@ models.append(LlamaLanguageModel(modelPath: "/path/to/model.gguf")) for model in models { let session = LanguageModelSession(model: model, tools: [WeatherTool()]) - let response = try await session.respond(to: "What's the weather in Cupertino?") + let response = try await session.respond(to: "How's the weather in Cupertino?") print(response.text) // "It's sunny and 72°F in Cupertino" } ``` diff --git a/Tests/AnyLanguageModelTests/AnthropicLanguageModelTests.swift b/Tests/AnyLanguageModelTests/AnthropicLanguageModelTests.swift index e71cb275..8fccc314 100644 --- a/Tests/AnyLanguageModelTests/AnthropicLanguageModelTests.swift +++ b/Tests/AnyLanguageModelTests/AnthropicLanguageModelTests.swift @@ -90,7 +90,7 @@ struct AnthropicLanguageModelTests { let weatherTool = WeatherTool() let session = LanguageModelSession(model: model, tools: [weatherTool]) - let response = try await session.respond(to: "What's the weather in San Francisco?") + let response = try await session.respond(to: "How's the weather in San Francisco?") var foundToolOutput = false for case let .toolOutput(toolOutput) in response.transcriptEntries { diff --git a/Tests/AnyLanguageModelTests/MLXLanguageModelTests.swift b/Tests/AnyLanguageModelTests/MLXLanguageModelTests.swift index c9d35470..32aafdd9 100644 --- a/Tests/AnyLanguageModelTests/MLXLanguageModelTests.swift +++ b/Tests/AnyLanguageModelTests/MLXLanguageModelTests.swift @@ -64,7 +64,7 @@ import Testing instructions: "You are a helpful assistant. Use available tools when needed." ) - let response = try await session.respond(to: "What's the weather in San Francisco?") + let response = try await session.respond(to: "How's the weather in San Francisco?") var foundToolOutput = false for case let .toolOutput(toolOutput) in response.transcriptEntries { diff --git a/Tests/AnyLanguageModelTests/OllamaLanguageModelTests.swift b/Tests/AnyLanguageModelTests/OllamaLanguageModelTests.swift index b9e79e41..bc844aed 100644 --- a/Tests/AnyLanguageModelTests/OllamaLanguageModelTests.swift +++ b/Tests/AnyLanguageModelTests/OllamaLanguageModelTests.swift @@ -85,7 +85,7 @@ struct OllamaLanguageModelTests { let weatherTool = spy(on: WeatherTool()) let session = LanguageModelSession(model: model, tools: [weatherTool]) - let response = try await session.respond(to: "What's the weather in San Francisco?") + let response = try await session.respond(to: "How's the weather in San Francisco?") var foundToolOutput = false for case let .toolOutput(toolOutput) in response.transcriptEntries { diff --git a/Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift b/Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift index fe83334f..e2f23330 100644 --- a/Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift +++ b/Tests/AnyLanguageModelTests/OpenAILanguageModelTests.swift @@ -104,7 +104,7 @@ struct OpenAILanguageModelTests { let weatherTool = WeatherTool() let session = LanguageModelSession(model: model, tools: [weatherTool]) - let response = try await session.respond(to: "What's the weather in San Francisco?") + let response = try await session.respond(to: "How's the weather in San Francisco?") var foundToolOutput = false for case let .toolOutput(toolOutput) in response.transcriptEntries { @@ -196,7 +196,7 @@ struct OpenAILanguageModelTests { let weatherTool = WeatherTool() let session = LanguageModelSession(model: model, tools: [weatherTool]) - let response = try await session.respond(to: "What's the weather in San Francisco?") + let response = try await session.respond(to: "How's the weather in San Francisco?") var foundToolOutput = false for case let .toolOutput(toolOutput) in response.transcriptEntries {