Skip to content

Commit

Permalink
Lift SpeziFHIR to SpeziLLM abstractions
Browse files Browse the repository at this point in the history
  • Loading branch information
philippzagar committed Feb 9, 2024
1 parent edacb79 commit e4a150b
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 59 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ let package = Package(
.package(url: "https://github.com/StanfordBDHG/HealthKitOnFHIR", .upToNextMinor(from: "0.2.4")),
.package(url: "https://github.com/StanfordSpezi/Spezi", from: "1.1.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziHealthKit.git", .upToNextMinor(from: "0.5.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziLLM.git", .upToNextMinor(from: "0.6.0")),
.package(url: "https://github.com/StanfordSpezi/SpeziLLM.git", branch: "feat/structural-improvements"),
.package(url: "https://github.com/StanfordSpezi/SpeziStorage.git", from: "1.0.0"),
.package(url: "https://github.com/StanfordSpezi/SpeziChat.git", .upToNextMinor(from: "0.1.4"))
],
Expand Down
16 changes: 7 additions & 9 deletions Sources/SpeziFHIRInterpretation/FHIRResourceInterpreter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,25 @@
//

import Foundation
import Observation
import SpeziFHIR
import SpeziLLM
import SpeziLLMOpenAI
import SpeziLocalStorage


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


/// - Parameters:
/// - localStorage: Local storage module that needs to be passed to the ``FHIRResourceInterpreter`` to allow it to cache interpretations.
/// - openAIModel: OpenAI module that needs to be passed to the ``FHIRResourceInterpreter`` to allow it to retrieve interpretations.
public init(localStorage: LocalStorage, llmRunner: LLMRunner, llm: any LLM) {
self.resourceProcesser = FHIRResourceProcesser(
public init(localStorage: LocalStorage, llmRunner: LLMRunner, llmSchema: any LLMSchema) {
self.resourceProcessor = FHIRResourceProcessor(
localStorage: localStorage,
llmRunner: llmRunner,
llm: llm,
llmSchema: llmSchema,
storageKey: "FHIRResourceInterpreter.Interpretations",
prompt: FHIRPrompt.interpretation
)
Expand All @@ -42,15 +40,15 @@ public class FHIRResourceInterpreter {
/// - Returns: An asynchronous `String` representing the interpretation of the resource.
@discardableResult
public func interpret(resource: FHIRResource, forceReload: Bool = false) async throws -> String {
try await resourceProcesser.process(resource: resource, forceReload: forceReload)
try await resourceProcessor.process(resource: resource, forceReload: forceReload)
}

/// Retrieve the cached interpretation of a given FHIR resource. Returns a human-readable interpretation or `nil` if it is not present.
///
/// - Parameter resource: The resource where the cached interpretation should be loaded from.
/// - Returns: The cached interpretation. Returns `nil` if the resource is not present.
public func cachedInterpretation(forResource resource: FHIRResource) -> String? {
resourceProcesser.results[resource.id]
public func cachedInterpretation(forResource resource: FHIRResource) async -> String? {
await resourceProcessor.results[resource.id]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,20 @@
// SPDX-License-Identifier: MIT
//

import Observation
import SpeziChat
import SpeziFHIR
import SpeziLLM
import SpeziLLMOpenAI
import SpeziLocalStorage


@Observable
class FHIRResourceProcesser<Content: Codable & LosslessStringConvertible> {
actor FHIRResourceProcessor<Content: Codable & LosslessStringConvertible> {
typealias Results = [FHIRResource.ID: Content]


private let localStorage: LocalStorage
private let llmRunner: LLMRunner
private let llm: any LLM
private let llmSchema: any LLMSchema
private let storageKey: String
private let prompt: FHIRPrompt

Expand All @@ -40,13 +38,13 @@ class FHIRResourceProcesser<Content: Codable & LosslessStringConvertible> {
init(
localStorage: LocalStorage,
llmRunner: LLMRunner,
llm: any LLM,
llmSchema: any LLMSchema,
storageKey: String,
prompt: FHIRPrompt
) {
self.localStorage = localStorage
self.llmRunner = llmRunner
self.llm = llm
self.llmSchema = llmSchema
self.storageKey = storageKey
self.prompt = prompt
self.results = (try? localStorage.read(storageKey: storageKey)) ?? [:]
Expand All @@ -59,19 +57,21 @@ class FHIRResourceProcesser<Content: Codable & LosslessStringConvertible> {
return result
}

let llm = await llmRunner(with: llmSchema)

await MainActor.run {
llm.context.append(.init(role: .system, content: prompt.prompt(withFHIRResource: resource.jsonDescription)))
llm.context.append(systemMessage: prompt.prompt(withFHIRResource: resource.jsonDescription))
}

let chatStreamResults = try await llmRunner(with: llm).generate()
let chatStreamResults = try await llm.generate()
var result = ""

for try await chatStreamResult in chatStreamResults {
result.append(chatStreamResult)
}

guard let content = Content(result) else {
throw FHIRResourceProcesserError.notParsableAsAString
throw FHIRResourceProcessorError.notParsableAsAString
}

results[resource.id] = content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation


enum FHIRResourceProcesserError: LocalizedError {
enum FHIRResourceProcessorError: LocalizedError {
case notParsableAsAString


Expand Down
16 changes: 7 additions & 9 deletions Sources/SpeziFHIRInterpretation/FHIRResourceSummary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@
//

import Foundation
import Observation
import SpeziFHIR
import SpeziLLM
import SpeziLLMOpenAI
import SpeziLocalStorage


/// Responsible for summarizing FHIR resources.
@Observable
public class FHIRResourceSummary {
/// Summary of a FHIR resource emited by the ``FHIRResourceSummary``.
/// Summary of a FHIR resource emitted by the ``FHIRResourceSummary``.
public struct Summary: Codable, LosslessStringConvertible {
/// Title of the FHIR resource, should be shorter than 4 words.
public let title: String
Expand All @@ -42,17 +40,17 @@ public class FHIRResourceSummary {
}


private let resourceProcesser: FHIRResourceProcesser<Summary>
private let resourceProcesser: FHIRResourceProcessor<Summary>


/// - Parameters:
/// - localStorage: Local storage module that needs to be passed to the ``FHIRResourceSummary`` to allow it to cache summaries.
/// - openAIModel: OpenAI module that needs to be passed to the ``FHIRResourceSummary`` to allow it to retrieve summaries.
public init(localStorage: LocalStorage, llmRunner: LLMRunner, llm: any LLM) {
self.resourceProcesser = FHIRResourceProcesser(
public init(localStorage: LocalStorage, llmRunner: LLMRunner, llmSchema: any LLMSchema) {
self.resourceProcesser = FHIRResourceProcessor(
localStorage: localStorage,
llmRunner: llmRunner,
llm: llm,
llmSchema: llmSchema,
storageKey: "FHIRResourceSummary.Summaries",
prompt: FHIRPrompt.summary
)
Expand All @@ -74,8 +72,8 @@ public class FHIRResourceSummary {
///
/// - Parameter resource: The resource where the cached summary should be loaded from.
/// - Returns: The cached summary. Returns `nil` if the resource is not present.
public func cachedSummary(forResource resource: FHIRResource) -> Summary? {
resourceProcesser.results[resource.id]
public func cachedSummary(forResource resource: FHIRResource) async -> Summary? {
await resourceProcesser.results[resource.id]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ public struct FHIRResourceSummaryView: View {
@Environment(FHIRResourceSummary.self) private var fhirResourceSummary

@State private var viewState: ViewState = .idle
@State private var cachedSummary: FHIRResourceSummary.Summary?

private let resource: FHIRResource


public var body: some View {
Group {
if let summary = fhirResourceSummary.cachedSummary(forResource: resource) {
if let summary = cachedSummary {
VStack(alignment: .leading, spacing: 0) {
Text(summary.title)
if let date = resource.date {
Expand Down Expand Up @@ -58,6 +59,9 @@ public struct FHIRResourceSummaryView: View {
}
}
.viewStateAlert(state: $viewState)
.task {
cachedSummary = await fhirResourceSummary.cachedSummary(forResource: resource)
}
}


Expand Down
2 changes: 1 addition & 1 deletion Sources/SpeziFHIRInterpretation/Settings/FHIRPrompt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public struct FHIRPrompt: Hashable {
}


/// Saves a new version of the propmpt.
/// Saves a new version of the prompt.
/// - Parameter prompt: The new prompt.
public func save(prompt: String) {
UserDefaults.standard.set(prompt, forKey: storageKey)
Expand Down
18 changes: 12 additions & 6 deletions Tests/UITests/TestApp/ExampleModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,25 @@ import SpeziLLM
import SpeziLocalStorage


class ExampleModule: Module {
private let llm = LLMMock()


class ExampleModule: Module, @unchecked Sendable {
@Dependency private var localStorage: LocalStorage
@Dependency private var llmRunner: LLMRunner

@Model private var resourceSummary: FHIRResourceSummary
@Model private var resourceInterpreter: FHIRResourceInterpreter

let llmSchema = LLMMockSchema()

func configure() {
resourceSummary = FHIRResourceSummary(localStorage: localStorage, llmRunner: llmRunner, llm: llm)
resourceInterpreter = FHIRResourceInterpreter(localStorage: localStorage, llmRunner: llmRunner, llm: llm)
resourceSummary = FHIRResourceSummary(
localStorage: localStorage,
llmRunner: llmRunner,
llmSchema: llmSchema
)
resourceInterpreter = FHIRResourceInterpreter(
localStorage: localStorage,
llmRunner: llmRunner,
llmSchema: llmSchema
)
}
}
4 changes: 3 additions & 1 deletion Tests/UITests/TestApp/TestAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import SwiftUI
class TestAppDelegate: SpeziAppDelegate {
override var configuration: Configuration {
Configuration(standard: FHIR()) {
LLMRunner()
LLMRunner {
LLMMockPlatform()
}
ExampleModule()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,25 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/llama.cpp",
"state" : {
"revision" : "bcbf5bf9f677b92262aa28a2849defeddc00505d",
"version" : "0.1.6"
"revision" : "b0611c7d3cb049822f9911878514e4706b80e2ac",
"version" : "0.1.8"
}
},
{
"identity" : "openai",
"kind" : "remoteSourceControl",
"location" : "https://github.com/MacPaw/OpenAI",
"state" : {
"revision" : "ac5892fd0de8d283362ddc30f8e9f1a0eaba8cc0",
"version" : "0.2.5"
"revision" : "35afc9a6ee127b8f22a85a31aec2036a987478af"
}
},
{
"identity" : "semaphore",
"kind" : "remoteSourceControl",
"location" : "https://github.com/groue/Semaphore.git",
"state" : {
"revision" : "f1c4a0acabeb591068dea6cffdd39660b86dec28",
"version" : "0.0.8"
}
},
{
Expand All @@ -50,8 +58,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziChat.git",
"state" : {
"revision" : "9d45c10bcf859c98f2998ecd4f6a80f31894fe2c",
"version" : "0.1.4"
"revision" : "ea5e21b4f42d99a5549dd7a7033e2a3efeb5fd36",
"version" : "0.1.5"
}
},
{
Expand All @@ -72,22 +80,13 @@
"version" : "0.5.0"
}
},
{
"identity" : "spezillm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziLLM.git",
"state" : {
"revision" : "24d6c197f1821925e3fc1ee9589859b6853aee01",
"version" : "0.6.0"
}
},
{
"identity" : "spezionboarding",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziOnboarding",
"state" : {
"revision" : "3ee713576eaeaa03200ba26bbc1269ceeb6abb25",
"version" : "1.0.1"
"revision" : "8fb6d9f1a080661c0cc564a93b82ead3c8d44d4f",
"version" : "1.0.2"
}
},
{
Expand All @@ -113,17 +112,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziViews",
"state" : {
"revision" : "0137e69d156bf4001a8d6bf5661c9a37b2bbd0aa",
"version" : "1.0.0"
"revision" : "7210f72d6821d2eeb93438b29cb854a8ce334164",
"version" : "1.2.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307",
"version" : "1.0.5"
"revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
"version" : "1.1.0"
}
},
{
Expand Down

0 comments on commit e4a150b

Please sign in to comment.