feat: improved template-based system prompt
This commit is contained in:
@@ -6,7 +6,23 @@ struct Settings: Codable {
|
|||||||
var userBlurb: String
|
var userBlurb: String
|
||||||
var systemPrompt: String
|
var systemPrompt: String
|
||||||
|
|
||||||
static let defaultSystemPrompt = "Generate concise meeting notes from the transcript and any additional notes provided. Focus on key points, action items, and decisions made."
|
static let defaultSystemPrompt: String = {
|
||||||
|
guard let url = Bundle.main.url(forResource: "DefaultSystemPrompt", withExtension: "txt"),
|
||||||
|
let content = try? String(contentsOf: url) else {
|
||||||
|
assertionFailure("DefaultSystemPrompt.txt missing from bundle")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Required variables for the template
|
||||||
|
static let requiredVariables: Set<String> = [
|
||||||
|
"meeting_title",
|
||||||
|
"meeting_date",
|
||||||
|
"transcript",
|
||||||
|
"user_blurb",
|
||||||
|
"user_notes"
|
||||||
|
]
|
||||||
|
|
||||||
init(deepgramKey: String = "",
|
init(deepgramKey: String = "",
|
||||||
openAIKey: String = "",
|
openAIKey: String = "",
|
||||||
@@ -17,4 +33,40 @@ struct Settings: Codable {
|
|||||||
self.userBlurb = userBlurb
|
self.userBlurb = userBlurb
|
||||||
self.systemPrompt = systemPrompt
|
self.systemPrompt = systemPrompt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Template Methods
|
||||||
|
|
||||||
|
/// Extracts all template variables from a system prompt string
|
||||||
|
static func extractTemplateVariables(from template: String) -> Set<String> {
|
||||||
|
let pattern = #"\{\{(\w+)\}\}"#
|
||||||
|
let regex = try! NSRegularExpression(pattern: pattern, options: [])
|
||||||
|
let matches = regex.matches(in: template, options: [], range: NSRange(location: 0, length: template.count))
|
||||||
|
|
||||||
|
var variables: Set<String> = []
|
||||||
|
for match in matches {
|
||||||
|
if let range = Range(match.range(at: 1), in: template) {
|
||||||
|
variables.insert(String(template[range]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return variables
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates that all required variables are present in the system prompt
|
||||||
|
func validateSystemPrompt() -> (isValid: Bool, missingVariables: Set<String>) {
|
||||||
|
let presentVariables = Settings.extractTemplateVariables(from: systemPrompt)
|
||||||
|
let missingVariables = Settings.requiredVariables.subtracting(presentVariables)
|
||||||
|
return (isValid: missingVariables.isEmpty, missingVariables: missingVariables)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces template variables in the system prompt with actual values
|
||||||
|
static func processTemplate(_ template: String, with variables: [String: String]) -> String {
|
||||||
|
var result = template
|
||||||
|
|
||||||
|
for (key, value) in variables {
|
||||||
|
let placeholder = "{{\(key)}}"
|
||||||
|
result = result.replacingOccurrences(of: placeholder, with: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
Your job is to generate enhanced meeting notes for the following meeting:
|
||||||
|
|
||||||
|
Title: {{meeting_title}}
|
||||||
|
Date & Time: {{meeting_date}}
|
||||||
|
|
||||||
|
You will be provided with:
|
||||||
|
- a transcript of the meeting
|
||||||
|
- information the user provided as additional context
|
||||||
|
- any additional notes the user manually noted down
|
||||||
|
|
||||||
|
Your enhanced meeting notes should include:
|
||||||
|
- the key discussion points
|
||||||
|
- action items
|
||||||
|
- decisions made
|
||||||
|
- any deadlines or follow-up required
|
||||||
|
|
||||||
|
Create the meeting notes in markdown formatting, and only include these sections. The title, attendees, date, and other metadata must not be included as this information is already displayed elsewhere in the UI. Your job is the notes themselves only.
|
||||||
|
|
||||||
|
Here are explanations of how to handle the different information you will be given:
|
||||||
|
|
||||||
|
<transcript_explanation>
|
||||||
|
Transcripts use specific labels to identify speakers:
|
||||||
|
- "Me": The user using this meeting notes application. Technically speaking, these utterances were picked up by the user's microphone.
|
||||||
|
- "Them": The other person or other people in the meeting. Technically speaking, these utterances were picked up through system audio.
|
||||||
|
|
||||||
|
Audio transcription often mislabels speakers. Use context clues to infer the correct speaker.
|
||||||
|
|
||||||
|
The transcript of the meeting is provided in <transcript> tags below.
|
||||||
|
</transcript_explanation>
|
||||||
|
|
||||||
|
<user_blurb_explanation>
|
||||||
|
Any additional context the user provided in their global settings will be attached.
|
||||||
|
- This information is always included regardless of the meeting, so it may or may not be relevant.
|
||||||
|
- Use your best judgement to determine if the information is relevant, as it may not be.
|
||||||
|
- It could include personal details, preferences, spellings of companies/people/products, etc.
|
||||||
|
|
||||||
|
This information is provided in <user_blurb> tags below.
|
||||||
|
</user_blurb_explanation>
|
||||||
|
|
||||||
|
<user_notes_explanation>
|
||||||
|
The user may manually add notes to the meeting notes. If so, these notes are very important.
|
||||||
|
|
||||||
|
Here are some examples of things the user may note down, how to recognize them, and how to handle them:
|
||||||
|
|
||||||
|
<key_details>
|
||||||
|
The user may note down key details they want to include in the meeting notes.
|
||||||
|
|
||||||
|
How to recognize: the user writes things down that are related to content you can see in the transcript.
|
||||||
|
|
||||||
|
How to handle: ensure they are included and prioritized in the enhanced meeting notes.
|
||||||
|
</key_details>
|
||||||
|
|
||||||
|
<user_thoughts>
|
||||||
|
The user may note down thoughts that they don't say out loud (ex. thoughts on a candidate they're interviewing).
|
||||||
|
|
||||||
|
How to recognize: the user writes things down that are not related to content you can see in the transcript.
|
||||||
|
|
||||||
|
How to handle: include in its own section, or tagged onto other points throughout where relevant.
|
||||||
|
</user_thoughts>
|
||||||
|
|
||||||
|
<formatting_requests>
|
||||||
|
The user may note down formatting requests they want to include in the meeting notes.
|
||||||
|
|
||||||
|
How to recognize: the user provides a meeting notes structure without filling in the content (ex. they use hashtags to create markdown headers but don't fill in the content).
|
||||||
|
|
||||||
|
How to handle: structure the enhanced meeting notes according to the user's notes.
|
||||||
|
</formatting_requests>
|
||||||
|
|
||||||
|
In general, the user's notes are very important. These are things that the user has specifically manually written down.
|
||||||
|
|
||||||
|
These notes are provided in <user_notes> tags below.
|
||||||
|
</user_notes_explanation>
|
||||||
|
|
||||||
|
<transcript>
|
||||||
|
{{transcript}}
|
||||||
|
</transcript>
|
||||||
|
|
||||||
|
<user_blurb>
|
||||||
|
{{user_blurb}}
|
||||||
|
</user_blurb>
|
||||||
|
|
||||||
|
<user_notes>
|
||||||
|
{{user_notes}}
|
||||||
|
</user_notes>
|
||||||
@@ -10,15 +10,13 @@ class NotesGenerator {
|
|||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
/// Generates meeting notes from transcript and user notes
|
/// Generates meeting notes from meeting data using template-based system prompt
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - transcript: The meeting transcript
|
/// - meeting: The meeting object containing all necessary data
|
||||||
/// - userNotes: Additional notes from the user
|
|
||||||
/// - userBlurb: Information about the user for context
|
/// - userBlurb: Information about the user for context
|
||||||
/// - systemPrompt: The system prompt for AI generation
|
/// - systemPrompt: The system prompt template with placeholders
|
||||||
/// - Returns: Generated meeting notes
|
/// - Returns: Generated meeting notes
|
||||||
func generateNotes(transcript: String,
|
func generateNotes(meeting: Meeting,
|
||||||
userNotes: String,
|
|
||||||
userBlurb: String,
|
userBlurb: String,
|
||||||
systemPrompt: String) async throws -> String {
|
systemPrompt: String) async throws -> String {
|
||||||
|
|
||||||
@@ -28,15 +26,27 @@ class NotesGenerator {
|
|||||||
|
|
||||||
let openAI = OpenAI(apiToken: apiKey)
|
let openAI = OpenAI(apiToken: apiKey)
|
||||||
|
|
||||||
// Compose system prompt with user blurb wrapped in XML tags for clarity
|
// Create date formatter for meeting date
|
||||||
let systemContent = "\(systemPrompt)\n\n<user_blurb>\n\(userBlurb)\n</user_blurb>"
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateStyle = .full
|
||||||
|
dateFormatter.timeStyle = .short
|
||||||
|
|
||||||
|
// Prepare template variables
|
||||||
|
let templateVariables: [String: String] = [
|
||||||
|
"meeting_title": meeting.title.isEmpty ? "Untitled Meeting" : meeting.title,
|
||||||
|
"meeting_date": dateFormatter.string(from: meeting.date),
|
||||||
|
"transcript": meeting.formattedTranscript,
|
||||||
|
"user_blurb": userBlurb,
|
||||||
|
"user_notes": meeting.userNotes
|
||||||
|
]
|
||||||
|
|
||||||
|
// Process the system prompt template
|
||||||
|
let systemContent = Settings.processTemplate(systemPrompt, with: templateVariables)
|
||||||
let systemMessage = ChatQuery.ChatCompletionMessageParam(role: .system, content: systemContent)!
|
let systemMessage = ChatQuery.ChatCompletionMessageParam(role: .system, content: systemContent)!
|
||||||
|
|
||||||
// Provide the model with structured XML-tagged input variables for improved parsing
|
print(systemContent)
|
||||||
let userContent = "<task>Generate concise and well-structured meeting notes using the information below.</task>\n\n<transcript>\n\(transcript)\n</transcript>\n\n<user_notes>\n\(userNotes)\n</user_notes>"
|
|
||||||
let userMessage = ChatQuery.ChatCompletionMessageParam(role: .user, content: userContent)!
|
let query = ChatQuery(messages: [systemMessage], model: .gpt4_1)
|
||||||
|
|
||||||
let query = ChatQuery(messages: [systemMessage, userMessage], model: .gpt4_1_mini)
|
|
||||||
|
|
||||||
let result = try await openAI.chats(query: query)
|
let result = try await openAI.chats(query: query)
|
||||||
|
|
||||||
|
|||||||
@@ -118,10 +118,8 @@ class MeetingViewModel: ObservableObject {
|
|||||||
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
|
||||||
|
|
||||||
// Generate notes
|
|
||||||
meeting.generatedNotes = try await NotesGenerator.shared.generateNotes(
|
meeting.generatedNotes = try await NotesGenerator.shared.generateNotes(
|
||||||
transcript: meeting.transcript,
|
meeting: meeting,
|
||||||
userNotes: meeting.userNotes,
|
|
||||||
userBlurb: userBlurb,
|
userBlurb: userBlurb,
|
||||||
systemPrompt: systemPrompt
|
systemPrompt: systemPrompt
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ class SettingsViewModel: ObservableObject {
|
|||||||
errorMessage = nil
|
errorMessage = nil
|
||||||
saveSuccessful = false
|
saveSuccessful = false
|
||||||
|
|
||||||
|
// Validate system prompt template before saving
|
||||||
|
let validation = settings.validateSystemPrompt()
|
||||||
|
if !validation.isValid {
|
||||||
|
let missingVars = validation.missingVariables.joined(separator: ", ")
|
||||||
|
errorMessage = "System prompt is missing required variables: \(missingVars). Please include these variables in your template using double curly braces (e.g., {{transcript}})."
|
||||||
|
isSaving = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Save to Keychain
|
// Save to Keychain
|
||||||
let deepgramSaved = KeychainHelper.shared.save(settings.deepgramKey, forKey: "deepgramKey")
|
let deepgramSaved = KeychainHelper.shared.save(settings.deepgramKey, forKey: "deepgramKey")
|
||||||
let openAISaved = KeychainHelper.shared.save(settings.openAIKey, forKey: "openAIKey")
|
let openAISaved = KeychainHelper.shared.save(settings.openAIKey, forKey: "openAIKey")
|
||||||
|
|||||||
Reference in New Issue
Block a user