fix: 평가환경(python 3.9)에서 임포트 실패 해결

계획서 p.24 가 No.3/4/7 의 평가환경을 python 3.9 로 못박았는데, 실제로는
성능지표 평가 스크립트 3종이 전부 3.9 에서 임포트조차 되지 않았다.
python:3.9-slim 컨테이너에서 재현·수정·재검증했다.

원인: config.py 와 schemas.py 가 `from __future__ import annotations` 없이
`str | None` (PEP 604) 을 썼다. 3.10 부터의 문법이라 클래스 본문이 실행되는
순간 인터프리터가 평가하며 TypeError 로 죽는다. 이 두 파일을 타고 detector /
extractor / clustering / taxonomy / jobs.store / routes / main 이 연쇄로 깨졌다.

  No.7 eval_rouge.py       → summarizer → core.config
  No.3 eval_metadata_f1.py → extractor  → api.schemas
  No.4 evaluate_pairs.py   → detector   → api.schemas

개발환경이 3.14 라 로컬에서는 전혀 드러나지 않았다. 정답셋을 받아 측정하는
시점에 발견했다면 일정이 밀렸을 문제다.

수정은 두 가지가 모두 필요하다. future 임포트만 넣으면 pydantic 이 문자열
'str | None' 을 해석하지 못하고("Unable to evaluate type annotation"),
eval_type_backport 만 넣으면 인터프리터가 먼저 죽는다.

검증: 3.9 에서 eval_rouge.py / eval_metadata_f1.py 가 정상 실행되고 수치가
3.14 와 동일하다(ROUGE-1 recall 0.5778). evaluate_pairs.py 와 app.main 임포트도
통과. 3.14 회귀 없음.

재발 방지로 tests/test_py39_compat.py 를 추가했다. Pydantic 모델을 정의하는
모듈에 future 임포트가 있는지 AST 로 검사하므로 3.14 에서도 회귀를 잡는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hbyang 2026-08-19 14:02:29 +09:00
parent 91aa077898
commit 28c9ad1ee7
5 changed files with 120 additions and 13 deletions

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from datetime import datetime from datetime import datetime
from typing import Literal from typing import Literal

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path

View File

