No description
Find a file
changukyu 6be263a69d feat: LLM 을 요약기에서 증거 판독기로 (구조화 인용 + 입력 경계 + 3B)
사용자 결정: LLM 역할을 "요약"에서 "증거 판독 + 구조화 판단"으로 확대,
타깃에 GPU 있음. 최종 판정은 여전히 사람이 한다.

## 입력 경계

프롬프트가 증거 data 딕셔너리를 통째로 f-string 에 박아 제출건당
13,733~62,291 자를 만들고 있었다. 화이트리스트 9개 필드 + 상위 12건으로
제한한다. promotion_reason 을 제외한 것이 핵심 — 모델이 그대로 베껴서
"요약"이 증거 목록 복붙이 됐다. 얼굴 크롭 좌표 / privacy_note /
thumbnail_url 도 모델 입력에서 뺀다(요약 입력이 아니다).

선별은 위험을 실제로 지는 행 우선: 지문 유사도 0.9 이상 -> full/partial/page
매칭과 promoted -> 얼굴 -> 나머지.

## 구조화 인용과 검증

format=json 으로 {claims:[{text, evidence}], needs_check:[]} 를 받고, 각
claim 이 인용한 라벨이 실제로 프롬프트에 들어간 라벨 집합의 부분집합인지
검사해 아니면 버린다. 화면에 렌더되는 텍스트는 검증을 통과한 claim 으로만
조립하므로 근거 없는 문장이 운영자에게 도달할 경로가 없다.

인용을 JSON 필드로 받는 것은 의도적이다. 산문을 문장 분해해 개체(URL, 숫자)를
대조하는 방식은 'confirming the image's authenticity [E3]' 같은 근거 없는
서술어를 100% 통과시켜, 오히려 환각 문장에 "근거 확인됨" 도장을 찍는다.
집합 포함 검사는 그 실패 모드가 구조적으로 발생하지 않는다.

증거 id 대신 [E1] 라벨을 쓴다 — 24자 해시를 소형 모델에게 받아쓰게 하면
오타와 조작을 구별할 수 없다.

grounded_claims / dropped_claims 를 증거에 기록한다. 모델이 지어내기
시작하면 수치로 드러난다.

## 모델과 컨텍스트

기본 모델 qwen2.5:0.5b-instruct -> qwen2.5:3b-instruct. 0.5B 는 출력 계약을
지키지 못했다(영어로 답하고 증거 목록을 반복했다).

num_ctx 를 명시한다. Ollama 기본 컨텍스트는 수천 토큰이고 넘으면 조용히
자르는데, source_urls / source_evidence_ids 는 잘리지 않은 전체 증거에서
뽑혀 붙었다. 즉 모델이 읽은 적 없는 근거를 근거라고 표시하고 있었다.
num_predict / keep_alive / timeout(30->120s) 도 환경변수로 노출.

## 시드 잠금 해제

_ensure_llm_summary 호출 2곳을 _write_lock 밖으로 옮긴다. record_decision 이
같은 락을 쓰므로, 폴더 재적재 중에는 운영자가 보고 있지도 않은 이미지의
요약이 끝날 때까지 판정 버튼이 대기했다. 시드 1회당 20건 상한을 두되 조용히
자르지 않고 로그를 남긴다(only-if-missing 이라 다음 재적재에서 이어진다).

이 이동은 provider 커버리지 회귀를 만들었다 —
_sync_submission_provider_state 가 잠금 구간 안에서, 즉 나레이션 전에 돌아
요약이 존재하는데도 llm 을 not_run 으로 보고했다. 나레이션이 실제로
발생했을 때만 커버리지를 다시 계산한다.

pytest 515 passed.
2026-07-25 23:34:02 +09:00
docs feat: 실사용 투입 전 T0 스탬프 4종 + 임베딩 정책 개정 2026-07-25 23:08:19 +09:00
src/rights_filter feat: LLM 을 요약기에서 증거 판독기로 (구조화 인용 + 입력 경계 + 3B) 2026-07-25 23:34:02 +09:00
tests feat: LLM 을 요약기에서 증거 판독기로 (구조화 인용 + 입력 경계 + 3B) 2026-07-25 23:34:02 +09:00
web/operator-gui feat: 실사용 투입 전 T0 스탬프 4종 + 임베딩 정책 개정 2026-07-25 23:08:19 +09:00
.env.example feat: LLM 을 요약기에서 증거 판독기로 (구조화 인용 + 입력 경계 + 3B) 2026-07-25 23:34:02 +09:00
.gitattributes chore: add .gitattributes to enforce LF line endings 2026-06-22 11:22:23 +09:00
.gitignore feat: 실사용 투입 전 T0 스탬프 4종 + 임베딩 정책 개정 2026-07-25 23:08:19 +09:00
package.json chore: initial commit of copyrighter (rights_filter) 2026-06-09 09:50:31 +09:00
README.md docs: 2026-07-24 review report, implementation checklist, spec revision 2026-07-24 14:18:27 +09:00
requirements-dev.txt test: add Playwright E2E harness for operator GUI with decision-flow spec 2026-06-21 21:10:22 +09:00
requirements.txt chore: pin runtime dependencies for offline air-gapped install 2026-06-20 18:19:08 +09:00
run_copyrighter_server.py chore: startup path anchoring, env parsing and gitignore 2026-07-11 16:07:49 +09:00

