아무도 이야기하지 않는 문제
약한 포인트를 주었을 때 대부분의 AI 이력서 리와이터가 실제로 하는 일은 다음과 같습니다
원본: "일일 스탠업에 참석했습니다"
AI로 리와이팅된 버전: "React와 TypeScript를 사용하여 반응형 웹 애플리케이션을 개발하고, 애그리지 일일 스탠업에서 협력하여 높은 품질의 프론트엔드 솔루션을 제공했습니다"
그것이 더 좋아 보입니다. 키워드가 있습니다. 구조가 있습니다. 전문적해 보입니다.
그것은 또한 거짓입니다.
후보자는 결코 React 애플리케이션을 개발한 적이 없습니다. 회의에 참석했습니다. AI는 직무 기술서를 보고, React와 TypeScript를 보고, 조용히 후보자가 가지고 있지 않은 경험을 창조했습니다. 만약 그들이 면접을 받는다면, 첫 번째 기술 질문이 그것을 드러낼 것입니다.
이것은 추상적인 것이 아닙니다. 이것은 제 레지던스 리라이터 — Resume AI Tailor — 이 제가 고치기 전에 하는 일입니다. 제가 측정했기 때문에 알고 있습니다.
Resume AI Tailor는 무엇인가
Resume AI Tailor는 NanoCrafts 아래에서 개발 중인 SaaS 제품입니다. 레지던스를 업로드하고 채용 공고를 붙여넣으면 초에 맞춤형 버전을 받을 수 있습니다. 스택은 Next.js, gpt-4o-mini, Clerk, Neon/Drizzle, Vercel입니다.
핵심 약속은 간단합니다: 더 나은 이력서, 더 나은 일치 점수, 더 나은 면접 기회. 문제는 AI가 후보자가 가지고 있지 않은 기술을 발명하기 시작하면 약속이 깨지는 것입니다.
WordPress 개발자에게 React 경험을 허위로 만들어내는 이력서 다듬기 도구는 그 개발자를 도와주지 않습니다. 오히려 그들을 면접실에서 실패하게 만듭니다.
나는 무엇을 시작하려 했습니까
두 주일 동안 고급 프롬프트 엔지니어링 기술을 적용했습니다 - 몇 샷 프롬프팅, 역할 프롬프팅, 그리고 LLM을 판단자로 하는 평가 루프 - 특히 Resume AI Tailor의 리팩토링 및 분석 경로에 대해.
목표는 출력이 더 좋아 보이도록 하는 것이 아니라, 각 프롬프트 변경 사항을 통해 추적할 수 있는 숫자로 실질적으로 더 나은 것을 만드는 것이었습니다.
나는 평가 루프를 만들어 시작했습니다: 두 번째 모델 호출로 각 재작성된 항목을 다섯 가지 기준(동사, 키워드 적합성, 결과, 진실성, 간결성)에 대해 구조화된 루브릭을 사용하여 점수를 매겼습니다. 그런 다음 10 개의 이력서를 파이프라인을 통해 실행하고 기준선을 설정했습니다.
1주차 기준선: 38개 항목에 걸쳐 7.37/10입니다.
그 후에 나는 반복했습니다. 두 주, 여러 프롬프트 변경, 5개의 이력서 프로필을 가로질러 경계 사례 테스트. 모든 변경 사항은 그 기준선에 따라 측정되었습니다.
최종 점수: 8.37/10. 순수 개선: +1.0점.
이 포스트는 어떤 변화가 있었는지, 왜 작동했는지, 그리고 어떤 것을 배웠는지 기록합니다. 프롬프트 엔지니어링 튜토리얼에 없는 내용입니다.
파이어쇼트 프롬프팅: 보여주지 말고 말해주라
원본 리팩토링 프롬프트는 이렇게 보였습니다:
const prompt = `You are an expert resume writer. Rewrite the following
bullet point to be stronger, specific, and results-oriented.
Original bullet: "${bullet}"
Rewritten bullet:`;
이것은 절대 예시 없는 프롬프팅입니다. 하나의 지시사항, 예시 없습니다. 모델은 '더 강력한'이 추상적으로 무엇인지 알지만, 소프트웨어 엔지니어가 촉진 역할에 지원하는 이력서 별표에서 '더 강력한'이 구체적으로 어떤 모습인지는 아무것도 모릅니다.
출력은 자신감 넘치는 듯했지만 구조적으로 일관성이 없었습니다. 때로는 단락이었고, 때로는 번호 목록이었으며, 때로는 모델이 지표를 추가했고, 때로는 추가하지 않았으며, 때로는 전혀 기술을 환상했습니다.
파이어쇼트 프롬프팅은 원하는 출력을 보여주는 대신 설명하는 것으로 형식 문제를 해결합니다.
const REWRITE_SYSTEM_PROMPT = `You are a senior ATS engineer and resume
specialist with 10 years of experience configuring applicant tracking
systems at Fortune 500 companies.
Study the examples below carefully. Your output must match this format
exactly — return only the rewritten bullet, no explanation.
EXAMPLE 1:
Original: "Worked on the backend of the company's main product"
Target keywords: Node.js, REST APIs, microservices
Job title: Senior Software Engineer
Candidate skills: Node.js, PostgreSQL, Docker, REST APIs
Rewritten: Engineered Node.js REST APIs for core product, improving
system performance across microservices architecture
EXAMPLE 2:
Original: "Attended daily standups"
Target keywords: React, TypeScript, agile, frontend
Job title: Frontend Developer
Candidate skills: HTML, CSS, JavaScript, WordPress
Rewritten: Participated in agile daily standups supporting [React]
and [TypeScript] frontend delivery
Return ONLY the rewritten bullet. No explanation. Maximum 20 words.`;
그 프롬프트에서 동시에 세 가지 일이 일어나고 있습니다:
역할은 주의를 집중시킵니다."고급 ATS 엔지니어"는 모델이 어떤 훈련 부분을 활용할지 알려줍니다 — 키워드 준수, 형식 준수, 파싱 가능성. 일반적인 글쓰기 품질은 아닙니다.
예시는 형식을 고정합니다. 지금부터는 정확히 보여진 구조의 단일 줄 출력이 생성됩니다. 단락은 없고, 번호 목록은 없고, 설명적인 접두사도 없습니다.
예제 2는 진실성을 보여줍니다. 후보자는 HTML, CSS, JavaScript, WordPress가 있지만, React나 TypeScript는 없습니다. 예제는 모델이 부족한 기술에 대해 정확히 어떻게 해야 하는지를 보여줍니다: [React]와 같은 괄호 플레이스홀더를 사용하는 대신 기술을 직접 주장하지 말라는 것입니다.
마지막 포인트가 가장 중요합니다. 지시사항만으로 환각 문제를 해결하지 못했습니다. 올바른 플레이스홀더 행동의 작동 예제를 모델에 보여주었습니다.
핵심 통찰력: 추가 예제는 충돌할 때 쓰여진 지시사항을 재정의합니다. 만약 당신의 예시들이 모두 해당 기술을 가진 후보들에서 자신감 있는 키워드 통합을 보여준다면, 모델은 예시를 따를 것입니다 — "진실을 말하라"라고 말하는 지시를 따르지 않습니다. 당신이 원하는 것을 보여줘. 보여주는 것에 주의해.
역할 프롬프팅: 모델에게 관점을 주는 것
역할 프롬프팅은 사용자 콘텐츠가 처리되기 전에 시스템 프롬프트를 통해 모델에 페르소나를 할당하는 것입니다. 모델이 그 사람이 되는 것은 아닙니다. 그것은 훈련 데이터의 어떤 부분을 가장 많이 사용할지를 바꿉니다.
차이는 중요합니다. "너는 채용 매니저야"는 주의 지시이지 기능 업그레이드는 아닙니다. 모델은 이미 채용 매니저가 무엇을 중요하게 생각하는지 알고 있습니다. 역할은 그 지식을 모든 것보다 더 중요하게 생각하도록 알려줍니다.
나는 하루에 세 명의 인물을 같은 이력서에 대해 테스트했습니다.
ATS 엔지니어
`You are a senior ATS engineer with 10 years of experience configuring
applicant tracking systems at Fortune 500 companies like Greenhouse,
Workday, and Lever.
When analysing a resume, evaluate it exactly as an ATS would before
a human ever sees it. Your priorities are:
1. Keyword match — does the resume contain exact terms a recruiter
would search for?
2. Formatting compliance — tables, columns, and text boxes cause
parsing failures. Flag them.
3. Date formatting — inconsistent dates confuse parsers.`
결과: 임상적, 좁은, 신뢰할 수 있는. 기계 수준의 문제를 포착했습니다 — 일관되지 않은 날짜 형식, 누락된 키워드, 표준이 아닌 섹션 이름. 인간이 신경 쓰는 모든 것을 무시했습니다.
FAANG 채용 관리자
`You are a senior engineering hiring manager at a FAANG company.
You have conducted over 500 hiring committee reviews across multiple
product and infrastructure teams.
When analysing a resume, evaluate it as you would in a 30-second
hiring committee pre-screen. Your priorities are:
1. Impact framing — does each bullet describe an outcome, not a task?
2. Scope signals — team size, user base, revenue influenced.
3. Levelling language — "Helped" signals junior. "Led" signals senior.`
"30초 예고 화면" 구조화는 가장 효과적인 추가 요소였습니다. 이는 모델의 평가 태도를 철저함에서 필터링으로 변경했습니다 — 완전성보다 신호 밀도를 우선시했습니다.
출력: 직접적이고 구체적이며 고신호. 레벨링 언어 간극, 누락된 범위 맥락 및 채용 위원회가 평가할 수 없는 모호한 볼트를 지적했습니다. 세 가지 중 가장 실행 가능한 것입니다.
커리어 코치
`You are a senior career coach with 12 years of experience helping
engineers at all levels land roles they care about.
When analysing a resume, evaluate it as you would in a first coaching
session: with honesty, warmth, and a focus on what the candidate can
improve before their next application.`
출력: 따뜻하고 이야기 중심적이지만, 가끔 너무 격려하기도 했습니다. 기술적인 직무에 관심이 없는 언어와 일반적인 표현을 포착했습니다. 기본적으로 "이력서를 공유해 주셔서 감사합니다!"를 시작으로 삼았습니다 - 명시적으로 추가해야 했던 보안 장치입니다: "인사를 시작하지 마십시오."
제가 배포한 것
FAANG 채용 매니저가 주요 분석 프롬프트가 되었습니다. 명확한 격차로 가장 행동 가능한 피드백을 생성했습니다.
ATS 엔지니어가 가볍게 병렬로 실행됩니다 — 저렴한 출력, 집중된 범위, 채용 매니저가 무시하는 기계 계층을 포착합니다.
커리어 코치는 향후 Pro 티어 기능으로 유지됩니다. 본능이 맞지만 생산 전에 단단히 해야 합니다.
/api/analyse
├── hiring manager prompt (always — primary analysis)
└── ATS engineer prompt (always — parallel pass)
└── career coach prompt (Pro tier — future)
핵심 교훈: 역할 설명에 포함된 용어는 직책과 마찬가지로 중요합니다."너는 채용 매니저야"는 "너는 30초 예약 스크린에서 후보자를 평가하고 레벨링 언어에 신경 쓰는 채용 매니저야"보다 약합니다. 두 번째 버전은 모델에 구체적인 관점을 제공합니다. 레이블만 제공하는 것이 아닙니다.
평가 루프: 만든 것을 측정합니다
한 주 동안 신속한 반복 후에 더 좋아 보이는 결과물을 얻었습니다. 그 결과물들이 실제로 더 좋은지 알 방법이 없었습니다.
5개의 이력서에 대한 수동 평가는 45분이 걸렸고, 믿을 수 없는 점수를 만들었습니다 — 충분히 많은 다시 쓰기 읽은 후, 판단이 흐트러집니다. 일관성 있고 빠르며 반복 가능한 것이 필요했습니다.
LLM을 판사로 하는 패턴은 원본과 다시 쓴 항목을 모두 받아들이는 두 번째 모델 호출로, 구조화된 루브릭에 따라 평가하고 품질 점수와 이유를 반환합니다.
export async function evalBullet(
original: string,
rewritten: string,
targetKeywords: string[],
candidateSkills: string[]
): Promise<EvalResult> {
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: JUDGE_SYSTEM_PROMPT },
{ role: 'user', content: `
Original bullet: "${original}"
Rewritten bullet: "${rewritten}"
Target keywords: ${targetKeywords.join(', ')}
Candidate skills: ${candidateSkills.join(', ')}
`}
],
temperature: 0 // always 0 for judges
});
// parse and return structured score
}
온도는 0이어야 합니다. 당신의 리라이터는 0.3을 사용합니다 زیر에 어휘적 변화가 선호됩니다. 당신의 판정자는 동일한 볼렛을 항상 동일한 방식으로 평가해야 합니다 — 온도가 0보다 높으면 평가의 이동이 발생하여 기준 비교가 무의미해집니다.
평가 기준
평가 기준은 가장 중요한 부분입니다. 모호한 평가 기준은 사용하는 모델에 관계없이 모호한 점수를 생성합니다.
Score this rewritten bullet on 5 criteria, 2 points each (total 10):
1. Action verb (0-2)
2 = strong past-tense ownership verb: Led, Built, Architected
1 = weak or passive: Helped, Worked on, Assisted
0 = no clear action verb
2. Keyword fit (0-2)
2 = target keywords integrated naturally — reads well without them
1 = keywords present but sentence feels constructed around them
0 = no target keywords present
3. Outcome present (0-2)
2 = clear measurable outcome, or placeholder [X%] used correctly
1 = outcome implied but not quantified
0 = task description only, no outcome signal
4. Truthfulness (0-2)
2 = all claims supported by original or candidate skills,
OR missing keywords correctly wrapped in [brackets]
1 = minor extrapolation, defensible
0 = claims skill not in original or skills list, not bracketed
5. Brevity (0-2)
2 = one sentence, 20 words or under, no filler phrases
1 = slightly long or contains padding
0 = multiple sentences or significantly over 20 words
추가 구조 — 5개 기준, 각각 2점씩 — 심사관이 각 차원을 독립적으로 평가하도록 강요합니다. 전체적인 점수 부여 ("이것을 1-10으로 평가하십시오")는 모든 것을 감각적인 느낌으로 축소시켜 흐트러뜨립니다. 명확한 수준 설명을 가진 기계적인 기준은 일관된 점수를 생성하여 집계하고 추세를 파악할 수 있습니다.
루브릭 설계는 심사 모델보다 중요합니다
gpt-4o-mini에서 gpt-4o로 업그레이드하는 것이 당신의 판단을 향상시키는 것보다 규준을 하나의 기준으로 조정하는 것이 덜 개선됩니다. LLM을 판단자로 사용하는 시스템의 병목 현상은 거의 모델 능력이 아니라 지시의 명확성입니다.
비교해보세요:
// Vague — produces drift
"Rate keyword integration 0-2"
// Precise — produces consistent scores
"Keyword fit (0-2):
2 = target keywords integrated naturally — the sentence reads
well without them
1 = keywords present but the sentence feels constructed around them
0 = no target keywords present"
정확한 버전은 판사가 기계적으로 적용할 수 있는 테스트를 제공합니다. "키워드 없이 이 문장이 잘 읽히는가?"는 예/아니오 질문입니다. "키워드 통합이 좋은가?"는 판단의 문제입니다. 판단의 문제는 흐트러집니다. 기계적인 테스트는 그렇지 않습니다.
점수는 절대적이지 않습니다.
7/10 점수를 받은 총알은 객관적으로 7/10 자격증 문항이란 의미가 없습니다. 이는 당신의 평가 기준에 따라, 당신의 판단자에 의해, 이 버전의 당신의 프롬프트에서 7/10 점수를 받았다는 의미입니다.
그것이 가진 것은 비교 가치입니다. 만약 리ライト 프롬프트를 변경하고 동일한 10개의 문항이 8.2/10 대신 6.8/10으로 평균한다면 — 모든 다른 것은 동일하게 유지된다면 — 프롬프트가 개선되었습니다. 이것이 체계적인 프롬프트 엔지니어링이 보이는 모습입니다: 직관이 아닌, 증거입니다.
이유는 이렇기 때문에 I 결과를 여러 실행 간에 JSON 파일에 로그 합니다:
export function logResults(results: EvalResult[], outputPath: string): void {
const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const existing = fs.existsSync(outputPath)
? JSON.parse(fs.readFileSync(outputPath, 'utf-8'))
: [];
fs.writeFileSync(outputPath, JSON.stringify(
[...existing, ...results], null, 2
));
}
각 실행은 파일에 추가됩니다. 파일은 당신의 기준선입니다. 그것이 없으면 당신은 어두운 곳에서 반복합니다.
실제로 작동한 수정: 구조적 제약 조건이 지시 사항보다 우선합니다
몇 개의 예제를 추가하고 역할 프롬프팅을 했지만, 리라이터는 여전히 부족한 후보자들에게 허구를 만들어냈다.
Sarah Johnson은 HTML, CSS, JavaScript, 그리고 WordPress를 가지고 있다. 그녀는 React와 TypeScript 역할에 지원했다. 리라이터는 이렇게 생성했다:
Original: "Attended daily standups"
Rewritten: "Developed responsive web applications using React and TypeScript,
collaborating in agile daily standups to deliver frontend solutions"
React와 TypeScript. 허구. 매번.
시스템 프롬프트에서 진실성 지시사항을 받았습니다:
Truthfulness — never invent numbers or metrics not present in the original.
If a target keyword represents a skill the candidate has not explicitly used,
do NOT claim they used it.
작동하지 않았습니다. 세 번의 프롬프트 반복, 같은 결과. 지시사항은 몇 개의 예시에 의해 무시당했습니다 — 모든 예시는 해당 기술을 가진 후보자로부터 자신감 있는 키워드 통합을 보여주었습니다. 모델은 예시를 따랐고, 쓰여진 규칙은 따르지 않았습니다.
수정은 더 나은 지시는 아니었다. 구조적인 변화였다.
이전에는 — 지시만
export async function rewriteBullet(
bullet: string,
targetKeywords: string[],
jobTitle: string
): Promise<string>
모델은 키워드를 확인하기 위한 참조 목록이 없었다. 모델은 작업이 React를 요구한다는 것을 알았다. 후보자가 그것을 가지고 있지 않다는 것을 몰랐다. 그래서 모델은 React를 통합했다 — 자신감 있고, 자연스럽게, 잘못했다.
후 — 후보자 기술(CandidateSkills)을 매개변수로
export async function rewriteBullet(
bullet: string,
targetKeywords: string[],
jobTitle: string,
candidateSkills: string[] // added
): Promise<string> {
const userMessage = `Original: "${bullet}"
Target keywords: ${targetKeywords.join(', ')}
Job title: ${jobTitle}
Candidate skills: ${candidateSkills.join(', ')} // in every call
Rewritten:`;
이제 모델은 각 호출마다 방정식의 양쪽을 가지고 있습니다. 한쪽에는 목표 키워드가 있고, 다른 한쪽에는 후보자의 실제 기술입니다. React가 키워드에 나타나지만 기술 목록에 없을 때, 모델은 기술을 주장하기 대신 플레이스홀더를 사용합니다:
Original: "Attended daily standups"
Rewritten: "Participated in agile daily standups supporting [React]
and [TypeScript] frontend delivery"
[React]와 [TypeScript]는 성실합니다. 그들은 후보자에게 이러한 기술들이 직무에 요구되지만 현재는 가지고 있지 않은 것이라고 말합니다. 이력서는 허위 정보 생성보다는 간극 분석이 됩니다.
이 지시가 아니었을 때 왜 이것이 작동했는가
통과candidateSkills는 매개변수로서 구조적 제약 조건입니다. 데이터는 매 호출마다 컨텍스트 창에 존재합니다. 모델은 명시적으로 이를 처리해야 합니다.
"진실을 말하라"는 지시는 모델이 자신의 컨텍스트에서 모든 것과 대조적으로 고려하는 제안입니다 — 자신감 있는 통합을 보여주는 몇 샷 예제를 포함합니다. 예제와 지시가 충돌하면 예제가 승리합니다.
하지만 컨텍스트 창에 있는 기술 목록은 예시와 경쟁하지 않습니다. 그것은 데이터입니다. 모델은 명령을 우선순위를 낮추는 방식으로 데이터를 무시할 수 없습니다.
배운 점: 구조적 제약 조건은 진실성 강제를 위해 지시적 제약 조건보다 우위에 있습니다. 모델이 행동하기 전에 무언가를 확인하도록 하고 싶다면, 확인할 대상을 주지 말고 그냥 확인하라고 말해주세요.
이것은 두 주간의 프롬프트 반복에서 얻은 가장 이전에 전달할 수 있는 통찰입니다. 이는 이력서 작성을 다시 쓰는 것을 넘어 훈련 데이터가 경계를 침범할 수 있는 모든 상황에 적용됩니다.
결과: 이전과 이후
헐리셔닝 문제, 해결됨
여기 동일한 후보자 — Sarah Johnson, HTML/CSS/JavaScript/WordPress 개발자, React와 TypeScript 역할에 지원 중 — 세 가지 버전의 프롬프트를 통해 검토하였습니다.
Zero-shot (1주차 — 예시 없음, 후보자Skills 없음):
Original: "Worked on client websites"
Rewritten: "Developed responsive client websites using React and TypeScript,
implementing modern CSS frameworks in an agile environment"
Original: "Helped with bug fixes"
Rewritten: "Resolved bugs in React and TypeScript, enhancing frontend
performance and responsiveness within agile development cycles"
Original: "Attended daily standups"
Rewritten: "Developed responsive web applications using React and TypeScript,
collaborating in agile daily standups to deliver frontend solutions"
React와 TypeScript가 각 항목에 나타납니다. Sarah는 둘 다 없습니다. 리팩터는 직무 설명에 최적화되어 후보자를 전혀 무시했습니다.
2주차 (역할 힌트 + 몇 샷 + 후보자Skills):
Original: "Worked on client websites"
Rewritten: "Developed client websites using HTML, CSS, and JavaScript,
ensuring responsive design and supporting [React] and
[TypeScript] frameworks"
Original: "Helped with bug fixes"
Rewritten: "Assisted in bug fixes for frontend components using HTML,
CSS, and JavaScript while supporting agile development
and [React] integration, improving [metric] by [X%]"
Original: "Attended daily standups"
Rewritten: "Participated in agile daily standups supporting [React]
and [TypeScript] frontend delivery"
차이는 구조적인 것입니다. HTML와 CSS와JavaScript — Sarah가 실제로 가진 기술 —는 직접 통합됩니다. [React]와 [TypeScript]는 괄호로 감싼 플레이스홀더로 표시되며, 주장은 아닙니다. 이제 이력서는 솔직한 이야기를 전합니다: 저는 무엇을 가지고 있고, 역할이 요구하는 것 중 아직 가지고 있지 않은 것은 무엇인지 여기에 있습니다.
이는 지원자에게 허위 정보보다 더 유용합니다. 또한 그들은 실제로 방 안에서 변호할 수 있는 것입니다.
강력한 포인트는 올바르게 보존되었습니다.
파이프라인은 모든 것을 다시 쓰지 않습니다. scoreBullet는 재작성 패스 전에 각 항목을 평가하고 실제로 작업이 필요한 항목을 표시합니다.
여기는 프리야 패테尔的 이력서 — 스태프 엔지니어로서 주임 엔지니어 역할을 지원하는 — 같은 파이프라인을 거친 것입니다.
Original: "Architected event-driven microservices platform handling
50M daily events, reducing infrastructure cost by 40%"
Rewritten: [unchanged — scoreBullet returned needs_rewrite: false]
Original: "Led team of 8 engineers across 3 time zones to deliver
platform re-architecture 2 weeks ahead of schedule"
Rewritten: [unchanged]
Original: "Defined engineering standards adopted across 4 product
teams, reducing incident rate by 35%"
Rewritten: [unchanged]
프리야의 총 세 발이 보존되었습니다. 실제 지표, 소유 동사, 범위 맥락이 강한 총알은 다시 쓰지 않아도 됩니다 — 그리고 파이프라인이 올바르게 그것을 식별합니다. 리터이터는 좋은 입력을 증폭시킵니다. 존재하지 않는 신호를 제조할 수는 없습니다.
점수 밑변
평가 루프에서 6/10 미만으로 점수를 받은 총알은 모두 원본에 행동 동사가 없고, 기술이 없으며, 결과가 없는 것으로 추적되었습니다:
5/10 — original: "Helped with bug fixes"
no tool, no scale, no outcome — nothing to amplify
5/10 — original: "Attended daily standups"
describes presence, not contribution
프롬프트 엔지니어링으로는 이를 수정할 수 없습니다. 강력한 볼렛을 작성하기에 필요한 정보가 원본에 존재하지 않습니다. 이것은 제품 문제가 아니라 프롬프트 문제가 아닙니다. 올바른 해결책은 사용자에게 코칭 메시지를 노출시키는 것입니다: "이 볼렛은 우리가 작업할 만큼 충분한 정보를 주지 못합니다. 작업한 내용, 사용한 도구, 또는 작업으로 인해 발생한 변경 사항을 추가해 보세요."
무내용의 볼트의 리와이터 천장은 프롬프트 품질에 관계없이 대략 6/10입니다. 그 천장이 존재한다는 것을 알고 — 그리고 사용자들과 솔직하게 그것을 이야기한다는 것 — AI가 아무것도 고칠 수 있다고 속이는 것보다 더 가치가 있습니다.
숫자
점수 진행
| 런 | 점수 | 주요 변경 사항 |
|---|---|---|
| 1주차 기준선 | 7.37/10 | 조합된 역할 + 몇 샷 프롬프트 |
| 2주차 6일차 | 8.26/10 | 결과 보류 지시 |
| 2주차 7일차 | 8.18/10 | 경계 사례 수정 — 노이즈 내 |
| 2주차 8일차 최종 | 8.37/10 | 안정적 확정 점수 |
총점 개선: 두 주간 +1.0 점.
8.37로 두 번의 실행에서 동일한 최저 점수의 단추가 이것이 안정적인 측정값이지 운이 좋은 실행이 아님을 확인했습니다. 온도 0에서 실행 간의 ±0.15 변동은 예상되는 잡음 바닥입니다 - 그 범위 내의 변화는 의미가 없습니다.
차원 분해
| 기준 | 1주차 | 2주차 | 변경 |
|---|---|---|---|
| 동작 동사 | 1.82/2 | 1.89/2 | +0.07 |
| 키워드 적합도 | 1.45/2 | 1.37/2 | -0.08 |
| 결과 | 1.08/2 | 1.97/2 | +0.89 |
| 진실성 | 1.68/2 | 1.66/2 | -0.02 |
| 간결함 | 1.26/2 | 1.47/2 | +0.21 |
결과 이야기
결과는 눈에 띄는 개선입니다 — 단일 지시 변경으로 2점 척도에서 +0.89 했습니다.
주1: 리라이터는 동사와 키워드에 능했지만 결과 신호를 거의 추가하지 않았습니다. 원본 볼렛에 결과가 없었을 때 모델은 리라이트 결과도 없는 채 남겼습니다. 결과를 설명하지 않는 작업을 설명하는 볼렛은 채용 위원회에게 평가할 점이 없습니다.
수정 방법은 시스템 프롬프트에 추가적인 우선순위가 하나 있었습니다.
7. Outcome placeholder — if no outcome exists in the original bullet,
add a placeholder rather than leaving the bullet outcome-free:
"improving [metric] by [X%]", "resulting in [outcome]",
"reducing [problem] by [X%]", or "enabling [result]".
Never leave a rewritten bullet without any outcome signal.
그 지시는 결과를 1.08/2에서 1.97/2로 이동시켰습니다. 2점 척도에서 거의 전체 점수입니다. 두 주 중 가장 높은 ROI 프롬프트 변경입니다.
키워드 적합 회귀
키워드 적합도는 1.45/2에서 1.37/2로 약간 감소했습니다. 이는 의도된 것이었습니다.
리와이터는 관련성에 관계없이 모든 약한 볼트에 일반적인 ATS 문구 — "배송 기록", "강력한 포트폴리오", "5년 이상 경험" — 를 주입하고 있었습니다. 라우트 핸들러에 필터가 추가되었습니다.
const GENERIC_KEYWORD_FILTER = [
'delivery record',
'strong delivery record',
'5+ years experience',
'strong portfolio',
'fast learner',
'team player',
];
const targetKeywords = [
...new Set([...(missingSkills || []), ...(atsKeywords || [])])
].filter(k => !GENERIC_KEYWORD_FILTER.some(
g => k.toLowerCase().includes(g.toLowerCase())
));
일반적인 표현을 필터링하면 키워드 커버리지가 약간 감소했습니다. 그것은 긴 이력서에서 키워드 과다 투입을 완전히 제거했습니다. 그 트레이드오프는 올바르다 — 모든 볼렛이 "enhancing delivery record and meeting business requirements"로 끝나는 이력서는 경험 많은 리커터에게 기계 생성임을 신호합니다.
8.37/10이 실제로 의미하는 것은 무엇인가
이 등급이 아닙니다. 이는 기준선입니다.
모든 미래의 프롬프트 변경은 8.37에 대해 측정됩니다. 변경이 동일한 10개의 이력서와 동일한 루브릭 및 심사관으로 8.6을 생성하면 프롬프트가 개선되었습니다. 만약 8.1을 생성하면 개선되지 않았습니다. 숫자는 비교에서만 의미가 있으며 고립된 상태에서는 의미가 없습니다.
이것은 설계대로 작동하는 평가 루프입니다. 그것이 없다면 각 프롬프트 변경은 추측입니다. 그것이 있다면 각 프롬프트 변경은 실험이 됩니다.
평가 통계
Bullets evaluated: 38
Average score: 8.37 / 10
Improved over orig: 30 / 38 (79%)
Unchanged (strong): 8 / 38
Average breakdown:
Action verb: 1.89 / 2
Keyword fit: 1.37 / 2
Outcome: 1.97 / 2
Truthfulness: 1.66 / 2
Brevity: 1.47 / 2
문서에 없는 것에 대해 배운 점
1. 몇 샷 예제는 충돌할 때 쓰여진 지시를 재정의합니다
"후보자가 가지고 있지 않은 기술을 주장하지 마세요"라고 시스템 프롬프트에 작성했습니다. 몇몇 몇 샷 예제는 해당 기술을 가질 가능성이 있는 후보자로부터 자신감 있는 키워드 통합을 보여주었습니다. 모델은 예제를 따랐습니다.
이것은 버그가 아니라 맥락 학습이 작동하는 방식입니다. 모델은 작성된 규칙을 따르는 것보다 보여주어진 행동에 대해 더 잘 패턴 매칭합니다. 이는 예를 선택하는 것이 작성한 지시사항보다 더 중요하다는 의미입니다.
실질적 함의: 몇 개의 예제는 지시사항만큼 철저히 검토하세요. 예제가 경계 사례에서 원하지 않는 행동을 보여준다면, 모델은 지시사항이 무엇이든 경계 사례에서 그것을 반복할 것입니다.
보여주지 말고 말해주세요. 하지만 어떤 것을 보여주는지 주의하세요.
2. 안전과 품질은 같은 측정 기준이 아닙니다.
나는validateRewrite 함수는 재작성된 총알을 서비스하기 전에 게이트로 작동했습니다:
// Returns: "use_rewrite" | "use_original" | "rewrite_again"
export async function validateRewrite(
original: string,
rewritten: string,
targetKeywords: string[],
candidateSkills: string[]
)
이것이 안전하게 서비스할 수 있는지에 대한 질문에 답했습니다: 가공된 기술이 없고, 키워드가 존재하며, 원본과 다르다 — 사용합니다.
평가 루프는 다른 질문에 답했습니다: 이것이 얼마나 좋은지?
탄알은 첫 번째 질문을 통과하고 두 번째를 통과하지 못할 수 있었습니다:
validateRewrite: recommendation = use_rewrite
truthfulness_risk = low ← passed the gate
eval loop: total_score = 5/10
action_verb = 1 ← weak verb
outcome = 0 ← no outcome signal
brevity = 1 ← slightly long
탄알은 안전했습니다. 그것은 좋지 않았습니다. 두 가지 평가 모두 올바웠습니다 — 그들은 다른 것들을 측정하고 있었습니다.
AI 기능을 개발하는 대부분의 개발자들은 안전 레이어에서 멈춘다. 그들은 환각을 방지하기 위해 검증을 추가하고 완료하다고 생각한다. "서비스할 만큼 안전하다"와 "유용할 만큼 충분하다" 사이의 간극에서 사용자 신뢰가 조용히 약화된다. 평가 루프는 그 간극을 드러낸다.
생산 환경 수정 방법은 연결evalBullet를 두 번째 게이트로 사용하며 최소 임계값을 설정한다 — 안전을 통과했지만 품질은 통과하지 못하는 총알들은 평균적인 리팩토링 대신 코칭 메시지를 받는다. 이것은 3주차의 과제다.
3. 프롬프트 엔지니어링이 달성할 수 있는 최대한도는 명확하다
평가 루프에서 6/10 미만으로 점수를 받은 모든 총알들은 동일한 프로필을 가지고 있었다: 원본에는 행동 동사가 없고, 기술이 없으며, 결과가 없는 것
"Attended daily standups" → no tool, no contribution, no result
"Helped with bug fixes" → passive, no scope, no outcome
"Worked on improving things" → no specificity whatsoever
이런 포인트에 대한 리와이터 한도는 프롬프트가 얼마나 좋든 6/10으로 약간입니다. 강력한 포인트를 작성하기 위해 필요한 정보는 원본에 존재하지 않습니다.
이것은 프롬프트 문제가 아닙니다. 입력 문제입니다.
어떤 지시, 몇 샷 예시, 또는 역할 프롬프팅의 양이나 많아도 실제로 존재하지 않는 신호를 만들어낼 수는 없습니다. 빈 콘텐츠의 포인트에 대한 올바른 응답은 더 나은 다시 쓰기가 아니라 후보자에게 더 많은 정보를 요청하는 것입니다.
"This bullet doesn't give us enough to work with.
Try adding: what you built, what tool you used,
or what changed because of your work.
Example: 'Attended daily standups' →
'Attended daily standups for a 6-person React team
shipping features weekly'"
코칭 메시지는 후보자에게 모델이 원본에서 생성할 수 있는 어떤 재작성보다 더 유용합니다. 또한, 자신감 넘치는 AI 출력이 거의 가지지 못하는 방식으로 솔직합니다.
두 주간 동안 프롬프트 엔지니어링이 나에게 가르쳐준 가장 중요한 것은 그것의 한계가 어디인지입니다. 하한선을 알고 — 그리고 사용자들에게 그것을 투명하게 공개한다면 — AI가 주는 모든 것을 고칠 수 있다고 속이는 것보다 더 가치가 있습니다.
3주차에 어떤 변화가 일어납니다
평가 루프에서 네 가지 구체적인 간극이 드러났습니다. 이는 단독으로 변화만으로는 해결할 수 없는 것들입니다. 이것들은 3주차의 시작 과제들입니다
코드 수준의 키워드 분포 추적 시스템 프롬프트에서의 키워드 분포 지시는 상태 없는 API 호출에 걸쳐 효과가 없습니다 — 각 점은 이전 점에서 나타난 키워드를 기억하지 않는 별도의 호출입니다. 수정 방법은 라우트 핸들러에서 전체로 재작성된 집합 전체를 추적하여 키워드 빈도를 추적하고 과도하게 반복되는 구문을 제거하는 후처리 패스입니다. 부분 버전은 이미 생산 중입니다. 올바른 구현은 점 간 상태를 필요로 합니다.
기술적 기술이 없는 후보자에 대한 소프트 스킬 일치. 후보자가 직무 기술 설명과 일치하는 기술이 없을 때, 모델은 상위 수준의 "일치하지 않음" 평가를 수행하고 소프트 스킬 겹침을 평가하기 위해 기술 배열을 중단합니다. 데이터 분석가로 지원하는 교사는 소통 및 리더십을 그의 기술 배열에 가지고 있지만 — 이는 직무에서 요구됩니다 — 분석기는 반환합니다matched_skills: []은 코드 수준의 후처리 단계로, TypeScript에서 직접 기술 스킬 배열을 JD 키워드와 비교하여 이 필드에 대한 모델을 무시합니다.
운영 환경 평가 게이트. 평가 루프는 현재 측정 도구로 오프라인에서 실행됩니다. 3주차에 evalBullet는 비동기 품질 게이트로 운영 경로에 연결되어 합니다 — 통과하는 점프validateRewrite 하지만 평가 기준에서 6/10 미만 점수를 받으면 평범한 재작성 대신 코칭 메시지를 받습니다. 게이트는 백그라운드에서 실행되므로 중요 경로에 지연을 추가하지 않습니다.
제어된 비교: 결합된 것과 몇 샷만의 것만 비교합니다. 1주차와 2주차는 결합된 프롬프트(역할 + 몇 샷)만 테스트했습니다. 역할 프롬프팅의 독립적인 기여도는 결코 측정되지 않았습니다. 3주차는 같은 10개의 이력서를 몇 샷만의 변형(역할 선언 없음)을 통해 실행하고 그 점수도 평가합니다. 두 점수 사이의 차이가 역할 프롬프팅의 실제 측정된 기여도입니다. 이것이 블로그 포스트가 전형적인 이야기보다는 기술적으로 신뢰할 만한 증거를 만드는 종류의 증거입니다.
타겟: 같은 10개의 이력서에 걸쳐 평균 8.8/10.
공개된 부분
이 포스트는 10주의 교육과정의 2주차를 기록합니다. 여기서는 실제 사용자가 돈을 내고 사용하는 실제 SaaS 제품인 Resume AI Tailor에 프롬프트 엔지니어링 기술을 적용합니다.
평가 기준선은 7.37/10에서 시작하여 8.37/10으로 끝났습니다. 3주차의 목표는 8.8/10입니다.
다음 포스트에서 주 3 결과를 발표할게요 — 제어된 비교, 생산 평가 게이트, 그리고 새로 나타나는 모든 새로운 경계 사례를 포함해요 — 만약 점수가 8.8을 밟지 못하면 그것도 말할게요.
평가 루프 코드, 프롬프트 변형, 그리고 전체 파이프라인은 GitHub에 있어요: github.com/Azeez1314/resume-ai
제품은 resumetailor.cv에서 활성화되어 있어요
당신에게 질문 하나 있어요
AI 기반 기능을 개발하고 "나에게는 좋아 보인다" 이상의 출력 품질을 측정하고 있다면, 진심으로 당신이 무엇을 사용하고 있는지 알고 싶습니다.
LLM을 판단자로 사용하는 패턴은 연구에서 잘 문서화되어 있지만, 프로덕션 SaaS에서는 잘 활용되지 않고 있습니다. 기술이 존재함을 알고 있음과 실제로 당신이 결정을 내리는 점수화된 기준을 가지고 있음 사이의 격차는 매우 큽니다. 만약 그 격차를 메우셨다면, 어떻게 하셨는지 듣고 싶습니다.
댓글을 남기거나, X와 LinkedIn에서 NanoCrafts로 나를 찾아보세요.
Resume AI Tailor는 NanoCrafts 포트폴리오의 일부로, 공개적으로 구축되고 배포된 집중형 SaaS 도구 모음입니다.
resumetailor.cv · nanocrafts.xyz · azeezroheem.hashnode.dev