@ -69,20 +69,37 @@
| 4 | 자체 제작 실제 표절 글 vs 비표절 글을 classification 하여 precision 계산 | **Ubuntu 22.04, python 3.9**, 자체 test script (GPU 불필요) | | 4 | 자체 제작 실제 표절 글 vs 비표절 글을 classification 하여 precision 계산 | **Ubuntu 22.04, python 3.9**, 자체 test script (GPU 불필요) |
| 7 | 요약 데이터셋 구축 후 n-gram ROUGE **recall** 계산 (수식 분모가 참조 n-gram 수). 다중 참조 전제 | **A100 GPU, python 3.9, pytorch 2.2.2+cu121, transformers 4.39.3** | | 7 | 요약 데이터셋 구축 후 n-gram ROUGE **recall** 계산 (수식 분모가 참조 n-gram 수). 다중 참조 전제 | **A100 GPU, python 3.9, pytorch 2.2.2+cu121, transformers 4.39.3** |
### ⚠️ python 3.9 호환성 위험 (확인 필요) ### python 3.9 호환성 — 확인 완료, 수정됨 (2026-08-19)
평가환경이 **python 3.9 고정**인데, 현재 코드 일부가 3.9에서 임포트 단계에 실패할 평가환경이 **python 3.9 고정**인데 실제로 **성능지표 평가 스크립트 3종이 전부
가능성이 있다. 임포트 불가**였다. Docker `python:3.9-slim` 에서 재현·수정·재검증했다.
- `app/core/config.py`, `app/api/schemas.py``from __future__ import annotations` **원인**: `app/core/config.py`, `app/api/schemas.py` 가 `from __future__ import
없이 `float | None` (PEP 604) 문법을 쓴다. annotations` 없이 `str | None` (PEP 604) 을 썼다. 3.10 부터의 문법이라 클래스
- 두 파일 모두 Pydantic 모델이라 어노테이션이 **런타임에 평가**된다. python 3.9에는 본문 실행 시점에 인터프리터가 평가하며 `TypeError` 로 죽는다.
`X | Y` 연산자가 없어 `TypeError` 가 난다.
- 현재 개발 환경은 python 3.14 라 이 문제가 드러나지 않는다.
**해야 할 일**: python 3.9 컨테이너에서 임포트 스모크 테스트를 돌려 실제로 깨지는지 **파급**: 이 두 파일을 타고 `detector` / `extractor` / `clustering` / `taxonomy` /
확인하고, 깨지면 `Optional[float]` 로 바꾸거나 평가 스크립트를 본체와 분리한다. `jobs.store` / `routes` / `main` 이 연쇄로 깨졌고, 결과적으로
No.4 측정 직전에 발견하면 일정이 밀린다.
| 지표 | 평가 스크립트 | 3.9 임포트 경로 |
|---|---|---|
| No.7 | `eval_rouge.py` | → `summarizer``core.config` ❌ |
| No.3 | `eval_metadata_f1.py` | → `extractor``api.schemas` ❌ |
| No.4 | `evaluate_pairs.py` | → `detector``api.schemas` ❌ |
개발환경이 3.14 라 로컬에서는 전혀 드러나지 않았다.
**수정**: 두 파일에 `from __future__ import annotations` 추가 + `requirements.txt`
`eval_type_backport>=0.2; python_version < "3.10"` 추가. 둘 다 필요하다 —
future 임포트만 넣으면 pydantic 이 문자열 `'str | None'` 을 해석하지 못하고,
백포트만 넣으면 인터프리터가 먼저 죽는다.
**검증**: 3.9 컨테이너에서 `eval_rouge.py` / `eval_metadata_f1.py` 가 정상 실행되고
수치가 3.14 와 동일함을 확인했다(ROUGE-1 recall 0.5778). `evaluate_pairs.py`
`app.main` 임포트도 통과. 3.14 회귀 없음(153 passed).
**재발 방지**: `tests/test_py39_compat.py` — Pydantic 모델을 정의하는 모듈에
future 임포트가 있는지 AST 로 검사한다. 3.14 에서도 회귀를 잡는다.
## 5. AI 생성 의심도의 위상 — 지표가 아니다 ## 5. AI 생성 의심도의 위상 — 지표가 아니다
@ -100,7 +117,7 @@ No.4 측정 직전에 발견하면 일정이 밀린다.
1. **사용자 맞춤형 요약 옵션** — 상세도, 강조 내용 (계획서 명시, 현재 없음) 1. **사용자 맞춤형 요약 옵션** — 상세도, 강조 내용 (계획서 명시, 현재 없음)
2. **Human Feedback Preference Optimization** — 현재 골격만. 선호 라벨 + GPU 필요 2. **Human Feedback Preference Optimization** — 현재 골격만. 선호 라벨 + GPU 필요
3. **도메인별 요약 데이터셋 구축** — 컴북스 데이터 수령 후 3. **도메인별 요약 데이터셋 구축** — 컴북스 데이터 수령 후
4. **python 3.9 호환성 정리** — 평가환경 대비 4. ~~python 3.9 호환성 정리~~**완료** (2026-08-19)
### 측정해야 하는 것 (전부 데이터 대기) ### 측정해야 하는 것 (전부 데이터 대기)
1. **No.4 표절 정밀도 97%** — 자체 제작 표절/비표절 글 필요. 정밀도 지표이므로 1. **No.4 표절 정밀도 97%** — 자체 제작 표절/비표절 글 필요. 정밀도 지표이므로
@ -114,5 +131,4 @@ No.4 측정 직전에 발견하면 일정이 밀린다.
### 데이터와 무관하게 지금 가능한 것 ### 데이터와 무관하게 지금 가능한 것
- SW 저작권 등록 1건 (서류 제출) - SW 저작권 등록 1건 (서류 제출)
- 사용자 맞춤형 요약 옵션 개발 - 사용자 맞춤형 요약 옵션 개발
- python 3.9 호환성 확인
- 바이칼/컴북스 E2E 통합 - 바이칼/컴북스 E2E 통합

