Skip to content

Commit

Permalink
Swift 6 language mode readiness and bump dependencies (#23)
Browse files Browse the repository at this point in the history
# Swift 6 language mode readiness and bump dependencies

## ♻️ Current situation & Problem
As the repo wasn't properly maintained for quite some months, most
dependencies have been severely outdated.

## ⚙️ Release Notes 
- Swift 6 language mode readiness
- Bump dependencies to the newest versions


## 📚 Documentation
--


## ✅ Testing
Tested locally and UI tests passing


## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
philippzagar authored Dec 3, 2024
1 parent 8a705dd commit d368a1e
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 116 deletions.
39 changes: 9 additions & 30 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.9
// swift-tools-version:6.0

//
// This source file is part of the Stanford Spezi open-source project
Expand All @@ -12,12 +12,6 @@ import class Foundation.ProcessInfo
import PackageDescription


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

let package = Package(
name: "SpeziFHIR",
defaultLocalization: "en",
Expand All @@ -31,14 +25,14 @@ let package = Package(
.library(name: "SpeziFHIRMockPatients", targets: ["SpeziFHIRMockPatients"])
],
dependencies: [
.package(url: "https://github.com/apple/FHIRModels", .upToNextMinor(from: "0.5.0")),
.package(url: "https://github.com/StanfordBDHG/HealthKitOnFHIR", .upToNextMinor(from: "0.2.4")),
.package(url: "https://github.com/StanfordSpezi/Spezi", from: "1.2.1"),
.package(url: "https://github.com/StanfordSpezi/SpeziHealthKit.git", .upToNextMinor(from: "0.5.1")),
.package(url: "https://github.com/StanfordSpezi/SpeziLLM.git", .upToNextMinor(from: "0.8.1")),
.package(url: "https://github.com/StanfordSpezi/SpeziStorage.git", from: "1.0.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziChat.git", .upToNextMinor(from: "0.2.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziSpeech.git", from: "1.0.0")
.package(url: "https://github.com/apple/FHIRModels", .upToNextMinor(from: "0.6.0")),
.package(url: "https://github.com/StanfordBDHG/HealthKitOnFHIR", .upToNextMinor(from: "0.2.11")),
.package(url: "https://github.com/StanfordSpezi/Spezi", from: "1.8.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziHealthKit", .upToNextMinor(from: "0.6.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziLLM", .upToNextMinor(from: "0.8.4")),
.package(url: "https://github.com/StanfordSpezi/SpeziStorage", from: "1.2.1"),
.package(url: "https://github.com/StanfordSpezi/SpeziChat", .upToNextMinor(from: "0.2.1")),
.package(url: "https://github.com/StanfordSpezi/SpeziSpeech", from: "1.1.0")
] + swiftLintPackage(),
targets: [
.target(
Expand All @@ -49,9 +43,6 @@ let package = Package(
.product(name: "ModelsDSTU2", package: "FHIRModels"),
.product(name: "HealthKitOnFHIR", package: "HealthKitOnFHIR")
],
swiftSettings: [
strictConcurrency
],
plugins: [] + swiftLintPlugin()
),
.target(
Expand All @@ -61,9 +52,6 @@ let package = Package(
.product(name: "HealthKitOnFHIR", package: "HealthKitOnFHIR"),
.product(name: "SpeziHealthKit", package: "SpeziHealthKit")
],
swiftSettings: [
strictConcurrency
],
plugins: [] + swiftLintPlugin()
),
.target(
Expand All @@ -81,9 +69,6 @@ let package = Package(
resources: [
.process("Resources")
],
swiftSettings: [
strictConcurrency
],
plugins: [] + swiftLintPlugin()
),
.target(
Expand All @@ -95,19 +80,13 @@ let package = Package(
resources: [
.process("Resources")
],
swiftSettings: [
strictConcurrency
],
plugins: [] + swiftLintPlugin()
),
.testTarget(
name: "SpeziFHIRTests",
dependencies: [
.target(name: "SpeziFHIR")
],
swiftSettings: [
strictConcurrency
],
plugins: [] + swiftLintPlugin()
)
]
Expand Down
4 changes: 2 additions & 2 deletions Sources/SpeziFHIR/Extensions/FHIR+Identifiable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import ModelsR4


extension Resource: Identifiable {
extension Resource: @retroactive Identifiable {
public typealias ID = FHIRPrimitive<FHIRString>?
}


extension FHIRPrimitive: Identifiable where PrimitiveType: Identifiable { }
extension FHIRPrimitive: @retroactive Identifiable where PrimitiveType: Identifiable { }
7 changes: 5 additions & 2 deletions Sources/SpeziFHIR/FHIRStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ import enum ModelsDSTU2.ResourceProxy
import Spezi


/// Module to manage FHIR resources grouped into automatically computed and updated categories.
/// `Module` to manage FHIR resources grouped into automatically computed and updated categories.
///
/// The ``FHIRStore`` is automatically injected in the environment if you use the ``FHIR`` standard or can be used as a standalone module.
@Observable
public class FHIRStore: Module, EnvironmentAccessible, DefaultInitializable {
public final class FHIRStore: Module,
EnvironmentAccessible,
DefaultInitializable,
@unchecked Sendable /* `unchecked` `Sendable` conformance fine as access to `_resources` protected by `NSLock` */ {
private let lock = NSLock()
@ObservationIgnored private var _resources: [FHIRResource]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,31 @@ struct FHIRGetResourceLLMFunction: LLMFunction {
}


private static func filterFittingResources(_ fittingResources: [FHIRResource]) -> [FHIRResource] {
Self.logger.debug("Overall fitting Resources: \(fittingResources.count)")

var fittingResources = fittingResources

if fittingResources.count > 64 {
fittingResources = fittingResources.lazy.sorted(by: { $0.date ?? .distantPast < $1.date ?? .distantPast }).suffix(64)
Self.logger.debug(
"""
Reduced to the following 64 resources: \(fittingResources.map { $0.functionCallIdentifier }.joined(separator: ","))
"""
)
}

return fittingResources
}


func execute() async throws -> String? {
var functionOutput: [String] = []

try await withThrowingTaskGroup(of: [String].self) { outerGroup in
// Iterate over all requested resources by the LLM
for requestedResource in resources {
outerGroup.addTask {
outerGroup.addTask { @Sendable [fhirStore, resourceSummary] in
// Fetch relevant FHIR resources matching the resources requested by the LLM
var fittingResources = fhirStore.llmRelevantResources.filter { $0.functionCallIdentifier.contains(requestedResource) }

Expand All @@ -71,13 +89,14 @@ struct FHIRGetResourceLLMFunction: LLMFunction {
}

// Filter out fitting resources (if greater than 64 entries)
fittingResources = filterFittingResources(fittingResources)

fittingResources = Self.filterFittingResources(fittingResources)
try await withThrowingTaskGroup(of: String.self) { innerGroup in
// Iterate over fitting resources and summarizing them
for resource in fittingResources {
innerGroup.addTask {
try await summarizeResource(fhirResource: resource, resourceType: requestedResource)
innerGroup.addTask { @Sendable [resourceSummary] in
let summary = try await resourceSummary.summarize(resource: resource)
Self.logger.debug("Summary of appended FHIR resource \(requestedResource): \(summary.description)")
return String(localized: "This is the summary of the requested \(requestedResource):\n\n\(summary.description)")
}
}

Expand All @@ -97,27 +116,4 @@ struct FHIRGetResourceLLMFunction: LLMFunction {

return functionOutput.joined(separator: "\n\n")
}

private func summarizeResource(fhirResource: FHIRResource, resourceType: String) async throws -> String {
let summary = try await resourceSummary.summarize(resource: fhirResource)
Self.logger.debug("Summary of appended FHIR resource \(resourceType): \(summary.description)")
return String(localized: "This is the summary of the requested \(resourceType):\n\n\(summary.description)")
}

private func filterFittingResources(_ fittingResources: [FHIRResource]) -> [FHIRResource] {
Self.logger.debug("Overall fitting Resources: \(fittingResources.count)")

var fittingResources = fittingResources

if fittingResources.count > 64 {
fittingResources = fittingResources.lazy.sorted(by: { $0.date ?? .distantPast < $1.date ?? .distantPast }).suffix(64)
Self.logger.debug(
"""
Reduced to the following 64 resources: \(fittingResources.map { $0.functionCallIdentifier }.joined(separator: ","))
"""
)
}

return fittingResources
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public class FHIRMultipleResourceInterpreter {
}

/// Change the `LLMSchema` used by the ``FHIRMultipleResourceInterpreter``.
@MainActor
public func changeLLMSchema(
openAIModel model: LLMOpenAIModelType,
resourceCountLimit: Int,
Expand All @@ -123,7 +124,7 @@ public class FHIRMultipleResourceInterpreter {
}
self.llm = nil

Task { @MainActor in
Task {
await prepareLLM()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import SpeziLocalStorage

/// Responsible for interpreting FHIR resources.
@Observable
public class FHIRResourceInterpreter {
public final class FHIRResourceInterpreter: Sendable {
private let resourceProcessor: FHIRResourceProcessor<String>


Expand Down
14 changes: 7 additions & 7 deletions Sources/SpeziFHIRLLM/FHIRInterpretationModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ public class FHIRInterpretationModule: Module, DefaultInitializable {
public static var llmSchema: LLMOpenAISchema {
.init(
parameters: .init(
modelType: .gpt4_turbo_preview,
modelType: .gpt4_turbo,
systemPrompts: [] // No system prompt as this will be determined later by the resource interpreter
)
)
}
}


@Dependency private var localStorage: LocalStorage
@Dependency private var llmRunner: LLMRunner
@Dependency private var fhirStore: FHIRStore
@Dependency(LocalStorage.self) private var localStorage
@Dependency(LLMRunner.self) private var llmRunner
@Dependency(FHIRStore.self) private var fhirStore

@Model private var resourceSummary: FHIRResourceSummary
@Model private var resourceInterpreter: FHIRResourceInterpreter
Expand All @@ -43,9 +43,9 @@ public class FHIRInterpretationModule: Module, DefaultInitializable {


/// - Warning: Ensure that passed LLM schema's don't contain a system prompt! This will be configured by the ``FHIRInterpretationModule``.
public init<SummaryLLM: LLMSchema, InterpretationLLM: LLMSchema>( // swiftlint:disable:this function_default_parameter_at_end
public init<SummaryLLM: LLMSchema, InterpretationLLM: LLMSchema>(
summaryLLMSchema: SummaryLLM = Defaults.llmSchema,
interpretationLLMSchema: InterpretationLLM = Defaults.llmSchema,
interpretationLLMSchema: InterpretationLLM = Defaults.llmSchema, // swiftlint:disable:this function_default_parameter_at_end
multipleResourceInterpretationOpenAIModel: LLMOpenAIModelType, // swiftlint:disable:this identifier_name
resourceCountLimit: Int = 250,
allowedResourcesFunctionCallIdentifiers: Set<String>? = nil // swiftlint:disable:this discouraged_optional_collection
Expand All @@ -62,7 +62,7 @@ public class FHIRInterpretationModule: Module, DefaultInitializable {
self.init(
summaryLLMSchema: Defaults.llmSchema,
interpretationLLMSchema: Defaults.llmSchema,
multipleResourceInterpretationOpenAIModel: .gpt4_turbo_preview
multipleResourceInterpretationOpenAIModel: .gpt4_turbo
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import SpeziLLMOpenAI
import SpeziLocalStorage


class FHIRResourceProcessor<Content: Codable & LosslessStringConvertible> {
// Unchecked `Sendable` conformance is fine as storage is guarded by `NSLock`.
final class FHIRResourceProcessor<Content: Codable & LosslessStringConvertible>: @unchecked Sendable {
typealias Results = [FHIRResource.ID: Content]


Expand Down
4 changes: 2 additions & 2 deletions Sources/SpeziFHIRLLM/FHIRSummary/FHIRResourceSummary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import SpeziLocalStorage

/// Responsible for summarizing FHIR resources.
@Observable
public class FHIRResourceSummary {
public final class FHIRResourceSummary: Sendable {
/// Summary of a FHIR resource emitted by the ``FHIRResourceSummary``.
public struct Summary: Codable, LosslessStringConvertible {
public struct Summary: Codable, LosslessStringConvertible, Sendable {
/// Title of the FHIR resource, should be shorter than 4 words.
public let title: String
/// Summary of the FHIR resource, should be a single line of text.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziFHIRLLM/Settings/FHIRPrompt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation


/// Handle dynamic, localized LLM prompts for FHIR resources.
public struct FHIRPrompt: Hashable {
public struct FHIRPrompt: Hashable, Sendable {
/// Placeholder for FHIR resource in prompts.
public static let fhirResourcePlaceholder = "{{FHIR_RESOURCE}}"
/// Placeholder for the current locale in a prompt
Expand Down
Loading

0 comments on commit d368a1e

Please sign in to comment.