Skip to content

Commit 45dc510

Browse files
authored
Merge pull request #14 from 2026-KUSITMS-GLIT/feat/#7-tagging-v1
test: tagging eval_set_v2 평가 인프라 + v2 좁힘 제거 (v3 준비)
2 parents 71b761c + a7093f3 commit 45dc510

9 files changed

Lines changed: 5404 additions & 44 deletions

File tree

app/services/tagging/v2_postscore.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@
3030
JOB_ROLE_LABELS_KO,
3131
PRIMARY_CATEGORY_LABELS_KO,
3232
JobRole,
33-
PrimaryCategory,
3433
)
3534
from app.schemas.tagging import TaggingRequest, TaggingResponse
3635
from app.services._clients.llm_client import LLMClient, WorkloadType
37-
from app.services.tagging.data import ALL_TAGS, TAGS_BY_CATEGORY, tag_score
36+
from app.services.tagging.data import ALL_TAGS, tag_score
3837
from app.services.tagging.exceptions import TaggingValidationError
3938
from app.services.tagging.v1_baseline import _extract_text
4039

@@ -95,21 +94,13 @@ def _strip_code_fence(text: str) -> str:
9594
return text
9695

9796

98-
def _allowed_tags_for(category: PrimaryCategory) -> frozenset[str]:
99-
"""선택된 ``primaryCategory`` 안에서 허용되는 태그 셋.
100-
101-
카테고리 외 태그를 후보 단계에서 걸러내 contract (선택 역량 내 태그만 출력)
102-
를 보장한다.
103-
"""
104-
return TAGS_BY_CATEGORY[category]
105-
106-
107-
def _parse_and_validate(raw: str, allowed_tags: frozenset[str] = ALL_TAGS) -> list[str]:
97+
def _parse_and_validate(raw: str) -> list[str]:
10898
"""후보 검증 — 갯수 :data:`_CANDIDATE_MIN`~:data:`_CANDIDATE_MAX`, 풀 내, 중복 X.
10999
110100
실패 시 :class:`TaggingValidationError`. 후처리에서 ``_FINAL_TOP`` 으로 자르므로
111-
여기서는 후보 단계 갯수만 검증한다. ``allowed_tags`` 는 카테고리별 허용 셋을
112-
좁혀 넘길 수 있다 (기본 ``ALL_TAGS``).
101+
여기서는 후보 단계 갯수만 검증한다. 화이트리스트는 :data:`ALL_TAGS` 전체 —
102+
프롬프트 명세("카테고리 무관, 풀 전체 열려 있다") 와 일치하도록 카테고리
103+
좁힘은 적용하지 않는다.
113104
"""
114105
text = _strip_code_fence(raw)
115106
try:
@@ -137,7 +128,7 @@ def _parse_and_validate(raw: str, allowed_tags: frozenset[str] = ALL_TAGS) -> li
137128
if len(set(str_tags)) != len(str_tags):
138129
raise TaggingValidationError("detailTags 중복 포함")
139130

140-
out_of_pool = [t for t in str_tags if t not in allowed_tags]
131+
out_of_pool = [t for t in str_tags if t not in ALL_TAGS]
141132
if out_of_pool:
142133
raise TaggingValidationError(f"허용 태그 풀 외 태그 포함: {out_of_pool}")
143134