View File

@ -14,3 +14,6 @@ joblib>=1.4
scipy>=1.13 scipy>=1.13
pypdf>=5.0 pypdf>=5.0
python-docx>=1.1 python-docx>=1.1
# 공인인증 평가환경이 python 3.9 고정이라(계획서 p.24) PEP 604 어노테이션을
# pydantic 이 해석하려면 이 백포트가 필요하다. 3.10+ 에서는 설치되지 않는다.
eval_type_backport>=0.2; python_version < "3.10"

84
tests/test_py39_compat.py Normal file
View File

@ -0,0 +1,84 @@
"""공인인증 평가환경(python 3.9) 호환성 가드.
필요한가:
계획서 p.24 No.3/4/7 평가환경을 **python 3.9** 못박았다. 그런데
`str | None` (PEP 604) 3.10 부터의 문법이라, `from __future__ import
annotations` 없이 쓰면 클래스 본문이 실행되는 순간 인터프리터가 평가해
TypeError 죽는다. Pydantic 모델은 어노테이션이 런타임에 해석되므로
특히 위험하다.
개발 환경이 3.14 문제는 로컬에서 절대 드러나지 않는다. 실제로
`app/core/config.py` `app/api/schemas.py` 상태였고, 파일을
타고 detector·routes·main **성능지표 평가 스크립트 3종이 전부** 3.9 에서
임포트 불가였다(2026-08-19 확인·수정).
테스트는 3.14 에서도 회귀를 잡는다. 문법이 아니라 소스에 선언이
있는지를 본다.
"""
from __future__ import annotations
import ast
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
PYDANTIC_BASES = {"BaseModel", "BaseSettings"}
def _has_future_annotations(tree: ast.Module) -> bool:
for node in tree.body:
if isinstance(node, ast.ImportFrom) and node.module == "__future__":
if any(alias.name == "annotations" for alias in node.names):
return True
return False
def _defines_pydantic_model(tree: ast.Module) -> bool:
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
for base in node.bases:
name = base.id if isinstance(base, ast.Name) else getattr(base, "attr", None)
if name in PYDANTIC_BASES:
return True
return False
def _pydantic_modules() -> list[Path]:
found = []
for path in sorted((ROOT / "app").rglob("*.py")):
tree = ast.parse(path.read_text(encoding="utf-8"))
if _defines_pydantic_model(tree):
found.append(path)
return found
def test_pydantic_modules_exist():
"""탐지 자체가 망가지면 아래 테스트가 조용히 무력화되므로 먼저 확인한다."""
assert _pydantic_modules(), "Pydantic 모델 모듈을 하나도 못 찾았습니다."
@pytest.mark.parametrize(
"path", _pydantic_modules(), ids=lambda p: str(p.relative_to(ROOT))
)
def test_pydantic_module_defers_annotations(path: Path):
"""Pydantic 모델을 정의하는 모듈은 어노테이션 평가를 미뤄야 한다."""
tree = ast.parse(path.read_text(encoding="utf-8"))
assert _has_future_annotations(tree), (
f"{path.relative_to(ROOT)} 에 `from __future__ import annotations` 가 "
"없습니다. PEP 604(`X | None`)를 쓰면 평가환경(python 3.9)에서 임포트가 "
"실패합니다."
)
def test_eval_type_backport_pinned_for_py39():
"""3.9 에서는 __future__ 만으로 부족하고 pydantic 이 백포트를 요구한다."""
req = (ROOT / "requirements.txt").read_text(encoding="utf-8")
assert "eval_type_backport" in req, (
"requirements.txt 에 eval_type_backport 가 없습니다. python 3.9 에서 "
"pydantic 이 문자열 어노테이션 'str | None' 을 해석하지 못합니다."
)
assert 'python_version < "3.10"' in req, (
"eval_type_backport 는 3.9 전용입니다. 환경 마커를 붙이세요."
)