Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift 6 Complete Concurrency Checking #85

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 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
24 changes: 24 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

import PackageDescription

#if swift(<6)
let swiftConcurrency: SwiftSetting = .enableExperimentalFeature("StrictConcurrency")
#else
let swiftConcurrency: SwiftSetting = .enableUpcomingFeature("StrictConcurrency")
#endif

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to do that if you just specify // swift-tools-version:6.0 at the top of the file.
See https://github.com/StanfordSpezi/Spezi/blob/main/Package.swift


let package = Package(
name: "SpeziLLM",
Expand Down Expand Up @@ -45,6 +51,9 @@ let package = Package(
.product(name: "Spezi", package: "Spezi"),
.product(name: "SpeziChat", package: "SpeziChat"),
.product(name: "SpeziViews", package: "SpeziViews")
],
swiftSettings: [
swiftConcurrency
jdisho marked this conversation as resolved.
Show resolved Hide resolved
]
),
.target(
Expand All @@ -60,6 +69,9 @@ let package = Package(
.product(name: "MLXRandom", package: "mlx-swift"),
.product(name: "Transformers", package: "swift-transformers"),
.product(name: "LLM", package: "mlx-swift-examples")
],
swiftSettings: [
swiftConcurrency
]
),
.target(
Expand All @@ -69,6 +81,9 @@ let package = Package(
.product(name: "SpeziViews", package: "SpeziViews"),
.target(name: "SpeziLLMLocal"),
.product(name: "LLM", package: "mlx-swift-examples")
],
swiftSettings: [
swiftConcurrency
]
),
.target(
Expand All @@ -81,6 +96,9 @@ let package = Package(
.product(name: "SpeziChat", package: "SpeziChat"),
.product(name: "SpeziSecureStorage", package: "SpeziStorage"),
.product(name: "SpeziOnboarding", package: "SpeziOnboarding")
],
swiftSettings: [
swiftConcurrency
]
),
.target(
Expand All @@ -89,12 +107,18 @@ let package = Package(
.target(name: "SpeziLLM"),
.product(name: "Spezi", package: "Spezi"),
.product(name: "OpenAI", package: "OpenAI")
],
swiftSettings: [
swiftConcurrency
]
),
.testTarget(
name: "SpeziLLMTests",
dependencies: [
.target(name: "SpeziLLMOpenAI")
],
swiftSettings: [
swiftConcurrency
]
)
]
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziLLM/Helpers/LLMContext+Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// SPDX-License-Identifier: MIT
//

import SpeziChat
@preconcurrency import SpeziChat
jdisho marked this conversation as resolved.
Show resolved Hide resolved


extension LLMContext {
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziLLM/LLMPlatformState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/// Describes the current state of the ``LLMPlatform`` which is responsible for sending ``LLMSchema``s to execution via ``LLMSession``s.
///
/// The ``LLMPlatformState`` is quite minimal with only ``LLMPlatformState/idle`` and ``LLMPlatformState/processing`` states.
public enum LLMPlatformState {
public enum LLMPlatformState: Sendable {
/// Indicates that the ``LLMPlatform`` is currently idle and doesn't execute any ``LLMSession``s.
case idle
/// Indicates that the ``LLMPlatform`` is currently processing and executing ``LLMSession``s.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziLLM/LLMSessionProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public struct _LLMSessionProvider<Schema: LLMSchema>: DynamicProperty { // s
}

/// Creates a `Binding` to the ``LLMSession``that one can pass around. Useful for passing the ``LLMSession`` as a `Binding` to the ``LLMChatView``.
public var projectedValue: Binding<Schema.Platform.Session> {
@MainActor public var projectedValue: Binding<Schema.Platform.Session> {
jdisho marked this conversation as resolved.
Show resolved Hide resolved
Binding {
wrappedValue
} set: {
Expand Down
6 changes: 3 additions & 3 deletions Sources/SpeziLLM/Models/LLMContextEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import Foundation
/// A ``LLMContextEntity`` can be thought of as a single message entity within a ``LLMContext``
/// It consists of a ``LLMContextEntity/Role``, a unique identifier, a timestamp in the form of a `Date` as well as an `String`-based ``LLMContextEntity/content`` property which can contain Markdown-formatted text.
/// Furthermore, the ``LLMContextEntity/complete`` flag indicates if the current state of the ``LLMContextEntity`` is final and the content will not be updated anymore.
public struct LLMContextEntity: Codable, Equatable, Hashable, Identifiable {
public struct LLMContextEntity: Codable, Equatable, Hashable, Identifiable, Sendable {
/// Represents a tool call by the LLM, including its parameters
public struct ToolCall: Codable, Equatable, Hashable {
public struct ToolCall: Codable, Equatable, Hashable, Sendable {
/// The ID of the function call, uniquely identifying the specific function call and matching the response to it.
public let id: String
/// The name of the function call.
Expand All @@ -39,7 +39,7 @@ public struct LLMContextEntity: Codable, Equatable, Hashable, Identifiable {
}

/// Indicates which ``LLMContextEntity/Role`` is associated with a ``LLMContextEntity``.
public enum Role: Codable, Equatable, Hashable {
public enum Role: Codable, Equatable, Hashable, Sendable {
case user
case assistant(toolCalls: [ToolCall] = [])
case system
Expand Down
11 changes: 2 additions & 9 deletions Sources/SpeziLLMFog/LLMFogSession+Generation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@ import SpeziChat


extension LLMFogSession {
private static let modelNotFoundRegex: Regex = {
guard let regex = try? Regex("model '([\\w:]+)' not found, try pulling it first") else {
preconditionFailure("SpeziLLMFog: Error Regex could not be parsed")
}

return regex
}()

private static let modelNotFoundRegex = "model '([\\w:]+)' not found, try pulling it first"

/// Based on the input prompt, generate the output via some OpenAI API, e.g., Ollama.
///
Expand Down Expand Up @@ -61,7 +54,7 @@ extension LLMFogSession {
}
} catch let error as APIErrorResponse {
// Sadly, there's no better way to check the error messages as there aren't any Ollama error codes as with the OpenAI API
if error.error.message.contains(Self.modelNotFoundRegex) {
if error.error.message.range(of: Self.modelNotFoundRegex, options: .regularExpression) != nil {
Self.logger.error("SpeziLLMFog: LLM model type could not be accessed on fog node - \(error.error.message)")
await finishGenerationWithError(LLMFogError.modelAccessError(error), on: continuation)
} else if error.error.code == "401" || error.error.code == "403" {
Expand Down
Loading