feat: improved template-based system prompt

This commit is contained in:
Owen Gretzinger
2025-07-11 15:39:26 -04:00
parent 19d7ae340b
commit d3d42dabee
5 changed files with 171 additions and 18 deletions
+53 -1
View File
@@ -6,7 +6,23 @@ struct Settings: Codable {
var userBlurb: 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 = "",
openAIKey: String = "",
@@ -17,4 +33,40 @@ struct Settings: Codable {
self.userBlurb = userBlurb
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>
+24 -14
View File
@@ -10,15 +10,13 @@ class NotesGenerator {
private init() {}
/// Generates meeting notes from transcript and user notes
/// Generates meeting notes from meeting data using template-based system prompt
/// - Parameters:
/// - transcript: The meeting transcript
/// - userNotes: Additional notes from the user
/// - meeting: The meeting object containing all necessary data
/// - 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
func generateNotes(transcript: String,
userNotes: String,
func generateNotes(meeting: Meeting,
userBlurb: String,
systemPrompt: String) async throws -> String {
@@ -28,15 +26,27 @@ class NotesGenerator {
let openAI = OpenAI(apiToken: apiKey)
// Compose system prompt with user blurb wrapped in XML tags for clarity
let systemContent = "\(systemPrompt)\n\n<user_blurb>\n\(userBlurb)\n</user_blurb>"
// Create date formatter for meeting date
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)!
// Provide the model with structured XML-tagged input variables for improved parsing
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, userMessage], model: .gpt4_1_mini)
print(systemContent)
let query = ChatQuery(messages: [systemMessage], model: .gpt4_1)
let result = try await openAI.chats(query: query)
+1 -3
View File
@@ -118,10 +118,8 @@ class MeetingViewModel: ObservableObject {
let userBlurb = KeychainHelper.shared.get(forKey: "userBlurb") ?? ""
let systemPrompt = KeychainHelper.shared.get(forKey: "systemPrompt") ?? Settings.defaultSystemPrompt
// Generate notes
meeting.generatedNotes = try await NotesGenerator.shared.generateNotes(
transcript: meeting.transcript,
userNotes: meeting.userNotes,
meeting: meeting,
userBlurb: userBlurb,
systemPrompt: systemPrompt
)
@@ -26,6 +26,15 @@ class SettingsViewModel: ObservableObject {
errorMessage = nil
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
let deepgramSaved = KeychainHelper.shared.save(settings.deepgramKey, forKey: "deepgramKey")
let openAISaved = KeychainHelper.shared.save(settings.openAIKey, forKey: "openAIKey")