From 64e003a639038800555b13836a773fd1e1629417 Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 1 Jun 2026 16:56:29 +0300 Subject: [PATCH] Use language-specific AI prompts instead of wildcard substitution Refactored the AI prompt system to use proper language-specific prompts (en and ro) instead of a single wildcard prompt with runtime {{languageName}} placeholder substitution. Benefits: - Language-specific instructions optimized for each language - Better control over LLM behavior per language - Cleaner code without placeholder substitution - Easier to maintain and update prompts per language Changes: - Updated cvMatcher InitialSchema migration to seed en and ro prompts separately - Modified CvMatcherService to retrieve language-specific prompts directly - Removed LanguageName() helper method (no longer needed) - Added fallback prompts in service for safety The English and Romanian prompts now include specific JSON examples in their respective languages, ensuring the LLM understands the expected output format for each language variant. Co-Authored-By: Claude Haiku 4.5 --- Apis/cv-matcher-api/Services/CvMatcherService.cs | 16 ++++------------ .../Migrations/20260601133028_InitialSchema.cs | 16 +++++++++------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/Apis/cv-matcher-api/Services/CvMatcherService.cs b/Apis/cv-matcher-api/Services/CvMatcherService.cs index 02f0ddd..2c8ac17 100644 --- a/Apis/cv-matcher-api/Services/CvMatcherService.cs +++ b/Apis/cv-matcher-api/Services/CvMatcherService.cs @@ -123,11 +123,11 @@ public sealed class CvMatcherService : ICvMatcherService var cvText = Limit(cv.Text, 18000); var jobText = Limit(job.Text, 14000); var evidence = evidenceChunks.Count > 0 ? string.Join("\n\n", evidenceChunks.Take(4)) : Limit(job.Text, 4000); - var languageName = LanguageName(language); - var promptTemplate = await _aiPrompts.GetAsync("ai.cv-match.system-prompt", "*", ct) - ?? "You are a strict CV-to-job matching engine. Return JSON only."; - var systemPrompt = promptTemplate.Replace("{{languageName}}", languageName, StringComparison.OrdinalIgnoreCase); + var systemPrompt = await _aiPrompts.GetAsync("ai.cv-match.system-prompt", language, ct) + ?? (language == "ro" + ? "Ești un motor strict de potrivire CV-job. Returnează doar JSON. Punctează realist între 0 și 100." + : "You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100."); var userPrompt = $""" CV: @@ -195,14 +195,6 @@ public sealed class CvMatcherService : ICvMatcherService private static string NormalizeLanguage(string? language) => string.IsNullOrWhiteSpace(language) ? "en" : language.ToLowerInvariant().Split('-')[0].Trim(); - /// Maps a language code to its full English name for use in the LLM system prompt. - private static string LanguageName(string language) => language switch - { - "ro" => "Romanian", - "en" => "English", - _ => "English" - }; - /// Truncates to at most characters. private static string Limit(string value, int max) => value.Length <= max ? value : value[..max]; } diff --git a/Apis/cv-matcher-data/Migrations/20260601133028_InitialSchema.cs b/Apis/cv-matcher-data/Migrations/20260601133028_InitialSchema.cs index cafb479..6cebb5c 100644 --- a/Apis/cv-matcher-data/Migrations/20260601133028_InitialSchema.cs +++ b/Apis/cv-matcher-data/Migrations/20260601133028_InitialSchema.cs @@ -71,16 +71,18 @@ namespace CvMatcher.Data.Migrations columns: new[] { "CvDocumentId", "JobDocumentId", "Language" }, unique: true); - // Seed AI prompts for CV matching + // Seed AI prompts for CV matching (language-specific) migrationBuilder.Sql($$""" INSERT INTO [cvMatcher].AiPrompts ([Key], [Language], [Value], [Description]) VALUES - ('ai.cv-match.system-prompt', '*', - 'You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100. -Penalize missing required skills. Do not invent experience. Use concise business language. -Respond entirely in {{languageName}} — all text fields in the JSON must be in {{languageName}}. -JSON shape: {""score"":number,""summary"":""..."",""strengths"":[""...""],""gaps"":[""...""],""recommendations"":[""...""],""evidence"":[""...""]}', - 'System prompt template for the CV-to-job LLM matching call. {{languageName}} is substituted at runtime.'); + ('ai.cv-match.system-prompt', 'en', + 'You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100. Penalize missing required skills. Do not invent experience. Use concise business language. All text fields in the JSON response must be in English. +JSON shape: {""score"":number,""summary"":""one-line summary in English"",""strengths"":[""strength 1 in English"",""strength 2 in English""],""gaps"":[""gap 1 in English""],""recommendations"":[""recommendation 1 in English""],""evidence"":[""evidence 1 in English""]}', + 'System prompt for CV-to-job matching in English. Instructs LLM to return JSON with CV strengths, gaps, and recommendations relative to the job.'), + ('ai.cv-match.system-prompt', 'ro', + 'Ești un motor strict de potrivire CV-job. Returnează doar JSON. Punctează realist între 0 și 100. Penalizează abilitățile lipsă necesare. Nu inventa experiență. Folosește limbaj profesional concis. Toate câmpurile text din răspunsul JSON trebuie să fie în limba română. +JSON shape: {""score"":number,""summary"":""rezumat pe o linie în română"",""strengths"":[""punct forte 1 în română"",""punct forte 2 în română""],""gaps"":[""lipsă 1 în română""],""recommendations"":[""recomandare 1 în română""],""evidence"":[""dovadă 1 în română""]}', + 'System prompt pentru potrivire CV-job în limba română. Instruiește LLM-ul să returneze JSON cu punctele forte ale CV-ului, lacunele și recomandări relative la job.'); """); }