@@ -169,7 +160,6 @@ def _build_corrective_user_message(reason: str) -> str:
169160
async def run(req: TaggingRequest, llm: LLMClient, model: str) -> TaggingResponse:
170161
"""Tagging v2_postscore 실행."""
171162
system = _render_prompt(req)
172-
allowed = _allowed_tags_for(req.selected_competency)
173163
log_ctx: dict[str, Any] = {
174164
"job_role": req.job_role.value,
175165
"primary_category": req.selected_competency.value,
@@ -185,7 +175,7 @@ async def run(req: TaggingRequest, llm: LLMClient, model: str) -> TaggingRespons
185175
first_raw = _extract_text(first_msg.content)
186176

187177
try:
188-
candidates = _parse_and_validate(first_raw, allowed)
178+
candidates = _parse_and_validate(first_raw)
189179
except TaggingValidationError as first_err:
190180
logger.warning(
191181
"tagging.validation_failed",
@@ -204,7 +194,7 @@ async def run(req: TaggingRequest, llm: LLMClient, model: str) -> TaggingRespons
204194
)
205195
retry_raw = _extract_text(retry_msg.content)
206196
try:
207-
candidates = _parse_and_validate(retry_raw, allowed)
197+
candidates = _parse_and_validate(retry_raw)
208198
except TaggingValidationError as retry_err:
209199
logger.warning(
210200
"tagging.validation_failed",

experiments/tagging/2026-05-22_v2_postscore.md

Lines changed: 253 additions & 0 deletions
Large diffs are not rendered by default.

experiments/tagging/_template.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ _생성: `{{RAN_AT}}` · raw: [{{RAW_JSON_NAME}}](results/{{RAW_JSON_NAME}})_
1212
| --- | --- |
1313
| 모델 | `{{MODEL}}` |
1414
| variant | `{{VARIANT}}` |
15-
| 케이스 수 | {{N_CASES}} |
16-
| 평균 일치율 | **{{AVG_SCORE}}%** (exact {{EXACT}} · partial {{PARTIAL}} · none {{NONE}}) |
15+
| 케이스 수 | {{N_CASES}} (파싱 성공 {{N_OK}}) |
16+
| 평균 must 회수율 | **{{AVG_MUST_RECALL}}%** (must 전체 충족 {{N_FULL_MUST}}/{{N_OK}}) |
17+
| 평균 accept 회수율 | {{AVG_ACCEPT_RECALL}}% |
18+
| 평균 풀 외 비율 | {{AVG_OOF_RATE}}% |
1719
| 평균 응답시간 | {{AVG_LATENCY}}ms (corrective 포함 합산) |
1820
| 평균 입력 토큰 | {{AVG_INPUT}} (corrective 포함 합산) |
1921
| 평균 출력 토큰 | {{AVG_OUTPUT}} |
@@ -27,29 +29,38 @@ _생성: `{{RAN_AT}}` · raw: [{{RAW_JSON_NAME}}](results/{{RAW_JSON_NAME}})_
2729

2830
{{CASE_TABLE}}
2931

30-
(굵게: `expected ∩ actual` — 표면 매치)
32+
(actual 강조: **must 정답** · *accept 정답* · 일반=풀외. `⚠️` = 직군 모호 케이스)
3133

32-
### 직군별 평균 일치율
34+
### 직군별
3335

3436
{{ROLE_TABLE}}
3537

36-
### 카테고리별 평균 일치율
38+
### 카테고리별
3739

3840
{{CATEGORY_TABLE}}
3941

42+
### 길이별 (ST+A+R 합산 글자수 tertile)
43+
44+
{{LENGTH_TABLE}}
45+
46+
### 직군 모호 vs 비모호
47+
48+
{{AMBIGUOUS_TABLE}}
49+
4050
<!-- AUTO:END METRICS -->
4151

4252
## 요약
4353

44-
> TODO (1~2줄): 결과 핵심 정리. 평균 일치율 · corrective 발생 여부 · 이전 variant 대비 변화 .
54+
> TODO (1~2줄): must/accept 회수율 · 풀외율 · 직군 모호 영향 · 이전 variant 대비 변화 핵심 정리.
4555
4656
## 관찰
4757

48-
> TODO: 케이스별 raw response 보면서 인사이트 정리. 자주 유용한 항목:
49-
> - 어느 케이스가 expected 와 크게 달랐는지, 의미적으로는 합리적인지
58+
> TODO: 케이스별 raw response 보면서 인사이트 정리. 유용한 항목:
59+
> - must miss 가 발생한 케이스 패턴 (의미적으로는 가까운 accept 로 대체했는지, 완전히 빗나갔는지)
60+
> - 풀 외 태그가 나온 케이스 — 어떤 태그를 골랐고 왜 합리적인지/아닌지
61+
> - 직군 모호 케이스 (P-PA-04, DS-DA-04) 의 직군 가중치 처리 흐름
5062
> - corrective 발생 패턴 (코드블록 / 갯수 / 풀-외 / 기타)
51-
> - 직군 / 카테고리별 편차 해석
52-
> - 일치율 metric 의 표면적 한계 (semantic vs surface)
63+
> - 직군 / 카테고리 / 길이별 편차 해석
5364
5465
## 결정
5566

0 commit comments

Comments
 (0)