PPS Mono Repo · 기술 분석 리포트

eval-system-premium
AI 모델 분리 구조 분석

HWPX 문서 추출 파이프라인이 어떤 축으로 모델을 나누고, 환경별로 어떻게 라우팅하며, 최근 리팩토링으로 무엇이 바뀌었는지 정밀 분석.

branch: feat/unified-deploy HEAD: 5f15722 분석일: 2026-06-24 개인용 · noindex

TL;DR

01 모델 분리 축 (task별)

eval-system-premium의 HWPX 파이프라인은 7개 extractor를 돌리며, 작업 성격에 따라 LLM(텍스트)과 VLM(비전) 2축으로 모델을 나눈다. extractor는 LLMPort/VLMPort 프로토콜에만 의존해 구현 교체가 자유롭다.

작업purposepubliconprem (prod)
LLMidentity·features·items·patents·quality·warranty (텍스트 구조화)hwpx-extractOpenRouter qwen/qwen3-32bkakaocorp/kanana-2-30b
VLMphotos (제품 이미지 분석)hwpx-visionOpenRouter qwen3-vl-8bQwen/Qwen3-VL-8B

02 2가지 백엔드 모드 HWPX_LLM_BACKEND

hwpx가 모델을 부르는 경로가 2가지다. 통합 배포(public/staging/onprem)는 전부 ai-engine 게이트웨이 모드를 쓰고, 직접호출 모드는 hwpx 단독 배포용 레거시 경로다.

