feat(job-search): extract keywords from LLM match call instead of heuristics

Piggybacks keyword extraction onto the existing CV-to-job LLM call —
no extra API calls. The system prompt now instructs the model to return
8-12 English job-search terms (job titles, technologies, skills, domains)
in a new `keywords` field alongside the existing score/summary fields.

Keywords flow: LLM JSON → JobMatchResponse.Keywords → CreateJobSearchTokenRequest →
JobSearchTokenEntity.Keywords (stored comma-separated) → JobSearchSessionEntity.Keywords
(copied at session-creation time, no RAG call needed).

Changes:
- Add Keywords to JobMatchResponse, CreateJobSearchTokenRequest, JobSearchTokenEntity
- IJobTokenService.CreateTokenAsync now accepts IReadOnlyList<string> keywords
- JobTokenService: store keywords on token; TriggerStartAsync reads token.Keywords
  instead of fetching CV text from RAG — removes IRagApiClient dependency
- Remove heuristic ExtractKeywords method
- Migration AddKeywordsToJobSearchTokens: adds Keywords column to cvSearch.JobSearchTokens
- Migration UpdateCvMatchSystemPromptKeywords: updates ai.cv-match.system-prompt seed
  to include keywords in the JSON shape

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 12:44:13 +03:00
parent a467fac35d
commit b78ede23cf
13 changed files with 462 additions and 39 deletions
@@ -34,6 +34,7 @@ public sealed class CvSearchDbContext : DbContext
entity.Property(x => x.CvDocumentId).HasMaxLength(64).IsRequired();
entity.Property(x => x.Email).HasMaxLength(256).IsRequired();
entity.Property(x => x.Language).HasMaxLength(8).HasDefaultValue("en").IsRequired();
entity.Property(x => x.Keywords).HasMaxLength(1000).HasDefaultValue(string.Empty);
entity.Property(x => x.Used).HasDefaultValue(false);
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
});
@@ -9,4 +9,5 @@ public sealed class JobSearchTokenEntity : BaseEntity
public string Language { get; set; } = "en";
public DateTime ExpiresAt { get; set; }
public bool Used { get; set; }
public string Keywords { get; set; } = string.Empty;
}