From 2a49d05d8b4f981dc712ae1169f55d3d14cf519e Mon Sep 17 00:00:00 2001 From: hywznn Date: Tue, 11 Aug 2026 00:24:18 +0900 Subject: [PATCH] =?UTF-8?q?docs(data):=20=EC=A0=80=EC=9E=A5=EC=86=8C=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EC=99=80=20=EC=B5=9C=EC=A2=85=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B8=B0=EC=A4=80=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 184 +++++--------- fowoco-knowledge/CHANGELOG.md | 7 + fowoco-knowledge/README.md | 108 ++++----- fowoco-knowledge/data/README.md | 47 ++++ .../data/intent/hr_intent_dataset_final.jsonl | 6 +- fowoco-knowledge/data/intent/manifest.yaml | 32 ++- .../data/intent/splits/manifest.yaml | 110 ++++----- fowoco-knowledge/docs/INTENT_DATA.md | 35 +-- fowoco-knowledge/hr-intent-service/README.md | 12 +- fowoco-knowledge/knowledge/manifest.yaml | 4 +- .../schemas/intent-split-manifest.schema.json | 224 ++++++++++++++++++ .../src/fowoco_knowledge/validation.py | 155 ++++++++++++ fowoco-knowledge/tests/test_validation.py | 36 ++- 13 files changed, 687 insertions(+), 273 deletions(-) create mode 100644 fowoco-knowledge/data/README.md create mode 100644 fowoco-knowledge/schemas/intent-split-manifest.schema.json diff --git a/README.md b/README.md index 1323cf5..55b13c1 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,62 @@ -# FOWOCO Knowledge & Intent Modeling +# FOWOCO Knowledge -FOWOCO는 E-9 외국인근로자를 고용한 사업장의 반복 HR·행정업무를 구조화하고, -담당자가 다음 행동을 놓치지 않도록 지원하는 AI 업무보조 서비스입니다. +FOWOCO는 E-9 외국인근로자를 고용한 사업장의 HR·총무 업무를 구조화하고, +담당자가 놓치기 쉬운 다음 행동을 업무카드로 관리하는 AI 업무보조 서비스입니다. -이 저장소는 Agent가 참고할 업무 지식과 공식 출처뿐 아니라, HR 발화문을 -`Intent + evidence`로 분류하기 위한 데이터 계약·검수·평가 및 모델 실험 이력을 -함께 관리합니다. 운영 앱과 모델 서버를 구현하는 저장소는 아닙니다. +이 저장소는 FOWOCO Agent가 사용하는 **업무 지식, 공식자료 정규화 데이터, Intent +라벨, 데이터 계약과 검증 기준**의 기준 저장소입니다. 외부기관 제출이나 법적 판단을 +자동화하지 않으며, 민감 업무는 HR 담당자의 승인 후 진행합니다. > [!IMPORTANT] -> **🤗 [FOWOCO Hugging Face Model Hub](https://huggingface.co/fowoco)** +> ## 🤗 [FOWOCO Hugging Face](https://huggingface.co/fowoco) > -> 학습 checkpoint, adapter, tokenizer, model card와 공개 가능한 데이터셋은 -> FOWOCO Hugging Face 조직에서 배포합니다. +> 학습된 BERT 모델과 A.X adapter의 배포 기준 위치입니다. 저장소 접근 권한에 따라 +> 일부 모델은 비공개일 수 있습니다. GitHub에는 재현에 필요한 데이터 기준·코드와 +> 프로젝트 제출용 Git LFS 스냅샷을 관리합니다. -## 목표 +## 현재 결과 -HR 담당자의 입력에서 다음 7개 Intent를 하나 이상 찾고, 판단 근거가 되는 원문의 -연속 구간을 `evidence`로 반환합니다. - -- `WORKER_ONBOARDING`: 신규 근로자 등록·초기 처리 -- `EXPIRY_RENEWAL`: 체류·계약·고용허가 만료와 갱신 준비 -- `DOCUMENT_REQUEST`: 서류 요청·수령·미제출 추적 -- `PAYROLL_EXPLANATION`: 급여·수당·공제 설명 -- `WORK_INSTRUCTION`: 작업·근무일정·현장 안내 -- `EMPLOYMENT_CHANGE`: 퇴사·결근·사업장 변경 등 고용상태 변동 -- `OUT_OF_SCOPE`: 지원 범위 밖 요청 - -모델은 Intent와 evidence까지만 판단합니다. Workflow 선택, 외부기관 제출, 법적 -판단과 업무 완료는 규칙 검증과 HR 담당자 승인 이후에 처리합니다. - -## 진행 과정 - -| 단계 | 내용 | 저장소 상태 | +| 구분 | 현재 상태 | 기준 파일 | | --- | --- | --- | -| 규칙·후보 데이터 | Intent 규칙 v1.1, evidence exact substring, HR 발화문 1,340건 | `main` 반영 | -| A/B 재검수 | 경계 사례 독립 검수와 consensus | [#31](https://github.com/fowoco/knowledge/pull/31), [#35](https://github.com/fowoco/knowledge/pull/35) 검토 중 | -| 모델링 계약 | 유사 템플릿 누수를 막은 Train 1,072건 / Validation 268건 분할 | [#33](https://github.com/fowoco/knowledge/pull/33) 검토 중 | -| 기준 실험 | 수정 전 라벨 baseline과 A.X 테스트 도구 | [#37](https://github.com/fowoco/knowledge/pull/37)이 #33 브랜치에 병합됨 | -| 모델 비교 | A.X-4.0-Light와 KLUE-RoBERTa 실험 | 산출물 반영 예정 | +| Intent | 7개 Intent와 evidence exact substring 규칙 v1.1 | [`intents.yaml`](fowoco-knowledge/knowledge/intents.yaml), [`INTENT_DATA.md`](fowoco-knowledge/docs/INTENT_DATA.md) | +| 최종 검수 데이터 | HR 발화문 1,340건 | [`hr_intent_dataset_final.jsonl`](fowoco-knowledge/data/intent/hr_intent_dataset_final.jsonl) | +| 고정 분할 | Train 1,072건 / Validation 268건 | [`splits/`](fowoco-knowledge/data/intent/splits) | +| 업무 지식 | Intent·Workflow·필수 Slot·서류·공식 출처·Guardrail | [`knowledge/`](fowoco-knowledge/knowledge) | +| 모델 | KLUE-RoBERTa 메인 + A.X-4.0-Light 보조 cascade 참고 구현 | [`hr-intent-service/`](fowoco-knowledge/hr-intent-service) | -## 모델 실험 요약 +검수 전 데이터는 변경 이력 확인을 위해 +[`hr_intent_dataset.jsonl`](fowoco-knowledge/data/intent/hr_intent_dataset.jsonl)에 +보존합니다. 최종 학습·검증에는 `hr_intent_dataset_final.jsonl`과 split ID 파일을 +함께 사용합니다. -아래 수치는 팀의 최신 Validation 268건 실험 기록입니다. 독립적으로 잠긴 Test -성능이나 운영 성능을 의미하지 않습니다. +Validation 268건에서 기록한 모델 결과는 BERT 95.5%, A.X QLoRA 92.2%, Cascade +93.2%입니다. 같은 데이터로 모델을 개발하고 비교한 **내부 Validation 결과**이며, +독립 Gold Test나 운영 성능을 뜻하지 않습니다. 현재 가중치는 데이터 구조 오류 3건을 +수정하기 전 version 1.2.0 snapshot 기준이며, 자세한 SHA-256은 모델 README에 기록합니다. -| 모델 | Intent Exact Match | 결론 | -| --- | ---: | --- | -| A.X-4.0-Light Few-shot | 0.7612 | 초기 기준선 | -| A.X-4.0-Light QLoRA | 0.9254 | 복잡한 입력의 보조 모델 후보 | -| KLUE-RoBERTa Full FT | 0.9590 | 메인 모델 후보, 약 186ms | -| KLUE-RoBERTa LoRA | 0.9104 | Full FT보다 낮아 기각 | - -A.X QLoRA는 evidence exact match가 `0.7377`로 Few-shot의 `0.1667`보다 크게 -개선됐습니다. BERT Full FT는 Intent 분류 성능과 응답속도가 가장 좋았습니다. - -## 현재 모델 결론 +## 저장소 구성 ```text -HR 입력 - -> BERT Intent 분류 - -> 복잡도·경계 패턴·예측 margin 검사 - -> 위험하거나 불확실함: A.X로 라우팅 - -> 그 외: BERT 결과 사용 - -> 출력 Schema 검증 - -> Workflow 선택과 HR 승인 +. +├── fowoco-knowledge/ +│ ├── knowledge/ # Agent가 참조하는 업무 지식 원본 +│ ├── data/ # Intent·Seed·평가·공공 정규화 데이터 +│ ├── schemas/ # 데이터와 Agent 출력 JSON Schema +│ ├── src/ # Knowledge 조회·검증 CLI +│ ├── tests/ # Schema·해시·교차참조 검증 +│ ├── docs/ # 라벨·출처·검수·연동 기준 +│ └── hr-intent-service/ # 모델 서빙 참고 구현과 제출용 스냅샷 +├── Makefile +└── .github/workflows/ # PR 규칙과 Knowledge CI ``` -A.X 라우팅 후보 조건은 다음과 같습니다. - -- 활성 Intent가 3개 이상인 복잡한 문장 -- 완료·상태보고, 급여계좌, 서류 확보 등 검증된 경계 패턴 -- 선택·비선택 Intent 사이의 margin이 `0.76` 미만인 불확실한 예측 - -현재 Validation에서는 34.3%가 A.X 라우팅 대상으로 선택됐고, BERT 오답이 모두 -라우팅 조건에 포함됐습니다. 이는 A.X가 모든 오답을 정정했다는 뜻이 아니며, 같은 -Validation에서 만든 규칙이므로 별도 Test에서 다시 검증해야 합니다. - -## 현재 데이터 상태 - -- `main`에는 HR 발화문 후보 데이터 1,340건과 Intent 규칙 v1.1이 있습니다. -- A/B consensus와 고정 split은 아직 `main`에 병합되지 않았습니다. -- consensus 또는 원본이 변경되면 split과 모델 평가는 다시 생성해야 합니다. -- Validation은 모델 개발용이며 최종 성능 주장을 위한 Gold Test가 아닙니다. -- 실제 개인정보와 기업정보는 학습·평가 데이터에 저장하지 않습니다. - -최신 BERT·A.X 학습 checkpoint와 운영 서빙 코드는 아직 `main`에 포함하지 않습니다. - -## Hugging Face Model Hub - -[FOWOCO Hugging Face](https://huggingface.co/fowoco)는 모델 산출물의 공식 공개 -창구입니다. GitHub에는 재현과 검증에 필요한 규칙·계약·코드를, Hugging Face에는 -실제로 배포할 모델 파일과 설명을 둡니다. - -| 위치 | 관리 대상 | -| --- | --- | -| **[Hugging Face](https://huggingface.co/fowoco)** | checkpoint, adapter, tokenizer, model card, 공개 가능한 데이터셋 | -| GitHub | 업무 지식, 라벨 규칙, 데이터 계약, 분할·평가 코드, 실험 기록 | - -현재 Hugging Face 조직에는 공개된 모델·데이터셋이 없습니다. 산출물을 게시할 때는 -학습 데이터 version·SHA-256, 평가 조건, 라이선스와 사용 한계를 model card에 함께 -기록합니다. +`hr-intent-service/`는 모델 결과를 재현하고 인계하기 위한 참고 구현입니다. 운영 모델 +서버의 장기 소유권은 `fowoco/ai`에 두고, 이 저장소는 데이터와 지식 계약을 기준으로 +유지합니다. -## 참고 구현 (hr-intent-service) +## 빠른 검증 -Intent 모델을 실제로 서비스하는 참고 구현이 `fowoco-knowledge/hr-intent-service`에 -포함되어 있습니다. - -- **API**: `POST /api/v1/intents/classify` -- **입력**: `{"instruction": "발화문, INTENT_TAG(선택)"}` - -## 남은 과제 - -- 최종 consensus 데이터·manifest와 모델 실험 산출물의 저장소 반영 -- 독립 Gold Test에서 BERT·A.X·Cascade 재평가 -- BERT 경로의 evidence 추출 방식 확정 -- 다중 근로자·다중 지시 문장 보강 -- A.X GPU 서빙과 실제 운영 환경의 속도·자원 측정 -- 학습·서빙 코드는 최종적으로 `fowoco/ai`로 이전 - -## 저장소 구조 - -```text -fowoco-knowledge/ -├── knowledge/ # Intent·Workflow·Guardrail·공식 링크 -├── data/ # Seed·Intent·평가·공공 정규화 데이터 -├── schemas/ # 데이터·모델 출력 계약 -├── src/ # Knowledge 검증·조회 CLI -├── tests/ # Schema·누수·재현성 테스트 -└── docs/ # 라벨·검수·모델링·출처 문서 -``` - -## 실행 +Python 3.11 이상이 필요합니다. ```bash python3.11 -m venv .venv @@ -138,15 +64,23 @@ make install make check ``` -세부 기준은 다음 문서를 참고합니다. +`make check`는 Ruff, Knowledge/Intent manifest·Schema·SHA-256·분할 검증, 전체 테스트를 +실행합니다. 모델 서버 실행 방법은 +[`hr-intent-service/README.md`](fowoco-knowledge/hr-intent-service/README.md)를 확인합니다. -- [Intent 라벨 기준](fowoco-knowledge/docs/INTENT_DATA.md) -- [모델 계획](fowoco-knowledge/docs/MODEL_PLAN.md) -- [공식 데이터 파이프라인](fowoco-knowledge/docs/OFFICIAL_DATA_PIPELINE.md) +## 사용 경계 -## 안전 원칙 - -- 모델 출력만으로 법률·체류·급여·신고 결론을 확정하지 않습니다. -- 외부기관 제출과 근로자 안내 발송은 HR 승인 후 수행합니다. +- Intent 모델의 책임은 `Intent + evidence` 추출까지입니다. +- Workflow 선택, Slot 확인, 완료 처리는 규칙과 HR 담당자의 책임입니다. +- 체류·계약·급여·신고 관련 내용은 모델 출력만으로 확정하지 않습니다. +- 외부기관 제출과 근로자 안내 발송은 자동 실행하지 않습니다. - 실제 외국인등록번호, 여권번호, 전화번호, 계좌번호를 저장하지 않습니다. -- 날짜·금액·서류명·제출처·대상자·기한의 누락과 변경을 중점 검증합니다. +- Validation 데이터는 독립 Gold Test가 아니므로 최종 성능 주장에 사용하지 않습니다. + +## 주요 문서 + +- [데이터 사용 안내](fowoco-knowledge/data/README.md) +- [Intent 라벨 기준](fowoco-knowledge/docs/INTENT_DATA.md) +- [Agent 연동 계약](fowoco-knowledge/docs/AGENT_INTEGRATION.md) +- [공식 데이터 파이프라인](fowoco-knowledge/docs/OFFICIAL_DATA_PIPELINE.md) +- [E-9 신고·연장 Workflow](fowoco-knowledge/docs/E9_REPORTING_WORKFLOWS.md) diff --git a/fowoco-knowledge/CHANGELOG.md b/fowoco-knowledge/CHANGELOG.md index 833005c..533e6b4 100644 --- a/fowoco-knowledge/CHANGELOG.md +++ b/fowoco-knowledge/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +- 최종 검수 Intent 데이터 1,340건을 manifest의 기준 원본으로 지정 +- Train 1,072건 / Validation 268건의 실제 ID 경로·SHA-256·최종 라벨 분포 정정 +- 검수 전 데이터와 최종 데이터의 용도, Hugging Face와 Git LFS의 역할 구분 +- Intent split Schema와 중복·누락·해시·분포 검증 추가 + ## 0.2.0 - 2026-07-16 - 필요서류 187건과 EPS 세부업종 847건의 원본 해시·버전을 고정 diff --git a/fowoco-knowledge/README.md b/fowoco-knowledge/README.md index a41426c..b45b1cf 100644 --- a/fowoco-knowledge/README.md +++ b/fowoco-knowledge/README.md @@ -1,96 +1,84 @@ -# FOWOCO Knowledge +# FOWOCO Knowledge Package -FOWOCO Agent가 공통으로 참조하는 **버전형 업무 지식 패키지**입니다. -Intent·Domain·필수정보·Workflow·공식 출처·Guardrail과 평가 데이터를 한곳에서 관리합니다. +FOWOCO Agent가 공통으로 참조하는 버전형 업무 지식 패키지입니다. Intent·Workflow·필수 +Slot·공식 출처·Guardrail을 조회하고, 데이터 계약과 교차참조가 맞는지 검증합니다. -이 패키지는 LLM 자체가 아닙니다. Agent가 매 요청마다 같은 업무 기준을 사용하도록 -Context를 제공하고, 서로 맞지 않는 지식 변경을 CI에서 차단하는 역할을 합니다. +이 패키지는 법적 판단이나 외부기관 제출을 수행하지 않습니다. 모델이 분류한 요청을 +업무 지식과 연결하고 누락·모호성을 찾는 것이 핵심 책임입니다. -## MVP 범위 +## 디렉터리 -| 지원 업무 | 대표 Workflow | +| 경로 | 내용 | | --- | --- | -| 근로자 등록 | 문서 OCR 결과를 등록 초안으로 변환 후 HR 승인 | -| 체류·계약 만료 | 내부 알림일에 업무 생성, 필요자료 확인, 수동 기관 제출 안내 | -| 서류 요청 | 대상·서류·기한·제출처 점검 후 근로자 안내와 제출 추적 | -| 급여 설명 | 전월·당월 명세 차이를 계산하고 설명 초안 생성 | -| 업무·일정 안내 | 시간·장소·대상·행동을 명확히 한 다국어 안내 | -| 고용변동 | 사건정보를 구조화하고 공식 신고 준비 업무 생성 | +| [`knowledge/`](knowledge) | Agent Context Pack 원본 | +| [`data/`](data) | 최종 Intent 데이터, split ID, Seed·평가·공공 정규화 데이터 | +| [`schemas/`](schemas) | 입력·Workflow·Intent·분할 계약 | +| [`src/`](src) | 로더, 검증기, 조회 CLI | +| [`tests/`](tests) | Schema·SHA-256·교차참조·분할 테스트 | +| [`docs/`](docs) | 라벨링, 출처, 검수, Agent 연동 기준 | +| [`hr-intent-service/`](hr-intent-service) | BERT + A.X cascade 모델의 참고 서빙 구현 | -기관 자동 제출, 법적 최종판단, 노무위반 확률예측은 포함하지 않습니다. +모델 가중치의 배포 기준 위치는 [FOWOCO Hugging Face](https://huggingface.co/fowoco)입니다. +`hr-intent-service/`는 프로젝트 결과 재현과 AI 저장소 인계를 위한 스냅샷입니다. -## 구조 +## 데이터 기준 -```text -fowoco-knowledge/ -├── knowledge/ # Agent Context Pack 원본 -├── data/ # Seed와 독립 평가 데이터 -├── schemas/ # 입력·Workflow·라벨 데이터 계약 -├── src/ # 로더, 검증기, CLI -├── tests/ # 교차참조·동작 테스트 -├── examples/ # 실행 가능한 요청 예시 -└── docs/ # 데이터·Agent 연동·출처 정책 -``` +- 최종 학습·검증 원본: `data/intent/hr_intent_dataset_final.jsonl` 1,340건 +- Train ID: `data/intent/splits/train_ids.txt` 1,072건 +- Validation ID: `data/intent/splits/validation_ids.txt` 268건 +- 검수 전 원본: `data/intent/hr_intent_dataset.jsonl` — 감사·비교용, 신규 학습 금지 +- 독립 Gold Test: 아직 없음 + +자세한 사용 기준은 [`data/README.md`](data/README.md)를 확인합니다. ## 설치와 검증 -저장소 루트에서 실행합니다. +Git 저장소 루트에서 실행합니다. ```bash python3.11 -m venv .venv -.venv/bin/python -m pip install -e "./fowoco-knowledge[dev]" -.venv/bin/python -m fowoco_knowledge validate -.venv/bin/python -m pytest fowoco-knowledge/tests +make install +make check ``` -## CLI 사용 +패키지 디렉터리에서 직접 실행할 때는 다음 명령을 사용할 수 있습니다. + +```bash +../.venv/bin/python -m pip install -e ".[dev]" +../.venv/bin/python -m fowoco_knowledge validate +../.venv/bin/python -m pytest tests +``` + +## CLI 예시 ```bash # 지원 Workflow 목록 .venv/bin/python -m fowoco_knowledge list-workflows -# Agent에 전달할 Context 묶음 확인 +# Workflow에 필요한 Context 확인 .venv/bin/python -m fowoco_knowledge compile-context WF-STY-001 -# 분류·Slot Filling 결과가 Workflow를 실행할 수 있는지 확인 +# 분류·Slot Filling 결과 검증 .venv/bin/python -m fowoco_knowledge check-request \ fowoco-knowledge/examples/ambiguous_document_request.json -# 공식 원본 검증 후 제조업 Knowledge 스냅샷 재생성 -.venv/bin/python -m fowoco_knowledge sync-official-data - -# 신청서별 필요서류와 제조업 세부업종 조회 +# 신청 업무별 정규화 필요서류 조회 .venv/bin/python -m fowoco_knowledge \ list-required-documents "외국인 고용변동 등 신고" -.venv/bin/python -m fowoco_knowledge search-industries "금속가공제품" ``` -`check-request`는 자연어 모델을 대신하지 않습니다. 모델이 출력한 Workflow와 Slot이 -업무를 시작하기에 충분한지 규칙으로 검증합니다. +`check-request`는 자연어 모델을 대신하지 않습니다. 모델 출력이 Workflow를 시작하기에 +충분한지 규칙으로 검사합니다. -## Agent 연동 위치 +## 업무 경계 ```text -자연어/PDF/Excel - -> Intent·Domain·Slot 추론 - -> 이 패키지로 Workflow·필수정보·공식출처 조회 - -> 누락·모호성 검증 - -> HR 업무카드와 안내문 초안 생성 +HR 입력 + -> Intent + evidence 분류 + -> Workflow·필수 Slot·공식 출처 조회 + -> 누락·모호성·금지 실행 검증 + -> 업무카드와 안내문 초안 -> HR 승인 - -> 근로자 응답과 후속 티켓 ``` -## 데이터 상태 - -- `gold_seed.csv`: 프롬프트·분기 개발용 초기 Seed이며 모델 학습 완료 데이터가 아님 -- `golden_cases.jsonl`: 코드와 모델 평가에만 사용하는 독립 사례 -- `hr_intent_dataset.jsonl`: Intent Train/Validation 후보 1,340건이며 재검수 전 Gold Test가 아님 -- 공개데이터: 절차·용어·분포 보조자료이며 FOWOCO Intent의 정답 라벨로 간주하지 않음 -- 실제 운영 로그: 개인정보를 제거하고 별도 승인된 경우에만 Active Learning 후보로 사용 - -일반 데이터 기준은 [`docs/DATA_GUIDE.md`](docs/DATA_GUIDE.md), Intent 라벨과 -evidence 기준은 [`docs/INTENT_DATA.md`](docs/INTENT_DATA.md)를 확인합니다. - -공식 데이터 변환은 [`docs/OFFICIAL_DATA_PIPELINE.md`](docs/OFFICIAL_DATA_PIPELINE.md), -신고·연장 기능의 범위는 [`docs/E9_REPORTING_WORKFLOWS.md`](docs/E9_REPORTING_WORKFLOWS.md)를 -확인합니다. +기관 자동 제출, 법적 최종 판단, 노무위반 확률 예측은 MVP 범위가 아닙니다. diff --git a/fowoco-knowledge/data/README.md b/fowoco-knowledge/data/README.md new file mode 100644 index 0000000..4dbcf70 --- /dev/null +++ b/fowoco-knowledge/data/README.md @@ -0,0 +1,47 @@ +# 데이터 사용 안내 + +이 디렉터리는 FOWOCO Knowledge의 데이터 원본, 정규화 결과, 검수 자료와 평가 사례를 +관리합니다. 실제 근로자·기업 개인정보는 저장하지 않습니다. + +## Intent 데이터 + +| 파일 | 건수 | 용도 | 사용 기준 | +| --- | ---: | --- | --- | +| [`intent/hr_intent_dataset_final.jsonl`](intent/hr_intent_dataset_final.jsonl) | 1,340 | 최종 검수된 학습·검증 원본 | **현재 기준 파일** | +| [`intent/hr_intent_dataset.jsonl`](intent/hr_intent_dataset.jsonl) | 1,340 | 검수 전 라벨 원본 | 변경 이력 비교만 허용 | +| [`intent/splits/train_ids.txt`](intent/splits/train_ids.txt) | 1,072 | Train ID | 최종 JSONL에서 ID로 선택 | +| [`intent/splits/validation_ids.txt`](intent/splits/validation_ids.txt) | 268 | Validation ID | 모델 개발·비교용 | +| [`intent/manifest.yaml`](intent/manifest.yaml) | - | 최종 데이터 버전·해시·제한 | 데이터 사용 전 확인 | +| [`intent/splits/manifest.yaml`](intent/splits/manifest.yaml) | - | 분할 방식·해시·분포 | split 사용 전 확인 | + +Train과 Validation 데이터는 별도 복사본을 만들지 않습니다. 최종 JSONL을 읽은 뒤 ID +파일로 레코드를 선택합니다. 두 ID 파일은 겹치지 않으며 전체 1,340개 ID를 한 번씩 +포함합니다. + +Validation 268건은 모델 개발용입니다. 학습 prompt, threshold와 routing 규칙을 이 +Validation 결과에 맞춰 조정했으므로 독립 Test 성능이나 운영 성능으로 표현하지 않습니다. + +## 나머지 디렉터리 + +| 경로 | 내용 | +| --- | --- | +| [`external/`](external) | 외부 공식자료 출처, 버전, SHA-256 | +| [`curated/`](curated) | 팀이 수동 정리한 원본성 자료 | +| [`processed/`](processed) | 검증 가능한 정규화 결과와 manifest | +| [`seed/`](seed) | Workflow·분기 개발용 초기 Seed | +| [`evaluation/`](evaluation) | Knowledge/Agent 동작을 확인하는 독립 사례 | +| [`review/`](review) | 라벨링·전문가 검수 템플릿 | + +`evaluation/golden_cases.jsonl`은 Agent Workflow 검증 사례이며, Intent 모델의 잠긴 +Gold Test 240건과 동일하지 않습니다. 현재 독립 Intent Gold Test는 저장소에 없습니다. + +## 변경 시 필수 확인 + +1. 원본과 최종본의 목적을 섞지 않습니다. +2. 데이터가 바뀌면 manifest의 version, record count, SHA-256을 함께 갱신합니다. +3. split ID의 중복·누락과 source ID 존재 여부를 확인합니다. +4. JSON Schema, evidence exact substring, Intent 순서와 `OUT_OF_SCOPE` 단독성을 검사합니다. +5. 외국인등록번호, 여권번호, 전화번호, 계좌번호 패턴이 없는지 확인합니다. +6. 독립 Test를 Train·Validation 또는 prompt 예시로 재사용하지 않습니다. + +저장소 루트의 `make check`가 위 구조 검증의 기본 진입점입니다. diff --git a/fowoco-knowledge/data/intent/hr_intent_dataset_final.jsonl b/fowoco-knowledge/data/intent/hr_intent_dataset_final.jsonl index e2b8ddd..f064512 100644 --- a/fowoco-knowledge/data/intent/hr_intent_dataset_final.jsonl +++ b/fowoco-knowledge/data/intent/hr_intent_dataset_final.jsonl @@ -1088,7 +1088,7 @@ {"id": 1088, "hr_input": "WRK-698 무단결근으로 결원, 오늘 오전 급하게 인력 재배치했음", "intents": [{"intent": "EMPLOYMENT_CHANGE", "evidence": "무단결근으로 결원"}, {"intent": "WORK_INSTRUCTION", "evidence": "오늘 오전 급하게 인력 재배치했음"}]} {"id": 1089, "hr_input": "신규 WRK-699 여권 받아서 등록 진행하고, 최초 보험가입 및 급여계좌 개설까지 한번에 처리 부탁", "intents": [{"intent": "DOCUMENT_REQUEST", "evidence": "여권 받아서"}, {"intent": "WORKER_ONBOARDING", "evidence": "등록 진행하고, 최초 보험가입 및 급여계좌 개설까지 한번에 처리 부탁"}]} {"id": 1090, "hr_input": "WRK-700 오늘 작업 지연으로 잔업 지시했고, 잔업수당은 다음 급여에 반영해줘", "intents": [{"intent": "WORK_INSTRUCTION", "evidence": "오늘 작업 지연으로 잔업 지시했고"}, {"intent": "PAYROLL_EXPLANATION", "evidence": "잔업수당은 다음 급여에 반영해줘"}]} -{"id": 1091, "hr_input": "WRK-701 사업장 변경 서류 받아서 정리하고, 새 근무지 작업 배치까지 확인 부탁", "intents": [{"intent": "DOCUMENT_REQUEST", "evidence": "서류 받아서 정리"}, {"intent": "EMPLOYMENT_CHANGE", "evidence": "사업장 변경"}, {"intent": "WORK_INSTRUCTION", "evidence": "새 근무지 작업 배치까지 확인"}]} +{"id": 1091, "hr_input": "WRK-701 사업장 변경 서류 받아서 정리하고, 새 근무지 작업 배치까지 확인 부탁", "intents": [{"intent": "EMPLOYMENT_CHANGE", "evidence": "사업장 변경"}, {"intent": "DOCUMENT_REQUEST", "evidence": "서류 받아서 정리"}, {"intent": "WORK_INSTRUCTION", "evidence": "새 근무지 작업 배치까지 확인"}]} {"id": 1092, "hr_input": "WRK-702: 오늘 라인 교체 완료, 별도 지시 없음", "intents": [{"intent": "OUT_OF_SCOPE", "evidence": null}]} {"id": 1093, "hr_input": "WRK-703 급여 지급일 변경됐다고 안내해줘요", "intents": [{"intent": "PAYROLL_EXPLANATION", "evidence": "급여 지급일 변경됐다고 안내해줘요"}]} {"id": 1094, "hr_input": "WRK-704 무단결근 3일째, 자동 퇴사 처리 검토 및 대체인력 배치 시급함", "intents": [{"intent": "EMPLOYMENT_CHANGE", "evidence": "무단결근 3일째, 자동 퇴사 처리 검토"}, {"intent": "WORK_INSTRUCTION", "evidence": "대체인력 배치 시급함"}]} @@ -1122,7 +1122,7 @@ {"id": 1122, "hr_input": "WRK-732 여권과 등록증 받아서 연장 신청, 접수 결과는 급여명세서와 함께 안내해줘", "intents": [{"intent": "DOCUMENT_REQUEST", "evidence": "여권과 등록증 받아서"}, {"intent": "EXPIRY_RENEWAL", "evidence": "연장 신청"}, {"intent": "WORK_INSTRUCTION", "evidence": "접수 결과는 급여명세서와 함께 안내해줘"}]} {"id": 1123, "hr_input": "WRK-733: 오늘 라인 배치 그대로 유지", "intents": [{"intent": "WORK_INSTRUCTION", "evidence": "오늘 라인 배치 그대로 유지"}]} {"id": 1124, "hr_input": "WRK-734 퇴사 처리하고 남은 라인 인력 재배치 지시함", "intents": [{"intent": "EMPLOYMENT_CHANGE", "evidence": "퇴사 처리"}, {"intent": "WORK_INSTRUCTION", "evidence": "남은 라인 인력 재배치 지시함"}]} -{"id": 1125, "hr_input": "WRK-735 급여 명세 확인차 통장사본 받아서 대조 부탁드립니다.", "intents": [{"intent": "DOCUMENT_REQUEST", "evidence": "통장사본 받아서"}, {"intent": "PAYROLL_EXPLANATION", "evidence": "급여 명세 확인차"}]} +{"id": 1125, "hr_input": "WRK-735 급여 명세 확인차 통장사본 받아서 대조 부탁드립니다.", "intents": [{"intent": "PAYROLL_EXPLANATION", "evidence": "급여 명세 확인차"}, {"intent": "DOCUMENT_REQUEST", "evidence": "통장사본 받아서"}]} {"id": 1126, "hr_input": "WRK-736 오늘부터 신규 라인 근무, 3개월 후 정규 배치 예정", "intents": [{"intent": "WORK_INSTRUCTION", "evidence": "오늘부터 신규 라인 근무, 3개월 후 정규 배치 예정"}]} {"id": 1127, "hr_input": "WRK-737 무단결근 이틀째, 체류기간 만료도 임박해서 신고 서둘러야 함", "intents": [{"intent": "EMPLOYMENT_CHANGE", "evidence": "무단결근 이틀째"}, {"intent": "EXPIRY_RENEWAL", "evidence": "체류기간 만료도 임박"}]} {"id": 1128, "hr_input": "WRK-738: 급여 지연 문의, 확인 후 안내 요망", "intents": [{"intent": "PAYROLL_EXPLANATION", "evidence": "급여 지연 문의, 확인 후 안내 요망"}]} @@ -1319,7 +1319,7 @@ {"id": 1319, "hr_input": "WRK-929 계약만료 임박, 여권 받아서 연장 서류 접수, 새 근무 배치는 미정", "intents": [{"intent": "EXPIRY_RENEWAL", "evidence": "계약만료 임박"}, {"intent": "DOCUMENT_REQUEST", "evidence": "여권 받아서"}]} {"id": 1320, "hr_input": "WRK-930 오늘 무단결근으로 라인 공백, 급하게 대체인력 배치했고 신고도 접수함", "intents": [{"intent": "OUT_OF_SCOPE", "evidence": null}]} {"id": 1321, "hr_input": "WRK-931: 오늘 라인 청소 및 정리정돈 지시", "intents": [{"intent": "WORK_INSTRUCTION", "evidence": "오늘 라인 청소 및 정리정돈 지시"}]} -{"id": 1322, "hr_input": "신규 WRK-932 여권과 계약서 받아서 등록 진행, 배치 및 급여계좌는 다음 주 처리 예정", "intents": [{"intent": "DOCUMENT_REQUEST", "evidence": "여권과 계약서 받아서"}, {"intent": "WORKER_ONBOARDING", "evidence": "등록 진행, 급여계좌는 다음 주 처리 예정"}, {"intent": "WORK_INSTRUCTION", "evidence": "배치"}]} +{"id": 1322, "hr_input": "신규 WRK-932 여권과 계약서 받아서 등록 진행, 배치 및 급여계좌는 다음 주 처리 예정", "intents": [{"intent": "DOCUMENT_REQUEST", "evidence": "여권과 계약서 받아서"}, {"intent": "WORKER_ONBOARDING", "evidence": "등록 진행"}, {"intent": "WORK_INSTRUCTION", "evidence": "배치"}]} {"id": 1323, "hr_input": "WRK-933 급여계좌 오류, 통장사본 받아서 재등록 부탁", "intents": [{"intent": "DOCUMENT_REQUEST", "evidence": "통장사본 받아서"}, {"intent": "WORKER_ONBOARDING", "evidence": "재등록 부탁"}]} {"id": 1324, "hr_input": "WRK-934: 오늘 오전 결근, 오후 정상 출근 확인", "intents": [{"intent": "EMPLOYMENT_CHANGE", "evidence": "오늘 오전 결근, 오후 정상 출근 확인"}]} {"id": 1325, "hr_input": "WRK-935 사업장 변경 후 새 작업지시 및 급여체계 안내 필요", "intents": [{"intent": "EMPLOYMENT_CHANGE", "evidence": "사업장 변경"}, {"intent": "WORK_INSTRUCTION", "evidence": "새 작업지시"}, {"intent": "PAYROLL_EXPLANATION", "evidence": "급여체계 안내 필요"}]} diff --git a/fowoco-knowledge/data/intent/manifest.yaml b/fowoco-knowledge/data/intent/manifest.yaml index d257ee5..6bfc37b 100644 --- a/fowoco-knowledge/data/intent/manifest.yaml +++ b/fowoco-knowledge/data/intent/manifest.yaml @@ -1,16 +1,30 @@ -dataset_id: FOWOCO-HR-INTENT-CANDIDATES -version: 1.1.0 -status: recheck_required -path: data/intent/hr_intent_dataset.jsonl +dataset_id: FOWOCO-HR-INTENT-TRAIN-VALIDATION +version: 1.2.1 +status: reviewed_train_validation +path: data/intent/hr_intent_dataset_final.jsonl schema: schemas/intent-training-case.schema.json label_guide: docs/INTENT_DATA.md record_count: 1340 -sha256: 4f4ebfdd4170a78def33e31edbed8315921c0b67934f5ff8612595dcd479bed2 +sha256: 84d1b0de9ec4a009c36ffcf7e06d146affe5535d0f0791c1afd593ef9aa80cfb contains_real_personal_data: false +review: + rule_version: 1.1 + pre_review_path: data/intent/hr_intent_dataset.jsonl + pre_review_sha256: 4f4ebfdd4170a78def33e31edbed8315921c0b67934f5ff8612595dcd479bed2 + changed_label_record_count: 124 + ids_and_hr_inputs_unchanged: true +known_model_training_snapshot: + dataset_version: 1.2.0 + sha256: 447174a992e31bd44ec8abe658dc4082d3197bf284e0cc08b7ac5e3e6341a237 + matches_current_dataset: false + note: 최종 파일의 구조 오류 3건을 수정하기 전 모델 학습·검증에 사용한 snapshot intended_use: - - Intent 분기와 출력 구조 개발 - - Train/Validation 후보 데이터의 A/B 재검수 + - Intent 분류 모델의 Train 데이터 구성 + - 모델 개발 단계의 Validation 비교 + - Intent와 evidence 출력 구조 검증 limitations: - 독립 Gold Test가 아님 - - 최종 모델 성능 주장에 사용할 수 없음 - - 의미 라벨은 규칙 v1.1에 따른 A/B 합의 검수가 필요함 + - 같은 Validation으로 모델과 routing 규칙을 조정했으므로 최종 성능 주장에 사용할 수 없음 + - Workflow 선택, Slot Filling, 외부기관 실행 또는 법적 판단의 정답 데이터가 아님 + - 라벨 규칙이 바뀌면 재검수와 version 갱신이 필요함 + - 현재 포함 모델 가중치는 version 1.2.0 snapshot으로 학습되어 version 1.2.1로 재학습되지 않음 diff --git a/fowoco-knowledge/data/intent/splits/manifest.yaml b/fowoco-knowledge/data/intent/splits/manifest.yaml index 463a3f2..7319a68 100644 --- a/fowoco-knowledge/data/intent/splits/manifest.yaml +++ b/fowoco-knowledge/data/intent/splits/manifest.yaml @@ -1,20 +1,22 @@ -split_id: FOWOCO-HR-INTENT-PROVISIONAL-SPLIT -version: 0.1.0 -status: provisional_pending_consensus +split_id: FOWOCO-HR-INTENT-SPLIT +version: 1.0.1 +status: frozen_for_model_development schema: schemas/intent-split-manifest.schema.json source: - dataset_id: FOWOCO-HR-INTENT-CANDIDATES - version: 1.1.0 - status: recheck_required - path: data/intent/hr_intent_dataset.jsonl + dataset_id: FOWOCO-HR-INTENT-TRAIN-VALIDATION + version: 1.2.1 + status: reviewed_train_validation + path: data/intent/hr_intent_dataset_final.jsonl record_count: 1340 - sha256: 4f4ebfdd4170a78def33e31edbed8315921c0b67934f5ff8612595dcd479bed2 -consensus: - status: pending - assumed_for_provisional_split: true - dependency_issue: 30 - dependency_pr: 31 - regenerate_when_source_changes: true + sha256: 84d1b0de9ec4a009c36ffcf7e06d146affe5535d0f0791c1afd593ef9aa80cfb +review_lineage: + generated_from_pre_review_sha256: 4f4ebfdd4170a78def33e31edbed8315921c0b67934f5ff8612595dcd479bed2 + final_label_change_count: 124 + ids_and_hr_inputs_unchanged: true + split_ids_reused_after_review: true + rationale: >- + 검수 전·최종 데이터의 ID와 hr_input이 동일하고 기존 모델 학습에 사용한 ID 분할을 + 재현하기 위해 split ID를 유지한다. 현재 통계는 최종 라벨을 기준으로 다시 계산했다. policy: method: grouped_multilabel_greedy seed: 20260727 @@ -29,16 +31,16 @@ policy: casefold: true preserve_dates_amounts_and_task_terms: true stratification_features: - - intent_code - - intent_cardinality - - ordered_intent_combination + - intent_code + - intent_cardinality + - ordered_intent_combination outputs: train: - path: data/intent/splits/provisional-v1/train_ids.txt + path: data/intent/splits/train_ids.txt record_count: 1072 sha256: 264011422e233e28ab7ab4b93579f634a6e53c00730d254b7ca2749d71575cb6 validation: - path: data/intent/splits/provisional-v1/validation_ids.txt + path: data/intent/splits/validation_ids.txt record_count: 268 sha256: 96da74e15519f9610ef316bc3e7499d06832c36564180607a3565b851bc8ba85 statistics: @@ -47,48 +49,46 @@ statistics: max_template_group_size: 2 label_counts: source: - DOCUMENT_REQUEST: 345 - EMPLOYMENT_CHANGE: 273 - EXPIRY_RENEWAL: 234 - OUT_OF_SCOPE: 128 - PAYROLL_EXPLANATION: 275 - WORKER_ONBOARDING: 193 - WORK_INSTRUCTION: 339 + DOCUMENT_REQUEST: 327 + EMPLOYMENT_CHANGE: 255 + EXPIRY_RENEWAL: 223 + OUT_OF_SCOPE: 181 + PAYROLL_EXPLANATION: 242 + WORKER_ONBOARDING: 189 + WORK_INSTRUCTION: 325 train: - DOCUMENT_REQUEST: 275 - EMPLOYMENT_CHANGE: 217 - EXPIRY_RENEWAL: 186 - OUT_OF_SCOPE: 102 - PAYROLL_EXPLANATION: 219 - WORKER_ONBOARDING: 154 - WORK_INSTRUCTION: 269 + DOCUMENT_REQUEST: 258 + EMPLOYMENT_CHANGE: 201 + EXPIRY_RENEWAL: 176 + OUT_OF_SCOPE: 145 + PAYROLL_EXPLANATION: 194 + WORKER_ONBOARDING: 150 + WORK_INSTRUCTION: 258 validation: - DOCUMENT_REQUEST: 70 - EMPLOYMENT_CHANGE: 56 - EXPIRY_RENEWAL: 48 - OUT_OF_SCOPE: 26 - PAYROLL_EXPLANATION: 56 + DOCUMENT_REQUEST: 69 + EMPLOYMENT_CHANGE: 54 + EXPIRY_RENEWAL: 47 + OUT_OF_SCOPE: 36 + PAYROLL_EXPLANATION: 48 WORKER_ONBOARDING: 39 - WORK_INSTRUCTION: 70 + WORK_INSTRUCTION: 67 intent_cardinality_counts: source: - '1': 970 - '2': 297 - '3': 69 - '4': 4 + '1': 1003 + '2': 273 + '3': 63 + '4': 1 train: - '1': 778 - '2': 240 - '3': 52 - '4': 2 + '1': 809 + '2': 216 + '3': 47 validation: - '1': 192 + '1': 194 '2': 57 - '3': 17 - '4': 2 + '3': 16 + '4': 1 limitations: -- Reviewer B와 A/B consensus 완료 전에는 최종 학습 분할이 아님 -- Reviewer A의 proposed_intents_json은 현재 원본에 적용하지 않은 상태임 -- 독립 Gold Test를 포함하지 않음 -- source SHA-256이 바뀌면 생성기를 다시 실행해야 함 -- 분할 비율과 내부 목표치는 baseline 비교용이며 성능 보장을 뜻하지 않음 + - Validation은 모델 개발용이며 독립 Gold Test가 아님 + - split ID는 검수 전 라벨로 생성한 뒤 최종 라벨에도 동일하게 적용한 고정 분할임 + - ID 또는 hr_input이 바뀌면 grouped split을 다시 생성해야 함 + - 라벨 분포가 크게 바뀌면 분할 적합성을 재검토해야 함 diff --git a/fowoco-knowledge/docs/INTENT_DATA.md b/fowoco-knowledge/docs/INTENT_DATA.md index c75721f..db3b2ac 100644 --- a/fowoco-knowledge/docs/INTENT_DATA.md +++ b/fowoco-knowledge/docs/INTENT_DATA.md @@ -1,9 +1,9 @@ # Intent 라벨링 및 데이터 계약 - 규칙 버전: 1.1 -- 갱신일: 2026-07-27 -- 대상 파일: `data/intent/hr_intent_dataset.jsonl` -- 데이터 상태: Train/Validation 후보, 독립 Gold Test 아님 +- 갱신일: 2026-08-11 +- 기준 파일: `data/intent/hr_intent_dataset_final.jsonl` +- 데이터 상태: 검수 완료 Train/Validation 데이터, 독립 Gold Test 아님 이 문서는 HR 담당자의 발화에서 FOWOCO MVP가 지원하는 Intent와 근거 문구를 라벨링하는 기준을 정의한다. Intent 모델의 책임은 `Intent + evidence` 추출까지다. @@ -25,7 +25,7 @@ Workflow 선택, Slot 수집, 외부기관 제출, 법적 판단, 업무 실행 세부 설명의 기준 원본은 [`knowledge/intents.yaml`](../knowledge/intents.yaml)이다. 이 문서와 원본의 의미가 충돌하면 두 파일을 함께 수정하고 검수한다. -## 2. 학습·평가 후보 데이터 스키마 +## 2. 학습·검증 데이터 스키마 현재 JSONL의 한 줄은 다음 구조만 사용한다. @@ -54,9 +54,10 @@ Workflow 선택, Slot 수집, 외부기관 제출, 법적 판단, 업무 실행 | `intents[].intent` | string | 7개 Intent Code 중 하나 | | `intents[].evidence` | string \| null | 원문의 연속된 부분 문자열. `OUT_OF_SCOPE`만 `null` | -`source`와 `split`은 현재 레코드 필드가 아니다. 작성 출처와 Train/Validation/Test -분할은 데이터 검수 완료 후 별도 버전 manifest로 관리한다. 현재 1,340건을 독립 -Gold Test 또는 최종 성능 주장에 사용하지 않는다. +`source`와 `split`은 레코드 필드가 아니다. 데이터 버전은 +`data/intent/manifest.yaml`, Train/Validation 분할은 +`data/intent/splits/manifest.yaml`과 ID 파일로 관리한다. 현재 1,340건과 Validation +268건을 독립 Gold Test 또는 최종 성능 주장에 사용하지 않는다. 정식 구조 검증 기준은 [`schemas/intent-training-case.schema.json`](../schemas/intent-training-case.schema.json)이다. @@ -190,10 +191,14 @@ HR 요청 분류 데이터의 범위 밖이므로 `OUT_OF_SCOPE`로 처리한다 - `퇴사했으니 신고 준비 업무 만들어줘` → `EMPLOYMENT_CHANGE` - `등록 완료 여부 확인해줘` → `WORKER_ONBOARDING` -## 7. 재검수 기준 +## 7. 검수 결과와 변경 기준 -현재 1,340건은 이 규칙에 따라 A/B 독립 재검수 후 합의본을 만들어야 한다. 다음 -항목은 우선 검수 대상으로 표시한다. +`hr_intent_dataset_final.jsonl`은 규칙 v1.1 경계 사례 재검수를 반영한 현재 기준 +파일이다. 검수 전 원본과 비교해 124건의 Intent/evidence 라벨이 바뀌었고, ID와 +`hr_input`은 그대로 유지했다. 검수 전 파일은 감사와 비교를 위해 +`hr_intent_dataset.jsonl`에 보존한다. + +다음 항목은 라벨 규칙 또는 데이터를 변경할 때 다시 우선 검수한다. - Multi-Intent 순서가 원문의 evidence 순서와 다른 레코드 - `급여계좌`를 `PAYROLL_EXPLANATION`으로 분류한 레코드 @@ -202,9 +207,7 @@ HR 요청 분류 데이터의 범위 밖이므로 `OUT_OF_SCOPE`로 처리한다 - 외부기관 접수·자동 실행 문구가 evidence에 포함된 레코드 - evidence가 판단에 필요한 범위보다 길거나 다른 Intent까지 포함한 레코드 -자동 검증은 JSON Schema, ID 중복, Intent Code, `OUT_OF_SCOPE` 단독성, evidence -원문 포함 여부와 Multi-Intent 순서를 검사한다. 의미 경계는 자동으로 확정하지 않고 -검수자 합의로 변경한다. - -관련 작업은 [GitHub Issue #28](https://github.com/fowoco/knowledge/issues/28)에서 -추적한다. +자동 검증은 JSON Schema, SHA-256, ID 중복, Intent Code, `OUT_OF_SCOPE` 단독성, +evidence 원문 포함 여부, Multi-Intent 순서와 Train/Validation 분할을 검사한다. +의미 경계는 자동으로 확정하지 않고 검수자 합의로 변경한다. 변경 시 final 데이터와 +두 manifest의 version·통계·SHA-256을 함께 갱신한다. diff --git a/fowoco-knowledge/hr-intent-service/README.md b/fowoco-knowledge/hr-intent-service/README.md index c1adfe9..695e8b4 100644 --- a/fowoco-knowledge/hr-intent-service/README.md +++ b/fowoco-knowledge/hr-intent-service/README.md @@ -37,12 +37,20 @@ BERT(Full FT) 메인 모델 + A.X-4.0-Light(QLoRA) 보조 모델 cascade 구조 | A.X-4.0-Light | 보조 | QLoRA (checkpoint-402) | 92.2% | | Cascade 모델 | 최종 | 메인 모델 + 보조 모델 , 라우팅 조건 적용 | 93.2% | +위 가중치와 Validation 결과의 학습 데이터 snapshot SHA-256은 +`447174a992e31bd44ec8abe658dc4082d3197bf284e0cc08b7ac5e3e6341a237`입니다. +현재 Knowledge 데이터 version 1.2.1은 evidence·순서 구조 오류 3건을 고쳐 SHA-256이 +달라졌으며, 포함된 가중치는 아직 1.2.1로 재학습하지 않았습니다. + ## Hugging Face Hub 연동 https://huggingface.co/fowoco -모델 학습 가중치는 `fowoco` 조직의 private repo에 저장되어 있다. GitHub에는 코드만 올리고, 모델 파일은 여기서 관리한다 . +모델 배포의 기준 위치는 `fowoco` 조직의 Hugging Face 저장소다. 현재 GitHub에는 +프로젝트 결과 재현과 인계를 위한 동일 가중치 스냅샷이 Git LFS로 포함되어 있다. +새 모델 버전은 Hugging Face에 model card와 함께 게시하고, 이 저장소에는 데이터 +version·SHA-256과 호환되는 코드만 갱신한다. ``` fowoco/klue-roberta-base-intent-classifier fowoco/ax-intent-qlora @@ -79,4 +87,4 @@ docker build -t hr-intent-service:test . docker run -p 8000:8000 --env-file .env hr-intent-service:test ``` -로컬 환경에서는 `.env`에 `ENABLE_AX=False`로 둘 것을 권장함. \ No newline at end of file +로컬 환경에서는 `.env`에 `ENABLE_AX=False`로 둘 것을 권장함. diff --git a/fowoco-knowledge/knowledge/manifest.yaml b/fowoco-knowledge/knowledge/manifest.yaml index 9bed371..653a7e3 100644 --- a/fowoco-knowledge/knowledge/manifest.yaml +++ b/fowoco-knowledge/knowledge/manifest.yaml @@ -32,7 +32,9 @@ datasets: required_documents: data/processed/required_documents_manufacturing.csv manufacturing_industries: data/processed/manufacturing_industries.csv intent_manifest: data/intent/manifest.yaml - intent_training_candidates: data/intent/hr_intent_dataset.jsonl + intent_training_validation: data/intent/hr_intent_dataset_final.jsonl + intent_pre_review_archive: data/intent/hr_intent_dataset.jsonl + intent_split_manifest: data/intent/splits/manifest.yaml review_policy: minimum_reviewers_for_official_knowledge: 2 all_outbound_messages_require_hr_approval: true diff --git a/fowoco-knowledge/schemas/intent-split-manifest.schema.json b/fowoco-knowledge/schemas/intent-split-manifest.schema.json new file mode 100644 index 0000000..6210161 --- /dev/null +++ b/fowoco-knowledge/schemas/intent-split-manifest.schema.json @@ -0,0 +1,224 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://fowoco.dev/schemas/intent-split-manifest.schema.json", + "title": "FOWOCO Intent train and validation split manifest", + "type": "object", + "additionalProperties": false, + "required": [ + "split_id", + "version", + "status", + "schema", + "source", + "review_lineage", + "policy", + "outputs", + "statistics", + "limitations" + ], + "properties": { + "split_id": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "status": { + "const": "frozen_for_model_development" + }, + "schema": { + "const": "schemas/intent-split-manifest.schema.json" + }, + "source": { + "$ref": "#/$defs/source" + }, + "review_lineage": { + "$ref": "#/$defs/reviewLineage" + }, + "policy": { + "$ref": "#/$defs/policy" + }, + "outputs": { + "type": "object", + "additionalProperties": false, + "required": [ + "train", + "validation" + ], + "properties": { + "train": { + "$ref": "#/$defs/output" + }, + "validation": { + "$ref": "#/$defs/output" + } + } + }, + "statistics": { + "type": "object", + "required": [ + "template_group_count", + "duplicate_template_group_count", + "max_template_group_size", + "label_counts", + "intent_cardinality_counts" + ] + }, + "limitations": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "$defs": { + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "source": { + "type": "object", + "additionalProperties": false, + "required": [ + "dataset_id", + "version", + "status", + "path", + "record_count", + "sha256" + ], + "properties": { + "dataset_id": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "minLength": 1 + }, + "path": { + "type": "string", + "minLength": 1 + }, + "record_count": { + "type": "integer", + "minimum": 1 + }, + "sha256": { + "$ref": "#/$defs/sha256" + } + } + }, + "reviewLineage": { + "type": "object", + "additionalProperties": false, + "required": [ + "generated_from_pre_review_sha256", + "final_label_change_count", + "ids_and_hr_inputs_unchanged", + "split_ids_reused_after_review", + "rationale" + ], + "properties": { + "generated_from_pre_review_sha256": { + "$ref": "#/$defs/sha256" + }, + "final_label_change_count": { + "type": "integer", + "minimum": 0 + }, + "ids_and_hr_inputs_unchanged": { + "const": true + }, + "split_ids_reused_after_review": { + "const": true + }, + "rationale": { + "type": "string", + "minLength": 1 + } + } + }, + "policy": { + "type": "object", + "additionalProperties": false, + "required": [ + "method", + "seed", + "train_ratio", + "validation_ratio", + "target_train_count", + "target_validation_count", + "template_normalization", + "stratification_features" + ], + "properties": { + "method": { + "const": "grouped_multilabel_greedy" + }, + "seed": { + "type": "integer" + }, + "train_ratio": { + "type": "number", + "exclusiveMinimum": 0, + "exclusiveMaximum": 1 + }, + "validation_ratio": { + "type": "number", + "exclusiveMinimum": 0, + "exclusiveMaximum": 1 + }, + "target_train_count": { + "type": "integer", + "minimum": 1 + }, + "target_validation_count": { + "type": "integer", + "minimum": 1 + }, + "template_normalization": { + "type": "object" + }, + "stratification_features": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, + "output": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "record_count", + "sha256" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "record_count": { + "type": "integer", + "minimum": 1 + }, + "sha256": { + "$ref": "#/$defs/sha256" + } + } + } + } +} diff --git a/fowoco-knowledge/src/fowoco_knowledge/validation.py b/fowoco-knowledge/src/fowoco_knowledge/validation.py index 471dac5..4ca953e 100644 --- a/fowoco-knowledge/src/fowoco_knowledge/validation.py +++ b/fowoco-knowledge/src/fowoco_knowledge/validation.py @@ -3,6 +3,7 @@ import csv import json import re +from collections import Counter from typing import Any from jsonschema import Draft202012Validator @@ -85,6 +86,7 @@ def validate_all(self) -> list[str]: self._validate_seed_data() self._validate_evaluation_data() self._validate_intent_data() + self._validate_intent_split() return self.errors def _validate_manifest_files(self) -> None: @@ -417,6 +419,159 @@ def _validate_intent_data(self) -> None: if file_sha256(path) != intent_manifest["sha256"]: self.errors.append("intent data: checksum mismatch") + review = intent_manifest.get("review", {}) + pre_review_relative_path = review.get("pre_review_path") + if pre_review_relative_path: + pre_review_path = self.repository.root / pre_review_relative_path + if not pre_review_path.is_file(): + self.errors.append("intent data: pre-review archive missing") + return + if file_sha256(pre_review_path) != review.get("pre_review_sha256"): + self.errors.append("intent data: pre-review archive checksum mismatch") + + final_cases = [ + json.loads(line) + for line in path.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + pre_review_cases = [ + json.loads(line) + for line in pre_review_path.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + final_identity = [(case["id"], case["hr_input"]) for case in final_cases] + pre_review_identity = [(case["id"], case["hr_input"]) for case in pre_review_cases] + if review.get("ids_and_hr_inputs_unchanged") and final_identity != pre_review_identity: + self.errors.append("intent data: pre-review IDs or inputs differ from final data") + changed_count = sum( + final_case["intents"] != pre_review_case["intents"] + for final_case, pre_review_case in zip(final_cases, pre_review_cases, strict=False) + ) + if changed_count != review.get("changed_label_record_count"): + self.errors.append("intent data: changed label record count mismatch") + + def _validate_intent_split(self) -> None: + manifest = self.repository.load_yaml("data/intent/splits/manifest.yaml") + schema = self.repository.load_json(manifest["schema"]) + schema_errors = list(Draft202012Validator(schema).iter_errors(manifest)) + for error in schema_errors: + path = ".".join(str(item) for item in error.path) + self.errors.append(f"intent split schema [{path}]: {error.message}") + if schema_errors: + return + + intent_manifest = self.repository.load_yaml("data/intent/manifest.yaml") + source = manifest["source"] + source_path = self.repository.root / source["path"] + if source["path"] != intent_manifest["path"]: + self.errors.append("intent split: source path differs from intent manifest") + if source["sha256"] != intent_manifest["sha256"]: + self.errors.append("intent split: source checksum differs from intent manifest") + if not source_path.is_file(): + self.errors.append("intent split: source file missing") + return + if file_sha256(source_path) != source["sha256"]: + self.errors.append("intent split: source checksum mismatch") + + cases = [ + json.loads(line) + for line in source_path.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + case_by_id = {case["id"]: case for case in cases} + source_ids = set(case_by_id) + if len(cases) != source["record_count"]: + self.errors.append("intent split: source record count mismatch") + if len(case_by_id) != len(cases): + self.errors.append("intent split: duplicate source id") + + split_ids: dict[str, set[int]] = {} + for split_name in ("train", "validation"): + output = manifest["outputs"][split_name] + output_path = self.repository.root / output["path"] + if not output_path.is_file(): + self.errors.append(f"intent split {split_name}: output file missing") + continue + if file_sha256(output_path) != output["sha256"]: + self.errors.append(f"intent split {split_name}: checksum mismatch") + + raw_ids = [ + line.strip() + for line in output_path.read_text(encoding="utf-8").splitlines() + if line.strip() + ] + try: + parsed_ids = [int(case_id) for case_id in raw_ids] + except ValueError: + self.errors.append(f"intent split {split_name}: non-integer id") + continue + + current_ids = set(parsed_ids) + split_ids[split_name] = current_ids + if len(parsed_ids) != output["record_count"]: + self.errors.append(f"intent split {split_name}: record count mismatch") + if len(current_ids) != len(parsed_ids): + self.errors.append(f"intent split {split_name}: duplicate id") + if current_ids - source_ids: + self.errors.append(f"intent split {split_name}: unknown source id") + + if set(split_ids) != {"train", "validation"}: + return + + train_ids = split_ids["train"] + validation_ids = split_ids["validation"] + if train_ids & validation_ids: + self.errors.append("intent split: train and validation overlap") + if train_ids | validation_ids != source_ids: + self.errors.append("intent split: source ids are missing or duplicated across outputs") + + policy = manifest["policy"] + if manifest["outputs"]["train"]["record_count"] != policy["target_train_count"]: + self.errors.append("intent split: train count differs from policy target") + if manifest["outputs"]["validation"]["record_count"] != policy["target_validation_count"]: + self.errors.append("intent split: validation count differs from policy target") + + normalization = policy["template_normalization"] + worker_id_pattern = re.compile(normalization["worker_id_pattern"]) + + def normalize_template(value: str) -> str: + normalized = worker_id_pattern.sub(normalization["worker_id_replacement"], value) + if normalization["collapse_whitespace"]: + normalized = " ".join(normalized.split()) + if normalization["casefold"]: + normalized = normalized.casefold() + return normalized + + template_groups: dict[str, set[int]] = {} + for case in cases: + template = normalize_template(case["hr_input"]) + template_groups.setdefault(template, set()).add(case["id"]) + if any(group & train_ids and group & validation_ids for group in template_groups.values()): + self.errors.append("intent split: normalized template leaks across outputs") + + statistics = manifest["statistics"] + group_sizes = [len(group) for group in template_groups.values()] + if len(template_groups) != statistics["template_group_count"]: + self.errors.append("intent split: template group count mismatch") + if sum(size > 1 for size in group_sizes) != statistics["duplicate_template_group_count"]: + self.errors.append("intent split: duplicate template group count mismatch") + if max(group_sizes, default=0) != statistics["max_template_group_size"]: + self.errors.append("intent split: maximum template group size mismatch") + for split_name, ids in { + "source": source_ids, + "train": train_ids, + "validation": validation_ids, + }.items(): + selected_cases = [case_by_id[case_id] for case_id in ids] + label_counts = Counter( + item["intent"] for case in selected_cases for item in case["intents"] + ) + cardinality_counts = Counter(str(len(case["intents"])) for case in selected_cases) + if dict(label_counts) != statistics["label_counts"][split_name]: + self.errors.append(f"intent split {split_name}: label statistics mismatch") + if dict(cardinality_counts) != statistics["intent_cardinality_counts"][split_name]: + self.errors.append(f"intent split {split_name}: cardinality statistics mismatch") + def _index_unique(self, items: list[dict[str, Any]], kind: str) -> dict[str, dict[str, Any]]: indexed: dict[str, dict[str, Any]] = {} for item in items: diff --git a/fowoco-knowledge/tests/test_validation.py b/fowoco-knowledge/tests/test_validation.py index 5e1b95c..b2e0f6d 100644 --- a/fowoco-knowledge/tests/test_validation.py +++ b/fowoco-knowledge/tests/test_validation.py @@ -5,6 +5,8 @@ from collections import Counter from pathlib import Path +import yaml + from fowoco_knowledge.repository import KnowledgeRepository from fowoco_knowledge.validation import KnowledgeValidator, split_codes @@ -47,10 +49,10 @@ def test_evaluation_set_is_separate_and_has_compound_cases() -> None: assert any(case["expected_action"] == "OUT_OF_SCOPE" for case in cases) -def test_intent_training_candidates_match_documented_contract() -> None: +def test_final_intent_training_data_matches_documented_contract() -> None: cases = [ json.loads(line) - for line in (ROOT / "data/intent/hr_intent_dataset.jsonl") + for line in (ROOT / "data/intent/hr_intent_dataset_final.jsonl") .read_text(encoding="utf-8") .splitlines() if line.strip() @@ -58,3 +60,33 @@ def test_intent_training_candidates_match_documented_contract() -> None: assert len(cases) == 1340 assert [case["id"] for case in cases] == list(range(1, 1341)) + + +def test_intent_split_uses_final_data_without_overlap_or_missing_ids() -> None: + manifest = yaml.safe_load( + (ROOT / "data/intent/splits/manifest.yaml").read_text(encoding="utf-8") + ) + final_cases = [ + json.loads(line) + for line in (ROOT / manifest["source"]["path"]).read_text(encoding="utf-8").splitlines() + if line.strip() + ] + train_ids = { + int(case_id) + for case_id in (ROOT / manifest["outputs"]["train"]["path"]) + .read_text(encoding="utf-8") + .splitlines() + if case_id.strip() + } + validation_ids = { + int(case_id) + for case_id in (ROOT / manifest["outputs"]["validation"]["path"]) + .read_text(encoding="utf-8") + .splitlines() + if case_id.strip() + } + + assert len(train_ids) == 1072 + assert len(validation_ids) == 268 + assert train_ids.isdisjoint(validation_ids) + assert train_ids | validation_ids == {case["id"] for case in final_cases}