Files

16 KiB

아키텍처 설계

1. 설계 목표

최상 품질은 “그럴듯한 문장”이 아니라 재현 가능한 품질 계약으로 정의합니다. 하네스는 다음 네 가지를 동시에 달성해야 합니다.

  1. 사실 충실성: 입력하지 않은 사실을 생성하지 않는다.
  2. 직무 적합성: 공고 요구와 후보자 근거의 교집합을 가장 먼저 보여준다.
  3. 한국어 문서 품질: 짧고 자연스러운 한국어와 국내 채용 문맥에 맞는 구성을 쓴다.
  4. 개인정보·공정성: “한국식”을 과도한 개인정보 수집으로 해석하지 않는다.

2. 핵심 경계

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가 반환하는 종료 상태는 passedneeds_user_input 두 가지뿐입니다.

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는 다음 구조화 호출만 제공합니다.

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와 검증 순서는 배포 보안과 품질 승인 신뢰 경계에 정의합니다.

현재 코어는 한 번의 backend 응답이 스키마나 참조 계약을 위반하면 해당 단계를 실패시킵니다. 스키마·네트워크 재시도 정책은 현재 코어가 아닌 공급자 어댑터의 향후 배포 요구사항입니다. 콘텐츠 품질 수정은 코어가 최대 2회로 제한합니다.

6.1 ATS와 국내 지정 양식

ATS 기본 출력은 단일 열의 실제 텍스트 문서이며, 표·텍스트박스·다단·사진·아이콘과 헤더/푸터의 핵심 정보 배치를 피합니다. 경력, 프로젝트, 학력, 기술, 자격증처럼 명확한 제목을 사용합니다. 이는 Greenhouse의 공식 파싱 실패 안내한국어 파싱 지원 안내를 보수적으로 적용한 것입니다.

범용 “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 단위로 수정합니다.

{
  "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로 품질 보고서를 초안·공고·분석·매핑·계획·설정에 결합합니다. 다음 실행 메타데이터 저장은 현재 코어에 포함된 감사 로거가 아니라, 배포 계층이 구현해야 할 목표 계약입니다. 실제 개인정보·전체 프롬프트·원문은 일반 로그에 저장하지 않아야 합니다.

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 직무사전, 편향 쌍대 평가, 한국 채용담당자 평가