1. 모델 선정 및 비교
모델을 한 번에 고르지 않았다. 용도별로 나눠 시작했고(ADR-0011), 로컬 sLLM 을 실측해 접었다가 장비 조건을 바꿔 되살렸으며(ADR-0024 · 0032), 마지막에 자체학습 어댑터와 클라우드 API 를 같은 자로 견줘 확정했다(2026. 09. 18.). 이 절은 그 세 번의 비교다.
1-1. 용도별로 나눈다 — 비용이 데이터 양에 비례하는 쪽이 따로 있다
LLM 호출이 생기는 자리는 둘이고 요구가 다르다. 도구 호출은 호출 수가 적지만 정확도가 데모 품질을 결정하고, 이력서 요약은 건수가 지원자 수만큼 늘어 단가가 지배적이다. 하나로 고정하면 한쪽이 손해라 용도별로 갈랐다.
| 용도 | 모델 | 단가 (1M 토큰) | 고른 이유 |
|---|---|---|---|
| 도구 호출 에이전트 | claude-opus-5 |
입력 $5 / 출력 $25 | 문장 → 도구 순차 호출의 정확도가 곧 데모다. 여기서 아끼면 발표가 깨진다 |
| 이력서 요약 · 구조화 | claude-haiku-4-5 |
입력 $1 / 출력 $5 | 정해진 형식으로 요약하는 작업. 건수가 많아 단가가 지배적 |
요약 1건을 입력 3K + 출력 0.5K 토큰으로 잡으면 opus ≈ $0.028, haiku ≈ $0.006 이다.
더미 10만 건이면 ≈ $2,750 대 ≈ $550 — 어느 모델이든 사고다. 그래서 ADR-0011 의 본체는
모델 선택이 아니라 가드다. 더미 데이터에는 LLM 을 호출하지 않고, 요약 생성 경로를
접수 훅과 명시적 재생성 버튼 두 곳으로 막고, 호출마다 토큰·비용을 로깅하고,
생성에 쓴 모델명을 applications.ai_summary_model 에 남겨 어느 요약이 어느 모델
산출인지 추적한다.
실제로 opus 는 쓰지 않았다. 2026. 09. 15. claude-opus-5 를 실측했으나 비용상 미채택했고,
채팅·요약 기본 모델은 claude-haiku-4-5 로 유지된다.
1-2. 로컬 sLLM — 실측으로 접고, 조건을 바꿔 되살렸다
출발점은 지원자 개인정보가 외부 API 로 나가지 않는 “데이터 반출 없는 채용 에이전트”였다. 2026. 09. 01. 실측 조건은 RTX 3050 8GB · DB 지원자 500건 · 실제 도구 실행이다.
| 질의 | claude-haiku-4-5 | qwen3:4b | qwen3:8b |
|---|---|---|---|
| 읽기 도구 1개 | 13.6초 | 73.2초 | 107.5초 |
| 쓰기 (확인 카드까지) | 2.9초 | 32.6초 | 31.0초 |
| 도구 연쇄 시도 | 6.6초 | 86.8초 | 130.5초 |
| 출력 tok/s | 106 | 45 | 7.4 |
병목은 프롬프트가 아니라 출력 길이였다. 논의는 “고정 프리픽스 17KB 가 무겁다”에서
출발했는데, 같은 질의에서 qwen3:4b 가 3,248 출력 토큰을 쓰는 동안 haiku 는
1,450 토큰을 썼다. 사고 모드를 꺼도 3,444 → 3,248 로 거의 그대로여서 원인이 아니었다.
Ollama format 에 결정 스키마를 걸어 출력 형식을 강제하자 형식 붕괴가 사라졌고,
같은 읽기 질의에서 qwen3:4b 의 출력 토큰이 2,653 → 252 로 줄면서
읽기 60.4 → 16.9초 · 쓰기 42.9 → 3.5초 · 연쇄 69.0 → 11.2초가 됐다.
문법 제약이 <think> 누출을 원천 차단하니 사고 모드를 꺼도 안전해졌고,
그러자 남아 있던 사고 토큰까지 통째로 없어졌다 — 지우는 것과 안 만드는 것의 차이다.
qwen2.5:3b 는 여기서 접었다 탈락 —
“10명”을 “5명”이라 하고 도구가 주지 않은 학력을 지어낸다.
형식이 아니라 능력 문제라 문법 제약으로 안 잡힌다.
반대로 8B 의 7.4 tok/s 는 모델이 느린 값이 아니라 8GB 카드에 KV 캐시 자리가 없어 레이어가
CPU 로 흘러넘친 값으로 봤다 — 4B 의 1/6 이라는 낙차가 그 신호다.
시연 장비를 T4 16GB 로 잡으면서 ADR-0032 가 8B 를 되살렸다.
1-3. 추론 모델 3종 확정 (ADR-0032 · 2026. 09. 08.) 소연 D
| 역할 | 모델 | 라이선스 | VRAM | 비고 |
|---|---|---|---|---|
| 표정 | ViT (Vision Transformer) | Apache 2.0 | ~0.35GB | 판정기가 아니라 특징 벡터의 일부 (§4) |
| 전사 | Whisper large-v3-turbo + faster-whisper | MIT | ~1.6GB | 프로덕션 기본은 09. 15. API 로 전환 |
| sLLM | Qwen3-8B (Q4) | Apache 2.0 | ~5.2GB + KV ~1GB | 자체학습 대상 |
| 임베딩 | ko-sroberta-multitask (768차원) | Apache 2.0 | ~0.5GB | 이미 로컬 — 바꾸지 않았다 |
| 합 | 넷을 같이 올린다 | ≈ 8.7GB | T4 16GB 에 7GB 를 남기고 들어간다 | |
CPU 실측(2026. 09. 08.)이 곧 GPU 가 필요한 이유였다. Whisper large-v3-turbo(int8)는 로딩 48초 · 1.8배속(35.6초 음성 → 19.5초) · 메모리 ~1.0GB, ViT 는 로딩 1.4초 · 130ms/장 · ~0.9GB 다. 백엔드 API 는 t3.micro(1GiB)라 Whisper 하나가 들어가지 않는다.
전사는 2026. 09. 15. 프로덕션 기본을 갈아탔다(ADR-0038). t3.large 2 vCPU 에서 로컬 large-v3-turbo 가
43.9초 발화를 180초 상한 안에 끝내지 못했고, 밀린 시간이 뒤 답변까지 연쇄로 죽였다.
OpenAI whisper-1(분당 $0.006 · 면접당 ≈ $0.06)은 CPU 를 쓰지 않아 줄에 서지 않는다.
이후 GPU 로 옮긴 T4 실측은 STT 275ms/3초 · ViT 75ms/crop, 전사 7.5초 → ~1초,
8명 동시 접속에도 대기줄 0이다.
1-4. 자체학습 어댑터 vs 클라우드 API — 같은 자로 쟀다 수택 E
Qwen3-8B 를 QLoRA(4-bit + 어댑터)로 자체학습해 chat · summary · interview 어댑터 3갈래를
만들었다. 학습 데이터는 222건(실측 21 + 합성 192 + 인터뷰 chain 시드 9)이고, 초기 AWS G4dn(T4)에서
시작해 v7~v9 는 Google Colab GPU(L4/T4)에서 훈련했다. 비교는 동일 케이스 23건 · 동일 채점기
judge.py 로 했다 — 감이 아니라 근거로 정하기 위해서다.
| 항목 | Qwen 학습 전 (09. 11.) |
Claude Haiku 4.5 (09. 12.) |
Qwen v9 (09. 17.) |
|---|---|---|---|
| 도구 호출 정확도 | 26.1% (6/23) | 69.6% (16/23) | 73.9% (17/23) |
| 도구 이름 일치 | 26% | 70% | — |
| no-tool 위반 없음 | 100% | 96% | — |
| 위험한 실패 (임의 실행 · 지어낸 도구) |
0건 | 0건 | 0건 |
| 비용 | $0 + GPU $0.71/h | $0.0139 / eval건 $0.0075 / 실사용 |
$0 + GPU $0.71/h |
| 콜드 스타트 | 78초 | — | 78초 |
해석 주의 — 이 표를 어디까지 믿는가
- 채점기에 편향이 있다.
judge.py의 정답이 Qwen 쪽 gold 기준이라 절대 수치가 아니라 같은 자로 잰 상대 궤적만 유효하다. 이 사실을 보고서에 명시했다. - 표본이 23건이다. 73.9% 와 69.6% 의 차는 케이스 한 건이다 — “Qwen 이 이겼다”가 아니라 “동급”이 정직한 표현이다.
- 분모 23 이 상한이 아니다. 정답을 그대로 채점기에 넣어도 20/23 만 통과한다 (채점기·데이터 결함 3건을 고친 09. 14. 기준 · 그 전에는 19/23). 같은 자로 보면 Claude 16/23 은 상한 대비 80.0% 다.
- 학습 전 26.1% 는 결함을 고치기 전(09. 11.) 값이다. 09. 14. 채점기 수정 뒤 GPU 재기동이 필요해 재측정하지 못했다 — 세 값을 한 줄에 놓되 맨 왼쪽 칸만 채점기 판본이 다르다는 것을 적어 둔다.
- Claude 의 write 유형 0/5 는 모델 실패가 아니다. 쓰기 요청에 확인 카드를 띄우고 멈추는
것이 정상 동작인데 채점기가 한 턴만 본다 — 단일턴 채점의 한계다.
실패 7건 대부분이
change_stage를 부르지 않고search_applications뒤 확인을 요청한 경우로, 도구를 더 부른 것이 아니라 덜 부른 것이라 집합 완화로도 구제되지 않는다.
결정(2026. 09. 18.) — 심사·운영 서빙은 클라우드(AWS) + Claude 로 확정했다. 갈린 곳은 정확도가 아니었다. 요약 어댑터의 경력 연수 오차(3년 → 5년) · 프롬프트 예시 숫자 베끼기 · JSON 따옴표 깨짐 같은 형식·수치 안정성과, 콜드 스타트 78초 · 동시 접속 8명에서 STT 대기 15초 → 42초 같은 지연·동시성에서 갈렸고, 그 오류들이 심사 직전(코드 프리즈 09. 20.)에 누적됐다. 학원 PC 3대로 실제 구축·운영까지 해 본 온프레미스도 같은 날 폐쇄했다. 학습 파이프라인과 어댑터 3갈래는 비용 절감 R&D 자산으로 보존하며, Ollama 스위치 하나로 다시 끼울 수 있게 남겼다.
2. 프롬프트 및 파이프라인 설계
프롬프트를 한 덩어리로 두지 않았다. 작업 단위로 쪼개 실패를 격리하고, 점수는 세 축으로 나눠 근거를 남기며, 고정부를 캐시에 올려 원가를 관측한다. “프롬프트를 잘 쓴다”가 아니라 프롬프트가 곧 원가와 품질의 구조라는 것이 이 절의 내용이다.
2-1. 요약을 3단 체인으로 쪼갰다 (ADR-0022)
단일 프롬프트가 gist · fit · concerns 를 한 번에 뽑던 구조였다. 한 프롬프트에 여러 작업을 요구할수록 출력 품질이 떨어지고, 중간 결과를 재사용할 수 없어 요약만 필요할 때도 전체를 다시 호출해야 했다. 세 단계로 나눴다.
| 단계 | 입력 | 출력 | 비용/건 |
|---|---|---|---|
| 1. 요약 | 이름 · 학력 · 경력 · 기술 · 자기소개 | gist · skills_extracted · experience_summary | ~$0.001 |
| 2. 평가 | 1단계 출력 + 채용 공고 | fit[] · concerns[] · fit_score | ~$0.002 |
| 3. 추천 | 2단계 출력 | group (pass / review / reject) + 근거 | ~$0.001 |
전부 Haiku 로 합계 ~$0.004/건이라 단일 호출($0.001~0.005)과 비슷하다.
얻은 것은 비용이 아니라 구조다 — 단계별 출력을 ai_summary JSON 에 구조화 저장해
재호출 없이 재사용하고, 2단계가 실패해도 1단계 결과는 남는다.
v 필드로 버전을 구분해 기존 데이터 마이그레이션 없이 확장했다.
호출이 3회로 늘어 지연이 커지지만 백그라운드 태스크라 담당자 화면에 보이지 않는다.
2-2. 점수는 3축 100점, 임계는 공고가 갖는다 (ADR-0034)
2단계가 세 재료를 따로 채점한다 — 필수 요건(job_postings.requirements +
description) · 우대(preferred) ·
인재상(company_profile.talent_profile). 각 0~100 을 회사 가중치로 합산해
applications.doc_score 를 내고, 내역과 근거는 doc_score_detail JSON 에 남긴다.
합산 점수 하나만 남기면 왜 떨어졌는지 답할 수 없기 때문이다.
공고별 임계(pass_threshold, 기본 60) 이상이면
applied → screening → interview, 미만이면 rejected.
stage_history 에 changed_by=NULL(시스템)과
“아르 서류 심사 N점 (기준 M점)” 사유를 남긴다.
screening_mode='manual' 인 공고는 점수만 매기고 옮기지 않는다 — 자동을 끄는 스위치다.
면접 종료 시 백그라운드로 답변 요건 대조(전사 텍스트 ↔ 공고 요건 · 우대 · 인재상,
0~100)를 채점해 interview_sessions.ai_score 에 넣는다.
ADR-0034 의 설계는 여기에 진위 일관성을 30% 얹는 것이었으나,
표정 · 음성 신호를 점수 재료로 쓰지 않는 쪽으로 정리되면서 지금은 담당자 참고 지표로만
화면에 뜬다(§4). 면접관은 점수를 넣지 않고 코멘트만 남긴다.
final_score = doc_score × 50 + interview_ai_score × 50(회사별 조정 가능),
등급 S(85↑) · A(70↑) · B(55↑) · C.
최종 합격 · 불합격은 사람이 카드를 옮긴다 — 아르는 면접 단계에서 멈춘다.
2-3. 면접 분석 파이프라인 — STT → 근거 대조 → Claude 우정 B
Whisper 가 답변 음성을 전사하면, 해당 지원자의 이력서 · 자소서 · 직무 요건에서 관련 근거를 찾아 컨텍스트로 결합하고, Claude 가 그 근거를 인용하며 답변이 서류와 일치하는지 대조한다. 근거 문서 없이 모델이 단독으로 판단하는 경로를 두지 않았다. 대조는 원문을 인용하므로 지원자가 반박할 수 있는 유일한 AI 판단 근거이기도 하다(§4).
실시간 분석 신호는 세어서 AI 가 수치로 요약해 종합 평가에 표시하되, 수치에 없는 숫자나 사람을 단정하는 단어가 나오면 그 문장을 버리고 고정 틀로 대체한다 — 요약이 재료에 없는 것을 말하기 시작하면 참고 지표가 아니라 판정이 되기 때문이다.
2-4. 프롬프트가 곧 원가다
2026. 08. 31. API 예산이 하루 만에 소진됐다. 모델은 haiku 였고, 원인은 요약이 아니라 채팅이었다.
| 원인 | 조치 |
|---|---|
| 고정부(시스템 프롬프트 9.2KB + 도구 정의 11개 10.7KB ≈ 5~7K 토큰)를 매 호출 정가로 재과금 | 프롬프트 캐싱 (cache_control) |
| 담당자 이름이 프롬프트 6번째 줄 → 사람마다 캐시가 갈림 | 담당자 절을 맨 뒤로 — 앞 99.3% 가 전원 공통이 된다 |
| 대화 이력 상한 없음 — 프론트가 전 이력을 보내고 서버도 자르지 않았다 | MAX_HISTORY_MESSAGES=20 |
search_applications 기본 50건 · 최대 200건이 도구 결과로 들어와
이후 모든 라운드에 재전송 |
기본 10 · 최대 50 |
캐싱에는 함정이 하나 있다. claude-haiku-4-5 의 최소 캐시 가능 프리픽스는 4,096 토큰이고
(opus-5 512 · sonnet-5 1,024 로 세대순이 아니다), 미달하면 에러 없이 조용히 캐시가 안 걸린다.
그래서 cache_read_tokens 로 적중을 먼저 확인했다.
결과는 같은 4단계 시나리오에서 2026. 08. 28. $0.045 → 09. 17. 재측정 $0.036,
캐시 읽기 58,088 토큰이다. 실사용 평균은 아르 호출당 $0.0075.
다만 이 낙차를 캐싱 하나의 효과로 말하지 않는다 — 아래 규칙 라우터 몫이 섞여 있고,
측정된 고정부는 14,522 토큰이라 4,096 미달 걱정 자체는 해당이 없었다.
아예 LLM 을 태우지 않는 길도 냈다. “안녕” “뭘 할 수 있어” 같은 정적 발화가
호출당 ~$0.02 씩 나가던 것을 규칙 의도 라우터의 고정 응답으로 돌렸다
(^…$ 로 묶어 실제 요청이 붙으면 LLM 으로 넘긴다).
지원자 FAQ 도 인사 · 연봉 · 합격 가능성 · 감사는 규칙 응답이고, 나머지만 회사 프로필을
근거로 Claude 가 답한다. 실측 3건 대화 8.4K 토큰 $0.04 → 기본 질문 구간 $0.
로깅에서 교훈 하나가 남았다. 토큰 · 비용을 코드에서 넘기고 있었는데
JSONFormatter 가 다섯 필드만 화이트리스트로 골라 담아 전부 버려지고 있었다 —
호출이 있었다는 사실만 남고 숫자가 없었다. 로깅은 넣은 것만으로 가드가 되지 않고 출력까지 눈으로
확인해야 가드다. 화이트리스트를 걷어내자 이번엔 search_applications 의 검색어에
지원자 이름 · 이메일이 찍혀서, 값 대신 인자 키 이름만 남기도록 바꿨다.
3. 정확도 평가 및 개선
같은 자를 먼저 만들고 그 다음에 고쳤다. 프롬프트를 바꿀 때마다 몇 개 질문을 손으로 던져 보는 것으로는 나아졌는지 나빠졌는지 알 수 없다. 이 절은 채점기 → 학습 → 프롬프트 → 판정 알고리즘 순으로 무엇을 어떻게 재서 무엇이 바뀌었는지를 적는다.
3-1. 채점기 먼저 — eval 프레임워크 (ADR-0023)
ADR-0023 이 설계한 자리는 backend/eval/(케이스 YAML · runner.py ·
judge.py · report.py)였다. 실제로 선 것은 그 디렉터리가 아니라
ai/qwen-training/ 이다 — 자체학습과 같은 자리에서 자라야 했기 때문이다.
케이스는 dataset.test.jsonl 23건, 실행은 eval.py(로컬 어댑터 · GPU 필요)와
eval_anthropic.py(Claude · GPU 불필요) 둘이며, 그 둘이 judge.py 한 벌을 공유한다.
2026. 09. 12. 채점 규칙을 eval.py 에서 떼어낸 것이 이 절의 출발점이다 —
채점기가 두 벌이면 두 모델의 숫자를 나란히 놓을 수 없다.
CI 에는 넣지 않았다 — LLM 호출 비용과 실행 시간 때문에 로컬 수동 실행으로 정했다.
채점기 자체도 한 번 고쳤다(2026. 09. 14.). pending_action 판정이 정답 도구 집합에서
재유도되던 것을 데이터의 라벨만 보게 바꾸고, 도구 이름 완전 일치를
부분집합 + 추가분이 전부 읽기 도구면 통과로 완화했으며
(“먼저 찾고 실행”을 오답 처리하던 것), 정답 데이터에 있던 존재하지 않는 도구
update_candidate_stage 를 change_stage 로 고쳤다.
통과 수는 그대로였고 정답 상한이 19/23 → 20/23 으로 정확해졌다.
판정은 규칙 기반을 우선한다. 도구 이름 · 인자 비교, 응답 키워드 포함 / 미포함, 도구 결과에 없는 정보를 생성했는지(할루시네이션)는 전부 규칙으로 보고 비용 0이다. 응답 품질만 LLM-as-judge(~$0.002/건)로 보조한다 — LLM 판정 자체의 편향을 리스크로 적어 두고 그렇게 나눴다. 최종 비교에 쓴 판정 축은 다섯이다: 도구 이름 · no-tool 위반 · 확인 문구 · 인자 · 응답.
3-2. 손실은 떨어지는데 도구 호출은 전멸했다 수택 E
Qwen3-8B QLoRA 학습에서 eval_loss 가 0.339 → 0.074 로 교과서처럼 떨어졌는데,
도구 호출 정확도는 6/23 (26.1%) 였다. 대부분 <think> 안에서
“search_applications 를 불러야 한다”고 추론까지 하고 실제 호출을 내지 않았다.
원인은 데이터 구성이었다. 177개 샘플이 거의 같은 7,347자 시스템 프롬프트를 공유해
3 epoch 동안 프롬프트를 외우는 것으로 손실이 떨어졌고, 정작 배워야 할
<tool_call> 은 전체 토큰의 1.8% 라 기울기가 거의 닿지 않았다.
손실이 낮다는 것은 무언가를 잘 배웠다는 뜻이지 우리가 원한 것을 배웠다는 뜻이 아니었다.
손실이 프롬프트에 쏠리는 구조를 교정하고 같은 채점기로 v6 · v7 · v8 · v9 를 반복 측정해 v9 에서 17/23 = 73.9% 를 얻었다 해결. 실패 6건 중 5건은 확인 문구 · 안전한 다단계처럼 UX 상 정상 동작이라 실질은 ~95%+ 다 — 다만 이 보정치는 채점기가 아니라 사람이 실패 내용을 읽고 분류한 값이다.
3-3. 프롬프트로 고친 것 셋
① 서류 점수가 전원 64~65점으로 수렴했다
온프레미스 요약 재생성에서 모든 지원자가 요건 70 · 우대 40/50 · 인재상 60 으로 나와
변별력이 사라졌다. 소형 모델이 chain_evaluate.v2 프롬프트의
출력 예시 숫자 70/40/60 을 그대로 베낀 것이었고, Claude 는 베끼지 않아 드러나지 않았다.
v3 에서 예시 숫자를 지우고 채점 구간표(85~100 / 65~84 / …)와 절차를 명시하자
실측이 85 · 85 · 40 · 15 로 갈렸고 Claude 의 78 · 86 · 44 와 같은 방향을 가리켰다.
② 요약 JSON 이 깨져 점수가 NULL 이 됐다
56명 재생성 중 2명이 서류 점수 NULL 이었다. 소형 모델이 문자열 안에
이스케이프 안 된 따옴표를 써서 파싱에 실패했고 Ollama format 스키마도 막지 못했다.
파서에 문자열 내부 따옴표 복구 폴백을 넣어 56/56 파싱에 성공했다 —
정상 JSON 경로는 손대지 않았다.
③ 무관한 질문에 답을 다 해 버렸다
거절 규칙이 “정치 · 연예 · 주식” 식 카테고리 열거라 인물 · 상식 유형은 모델이 확률적으로 판단했고, 인물 설명을 다 생성한 뒤 거절 문구를 덧붙였다 — 토큰 낭비이자 무관한 답의 유출이다. 판단 기준을 “답의 근거가 우리 데이터냐” 로 재작성해, 외부 일반 지식은 내용 없이 한 문장으로 거절하고 이력서 · 자소서 속 인물 · 프로젝트는 원문을 조회해 정상 답변한다.
3-4. 판정 알고리즘도 실측으로 고쳤다
fit-check 지원자 24명을 실측해 판정이 아슬한 세 케이스를 찾았다 — 요건이 우수한데 문화 한 문구로 탈락 · 1점 차이로 탈락 · 필수는 통과인데 우대 부족으로 탈락. 인수인계 문서에 적힌 “자동 불합격 3명”도 실제로는 8명이었다. 8명 각각의 3축 점수를 근거로 수치화해 붙였다.
| 가중치 · 규칙 | 전 | 후 | 이유 |
|---|---|---|---|
| 필수 요건 | 50 | 60 | 직무를 할 수 있는가가 먼저다 |
| 우대 사항 | 20 | 10 | 있으면 좋음 — 없다고 떨어뜨릴 축이 아니다 |
| 인재상 (문화) | 30 | 30 | 유지 |
| 문화 점수 하한 | 없음 | 50 | 요건 점수가 70 이상이면 적용 — 요건 우수 지원자를 문화 한 문구로 떨어뜨리지 않는다 |
3-5. 아직 못 잰 것
안 된 것은 안 됐다고 적는다.
채점기 편향과 23건 표본 미해소
§1 의 해석 주의가 그대로 남는다. 케이스를 늘리거나 중립 gold 를 따로 만들지 못했다.
요약 어댑터는 정성 평가만 했다 미해소
JSON 형식 · 요지 · 역량은 정확했으나 경력 연수를 3년 → 5년으로 틀렸다. 도구 호출처럼 채점기로 수치화하지는 못했고, 이 오차가 클라우드 확정의 근거 하나가 됐다.
임베딩 교체 미착수 미해소
BGE-M3 비교는 손대지 못했고 ko-sroberta 의 max_seq_length=128 제약은
그대로다.
4. 편향성 검토
채용은 틀렸을 때 지원자가 대가를 치르는 도메인이다. 그래서 이 절의 결정은 대부분 “쓸 수 있는데 쓰지 않기로 한 것”이다. 무엇을 점수에 넣지 않았는지와 누가 되돌릴 수 있는지를 적는다.
4-1. 표정 · 음성은 점수에 넣지 않는다 소연 D
결정이 두 번 뒤집혔다. ADR-0002 가 표정 분석을, ADR-0026 결정 2 가 음성 기반 감정 · 기만 추론을
잘랐다. 그런데 ai/lie-detection/ 이 이미 main 에 들어가 있었고 AI 면접 설계가 그 기능을
전제로 짜이고 있었다. 문서만 반대로 남으면 아무도 지키지 않는 규칙이 된다고 보고
ADR-0029 로 개정했다(2026. 09. 07.).
ADR-0029 는 개정하면서 닫지 못한 것을 숨기지 않고 미결로 남겼다. 모델은 Michigan Real-Life Trial 121 표본에 교차검증 76% 였고, ADR-0026 이 지적한 것은 정확도 자체가 아니라 누가 불리해지는가 — 불안장애 · 말더듬 · 비원어민 · 자폐 지원자를 결함으로 점수화하게 된다는 것이었다. 2026. 09. 15. ADR-0030 이 세 항목에 답했다.
| ADR-0029 의 미결 | ADR-0030 의 답 (2026. 09. 15.) |
|---|---|
| ① 정확도와 공정성 | 참고 지표로만 쓰고 합불 근거로 쓰지 않는다. 담당자 화면에 “참고용 수치입니다. 단독 판단 근거로 쓰지 마세요” 를 표시한다 |
| ② 지원자의 반박 경로 | 지원자에게 보여주지 않는다. 지원자가 볼 수 있는 AI 판단 근거는 서류↔발언 대조 하나로 유지한다 — 원문을 인용하므로 반박할 수 있다 |
| ③ 법 · 제도 | EU AI Act(직장 감정 인식 금지) · 미국 고용 목적 거짓말 탐지 금지법은 국내 한정 서비스라 범위 밖. 국내 개인정보보호법의 자동화 결정 조항은 AI 가 단독으로 합불을 결정할 때 적용되며, 최종 결정은 사람이 하므로 해당하지 않는다 |
그래서 지금 운영은 이렇다 — 표정(ViT + MediaPipe) · 음성 신호는 담당자 화면에 참고 지표로만 뜨고 점수 · 합불 판정에 들어가지 않는다. 그 판단의 근거 하나를 ADR-0032 가 숫자로 남겼다. 법정 영상에 ViT 를 돌리면 거짓 영상이 happy 55% · happy 64% 로, 진실 영상이 fear 71% 로 나온다. 표정만으로는 갈리지 않는다. 그래서 ViT 출력은 판정이 아니라 특징 벡터의 일부이고, 발표에서 “ViT 로 거짓말을 잡는다”고 말하지 않기로 문서에 못 박았다 — 위 표가 그 문장을 반증하기 때문이다.
4-2. 진행 보조는 이유를 추론하지 않는다 — 테스트로 봉인했다
답변 길이 · 발화 속도를 보고 되묻거나 쉬어가기를 권하는 진행 보조
(backend/app/interview/pacing.py)는 진위 분석 호출과 같은 날 들어갔지만 성격이 다르다.
보는 것은 답변 길이 · 발화 속도뿐이고, 내는 것은 다음에 할 행동 한 문장이며,
저장하지 않고 점수도 없다.
“말이 느렸다”까지만 말하고 이유를 추론하지 않게 하려고,
메시지에 “긴장 · 불안 · 거짓 · 의심 · 점수” 다섯 낱말 중 하나라도 들어가면
테스트가 깨지도록 해 뒀다
(backend/tests/test_interview_pacing.py). 섞는 순간
“점수에 안 들어간다”는 약속이 어디까지 유효한지 아무도 모르게 되기 때문이다.
표정 라벨 하나도 같은 이유로 바꿨다 — 면접 맥락에 맞지 않는 disgust 라벨을
「긴장」으로 교체하고 문서에 반영했다.
4-3. 최종 합불은 사람이 확정한다
ADR-0003 이 “AI 는 추천까지, 확정은 사람”을 정했고, ADR-0034 가 그 범위를 최종 합격 · 불합격 한 곳으로 좁혔다. 서류 · 면접 단계 이동은 아르가 점수로 판정하고, 사람은 되돌린다. 좁아진 만큼 되돌릴 장치를 남겼다.
수동이 항상 이긴다
사람이 단계를 한 번 바꾸면 decision_source='human' 이 되고
그 지원자는 이후 자동 판정에서 빠진다. 자동이 사람의 판단을 덮어쓰지 않는다.
불합격 메일은 즉시 나가지 않는다
공고 마감 뒤 담당자가 일괄 발송한다. 오판했을 때 메일은 되돌릴 수 없어서 마감 전까지 번복 여지를 남겼다. 합격(면접) 안내는 일정 제안 메일이 대신한다.
쓰기 작업은 확인 카드로만 실행된다
에이전트는 초안(pending_action)까지 만들고 사람이 누른다.
23건 비교에서 임의 실행 · 지어낸 도구 같은 위험한 실패가 세 모델 모두 0건이었던 것도
이 구조 덕이다.
인적성 설문은 점수에 넣지 않는다 (ADR-0027)
카테고리 통계는 코드가 계산하고 아르는 응답을 관찰 문장으로 재서술만 한다. 성격 유형 판정 · 점수화 · 합불 의견을 만들지 않으며 미응답 불이익도 없다.
4-4. 자동 판정이 만든 불공정을 되돌린 사례
① AI 추정값이 지원자 신고값처럼 보였다
폼에 경력을 적지 않으면 AI 가 이력서에서 연차를 추출해 채우는데,
표식이 없어 신고값과 구별되지 않았다.
career_years_source="ai" 표식을 붙이고 프론트에 「AI 추정」 배지를 띄웠다.
재생성 시 표식이 사라지는 문제는 후속으로 남아 있다.
② 요건 우수자를 문화 한 문구로 떨어뜨렸다
가중치를 요건 60 · 우대 10 · 문화 30 으로 조정하고 요건 ≥ 70 이면 문화 하한 50 규칙을 넣었다(§3-4). hold 상태는 도입하지 않고 자동 판정을 유지했다 — 되돌리는 쪽을 사람에게 맡겼다.
③ 자료가 없는 지원자를 채점하려 했다
자소서 1줄 · 전부 미기재면 분석할 것이 없다. 요약 결과 insufficient=true 면
applied → screening → rejected 로 옮겨 「서류 탈락」 라벨이 붙게 했다 —
applied → rejected 직행이면 사유 불명의 「불합격」으로 보였다.
실제 역량이 있는 지원자(insufficient=false)는 건드리지 않아 대량 탈락을 막았다.
4-5. 개인정보 — 모델에 넣지 않는 것, 남기지 않는 것
프롬프트에 연락처 · 주민번호를 넣지 않는다. 로그에는 검색어 값 대신
인자 키 이름만 남긴다(§2-4) — search_applications 의 검색어에 지원자
이름 · 이메일이 들어오기 때문이다. 면접 영상은 저장하지 않는다;
실시간으로 보고 흘려보내며 저장하는 것은 답변 음성 · 전사 · 대조 결과다.
표정 · 음성 진위는 프레임과 개별 판정을 저장하지 않는다.
자체학습 노선을 끝까지 밀어 본 이유도 여기에 있었다 — “public API 없이도 갈 수 있는가”에 실측으로 답하기 위해서였다. 답은 “정확도는 동급까지 오지만 형식 안정성과 지연에서 갈린다”였고(§1-4), 그래서 서빙은 클라우드로 두되 개인정보는 모델을 바꾸는 것이 아니라 넣는 것을 줄이는 방식으로 지킨다.