모드흐름모델 결정 주체용도
openai
(직접)
hwpx → OpenRouter/vLLM 직접hwpx config의 HWPX_LLM_MODELhwpx 독립 배포(ai-engine 없음)
ai-engine
(게이트웨이)
hwpx → POST /online/{llm,vlm}/* → model_selector → engineai-engine의 MODEL_CONFIG_*통합 배포 (public·onprem 둘 다)
현재 상태 통합 배포는 모두 ai-engine 모드. openai 직접호출은 standalone/레거시 경로로만 남음.

03 5f15722 — 모델 정체성 SSOT화

가장 중요한 최근 변화. 모델명을 hwpx에 하드코딩하던 것을, 루트 .envMODEL_CONFIG_* 블록에서 도출하도록 바꿨다.

이전:  HWPX_LLM_MODEL=qwen/qwen3-32b           # hwpx에 모델명 하드코딩
현재:  MODEL_CONFIG_hwpx_extract_MODEL_NAME=…  # 루트 .env SSOT에서 도출
       + 벤더 리더(model_config.py, stdlib만)
         └ hwpx 컨테이너가 ai-engine 전체 import 없이 MODEL_CONFIG 파싱
       + 무회귀: 구 HWPX_* 가 있으면 우선 (점진 마이그레이션)

해석 순서: HWPX_LLM_MODEL(레거시) → MODEL_CONFIG_hwpx_extract.model_name → 기본값. hwpx Dockerfile이 src/만 COPY하므로 ai-engine 코드를 못 끌어와, 순수 stdlib 최소 리더를 벤더링한 게 이 커밋의 핵심 구현.

04 환경별 매트릭스 (현재)

환경BACKEND실모델 공급검증
public (인터넷)ai-engineOpenRouter qwenenv example
staging (kailab 데모)ai-engineOpenRouter qwen3-32b실측 확인
onprem (청사 H100)ai-engine사내 vLLM kanana-2-30b / Qwen3-VL코드 SSOT

.env.public

  • PROVIDER=openrouter
  • TYPE=api
  • MODEL_NAME=qwen/qwen3-32b
  • URL=…openrouter.ai/api/v1

.env.onprem

  • PROVIDER=kakaocorp
  • TYPE=local
  • MODEL_NAME=kanana-2-30b-a3b
  • URL=pps-kanana-2-30b:8000
물리 제약 kailab 서버 GPU는 GTX 1660 Ti 6GB — 30b 모델 자체 호스팅 불가. 그래서 staging은 OpenRouter로 우회. kanana-2-30b 실가동은 청사 H100에서만 성립.

05 ai_engine model_selector — purpose 매핑

게이트웨이는 (purpose, kind)로 모델을 고른다. hwpx 외 purpose도 같은 레지스트리를 공유한다.

purposekind사용처
hwpx-extractllmpremium HWPX 텍스트 추출
hwpx-visionvisionpremium 제품 이미지 분석
rfp-structurellmRFP 구조화
rfp-image-analysisvisionRFP 이미지 분석
rfp-embedding-textllmRFP 임베딩용 요약 c50920a 리네임
rfp-gen-chatbotllmRFP 생성 챗봇 c50920a 리네임
rfp-gen-document-assistantllmRFP 문서 어시스턴트 c50920a 리네임
eval-analysisllm기술평가 교차분석
eval-chatbotllm평가 챗봇
semantic-searchembeddingRFP 벡터 검색 (KURE-v1)

06 폴백 3단

1 · model_selector

  • purpose 정확매치
  • → 없으면 같은 kind 첫 모델 (경고 로그)
  • → 없으면 ModelNotFoundError (404)

2 · engine factory (916360e)

  • provider=qwen/hyperclovax 전용 엔진
  • type=local 미등록 provider(kakaocorp)는 generic vLLM(QwenEngine)로 폴백
  • 실모델은 MODEL_NAME이 결정, provider는 라벨

3 · JSON 파싱 (llm.py)

  • <think> 제거 → 마크다운 펜스 제거 → 괄호짝맞춤 → 잘림보정 → 최종 {}
  • kanana 약한 JSON 모드 / Qwen3 thinking 누출 대비. identity extractor의 정규식 폴백과 연결

07 전체 호출 흐름

HWP/HWPX 업로드 (hwpx 컨테이너)
   │
   ▼  pipeline.runner → 7 extractors
   ├─ identity / features / items / patents / quality / warranty ─→ LLMPort.chat_json()
   └─ photos ─────────────────────────────────────────────────────→ VLMPort.describe()
   │
   ▼  env: HWPX_LLM_BACKEND
   ├──"openai"────→ LLMClient/VLMClient ──→ OpenRouter/vLLM 직접
   │
   └──"ai-engine"─→ AIEngineClient
                      POST /online/llm/complete  {purpose:"hwpx-extract", ...}
                      POST /online/vlm/describe   {purpose:"hwpx-vision",  ...}
                         │
                         ▼  services/ai_engine
                      select_model(purpose, kind)
                         ├─ 1단계: purpose 정확 매치
                         └─ 2단계: 같은 kind 첫 모델 (경고)
                         │
                         ▼  create_engine(model_config)  — provider 라우팅
                         ├─ qwen → QwenEngine / QwenVisionEngine
                         ├─ hyperclovax → HyperCLOVAXEngine
                         └─ type=local (kakaocorp 등) → generic vLLM 폴백
                         │
                         ▼  최종 백엔드
                      [dev/staging] OpenRouter qwen
                      [prod/onprem] 사내 vLLM (Kanana / Qwen3-VL)

08 리스크 & 권고

① provider 라벨 vs 실엔진 괴리 onprem에서 provider=kakaocorp인데 실제로는 QwenEngine(ChatML)로 처리. kanana가 Qwen ChatML 포맷과 호환된다는 암묵 가정에 의존 — tool_call/<think> 포맷이 다르면 파싱 깨질 수 있음.
② purpose 문자열 = 코드·env lockstep c50920a처럼 purpose 리네임 시 양쪽 동시 수정 필수(매핑이 키가 아닌 문자열값). 미스매치 시 kind 폴백으로 조용히 잘못된 모델이 선택될 위험(404가 아님).
③ 무회귀 레거시 우선의 양날HWPX_LLM_MODEL이 어딘가 남아있으면 새 MODEL_CONFIG를 덮어써 SSOT를 무력화. staging hwpx env에 해당 키가 안 보였던 점과 일관 — 정리 필요.

09 최근 커밋 요약

커밋변경
916360etype=local 미등록 provider도 generic vLLM 어댑터로 fallback — kakaocorp/kanana도 QwenEngine 라우팅 가능
c50920aPURPOSE 3건 rfp-* 네임스페이스 정합 (embedding-text→rfp-embedding-text, chatbot→rfp-gen-chatbot, document-assistant→rfp-gen-document-assistant)
5f15722모델 정체성을 MODEL_CONFIG_* 컨벤션에서 도출 — 벤더 리더 추가, config.from_env() 리팩토링, 레거시 무회귀