Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ on:
pull_request:
branches: ["main"]

permissions:
contents: read
pull-requests: write

jobs:
test:
name: Swift ${{ matrix.swift }} on Xcode ${{ matrix.xcode }}
runs-on: ${{ matrix.runs-on }}
test-macos:
name: Swift ${{ matrix.swift }} on macOS ${{ matrix.macos }} with Xcode ${{ matrix.xcode }}
runs-on: macos-${{ matrix.macos }}
env:
DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer"
strategy:
fail-fast: false
matrix:
include:
- swift: "6.1"
- macos: "15"
swift: "6.1"
xcode: "16.3"
runs-on: macos-15
- swift: "6.2"
- macos: "26"
swift: "6.2"
xcode: "26.0"
runs-on: macos-26
timeout-minutes: 10
steps:
- name: Checkout code
Expand All @@ -42,3 +46,27 @@ jobs:

- name: Test
run: swift test -v

test-linux:
name: Swift ${{ matrix.swift-version }} on Linux
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
swift-version:
- 6.1.0
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Swift
uses: vapor/swiftly-action@v0.2
with:
toolchain: ${{ matrix.swift-version }}

- name: Build
run: swift build -v

- name: Test
run: swift test -v
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0"),
.package(url: "https://github.com/mattt/JSONSchema.git", from: "1.3.0"),
.package(url: "https://github.com/mattt/EventSource.git", from: "1.2.0"),
.package(url: "https://github.com/mattt/EventSource.git", from: "1.3.0"),
.package(url: "https://github.com/mattt/PartialJSONDecoder.git", from: "1.0.0"),
.package(url: "https://github.com/ml-explore/mlx-swift-examples/", branch: "main"),
.package(url: "https://github.com/huggingface/swift-transformers", from: "1.0.0"),
Expand Down
49 changes: 29 additions & 20 deletions Sources/AnyLanguageModel/Extensions/URLSession+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,37 +83,28 @@ extension URLSession {
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
}

let (bytes, response) = try await bytes(for: request)
let (data, response) = try await self.data(for: request)

guard let httpResponse = response as? HTTPURLResponse else {
throw URLSessionError.invalidResponse
}

guard (200 ..< 300).contains(httpResponse.statusCode) else {
var errorData = Data()
for try await byte in bytes {
errorData.append(byte)
}

if let errorString = String(data: errorData, encoding: .utf8) {
if let errorString = String(data: data, encoding: .utf8) {
throw URLSessionError.httpError(statusCode: httpResponse.statusCode, detail: errorString)
}
throw URLSessionError.httpError(statusCode: httpResponse.statusCode, detail: "Invalid response")
}

var buffer = Data()

for try await byte in bytes {
buffer.append(byte)
var buffer = data

while let newlineIndex = buffer.firstIndex(of: UInt8(ascii: "\n")) {
let chunk = buffer[..<newlineIndex]
buffer = buffer[buffer.index(after: newlineIndex)...]
while let newlineIndex = buffer.firstIndex(of: UInt8(ascii: "\n")) {
let chunk = buffer[..<newlineIndex]
buffer = buffer[buffer.index(after: newlineIndex)...]

if !chunk.isEmpty {
let decoded = try decoder.decode(T.self, from: chunk)
continuation.yield(decoded)
}
if !chunk.isEmpty {
let decoded = try decoder.decode(T.self, from: chunk)
continuation.yield(decoded)
}
}

Expand Down Expand Up @@ -151,10 +142,28 @@ extension URLSession {
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
}

let (bytes, _) = try await bytes(for: request)
let (asyncBytes, response) = try await self.data(for: request)

guard let httpResponse = response as? HTTPURLResponse else {
throw URLSessionError.invalidResponse
}

guard (200 ..< 300).contains(httpResponse.statusCode) else {
if let errorString = String(data: asyncBytes, encoding: .utf8) {
throw URLSessionError.httpError(statusCode: httpResponse.statusCode, detail: errorString)
}
throw URLSessionError.httpError(statusCode: httpResponse.statusCode, detail: "Invalid response")
}

let decoder = JSONDecoder()
let parser = EventSource.Parser()

for byte in asyncBytes {
await parser.consume(byte)
}
await parser.finish()

for try await event in bytes.events {
while let event = await parser.getNextEvent() {
guard let data = event.data.data(using: .utf8) else { continue }

if let decoded = try? decoder.decode(T.self, from: data) {
Expand Down
1 change: 1 addition & 0 deletions Tests/AnyLanguageModelTests/LlamaLanguageModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Testing
#if Llama
@Suite(
"LlamaLanguageModel",
.serialized,
.enabled(if: ProcessInfo.processInfo.environment["LLAMA_MODEL_PATH"] != nil)
)
struct LlamaLanguageModelTests {
Expand Down
6 changes: 5 additions & 1 deletion Tests/AnyLanguageModelTests/OllamaLanguageModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import Testing

@testable import AnyLanguageModel

@Suite("OllamaLanguageModel", .enabled(if: ProcessInfo.processInfo.environment["CI"] == nil))
@Suite(
"OllamaLanguageModel",
.serialized,
.enabled(if: ProcessInfo.processInfo.environment["CI"] == nil)
)
struct OllamaLanguageModelTests {
let model = OllamaLanguageModel(model: "qwen3:8b")

Expand Down
5 changes: 4 additions & 1 deletion Tests/AnyLanguageModelTests/SystemLanguageModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import AnyLanguageModel
}
}()

@Suite("SystemLanguageModel", .enabled(if: isSystemLanguageModelAvailable))
@Suite(
"SystemLanguageModel",
.enabled(if: isSystemLanguageModelAvailable)
)
struct SystemLanguageModelTests {
@available(macOS 26.0, *)
@Test func basicResponse() async throws {
Expand Down