feat: streaming for llm summary generation (#2)

This commit is contained in:
Owen Gretzinger
2025-07-12 21:25:44 -04:00
committed by GitHub
parent 78d93015e5
commit 05196ebe71
2 changed files with 59 additions and 40 deletions
+50 -39
View File
@@ -10,51 +10,62 @@ class NotesGenerator {
private init() {} private init() {}
/// Generates meeting notes from meeting data using template-based system prompt /// Generates meeting notes from meeting data using template-based system prompt with streaming
/// - Parameters: /// - Parameters:
/// - meeting: The meeting object containing all necessary data /// - meeting: The meeting object containing all necessary data
/// - userBlurb: Information about the user for context /// - userBlurb: Information about the user for context
/// - systemPrompt: The system prompt template with placeholders /// - systemPrompt: The system prompt template with placeholders
/// - Returns: Generated meeting notes /// - Returns: AsyncStream of partial generated notes
func generateNotes(meeting: Meeting, func generateNotesStream(meeting: Meeting,
userBlurb: String, userBlurb: String,
systemPrompt: String) async throws -> String { systemPrompt: String) -> AsyncStream<String> {
return AsyncStream { continuation in
guard let apiKey = KeychainHelper.shared.get(forKey: "openAIKey"), !apiKey.isEmpty else { Task {
throw NSError(domain: "NotesGenerator", code: 1, userInfo: [NSLocalizedDescriptionKey: "OpenAI API key not configured"]) do {
} guard let apiKey = KeychainHelper.shared.get(forKey: "openAIKey"), !apiKey.isEmpty else {
continuation.finish()
let openAI = OpenAI(apiToken: apiKey) return
}
// Create date formatter for meeting date
let dateFormatter = DateFormatter() let openAI = OpenAI(apiToken: apiKey)
dateFormatter.dateStyle = .full
dateFormatter.timeStyle = .short // Create date formatter for meeting date
let dateFormatter = DateFormatter()
// Prepare template variables dateFormatter.dateStyle = .full
let templateVariables: [String: String] = [ dateFormatter.timeStyle = .short
"meeting_title": meeting.title.isEmpty ? "Untitled Meeting" : meeting.title,
"meeting_date": dateFormatter.string(from: meeting.date), // Prepare template variables
"transcript": meeting.formattedTranscript, let templateVariables: [String: String] = [
"user_blurb": userBlurb, "meeting_title": meeting.title.isEmpty ? "Untitled Meeting" : meeting.title,
"user_notes": meeting.userNotes "meeting_date": dateFormatter.string(from: meeting.date),
] "transcript": meeting.formattedTranscript,
"user_blurb": userBlurb,
// Process the system prompt template "user_notes": meeting.userNotes
let systemContent = Settings.processTemplate(systemPrompt, with: templateVariables) ]
let systemMessage = ChatQuery.ChatCompletionMessageParam(role: .system, content: systemContent)!
// Process the system prompt template
let systemContent = Settings.processTemplate(systemPrompt, with: templateVariables)
let systemMessage = ChatQuery.ChatCompletionMessageParam(role: .system, content: systemContent)!
print(systemContent) print(systemContent)
let query = ChatQuery(messages: [systemMessage], model: .gpt4_1) let query = ChatQuery(messages: [systemMessage], model: .gpt4_1)
let result = try await openAI.chats(query: query) let stream: AsyncThrowingStream<ChatStreamResult, Error> = openAI.chatsStream(query: query)
guard let generatedNotes = result.choices.first?.message.content else { for try await result in stream {
throw NSError(domain: "NotesGenerator", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to generate notes"]) if let content = result.choices.first?.delta.content {
continuation.yield(content)
}
}
continuation.finish()
} catch {
print("Error in streaming generation: \(error)")
continuation.finish()
}
}
} }
return generatedNotes
} }
/// Validates if OpenAI API key is configured /// Validates if OpenAI API key is configured
@@ -148,17 +148,25 @@ class MeetingViewModel: ObservableObject {
isGeneratingNotes = true isGeneratingNotes = true
errorMessage = nil errorMessage = nil
// Clear existing notes for streaming
meeting.generatedNotes = ""
do { do {
// Load settings for generation // Load settings for generation
let userBlurb = KeychainHelper.shared.get(forKey: "userBlurb") ?? "" let userBlurb = KeychainHelper.shared.get(forKey: "userBlurb") ?? ""
let systemPrompt = KeychainHelper.shared.get(forKey: "systemPrompt") ?? Settings.defaultSystemPrompt() let systemPrompt = KeychainHelper.shared.get(forKey: "systemPrompt") ?? Settings.defaultSystemPrompt()
meeting.generatedNotes = try await NotesGenerator.shared.generateNotes( // Use streaming generation
let stream = NotesGenerator.shared.generateNotesStream(
meeting: meeting, meeting: meeting,
userBlurb: userBlurb, userBlurb: userBlurb,
systemPrompt: systemPrompt systemPrompt: systemPrompt
) )
for await chunk in stream {
meeting.generatedNotes += chunk
}
saveMeeting() saveMeeting()
} catch { } catch {
errorMessage = "Failed to generate notes: \(error.localizedDescription)" errorMessage = "Failed to generate notes: \(error.localizedDescription)"