Skip to content

Commit

Permalink
Refactor to use SpeziLLM, SpeziChat, and Spezi 1.x (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
vishnuravi authored Mar 28, 2024
1 parent 0d5fee8 commit cbc216e
Show file tree
Hide file tree
Showing 41 changed files with 1,022 additions and 411 deletions.
Binary file added Figures/Chat-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
Binary file added Figures/Chat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions Figures/Chat.png.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the HealthGPT open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Binary file added Figures/Export-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions Figures/Export-dark.png.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the HealthGPT open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Binary file added Figures/Export.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions Figures/Export.png.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the HealthGPT open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Binary file added Figures/Settings-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions Figures/Settings-dark.png.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the HealthGPT open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
Binary file added Figures/Settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions Figures/Settings.png.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This source file is part of the HealthGPT open-source project

SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)

SPDX-License-Identifier: MIT
112 changes: 51 additions & 61 deletions HealthGPT.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,75 +1,121 @@
{
"originHash" : "98a3d0589c1bc602c69147b70f8a44c2f8b44cfc1469b8ed82df6bf8db92de53",
"pins" : [
{
"identity" : "llama.cpp",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/llama.cpp",
"state" : {
"revision" : "7bfd6d4b5bbc9fd47bd023bdbb35f96c827977f3",
"version" : "0.2.1"
}
},
{
"identity" : "openai",
"kind" : "remoteSourceControl",
"location" : "https://github.com/MacPaw/OpenAI.git",
"state" : {
"revision" : "c45f3320ffa760f043c0239f724850c0e4f8bde5",
"version" : "0.2.4"
"revision" : "35afc9a6ee127b8f22a85a31aec2036a987478af",
"version" : "0.2.6"
}
},
{
"identity" : "spezi",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/Spezi.git",
"state" : {
"revision" : "9ad506d4d2e36eb7a0c1ff8cc6bb0ef9c972724c",
"version" : "0.7.1"
"revision" : "c43e4fa3d3938a847de2b677091a34ddaea5bc76",
"version" : "1.2.3"
}
},
{
"identity" : "spezichat",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziChat",
"state" : {
"revision" : "2334583105224b0c04fc36989db82b000021d31d",
"version" : "0.1.9"
}
},
{
"identity" : "spezifoundation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziFoundation",
"state" : {
"revision" : "01af5b91a54f30ddd121258e81aff2ddc2a99ff9",
"version" : "1.0.4"
}
},
{
"identity" : "spezihealthkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziHealthKit.git",
"state" : {
"revision" : "f8f664549e81c8fa107a1fff616e0eaca6e8a6fa",
"version" : "0.3.1"
"revision" : "1e9cb5a6036ac7f4ff37ea1c3ed4898103339ad1",
"version" : "0.5.3"
}
},
{
"identity" : "speziml",
"identity" : "spezillm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziML.git",
"location" : "https://github.com/StanfordSpezi/SpeziLLM",
"state" : {
"revision" : "63cb6659876c58529407d7fa3556228345f9faa1",
"version" : "0.2.6"
"revision" : "dc37b91ed55c9d50eaf58e645d454cb62e3681d1",
"version" : "0.7.2"
}
},
{
"identity" : "spezionboarding",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziOnboarding.git",
"state" : {
"revision" : "0ea46a66c17615e1a933481a07434bfd41717c54",
"version" : "0.6.0"
"revision" : "4971a82e94996ce0c3d8ecf64fdeec874a1f20d6",
"version" : "1.1.1"
}
},
{
"identity" : "spezispeech",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziSpeech",
"state" : {
"revision" : "60b8cdbf6f3d58b0d75eadf30db50f88848069aa",
"version" : "1.0.1"
}
},
{
"identity" : "spezistorage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziStorage.git",
"state" : {
"revision" : "77666f57cc0b7f148bc3949f173db8498ef9b4a6",
"version" : "0.4.1"
"revision" : "b958df9b31f24800388a7bfc28f457ce7b82556c",
"version" : "1.0.2"
}
},
{
"identity" : "speziviews",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziViews",
"state" : {
"revision" : "4b7cc423fd823123d354ec1d541ca7d2e0a9d6e3",
"version" : "0.5.1"
"revision" : "4d2a724d97c8f19ac7de7aa2c046b1cb3ef7b279",
"version" : "1.3.1"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
"version" : "1.1.0"
}
},
{
"identity" : "xctestextensions",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/XCTestExtensions.git",
"state" : {
"revision" : "625477e0937294cb3fd6e7bbf72b78f951644b1d",
"version" : "0.4.6"
"revision" : "1fe9b8e76aeb7a132af37bfa0892160c9b662dcc",
"version" : "0.4.10"
}
},
{
Expand All @@ -86,10 +132,10 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordBDHG/XCTRuntimeAssertions.git",
"state" : {
"revision" : "9226052589b8faece98861bc3d7b33b3ebfe4f5a",
"version" : "0.2.5"
"revision" : "51da3403f128b120705571ce61e0fe190f8889e6",
"version" : "1.0.1"
}
}
],
"version" : 2
"version" : 3
}
14 changes: 14 additions & 0 deletions HealthGPT.xcodeproj/xcshareddata/xcschemes/HealthGPT.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@
ReferencedContainer = "container:HealthGPT.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "--mockMode"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--showOnboarding"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--resetSecureStorage"
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
6 changes: 1 addition & 5 deletions HealthGPT/HealthGPT/HealthDataFetcher+Process.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ extension HealthDataFetcher {
/// Fetches and processes health data for the last 14 days.
///
/// - Returns: An array of `HealthData` objects, one for each day in the last 14 days.
///
/// - Throws: `HealthDataFetcherError.authorizationFailed` if health data authorization fails.
func fetchAndProcessHealthData() async throws -> [HealthData] {
try await requestAuthorization()

func fetchAndProcessHealthData() async -> [HealthData] {
let calendar = Calendar.current
let today = Date()
var healthData: [HealthData] = []
Expand Down
28 changes: 7 additions & 21 deletions HealthGPT/HealthGPT/HealthDataFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,15 @@
//

import HealthKit
import Spezi


class HealthDataFetcher {
private let healthStore = HKHealthStore()

/// Requests authorization to access the user's health data.
///
/// - Returns: A `Bool` value indicating whether the authorization was successful.
func requestAuthorization() async throws {
guard HKHealthStore.isHealthDataAvailable() else {
throw HKError(.errorHealthDataUnavailable)
}

let types: Set = [
HKQuantityType(.stepCount),
HKQuantityType(.appleExerciseTime),
HKQuantityType(.bodyMass),
HKQuantityType(.heartRate),
HKCategoryType(.sleepAnalysis)
]

try await healthStore.requestAuthorization(toShare: Set<HKSampleType>(), read: types)
}
@Observable
class HealthDataFetcher: DefaultInitializable, Module, EnvironmentAccessible {
@ObservationIgnored private let healthStore = HKHealthStore()

required init() { }


/// Fetches the user's health data for the specified quantity type identifier for the last two weeks.
///
Expand Down
108 changes: 54 additions & 54 deletions HealthGPT/HealthGPT/HealthDataInterpreter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,73 +8,73 @@

import Foundation
import Spezi
import SpeziOpenAI
import SpeziChat
import SpeziLLM
import SpeziLLMOpenAI
import SpeziSpeechSynthesizer


class HealthDataInterpreter: DefaultInitializable, Component, ObservableObject, ObservableObjectProvider {
@Dependency var openAIComponent = OpenAIComponent()
@Observable
class HealthDataInterpreter: DefaultInitializable, Module, EnvironmentAccessible {
@ObservationIgnored @Dependency private var llmRunner: LLMRunner
@ObservationIgnored @Dependency private var healthDataFetcher: HealthDataFetcher

var llm: (any LLMSession)?
@ObservationIgnored private var systemPrompt = ""

required init() { }


var querying = false {
willSet {
_Concurrency.Task { @MainActor in
objectWillChange.send()
}
/// Creates an `LLMSchema`, sets it up for use with an `LLMRunner`, injects the system prompt
/// into the context, and assigns the resulting `LLMSession` to the `llm` property. For more
/// information, please refer to the [`SpeziLLM`](https://swiftpackageindex.com/StanfordSpezi/SpeziLLM/documentation/spezillm) documentation.
///
/// If the `--mockMode` feature flag is set, this function will use `LLMMockSchema()`, otherwise
/// will use `LLMOpenAISchema` with the model type specified in the `model` parameter.
/// - Parameter model: the type of OpenAI model to use
@MainActor
func prepareLLM(with model: LLMOpenAIModelType) async {
var llmSchema: any LLMSchema

if FeatureFlags.mockMode {
llmSchema = LLMMockSchema()
} else {
llmSchema = LLMOpenAISchema(parameters: .init(modelType: model))
}

let llm = llmRunner(with: llmSchema)
systemPrompt = await generateSystemPrompt()
llm.context.append(systemMessage: systemPrompt)
self.llm = llm
}

var runningPrompt: [Chat] = [] {
willSet {
_Concurrency.Task { @MainActor in
objectWillChange.send()
}
/// Queries the LLM using the current session in the `llm` property and adds the output to the context.
@MainActor
func queryLLM() async throws {
guard let llm,
llm.context.last?.role == .user || !(llm.context.contains(where: { $0.role == .assistant }) ) else {
return
}
didSet {
_Concurrency.Task {
if runningPrompt.last?.role == .user {
do {
try await queryOpenAI()
} catch {
print(error)
}
}
}

let stream = try await llm.generate()

for try await token in stream {
llm.context.append(assistantOutput: token)
}
}


required init() {}


func generateMainPrompt() async throws {
let healthDataFetcher = HealthDataFetcher()
let healthData = try await healthDataFetcher.fetchAndProcessHealthData()

let generator = PromptGenerator(with: healthData)
let mainPrompt = generator.buildMainPrompt()
runningPrompt = [Chat(role: .system, content: mainPrompt)]
/// Resets the LLM context and re-injects the system prompt.
@MainActor
func resetChat() async {
systemPrompt = await generateSystemPrompt()
llm?.context.reset()
llm?.context.append(systemMessage: systemPrompt)
}

func queryOpenAI() async throws {
querying = true

let chatStreamResults = try await openAIComponent.queryAPI(withChat: runningPrompt)

for try await chatStreamResult in chatStreamResults {
for choice in chatStreamResult.choices {
if runningPrompt.last?.role == .assistant {
let previousChatMessage = runningPrompt.last ?? Chat(role: .assistant, content: "")
runningPrompt[runningPrompt.count - 1] = Chat(
role: .assistant,
content: (previousChatMessage.content ?? "") + (choice.delta.content ?? "")
)
} else {
runningPrompt.append(Chat(role: .assistant, content: choice.delta.content ?? ""))
}
}
}

querying = false
/// Fetches updated health data using the `HealthDataFetcher`
/// and passes it to the `PromptGenerator` to create the system prompt.
private func generateSystemPrompt() async -> String {
let healthData = await healthDataFetcher.fetchAndProcessHealthData()
return PromptGenerator(with: healthData).buildMainPrompt()
}
}
Loading

0 comments on commit cbc216e

Please sign in to comment.