From 352213a15adf85d1534448f4d8eafc5d907c7b09 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:51:23 -0800 Subject: [PATCH 01/14] onAppear functionality --- Intake/ChiefComplaint/LLMInteraction.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index a0c458b..0d4be58 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -51,6 +51,17 @@ struct LLMInteraction: View { .sheet(isPresented: $showOnboarding) { LLMOnboardingView(showOnboarding: $showOnboarding) } + .onAppear { + Task { + do { + let stream = try await runner(with: model).generate(prompt: "Say hello!") + + for try await token in stream { + model.context.append(assistantOutput: token) + } + } + } + } } } From 32d0b9a54f34bbc4b3aefa91100dbbc474cab6f2 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Fri, 2 Feb 2024 15:33:11 -0800 Subject: [PATCH 02/14] Finalize MVP Chat assistant --- Intake/ChiefComplaint/LLMInteraction.swift | 24 +++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index 0d4be58..8214f72 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -28,8 +28,16 @@ struct LLMInteraction: View { systemPrompt: """ You are acting as an intake person at a clinic and need to work with\ the patient to help clarify their chief complaint into a concise,\ - specific complaint which includes elements of laterality if\ - appropriate, as well as severity and duration.\ + specific complaint. + + You should always ask about severity and duration if the patient does not include this information. + + Additionally, help guide the patient into providing information specific to the condition that the define.\ + For example, if the patient is experiencing leg pain, you should prompt them to be more\ + specific about laterality and location. You should also ask if the pain is dull or sharp,\ + and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ + should inquire whether the cough is wet or dry, as well as any other characteristics of the\ + cough that might allow a doctor to rule out diagnoses. Please use everyday layman terms and avoid using complex medical terminology.\ Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). @@ -54,9 +62,19 @@ struct LLMInteraction: View { .onAppear { Task { do { - let stream = try await runner(with: model).generate(prompt: "Say hello!") + let stream = try await runner(with: model).generate(prompt: """ + Hello! I am a patient coming in to see the doctor and would like\ + to discuss the reason for my visit. + """) + + var isFirstToken = true for try await token in stream { + print(token) + if isFirstToken { + isFirstToken = false + continue + } model.context.append(assistantOutput: token) } } From 09b78d8145e58c6744a0cc1e6f6a6be2a7babfb4 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Fri, 2 Feb 2024 17:06:14 -0800 Subject: [PATCH 03/14] LLM Finish --- Intake.xcodeproj/project.pbxproj | 4 + Intake/ChiefComplaint/LLMInteraction.swift | 111 +++++++++++++++------ Intake/ChiefComplaint/SummaryView.swift | 29 ++++++ Intake/Resources/Localizable.xcstrings | 3 + 4 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 Intake/ChiefComplaint/SummaryView.swift diff --git a/Intake.xcodeproj/project.pbxproj b/Intake.xcodeproj/project.pbxproj index b427bb8..6f0590b 100644 --- a/Intake.xcodeproj/project.pbxproj +++ b/Intake.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ F42AB1DC2B637C8C002E13A6 /* LLMOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42AB1DB2B637C8C002E13A6 /* LLMOnboardingView.swift */; }; F42AB1DF2B637C9D002E13A6 /* LLMInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42AB1DE2B637C9C002E13A6 /* LLMInteraction.swift */; }; F42AB1E52B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42AB1E42B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift */; }; + F42AB1EC2B6DBF21002E13A6 /* SummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42AB1EB2B6DBF20002E13A6 /* SummaryView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -152,6 +153,7 @@ F42AB1DB2B637C8C002E13A6 /* LLMOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMOnboardingView.swift; sourceTree = ""; }; F42AB1DE2B637C9C002E13A6 /* LLMInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMInteraction.swift; sourceTree = ""; }; F42AB1E42B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMOpenAITokenOnboarding.swift; sourceTree = ""; }; + F42AB1EB2B6DBF20002E13A6 /* SummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummaryView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -389,6 +391,7 @@ F42AB1E12B637CAC002E13A6 /* OpenAI */, F42AB1DB2B637C8C002E13A6 /* LLMOnboardingView.swift */, F42AB1DE2B637C9C002E13A6 /* LLMInteraction.swift */, + F42AB1EB2B6DBF20002E13A6 /* SummaryView.swift */, ); path = ChiefComplaint; sourceTree = ""; @@ -638,6 +641,7 @@ 56F6F2A02AB441930022FE5A /* ContributionsList.swift in Sources */, 566155292AB8447C00209B80 /* Package+LicenseType.swift in Sources */, 5680DD392AB8983D004E6D4A /* PackageCell.swift in Sources */, + F42AB1EC2B6DBF21002E13A6 /* SummaryView.swift in Sources */, 2F5E32BD297E05EA003432F8 /* IntakeDelegate.swift in Sources */, 2FE5DC5229EDD7FA004B9AB4 /* IntakeScheduler.swift in Sources */, F42AB1E52B6383F9002E13A6 /* LLMOpenAITokenOnboarding.swift in Sources */, diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index 8214f72..d0fcb4b 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -17,11 +17,74 @@ import SpeziLLMOpenAI import SwiftUI struct LLMInteraction: View { + @State private var chiefComplaint: String? = "blah blah blah" + + struct SummarizeFunction: LLMFunction { + static let name: String = "summarize_complaint" + static let description: String = """ + When there is enough information to give to the doctor,\ + summarize the conversation into a concise Chief Complaint. + """ + + @Parameter(description: "The primary medical concern that the patient is experiencing.") var medicalConcern: String + + @Parameter(description: "The severity of the primary medical concern.") var severity: String + + @Parameter(description: "The duration of the primary medical concern.") var duration: String + + @Parameter(description: "Extra important information relevant to the primary medical concern that the doctor should be aware of.") + var supplementaryInfo: String + + @Binding var chiefComplaint: String + + func execute() async throws -> String? { + let summary = """ + Here is the summary that will be provided to your doctor:\n + Primary concern: \(medicalConcern)\n + Severity: \(severity)\n + Duration: \(duration)\n + Extra Info: \(supplementaryInfo)\n + """ + chiefComplaint = summary + return summary + } + } + + @State private var shouldNavigateToSummaryView = true @Binding var presentingAccount: Bool @Environment(LLMRunner.self) var runner: LLMRunner @State var responseText: String @State var showOnboarding = true +// @State var model: LLM + +// init() { +// model = LLMOpenAI( +// parameters: .init( +// modelType: .gpt3_5Turbo, +// systemPrompt: """ +// You are acting as an intake person at a clinic and need to work with\ +// the patient to help clarify their chief complaint into a concise,\ +// specific complaint. +// +// You should always ask about severity and duration if the patient does not include this information. +// +// Additionally, help guide the patient into providing information specific to the condition that the define.\ +// For example, if the patient is experiencing leg pain, you should prompt them to be more\ +// specific about laterality and location. You should also ask if the pain is dull or sharp,\ +// and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ +// should inquire whether the cough is wet or dry, as well as any other characteristics of the\ +// cough that might allow a doctor to rule out diagnoses. +// +// Please use everyday layman terms and avoid using complex medical terminology.\ +// Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). +// """ +// ) +// ) +// $model.function = SummarizeFunction(ChiefComplaint: $ChiefComplaint) +// } + + @State var model: LLM = LLMOpenAI( parameters: .init( modelType: .gpt3_5Turbo, @@ -43,7 +106,9 @@ struct LLMInteraction: View { Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). """ ) - ) + ) { +// SummarizeFunction(ChiefComplaint: $ChiefComplaint) + } var body: some View { NavigationStack { @@ -65,40 +130,24 @@ struct LLMInteraction: View { let stream = try await runner(with: model).generate(prompt: """ Hello! I am a patient coming in to see the doctor and would like\ to discuss the reason for my visit. - """) - + """) var isFirstToken = true - for try await token in stream { - print(token) if isFirstToken { isFirstToken = false - continue - } - model.context.append(assistantOutput: token) - } - } - } - } - } - } - - func executePrompt(prompt: String) async { - // Execute the query on the runner, returning a stream of outputs - let stream = try? await runner(with: model).generate(prompt: prompt) - - if let unwrappedStream = stream { - do { - for try await token in unwrappedStream { - responseText.append(token) - } - } catch { - // Handle any errors that occurred during the asynchronous operation - print("Error: \(error)") - } - } - } -} + continue } + model.context.append(assistantOutput: token)}}}} + .onChange(of: chiefComplaint) { _, newChiefComplaint in + if let newChiefComplaint = newChiefComplaint { + shouldNavigateToSummaryView = true}} + .background( + NavigationLink( + destination: SummaryView(chiefComplaint: chiefComplaint ?? "error"), + isActive: $shouldNavigateToSummaryView + ) { EmptyView() } + .isDetailLink(false) + .navigationDestination(isPresented: $shouldNavigateToSummaryView) { + EmptyView() })}}} #Preview { diff --git a/Intake/ChiefComplaint/SummaryView.swift b/Intake/ChiefComplaint/SummaryView.swift new file mode 100644 index 0000000..491f2c4 --- /dev/null +++ b/Intake/ChiefComplaint/SummaryView.swift @@ -0,0 +1,29 @@ +// +// SummaryView.swift +// Intake +// +// Created by Nick Riedman on 2/2/24. +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import Foundation +import SwiftUI + +struct SummaryView: View { + let chiefComplaint: String + + var body: some View { + VStack { + Text(chiefComplaint) + .padding() + .multilineTextAlignment(.center) + } + .navigationTitle("Summary") + } + +} diff --git a/Intake/Resources/Localizable.xcstrings b/Intake/Resources/Localizable.xcstrings index 7001427..7ef4ec8 100644 --- a/Intake/Resources/Localizable.xcstrings +++ b/Intake/Resources/Localizable.xcstrings @@ -381,6 +381,9 @@ }, "Summarize your surgical history." : { + }, + "Summary" : { + }, "Surgical History" : { From 2013ec20b9c781394104433c3068bde11a96dd01 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Sat, 3 Feb 2024 12:45:40 -0800 Subject: [PATCH 04/14] Cleanup --- Intake/ChiefComplaint/LLMInteraction.swift | 78 ++++++++++++---------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index d0fcb4b..26772e5 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -32,8 +32,7 @@ struct LLMInteraction: View { @Parameter(description: "The duration of the primary medical concern.") var duration: String - @Parameter(description: "Extra important information relevant to the primary medical concern that the doctor should be aware of.") - var supplementaryInfo: String + @Parameter(description: "Extra important information relevant to the primary medical concern that the doctor should be aware of.") var supplementaryInfo: String @Binding var chiefComplaint: String @@ -56,33 +55,33 @@ struct LLMInteraction: View { @State var responseText: String @State var showOnboarding = true -// @State var model: LLM + // @State var model: LLM -// init() { -// model = LLMOpenAI( -// parameters: .init( -// modelType: .gpt3_5Turbo, -// systemPrompt: """ -// You are acting as an intake person at a clinic and need to work with\ -// the patient to help clarify their chief complaint into a concise,\ -// specific complaint. -// -// You should always ask about severity and duration if the patient does not include this information. -// -// Additionally, help guide the patient into providing information specific to the condition that the define.\ -// For example, if the patient is experiencing leg pain, you should prompt them to be more\ -// specific about laterality and location. You should also ask if the pain is dull or sharp,\ -// and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ -// should inquire whether the cough is wet or dry, as well as any other characteristics of the\ -// cough that might allow a doctor to rule out diagnoses. -// -// Please use everyday layman terms and avoid using complex medical terminology.\ -// Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). -// """ -// ) -// ) -// $model.function = SummarizeFunction(ChiefComplaint: $ChiefComplaint) -// } + // init() { + // model = LLMOpenAI( + // parameters: .init( + // modelType: .gpt3_5Turbo, + // systemPrompt: """ + // You are acting as an intake person at a clinic and need to work with\ + // the patient to help clarify their chief complaint into a concise,\ + // specific complaint. + // + // You should always ask about severity and duration if the patient does not include this information. + // + // Additionally, help guide the patient into providing information specific to the condition that the define.\ + // For example, if the patient is experiencing leg pain, you should prompt them to be more\ + // specific about laterality and location. You should also ask if the pain is dull or sharp,\ + // and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ + // should inquire whether the cough is wet or dry, as well as any other characteristics of the\ + // cough that might allow a doctor to rule out diagnoses. + // + // Please use everyday layman terms and avoid using complex medical terminology.\ + // Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). + // """ + // ) + // ) + // $model.function = SummarizeFunction(ChiefComplaint: $ChiefComplaint) + // } @State var model: LLM = LLMOpenAI( @@ -107,7 +106,7 @@ struct LLMInteraction: View { """ ) ) { -// SummarizeFunction(ChiefComplaint: $ChiefComplaint) + // SummarizeFunction(ChiefComplaint: $ChiefComplaint) } var body: some View { @@ -136,18 +135,29 @@ struct LLMInteraction: View { if isFirstToken { isFirstToken = false continue } - model.context.append(assistantOutput: token)}}}} + model.context.append(assistantOutput: token) + } + } + } + } .onChange(of: chiefComplaint) { _, newChiefComplaint in if let newChiefComplaint = newChiefComplaint { - shouldNavigateToSummaryView = true}} + shouldNavigateToSummaryView = true + } + } .background( NavigationLink( destination: SummaryView(chiefComplaint: chiefComplaint ?? "error"), isActive: $shouldNavigateToSummaryView ) { EmptyView() } - .isDetailLink(false) - .navigationDestination(isPresented: $shouldNavigateToSummaryView) { - EmptyView() })}}} + .isDetailLink(false) + .navigationDestination(isPresented: $shouldNavigateToSummaryView) { + EmptyView() + } + ) + } + } +} #Preview { From 68e5a4b8b9eac4895c59e8e0b526e3e1f535e31d Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Sat, 3 Feb 2024 12:59:26 -0800 Subject: [PATCH 05/14] Got rid of weird old chatgpt code --- Intake/ChiefComplaint/LLMInteraction.swift | 32 ++-------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index 26772e5..a3e82b9 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -55,34 +55,6 @@ struct LLMInteraction: View { @State var responseText: String @State var showOnboarding = true - // @State var model: LLM - - // init() { - // model = LLMOpenAI( - // parameters: .init( - // modelType: .gpt3_5Turbo, - // systemPrompt: """ - // You are acting as an intake person at a clinic and need to work with\ - // the patient to help clarify their chief complaint into a concise,\ - // specific complaint. - // - // You should always ask about severity and duration if the patient does not include this information. - // - // Additionally, help guide the patient into providing information specific to the condition that the define.\ - // For example, if the patient is experiencing leg pain, you should prompt them to be more\ - // specific about laterality and location. You should also ask if the pain is dull or sharp,\ - // and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ - // should inquire whether the cough is wet or dry, as well as any other characteristics of the\ - // cough that might allow a doctor to rule out diagnoses. - // - // Please use everyday layman terms and avoid using complex medical terminology.\ - // Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). - // """ - // ) - // ) - // $model.function = SummarizeFunction(ChiefComplaint: $ChiefComplaint) - // } - @State var model: LLM = LLMOpenAI( parameters: .init( @@ -149,7 +121,9 @@ struct LLMInteraction: View { NavigationLink( destination: SummaryView(chiefComplaint: chiefComplaint ?? "error"), isActive: $shouldNavigateToSummaryView - ) { EmptyView() } + ) { + EmptyView() + } .isDetailLink(false) .navigationDestination(isPresented: $shouldNavigateToSummaryView) { EmptyView() From fe71d5ff2c992639483a9585f76d59cfbe987316 Mon Sep 17 00:00:00 2001 From: Nina Boord <86579493+ninaboord@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:02:42 -0800 Subject: [PATCH 06/14] changes --- Intake/ChiefComplaint/LLMInteraction.swift | 41 +++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index d0fcb4b..7a472d6 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -56,6 +56,9 @@ struct LLMInteraction: View { @State var responseText: String @State var showOnboarding = true +// @State var charSystemPrompt: self.ChatEntity +// @State var chatRole: self.ChatEntity + // @State var model: LLM // init() { @@ -111,6 +114,8 @@ struct LLMInteraction: View { } var body: some View { + + NavigationStack { LLMChatView( model: model @@ -124,19 +129,29 @@ struct LLMInteraction: View { .sheet(isPresented: $showOnboarding) { LLMOnboardingView(showOnboarding: $showOnboarding) } - .onAppear { - Task { - do { - let stream = try await runner(with: model).generate(prompt: """ - Hello! I am a patient coming in to see the doctor and would like\ - to discuss the reason for my visit. - """) - var isFirstToken = true - for try await token in stream { - if isFirstToken { - isFirstToken = false - continue } - model.context.append(assistantOutput: token)}}}} + .onAppear{ + model.context.append() + } + // greeting +// .onAppear { +// Task { +// do { +// let stream = try await runner(with: model).generate(prompt: """ +// Hello! I am a patient coming in to see the doctor and would like\ +// to discuss the reason for my visit. +// """) +// var isFirstToken = true +// for try await token in stream { +// if isFirstToken { +// isFirstToken = false +// continue } +// model.context.append(assistantOutput: token)}}}} +// + + + + + // navigation to summary view after chat .onChange(of: chiefComplaint) { _, newChiefComplaint in if let newChiefComplaint = newChiefComplaint { shouldNavigateToSummaryView = true}} From b21c2436dafdb0ae78904506dbd18b73886c380b Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:03:31 -0800 Subject: [PATCH 07/14] Resolved too long line --- Intake/ChiefComplaint/LLMInteraction.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index a3e82b9..38c8626 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -32,7 +32,11 @@ struct LLMInteraction: View { @Parameter(description: "The duration of the primary medical concern.") var duration: String - @Parameter(description: "Extra important information relevant to the primary medical concern that the doctor should be aware of.") var supplementaryInfo: String + static let desc: String = """ + Extra important information relevant to the primary\ + medical concern that the doctor should be aware of. + """ + @Parameter(description: desc) var supplementaryInfo: String @Binding var chiefComplaint: String From 6582fdf09d7883047959be443601c1e7dcc039ca Mon Sep 17 00:00:00 2001 From: Nina Boord <86579493+ninaboord@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:05:45 -0800 Subject: [PATCH 08/14] ehllo --- Intake/ChiefComplaint/LLMInteraction.swift | 150 ++++++++++++++++++++- 1 file changed, 148 insertions(+), 2 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index 901f87e..382cd41 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -109,12 +109,154 @@ struct LLMInteraction: View { """ ) ) { - // SummarizeFunction(ChiefComplaint: $ChiefComplaint) +// SummarizeFunction(ChiefComplaint: $ChiefComplaint) } var body: some View { + NavigationStack { + LLMChatView( + model: model + ) + .navigationTitle("Chief Complaint") + .toolbar { + if AccountButton.shouldDisplay { + AccountButton(isPresented: $presentingAccount) + } + } + .sheet(isPresented: $showOnboarding) { + LLMOnboardingView(showOnboarding: $showOnboarding) + } + .onAppear{ + model.context.append() + } + // greeting +// .onAppear { +// Task { +// do { +// let stream = try await runner(with: model).generate(prompt: """ +// Hello! I am a patient coming in to see the doctor and would like\ +// to discuss the reason for my visit. +// """) +// var isFirstToken = true +// for try await token in stream { +// if isFirstToken { +// isFirstToken = false +// continue } +// model.context.append(assistantOutput: token)}}}} +// + + + + + // navigation to summary view after chat + .onChange(of: chiefComplaint) { _, newChiefComplaint in + if let newChiefComplaint = newChiefComplaint { + shouldNavigateToSummaryView = true}} + .background( + NavigationLink( + destination: SummaryView(chiefComplaint: chiefComplaint ?? "error"), + isActive: $shouldNavigateToSummaryView + ) { EmptyView() } + .isDetailLink(false) + .navigationDestination(isPresented: $shouldNavigateToSummaryView) { + EmptyView() })}}} + + +#Preview { + LLMInteraction(presentingAccount: .constant(true), responseText: "Test") + .previewWith { + LLMRunner { + LLMOpenAIRunnerSetupTask() + } + } +} + +// +// LLMInteraction.swift +// Intake +// +// Created by Nick Riedman on 1/25/24. +// +// This source file is part of the Intake based on the Stanford Spezi Template Application project +// +// SPDX-FileCopyrightText: 2023 Stanford University +// +// SPDX-License-Identifier: MIT +// + +import SpeziLLM +import SpeziLLMLocal +import SpeziLLMOpenAI +import SwiftUI + +struct LLMInteraction: View { + @State private var chiefComplaint: String? = "blah blah blah" + + struct SummarizeFunction: LLMFunction { + static let name: String = "summarize_complaint" + static let description: String = """ + When there is enough information to give to the doctor,\ + summarize the conversation into a concise Chief Complaint. + """ + + @Parameter(description: "The primary medical concern that the patient is experiencing.") var medicalConcern: String + + @Parameter(description: "The severity of the primary medical concern.") var severity: String + + @Parameter(description: "The duration of the primary medical concern.") var duration: String + + @Parameter(description: "Extra important information relevant to the primary medical concern that the doctor should be aware of.") var supplementaryInfo: String + + @Binding var chiefComplaint: String + + func execute() async throws -> String? { + let summary = """ + Here is the summary that will be provided to your doctor:\n + Primary concern: \(medicalConcern)\n + Severity: \(severity)\n + Duration: \(duration)\n + Extra Info: \(supplementaryInfo)\n + """ + chiefComplaint = summary + return summary + } + } + + @State private var shouldNavigateToSummaryView = true + @Binding var presentingAccount: Bool + @Environment(LLMRunner.self) var runner: LLMRunner + + @State var responseText: String + @State var showOnboarding = true + + @State var model: LLM = LLMOpenAI( + parameters: .init( + modelType: .gpt3_5Turbo, + systemPrompt: """ + You are acting as an intake person at a clinic and need to work with\ + the patient to help clarify their chief complaint into a concise,\ + specific complaint. + + You should always ask about severity and duration if the patient does not include this information. + + Additionally, help guide the patient into providing information specific to the condition that the define.\ + For example, if the patient is experiencing leg pain, you should prompt them to be more\ + specific about laterality and location. You should also ask if the pain is dull or sharp,\ + and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ + should inquire whether the cough is wet or dry, as well as any other characteristics of the\ + cough that might allow a doctor to rule out diagnoses. + + Please use everyday layman terms and avoid using complex medical terminology.\ + Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). + """ + ) + ) { + // SummarizeFunction(ChiefComplaint: $ChiefComplaint) + } + + var body: some View { NavigationStack { LLMChatView( model: model @@ -140,7 +282,11 @@ struct LLMInteraction: View { if isFirstToken { isFirstToken = false continue } - model.context.append(assistantOutput: token)}}}} + model.context.append(assistantOutput: token) + } + } + } + } .onChange(of: chiefComplaint) { _, newChiefComplaint in if let newChiefComplaint = newChiefComplaint { shouldNavigateToSummaryView = true From 6001af8442c1904c088a5bfd4e3031deb520b445 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:13:28 -0800 Subject: [PATCH 09/14] Reset --- Intake/ChiefComplaint/LLMInteraction.swift | 175 --------------------- 1 file changed, 175 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index e4a4d63..38c8626 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -59,181 +59,6 @@ struct LLMInteraction: View { @State var responseText: String @State var showOnboarding = true -// @State var charSystemPrompt: self.ChatEntity -// @State var chatRole: self.ChatEntity - -// @State var model: LLM - -// init() { -// model = LLMOpenAI( -// parameters: .init( -// modelType: .gpt3_5Turbo, -// systemPrompt: """ -// You are acting as an intake person at a clinic and need to work with\ -// the patient to help clarify their chief complaint into a concise,\ -// specific complaint. -// -// You should always ask about severity and duration if the patient does not include this information. -// -// Additionally, help guide the patient into providing information specific to the condition that the define.\ -// For example, if the patient is experiencing leg pain, you should prompt them to be more\ -// specific about laterality and location. You should also ask if the pain is dull or sharp,\ -// and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ -// should inquire whether the cough is wet or dry, as well as any other characteristics of the\ -// cough that might allow a doctor to rule out diagnoses. -// -// Please use everyday layman terms and avoid using complex medical terminology.\ -// Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). -// """ -// ) -// ) -// $model.function = SummarizeFunction(ChiefComplaint: $ChiefComplaint) -// } - - - @State var model: LLM = LLMOpenAI( - parameters: .init( - modelType: .gpt3_5Turbo, - systemPrompt: """ - You are acting as an intake person at a clinic and need to work with\ - the patient to help clarify their chief complaint into a concise,\ - specific complaint. - - You should always ask about severity and duration if the patient does not include this information. - - Additionally, help guide the patient into providing information specific to the condition that the define.\ - For example, if the patient is experiencing leg pain, you should prompt them to be more\ - specific about laterality and location. You should also ask if the pain is dull or sharp,\ - and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ - should inquire whether the cough is wet or dry, as well as any other characteristics of the\ - cough that might allow a doctor to rule out diagnoses. - - Please use everyday layman terms and avoid using complex medical terminology.\ - Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). - """ - ) - ) { -// SummarizeFunction(ChiefComplaint: $ChiefComplaint) - } - - var body: some View { - - - NavigationStack { - LLMChatView( - model: model - ) - .navigationTitle("Chief Complaint") - .toolbar { - if AccountButton.shouldDisplay { - AccountButton(isPresented: $presentingAccount) - } - } - .sheet(isPresented: $showOnboarding) { - LLMOnboardingView(showOnboarding: $showOnboarding) - } - .onAppear{ - model.context.append() - } - // greeting -// .onAppear { -// Task { -// do { -// let stream = try await runner(with: model).generate(prompt: """ -// Hello! I am a patient coming in to see the doctor and would like\ -// to discuss the reason for my visit. -// """) -// var isFirstToken = true -// for try await token in stream { -// if isFirstToken { -// isFirstToken = false -// continue } -// model.context.append(assistantOutput: token)}}}} -// - - - - - // navigation to summary view after chat - .onChange(of: chiefComplaint) { _, newChiefComplaint in - if let newChiefComplaint = newChiefComplaint { - shouldNavigateToSummaryView = true}} - .background( - NavigationLink( - destination: SummaryView(chiefComplaint: chiefComplaint ?? "error"), - isActive: $shouldNavigateToSummaryView - ) { EmptyView() } - .isDetailLink(false) - .navigationDestination(isPresented: $shouldNavigateToSummaryView) { - EmptyView() })}}} - - -#Preview { - LLMInteraction(presentingAccount: .constant(true), responseText: "Test") - .previewWith { - LLMRunner { - LLMOpenAIRunnerSetupTask() - } - } -} - -// -// LLMInteraction.swift -// Intake -// -// Created by Nick Riedman on 1/25/24. -// -// This source file is part of the Intake based on the Stanford Spezi Template Application project -// -// SPDX-FileCopyrightText: 2023 Stanford University -// -// SPDX-License-Identifier: MIT -// - -import SpeziLLM -import SpeziLLMLocal -import SpeziLLMOpenAI -import SwiftUI - -struct LLMInteraction: View { - @State private var chiefComplaint: String? = "blah blah blah" - - struct SummarizeFunction: LLMFunction { - static let name: String = "summarize_complaint" - static let description: String = """ - When there is enough information to give to the doctor,\ - summarize the conversation into a concise Chief Complaint. - """ - - @Parameter(description: "The primary medical concern that the patient is experiencing.") var medicalConcern: String - - @Parameter(description: "The severity of the primary medical concern.") var severity: String - - @Parameter(description: "The duration of the primary medical concern.") var duration: String - - @Parameter(description: "Extra important information relevant to the primary medical concern that the doctor should be aware of.") var supplementaryInfo: String - - @Binding var chiefComplaint: String - - func execute() async throws -> String? { - let summary = """ - Here is the summary that will be provided to your doctor:\n - Primary concern: \(medicalConcern)\n - Severity: \(severity)\n - Duration: \(duration)\n - Extra Info: \(supplementaryInfo)\n - """ - chiefComplaint = summary - return summary - } - } - - @State private var shouldNavigateToSummaryView = true - @Binding var presentingAccount: Bool - @Environment(LLMRunner.self) var runner: LLMRunner - - @State var responseText: String - @State var showOnboarding = true @State var model: LLM = LLMOpenAI( parameters: .init( From a76b8bccc371db423d7443bd61d3b2d1aab38c11 Mon Sep 17 00:00:00 2001 From: Nina Boord <86579493+ninaboord@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:05:27 -0800 Subject: [PATCH 10/14] LLM GREETING WORKS FUCKYESSSSSSSSSSS --- Intake/ChiefComplaint/LLMInteraction.swift | 56 ++++++++++------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index 38c8626..90ce0fe 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -15,17 +15,33 @@ import SpeziLLM import SpeziLLMLocal import SpeziLLMOpenAI import SwiftUI +import SpeziChat struct LLMInteraction: View { @State private var chiefComplaint: String? = "blah blah blah" - + static let sysPromptString: String = """ + You are acting as an intake person at a clinic and need to work with\ + the patient to help clarify their chief complaint into a concise,\ + specific complaint. + + You should always ask about severity and duration if the patient does not include this information. + + Additionally, help guide the patient into providing information specific to the condition that the define.\ + For example, if the patient is experiencing leg pain, you should prompt them to be more\ + specific about laterality and location. You should also ask if the pain is dull or sharp,\ + and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ + should inquire whether the cough is wet or dry, as well as any other characteristics of the\ + cough that might allow a doctor to rule out diagnoses. + + Please use everyday layman terms and avoid using complex medical terminology.\ + Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). + """ struct SummarizeFunction: LLMFunction { static let name: String = "summarize_complaint" static let description: String = """ When there is enough information to give to the doctor,\ summarize the conversation into a concise Chief Complaint. """ - @Parameter(description: "The primary medical concern that the patient is experiencing.") var medicalConcern: String @Parameter(description: "The severity of the primary medical concern.") var severity: String @@ -81,9 +97,7 @@ struct LLMInteraction: View { Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). """ ) - ) { - // SummarizeFunction(ChiefComplaint: $ChiefComplaint) - } + ) var body: some View { NavigationStack { @@ -100,44 +114,24 @@ struct LLMInteraction: View { LLMOnboardingView(showOnboarding: $showOnboarding) } .onAppear { - Task { - do { - let stream = try await runner(with: model).generate(prompt: """ - Hello! I am a patient coming in to see the doctor and would like\ - to discuss the reason for my visit. - """) - var isFirstToken = true - for try await token in stream { - if isFirstToken { - isFirstToken = false - continue } - model.context.append(assistantOutput: token) - } - } - } + let assistantMessage = ChatEntity(role: .assistant, content: "Hello! What brings you to the doctor's office?") + model.context.insert(assistantMessage, at: 0) } .onChange(of: chiefComplaint) { _, newChiefComplaint in if let newChiefComplaint = newChiefComplaint { shouldNavigateToSummaryView = true } } - .background( - NavigationLink( - destination: SummaryView(chiefComplaint: chiefComplaint ?? "error"), - isActive: $shouldNavigateToSummaryView - ) { + NavigationLink( + destination: SummaryView(chiefComplaint: chiefComplaint ?? "No Chief Complaint"), + label: { EmptyView() } - .isDetailLink(false) - .navigationDestination(isPresented: $shouldNavigateToSummaryView) { - EmptyView() - } ) } } } - #Preview { LLMInteraction(presentingAccount: .constant(true), responseText: "Test") .previewWith { @@ -146,3 +140,5 @@ struct LLMInteraction: View { } } } + + From d082cab09e90ec5b6fc966b1b35246bf5a4393f6 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:08:49 -0800 Subject: [PATCH 11/14] Navigation --- Intake/ChiefComplaint/LLMInteraction.swift | 53 +++++++++------------- Intake/ChiefComplaint/SummaryView.swift | 1 - Intake/Home.swift | 2 +- 3 files changed, 22 insertions(+), 34 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index 38c8626..ff0658c 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -39,6 +39,7 @@ struct LLMInteraction: View { @Parameter(description: desc) var supplementaryInfo: String @Binding var chiefComplaint: String + @Binding var nextView: Bool func execute() async throws -> String? { let summary = """ @@ -49,6 +50,7 @@ struct LLMInteraction: View { Extra Info: \(supplementaryInfo)\n """ chiefComplaint = summary + nextView = true return summary } } @@ -57,7 +59,6 @@ struct LLMInteraction: View { @Binding var presentingAccount: Bool @Environment(LLMRunner.self) var runner: LLMRunner - @State var responseText: String @State var showOnboarding = true @State var model: LLM = LLMOpenAI( @@ -82,7 +83,7 @@ struct LLMInteraction: View { """ ) ) { - // SummarizeFunction(ChiefComplaint: $ChiefComplaint) +// SummarizeFunction(chiefComplaint: ) } var body: some View { @@ -99,47 +100,35 @@ struct LLMInteraction: View { .sheet(isPresented: $showOnboarding) { LLMOnboardingView(showOnboarding: $showOnboarding) } - .onAppear { - Task { - do { - let stream = try await runner(with: model).generate(prompt: """ - Hello! I am a patient coming in to see the doctor and would like\ - to discuss the reason for my visit. - """) - var isFirstToken = true - for try await token in stream { - if isFirstToken { - isFirstToken = false - continue } - model.context.append(assistantOutput: token) - } - } - } - } +// .onAppear { +// Task { +// do { +// let stream = try await runner(with: model).generate(prompt: """ +// Hello! I am a patient coming in to see the doctor and would like\ +// to discuss the reason for my visit. +// """) +// var isFirstToken = true +// for try await token in stream { +// if isFirstToken { +// isFirstToken = false +// continue } +// model.context.append(assistantOutput: token) +// } +// } +// } +// } .onChange(of: chiefComplaint) { _, newChiefComplaint in if let newChiefComplaint = newChiefComplaint { shouldNavigateToSummaryView = true } } - .background( - NavigationLink( - destination: SummaryView(chiefComplaint: chiefComplaint ?? "error"), - isActive: $shouldNavigateToSummaryView - ) { - EmptyView() - } - .isDetailLink(false) - .navigationDestination(isPresented: $shouldNavigateToSummaryView) { - EmptyView() - } - ) } } } #Preview { - LLMInteraction(presentingAccount: .constant(true), responseText: "Test") + LLMInteraction(presentingAccount: .constant(true)) .previewWith { LLMRunner { LLMOpenAIRunnerSetupTask() diff --git a/Intake/ChiefComplaint/SummaryView.swift b/Intake/ChiefComplaint/SummaryView.swift index 491f2c4..31f8366 100644 --- a/Intake/ChiefComplaint/SummaryView.swift +++ b/Intake/ChiefComplaint/SummaryView.swift @@ -25,5 +25,4 @@ struct SummaryView: View { } .navigationTitle("Summary") } - } diff --git a/Intake/Home.swift b/Intake/Home.swift index c7068ba..e1400b8 100644 --- a/Intake/Home.swift +++ b/Intake/Home.swift @@ -47,7 +47,7 @@ struct HomeView: View { Label("MOCK_WEB_SERVICE_TAB_TITLE", systemImage: "server.rack") } } - LLMInteraction(presentingAccount: $presentingAccount, responseText: "") + LLMInteraction(presentingAccount: $presentingAccount) .tag(Tabs.form) .tabItem { Label("Create Form", systemImage: "captions.bubble.fill") From 502c929aefedf8e50c87df93364e3f062aa48300 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:16:13 -0800 Subject: [PATCH 12/14] Cleaned up final push. --- Intake/ChiefComplaint/LLMInteraction.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index 90ce0fe..fc93eaf 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -36,6 +36,7 @@ struct LLMInteraction: View { Please use everyday layman terms and avoid using complex medical terminology.\ Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). """ + struct SummarizeFunction: LLMFunction { static let name: String = "summarize_complaint" static let description: String = """ @@ -73,7 +74,6 @@ struct LLMInteraction: View { @Binding var presentingAccount: Bool @Environment(LLMRunner.self) var runner: LLMRunner - @State var responseText: String @State var showOnboarding = true @State var model: LLM = LLMOpenAI( @@ -97,7 +97,9 @@ struct LLMInteraction: View { Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). """ ) - ) + ) { +// SummarizeFunction() + } var body: some View { NavigationStack { @@ -133,12 +135,10 @@ struct LLMInteraction: View { } #Preview { - LLMInteraction(presentingAccount: .constant(true), responseText: "Test") + LLMInteraction(presentingAccount: .constant(true)) .previewWith { LLMRunner { LLMOpenAIRunnerSetupTask() } } } - - From 5519575410e547ea88e1fee615d595153328c2f2 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:26:06 -0800 Subject: [PATCH 13/14] Linting check --- Intake/ChiefComplaint/LLMInteraction.swift | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index fc93eaf..d093173 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -11,32 +11,13 @@ // SPDX-License-Identifier: MIT // +import SpeziChat import SpeziLLM import SpeziLLMLocal import SpeziLLMOpenAI import SwiftUI -import SpeziChat struct LLMInteraction: View { - @State private var chiefComplaint: String? = "blah blah blah" - static let sysPromptString: String = """ - You are acting as an intake person at a clinic and need to work with\ - the patient to help clarify their chief complaint into a concise,\ - specific complaint. - - You should always ask about severity and duration if the patient does not include this information. - - Additionally, help guide the patient into providing information specific to the condition that the define.\ - For example, if the patient is experiencing leg pain, you should prompt them to be more\ - specific about laterality and location. You should also ask if the pain is dull or sharp,\ - and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ - should inquire whether the cough is wet or dry, as well as any other characteristics of the\ - cough that might allow a doctor to rule out diagnoses. - - Please use everyday layman terms and avoid using complex medical terminology.\ - Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). - """ - struct SummarizeFunction: LLMFunction { static let name: String = "summarize_complaint" static let description: String = """ @@ -70,6 +51,25 @@ struct LLMInteraction: View { } } + static let sysPromptString: String = """ + You are acting as an intake person at a clinic and need to work with\ + the patient to help clarify their chief complaint into a concise,\ + specific complaint. + + You should always ask about severity and duration if the patient does not include this information. + + Additionally, help guide the patient into providing information specific to the condition that the define.\ + For example, if the patient is experiencing leg pain, you should prompt them to be more\ + specific about laterality and location. You should also ask if the pain is dull or sharp,\ + and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ + should inquire whether the cough is wet or dry, as well as any other characteristics of the\ + cough that might allow a doctor to rule out diagnoses. + + Please use everyday layman terms and avoid using complex medical terminology.\ + Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). + """ + + @State private var chiefComplaint: String? = "blah blah blah" @State private var shouldNavigateToSummaryView = true @Binding var presentingAccount: Bool @Environment(LLMRunner.self) var runner: LLMRunner From bc00bf7fc8f40b36627a8297e0d77c96fda45109 Mon Sep 17 00:00:00 2001 From: nriedman <108841122+nriedman@users.noreply.github.com> Date: Sat, 3 Feb 2024 14:29:40 -0800 Subject: [PATCH 14/14] Cleaning unused variables --- Intake/ChiefComplaint/LLMInteraction.swift | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Intake/ChiefComplaint/LLMInteraction.swift b/Intake/ChiefComplaint/LLMInteraction.swift index d093173..825e986 100644 --- a/Intake/ChiefComplaint/LLMInteraction.swift +++ b/Intake/ChiefComplaint/LLMInteraction.swift @@ -51,24 +51,6 @@ struct LLMInteraction: View { } } - static let sysPromptString: String = """ - You are acting as an intake person at a clinic and need to work with\ - the patient to help clarify their chief complaint into a concise,\ - specific complaint. - - You should always ask about severity and duration if the patient does not include this information. - - Additionally, help guide the patient into providing information specific to the condition that the define.\ - For example, if the patient is experiencing leg pain, you should prompt them to be more\ - specific about laterality and location. You should also ask if the pain is dull or sharp,\ - and encourage them to rate their pain on a scale of 1 to 10. For a cough, for example, you\ - should inquire whether the cough is wet or dry, as well as any other characteristics of the\ - cough that might allow a doctor to rule out diagnoses. - - Please use everyday layman terms and avoid using complex medical terminology.\ - Only ask one question or prompt at a time, and keep your responses brief (one to two short sentences). - """ - @State private var chiefComplaint: String? = "blah blah blah" @State private var shouldNavigateToSummaryView = true @Binding var presentingAccount: Bool