266 lines
16 KiB
Markdown
266 lines
16 KiB
Markdown
# 아키텍처 설계
|
|
|
|
## 1. 설계 목표
|
|
|
|
최상 품질은 “그럴듯한 문장”이 아니라 재현 가능한 품질 계약으로 정의합니다. 하네스는 다음 네 가지를 동시에 달성해야 합니다.
|
|
|
|
1. **사실 충실성**: 입력하지 않은 사실을 생성하지 않는다.
|
|
2. **직무 적합성**: 공고 요구와 후보자 근거의 교집합을 가장 먼저 보여준다.
|
|
3. **한국어 문서 품질**: 짧고 자연스러운 한국어와 국내 채용 문맥에 맞는 구성을 쓴다.
|
|
4. **개인정보·공정성**: “한국식”을 과도한 개인정보 수집으로 해석하지 않는다.
|
|
|
|
## 2. 핵심 경계
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
A[CandidateProfile\n불변 사실] --> B[Privacy Guard]
|
|
J[JobPosting\n신뢰하지 않는 데이터] --> C[Job Analyzer]
|
|
B --> D[Evidence Mapper]
|
|
C --> D
|
|
D --> E[Content Planner]
|
|
E --> F[Draft Generator]
|
|
F --> G[Rule Validators]
|
|
G --> H[Independent Judge]
|
|
H -->|결함 있음| I[Targeted Repair]
|
|
I --> G
|
|
H -->|통과| R[ResumeDraft 정본]
|
|
R --> M[Markdown]
|
|
R -. 향후 .-> X[HTML/DOCX/PDF/HWPX]
|
|
```
|
|
|
|
원본 사실, 의미 모델, 표현 문서의 경계를 분리합니다.
|
|
|
|
- `CandidateProfile`: 사용자가 제공한 사실 원장입니다. 생성기가 수정하지 않습니다.
|
|
원자적 `EvidenceItem`과, 그 근거 ID에 연결된 선택형 `ResumeRecords`를 분리해
|
|
회사·직무·기간·고용형태·학위·자격 정보를 기계 판독 가능한 형태로 보존합니다.
|
|
- `JobAnalysis`: 공고의 필수/우대 요건과 책임을 안정적인 ID로 정규화합니다.
|
|
필수·우대 분류은 섹션 표시어부터 해당 요건 문구까지를 함께 담은 연속
|
|
`classification_quote`로 입증해 다른 위치의 분류 표식을 빌려 쓰지 못하게 합니다.
|
|
- `EvidenceMap`: 각 요건을 후보자 근거와 연결하고, 근거가 없으면 명시적으로 `gap`으로 둡니다.
|
|
- `ResumeDraft`: 문장과 섹션을 담는 도메인 정본입니다. 모든 주장에 `evidence_ids`가 필요합니다. JSON/YAML은 정본의 직렬화 형식이며 제출용 렌더 형식이 아닙니다.
|
|
- 렌더러: 현재는 정본을 단일 열 Markdown로만 표현합니다. HTML, DOCX, PDF, HWPX 어댑터는 향후 범위이며, 어떤 렌더러도 새로운 내용을 쓰면 안 됩니다.
|
|
|
|
구조화 레코드는 현재 로컬 입력·참조·날짜 검증에만 사용되며, 생성
|
|
backend 페이로드나 `ContentPlan`에 자동 반영되지 않습니다. 레코드를 최종
|
|
문서에 반영하려면 개인정보 정책을 적용해 근거가 붙은 `ResumeDraft` claim으로
|
|
materialize한 뒤, 같은 규칙 검사·품질 평가·fingerprint 결합을 통과해야 합니다.
|
|
렌더러가 `CandidateProfile.records`를 직접 합성하는 경로는 두지 않습니다.
|
|
|
|
개인 연락처는 생성 모델에 전달하지 않고 가능하면 최종 렌더 단계에서 삽입합니다.
|
|
|
|
## 3. 단계 계약과 실제 종료 상태
|
|
|
|
다음은 파이프라인의 개념적 단계이지 공개 상태 enum이 아닙니다.
|
|
현재 `PipelineResult.status`가 반환하는 종료 상태는 `passed`와
|
|
`needs_user_input` 두 가지뿐입니다.
|
|
|
|
```text
|
|
RECEIVED → VALIDATED → ANALYZED → MAPPED → PLANNED
|
|
→ DRAFTED → RULE_CHECKED → JUDGED ↔ REPAIRED
|
|
→ APPROVED → RENDERED
|
|
|
|
근거 부족 또는 수정 한도 후 릴리스 게이트 실패
|
|
→ NEEDS_USER_INPUT
|
|
```
|
|
|
|
입력 스키마, backend 호출, 단계 간 참조 무결성 위반은
|
|
`needs_user_input`으로 바꾸지 않고 예외로 실패합니다. 따라서 위 개념 단계를
|
|
영속 작업 큐의 상태 머신으로 해석해서는 안 됩니다.
|
|
|
|
| 단계 | 입력 | 출력 | 실패 조건 |
|
|
|---|---|---|---|
|
|
| Intake | 후보자·공고·설정 | 검증된 모델 | 스키마 오류, 중복 ID, 날짜 역전 |
|
|
| Privacy | 후보자·정책 | 최소화된 모델 | 비동의 민감정보 포함 |
|
|
| Analyze | 공고 원문 | `JobAnalysis` | 원문에 없는 요건 생성 |
|
|
| Map | 분석+사실 | `EvidenceMap` | 존재하지 않는 근거 참조 |
|
|
| Plan | 매핑+분량 | 콘텐츠 계획 | 근거 없는 요건을 강점으로 선택 |
|
|
| Draft | 계획+사실 | `ResumeDraft` | 주장에 근거 ID 없음 |
|
|
| Validate | 초안+사실+정책 | 규칙 결함 목록 | 하드 결함 존재 |
|
|
| Judge | 초안+분석+허용 사실+결정적 결함 | 영역 점수·결함 | 스키마/참조 위반, blocking 결함, 릴리스 기준 미달 |
|
|
| Repair | 초안+결함 | 부분 수정 초안 | 새 근거/주장 추가, 최대 횟수 초과 |
|
|
| Render | 승인 정본+품질 보고서 | Markdown | 참조·정책·공고 제약 재검사 실패 |
|
|
|
|
`Validate`의 완성도 검사는 단순 글자 수가 아니다. 구조화 레코드가 있는 경우
|
|
핵심 요약을 역할/대표 성과로 분리하고, 확인된 기술이 충분하면 핵심 역량 섹션을
|
|
요구하며, 경력·프로젝트마다 연결된 근거 수에 비례한 최소 서술 깊이를 검사한다.
|
|
따라서 모든 문장에 근거 ID가 있어도 한 줄짜리 프로젝트나 누락된 역량 섹션은
|
|
릴리스할 수 없다.
|
|
|
|
공고·업로드 문서 안의 지시문은 실행 명령이 아니라 데이터입니다. 분석 프롬프트는 공고의 텍스트를 인용 경계 안에 넣고, 그 안의 “이전 지시 무시” 같은 문구를 따르지 않도록 고정합니다.
|
|
|
|
## 4. 한국형 정책 프로필
|
|
|
|
### `private_modern` — 기본값
|
|
|
|
- 이름, 이메일, 전화, 시/도 수준 위치, 포트폴리오 링크만 허용
|
|
- 지원 직무 → 핵심 요약 → 핵심 역량 → 경력/프로젝트 → 학력/자격 순
|
|
- 최근 경력부터 쓰며 입력의 날짜 정밀도를 보존함. 연 단위 입력은 `YYYY`, 월 단위 입력은 기본 `YYYY.MM`로 표현
|
|
- 사진, 생년월일, 성별, 가족, 혼인, 종교, 신체정보는 제외
|
|
|
|
### `public_blind`
|
|
|
|
- 공고의 블라인드 규칙을 개별 lint 정책으로 우선 적용
|
|
- 출신지, 가족관계, 외모 등 편견 유발 정보와 학교명을 기본 차단
|
|
- 직무 관련 교육, 자격, 유급 경력, 무급 경험을 구분
|
|
- NCS 직무기술서의 지식·기술·태도와 실제 근거의 연결을 평가
|
|
|
|
### `employer_form`
|
|
|
|
- 현재는 지정 필드, 공고 제약, 명시적 동의, 채용사 요구를 표현·검증하는 정책 모드만 제공
|
|
- 민감 필드는 활성 동의와 기록된 채용사 요구가 모두 있어야 설정 가능하지만, 현재 Markdown 렌더러는 사진·지정 칸 삽입을 지원하지 않음
|
|
- 주민등록번호, 계좌, 건강정보는 이력서 생성 범위에서 항상 금지
|
|
- 지정 양식 요구가 법적으로 적절하다는 보장은 하지 않으며 사용자에게 경고
|
|
- `EMPLOYER_TEMPLATE` 제약은 전용 어댑터가 없으며 blocking으로 fail-closed
|
|
|
|
공고별 규칙이 프로필 기본값보다 우선하되, 안전상 절대 금지 항목을 해제할 수는 없습니다.
|
|
|
|
## 5. 경력 수준별 콘텐츠 전략
|
|
|
|
| 유형 | 우선순위 | 권장 구성 |
|
|
|---|---|---|
|
|
| 신입 | 프로젝트·교육·직무 경험 | 요약, 역량, 프로젝트, 교육/학력, 자격/활동 |
|
|
| 경력 | 최근 역할·성과·책임 범위 | 요약, 역량, 경력, 대표 프로젝트, 학력/자격 |
|
|
| 직무 전환 | 이전 경험의 이전 가능 역량 | 목표 직무 요약, 연결 역량, 관련 프로젝트, 경력 |
|
|
| 공공/NCS | 직무기술서 근거 커버리지 | 자격/교육, 경력, 경험, 문항별 기술서 |
|
|
|
|
문장 기본형은 `문제/맥락 → 본인의 행동 → 검증 가능한 결과`입니다. 수치가 없으면 억지로 정량화하지 않고 범위, 산출물, 의사결정, 품질 변화 같은 검증 가능한 정성 결과를 사용합니다.
|
|
|
|
## 6. LLM 경계
|
|
|
|
`LLMBackend`는 다음 구조화 호출만 제공합니다.
|
|
|
|
```python
|
|
class LLMBackend(Protocol):
|
|
def complete_json(
|
|
self,
|
|
*,
|
|
stage: str,
|
|
system_prompt: str,
|
|
task_prompt: str,
|
|
user_payload: dict,
|
|
output_model: type[BaseModel],
|
|
) -> BaseModel: ...
|
|
```
|
|
|
|
각 단계는 낮은 자유도의 구조화 JSON을 반환합니다. 코어는 프롬프트
|
|
로딩, Pydantic 출력 검증, 개인정보·기밀 최소화, 단계 간 참조 무결성을
|
|
구현합니다. 특정 공급자 어댑터는 포함되어 있지 않으며, 배포자가
|
|
재시도, 타임아웃, 모델 실행 격리, 토큰·비용 기록을 어댑터 계약으로
|
|
추가해야 합니다.
|
|
|
|
평가기의 주관적 점수와 하네스가 계산할 수 있는 지표를 섞지 않습니다. `draft_fingerprint`, claim 근거 연결률, 공고 요건의 우선순위 가중 커버리지, 실제 릴리스 임계값은 평가기 응답을 신뢰하지 않고 정본 후보자·초안·분석·설정에서 계산해 `QualityReport`에 덮어씁니다. 평가 fingerprint는 공고·분석·매핑·계획과 평가 프롬프트·가중치 정책까지 결합합니다.
|
|
|
|
결정적 finding과 주관 점수가 모순될 수도 없습니다. blocking finding이 속한 가중
|
|
영역은 최대 59점, warning 영역은 최대 89점으로 하네스가 상한을 적용한 뒤 총점을
|
|
재계산합니다. 개인정보·근거·완성도 오류를 평가 모델의 높은 점수로 상쇄할 수 없습니다.
|
|
|
|
단, SHA-256 fingerprint는 보고서 발급 주체를 인증하는 서명이 아닙니다. 현재 단독 CLI는
|
|
로컬 품질 보고서 파일을 신뢰하는 단일 사용자 경계입니다. 다중 사용자·원격 승인
|
|
서비스에서는 평가 모델·정책·보고서 본문·전체 컨텍스트를 서명한 attestation과 신뢰
|
|
저장소를 배포 계층에서 의무화해야 합니다. 구체적인 envelope와 검증 순서는
|
|
[배포 보안과 품질 승인 신뢰 경계](deployment-security.md)에 정의합니다.
|
|
|
|
현재 코어는 한 번의 backend 응답이 스키마나 참조 계약을 위반하면 해당
|
|
단계를 실패시킵니다. 스키마·네트워크 재시도 정책은 현재 코어가 아닌
|
|
공급자 어댑터의 향후 배포 요구사항입니다. 콘텐츠 품질 수정은 코어가
|
|
최대 2회로 제한합니다.
|
|
|
|
## 6.1 ATS와 국내 지정 양식
|
|
|
|
ATS 기본 출력은 단일 열의 실제 텍스트 문서이며, 표·텍스트박스·다단·사진·아이콘과 헤더/푸터의 핵심 정보 배치를 피합니다. `경력`, `프로젝트`, `학력`, `기술`, `자격증`처럼 명확한 제목을 사용합니다. 이는 [Greenhouse의 공식 파싱 실패 안내](https://support.greenhouse.io/hc/en-us/articles/200989175-Unsuccessful-resume-parse)와 [한국어 파싱 지원 안내](https://support.greenhouse.io/hc/en-us/articles/205019689-Resume-parsing-with-non-English-languages)를 보수적으로 적용한 것입니다.
|
|
|
|
범용 “ATS 합격 점수”는 제공하지 않습니다. 현재 코어는 단일 읽기 순서의
|
|
텍스트 Markdown을 만들고 필수 요건 커버리지와 키워드 근거를 검사합니다.
|
|
특정 ATS에 업로드한 뒤의 실제 파싱 성공률은 측정하지 않으며 배포별 통합
|
|
테스트가 필요합니다.
|
|
|
|
국내 공공기관 지정 양식을 위한 HWPX 어댑터는 향후 범위입니다.
|
|
구현할 때는 HWPX를 ATS 기본 출력으로 취급하지 않고, 기관이 제공한
|
|
양식의 항목명·글자 수·표 구조 보존과 텍스트 추출 사후 검사를 모두
|
|
제공해야 합니다. 현재는 HWPX·HWP를 처리하지 않으며, 지정 양식
|
|
제약은 fail-closed로 차단합니다.
|
|
|
|
### 6.2 출력 제약 게이트
|
|
|
|
공고 분석기가 추출한 규칙을 생성 모델이 스스로 준수했다고 판단하게 두지 않습니다. `output_constraints` 게이트를 결정적 검사, CLI 검증, 렌더 직전에 같은 방식으로 실행합니다.
|
|
|
|
- `REQUIRED_SECTION`: 섹션 타입·한국어 표준 별칭·실제 heading을 대조하고, 어떤 섹션을 뜻하는지 추출되지 않았으면 통과시키지 않음
|
|
- `CHARACTER_LIMIT`: 섹션 제목은 제외하고 NFC 정규화된 본문을 공백과 bullet 사이 줄바꿈까지 포함해 Unicode 문자 수로 계산
|
|
- `FILE_FORMAT`: `.md`, `Markdown`, MIME 표기 같은 안전한 별칭을 정규화하고 현재 렌더러 형식이 허용 목록에 없으면 차단
|
|
- 분석 무결성: `formats`, `max_characters`, 섹션명 값이 자체 `source_quote`의
|
|
원문 값과 일치하는지 먼저 대조하고, 여러 대상·값이 함께 있으면 가장 가까운
|
|
같은 문맥의 대상-값 쌍만 인정
|
|
- 제약 완전성: 원문에 명시된 blocking 제출 제약은 종류와 원문 절 단위로 모두
|
|
추출되었는지 재검사하며, 일부 제약이나 무관한 `OTHER` 제약만으로 누락을 덮지 않음
|
|
- `section_order`: 설정에 열거된 섹션끼리의 상대 순서를 검사하며, 설정에 없는 보조 섹션은 순서 판정에서 제외
|
|
- `date_format`: 제목·섹션명·claim에 있는 월/일 정밀도 날짜가 설정 `YYYY.MM` 또는 `YYYY.MM.DD`와 일치하는지 검사. 연도만 제공된 입력에 월이나 일을 새로 만들지 않음
|
|
- `EMPLOYER_TEMPLATE`: 원본 지정 양식 어댑터 없이 검증할 수 없으므로 blocking 규칙이면 닫힌 방식으로 실패
|
|
|
|
`max_pages`는 현재 구현에서 릴리스 게이트가 아닙니다. Markdown처럼 물리
|
|
레이아웃이 없는 출력을 문자 수로 페이지 추정해 합격 처리하지 않습니다.
|
|
향후 DOCX/PDF 어댑터는 실제 한국어 폰트, 용지, 여백, 줄바꿈을 적용한
|
|
산출물의 페이지 수와 오버플로를 postflight로 측정해야 합니다. 채용
|
|
포털이 다른 글자 수 규칙을 사용하면 해당 포털 전용 카운터도
|
|
별도로 연결해야 합니다.
|
|
|
|
## 7. 수정 루프
|
|
|
|
전체 이력서를 반복 재작성하면 이미 맞는 사실이 흔들립니다. 따라서 결함은 아래 계약으로 전달하고 `claim_id` 단위로 수정합니다.
|
|
|
|
```json
|
|
{
|
|
"finding_id": "finding-001",
|
|
"code": "GROUNDING.UNSUPPORTED_NUMBER",
|
|
"severity": "error",
|
|
"category": "evidence",
|
|
"claim_id": "claim-exp-01-2",
|
|
"message": "42%를 뒷받침하는 근거가 없습니다.",
|
|
"evidence_ids": ["ev-exp-01"],
|
|
"suggestion": "기존 근거의 값으로 교체하거나 숫자를 제거"
|
|
}
|
|
```
|
|
|
|
수정 후 다음 불변식을 다시 검사합니다.
|
|
|
|
- 기존 `evidence_ids` 집합 밖의 근거를 추가하지 않았는가
|
|
- 숫자·날짜·고유명사가 사실 원장과 일치하는가
|
|
- 수정 후에도 같은 하드 결함이 남아 있는가
|
|
- 수정으로 다른 섹션의 일관성이 깨지지 않았는가
|
|
|
|
최대 2회 후에도 하드 게이트가 남으면 추측하지 않고 사용자 입력이 필요한 질문을 최대 3개 반환합니다.
|
|
|
|
## 8. 감사와 재현성
|
|
|
|
현재 코어는 `ResumeDraft.fingerprint()`와 평가 컨텍스트 fingerprint로 품질
|
|
보고서를 초안·공고·분석·매핑·계획·설정에 결합합니다. 다음 실행
|
|
메타데이터 저장은 현재 코어에 포함된 감사 로거가 아니라, 배포 계층이
|
|
구현해야 할 목표 계약입니다. 실제 개인정보·전체 프롬프트·원문은 일반
|
|
로그에 저장하지 않아야 합니다.
|
|
|
|
```text
|
|
run_id, input_hash, schema_version, prompt_versions,
|
|
model_id, model_parameters, policy_profile, validator_version,
|
|
template_version, quality_scores, issue_fingerprints, generated_at
|
|
```
|
|
|
|
현재 테스트는 합성 아티팩트의 모델·참조·개인정보·출력 제약·릴리스
|
|
불변 조건을 확인합니다. 특정 외부 모델의 버전별 비결정성 회귀 평가는
|
|
배포 계층에서 별도로 구성해야 합니다.
|
|
|
|
## 9. 현재 구현과 향후 범위
|
|
|
|
**현재 코어**
|
|
|
|
- YAML/JSON 입력과 Pydantic 도메인 모델
|
|
- 구조화 공고 분석·매핑·계획·초안·평가·최대 2회 수정 조정
|
|
- 사실·참조·날짜 정밀도·개인정보·공고 제약 검사
|
|
- 품질 fingerprint 결합, CLI 릴리스 게이트, 단일 열 Markdown 렌더러, 합성 테스트
|
|
|
|
**향후/배포 범위**
|
|
|
|
- 특정 LLM 공급자 어댑터와 운영 재시도·타임아웃·비용 계측
|
|
- ATS HTML/DOCX/PDF, HWPX 지정 양식, 한국어 폰트, 텍스트 추출·오버플로 사후 검사
|
|
- 감사 로그, 암호화·보존·삭제, 포털별 문자 수 카운터
|
|
- 다중 사용자 배포용 Ed25519 품질 attestation, 공개키 trust store, 키 회전
|
|
- 다중 평가기, NCS 직무사전, 편향 쌍대 평가, 한국 채용담당자 평가
|