POSA Copyrighter

이미지 저작권 위험을 자동 분석하고, 운영자가 콘솔에서 근거를 확인해 승인 / 보류 / 반려를 판정하는 폐쇄망(air-gapped) 운영 도구입니다.

  • 분석 서버: 제출 이미지를 분석해 위험도·근거를 산출하고 SQLite에 저장
  • 운영자 콘솔: 심사 큐, 근거 검토, 판정, 지식 DB, 감사 로그를 제공하는 단일 페이지 웹 GUI
  • 외부 근거 검색: Naver 검색, Google Vision/Custom Search, 로컬 LLM(Ollama) 연동(선택)

요구 사항

  • Python 3.13
  • 런타임 서드파티 의존성은 이미지 처리 라이브러리뿐입니다(지연 로딩): numpy, opencv-python-headless, pillow

폐쇄망(air-gapped) 원칙: 모든 의존성·자산은 빌드/스테이징 머신에서 미리 받아 오프라인 휠로 설치합니다. 대상 서버는 설치·실행 시 인터넷에 접근하지 않습니다. 자세한 절차는 docs/operations/offline-install.md를 참고하세요.

설치

# 개발 머신 (온라인) — 의존성 설치
pip install -r requirements.txt          # 런타임
pip install -r requirements-dev.txt      # 테스트 포함

# 폐쇄망 대상 — 오프라인 휠로 설치 (예시)
pip install --no-index --find-links wheelhouse -r requirements.txt

.env.example를 복사해 .env를 만들고 필요한 값(인증 토큰, 검색 API 키 등)을 채웁니다. 키를 비워 두면 해당 외부 검색은 자동으로 비활성화됩니다.

cp .env.example .env

실행

python run_copyrighter_server.py
# 옵션은 런처에 그대로 전달됩니다
python run_copyrighter_server.py --host 127.0.0.1 --port 9500

python -m rights_filter.server 로 직접 실행하려면 src/ 가 import 경로에 있어야 합니다 (PYTHONPATH=src, Windows PowerShell 은 $env:PYTHONPATH="src"). 런처는 이 경로 설정과 작업 디렉터리 고정을 대신 해 주므로 run_copyrighter_server.py 사용을 권장합니다.

주요 옵션: --host, --port, --db(SQLite 경로), --images(제출 이미지 폴더), --static(운영자 GUI 경로), --env(.env 경로).

기본값으로 실행하면 http://127.0.0.1:9500 에서 운영자 콘솔이 열립니다.

보안: COPYRIGHTER_AUTH_TOKEN이 비어 있으면 데이터 라우트가 인증 없이 열립니다. 단일 호스트 개발 환경 외에는 반드시 토큰을 설정하세요.

프로젝트 구조

src/rights_filter/      분석·서버·통합·도메인 로직 (Python)
  server/               http.server 기반 API + 정적 파일 서빙, SQLite 저장소
  analysis/             이미지 분석·위험도 산출
  integrations/         Naver / Google / LLM 어댑터
web/operator-gui/       운영자 콘솔 (정적 SPA: index.html, app.js, styles.css)
tests/                  pytest 테스트
  operator_gui/         운영자 GUI 정적 검증 + Playwright E2E
docs/                   기획·설계·운영 문서

테스트

# 전체
python -m pytest

# 운영자 GUI E2E (Playwright)
playwright install chromium      # 빌드 머신에서 1회 — 브라우저 바이너리는 오프라인 번들에 포함
python -m pytest tests/operator_gui/
  • 브라우저(E2E) 테스트는 Playwright/Chromium이 없으면 자동으로 건너뜁니다(importorskip + graceful skip).
  • 운영자 워크벤치는 tests/operator_gui/pages/의 Page Object(OperatorWorkbench)와 conftest.py의 공용 픽스처를 사용합니다.

문서