PPS Mono Repo · 기술 분석 리포트
HWPX 문서 추출 파이프라인이 어떤 축으로 모델을 나누고, 환경별로 어떻게 라우팅하며, 최근 리팩토링으로 무엇이 바뀌었는지 정밀 분석.
MODEL_CONFIG_* SSOT 도출(5f15722), purpose rfp-* 네임스페이스 정합(c50920a), type=local generic vLLM 폴백(916360e).purpose(hwpx-extract / hwpx-vision)만 넘기면 ai-engine이 환경별 실모델로 라우팅.qwen3-32b 경유 / prod(청사 H100)는 사내 vLLM kanana-2-30b.eval-system-premium의 HWPX 파이프라인은 7개 extractor를 돌리며, 작업 성격에 따라 LLM(텍스트)과 VLM(비전) 2축으로 모델을 나눈다. extractor는 LLMPort/VLMPort 프로토콜에만 의존해 구현 교체가 자유롭다.
| 축 | 작업 | purpose | public | onprem (prod) |
|---|---|---|---|---|
| LLM | identity·features·items·patents·quality·warranty (텍스트 구조화) | hwpx-extract | OpenRouter qwen/qwen3-32b | kakaocorp/kanana-2-30b |
| VLM | photos (제품 이미지 분석) | hwpx-vision | OpenRouter qwen3-vl-8b | Qwen/Qwen3-VL-8B |
HWPX_LLM_BACKENDhwpx가 모델을 부르는 경로가 2가지다. 통합 배포(public/staging/onprem)는 전부 ai-engine 게이트웨이 모드를 쓰고, 직접호출 모드는 hwpx 단독 배포용 레거시 경로다.
| 모드 | 흐름 | 모델 결정 주체 | 용도 |
|---|---|---|---|
openai(직접) | hwpx → OpenRouter/vLLM 직접 | hwpx config의 HWPX_LLM_MODEL | hwpx 독립 배포(ai-engine 없음) |
ai-engine(게이트웨이) | hwpx → POST /online/{llm,vlm}/* → model_selector → engine | ai-engine의 MODEL_CONFIG_* | 통합 배포 (public·onprem 둘 다) |
ai-engine 모드. openai 직접호출은 standalone/레거시 경로로만 남음.
가장 중요한 최근 변화. 모델명을 hwpx에 하드코딩하던 것을, 루트 .env의 MODEL_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 최소 리더를 벤더링한 게 이 커밋의 핵심 구현.
| 환경 | BACKEND | 실모델 공급 | 검증 |
|---|---|---|---|
| public (인터넷) | ai-engine | OpenRouter qwen | env example |
| staging (kailab 데모) | ai-engine | OpenRouter qwen3-32b | 실측 확인 |
| onprem (청사 H100) | ai-engine | 사내 vLLM kanana-2-30b / Qwen3-VL | 코드 SSOT |
PROVIDER=openrouterTYPE=apiMODEL_NAME=qwen/qwen3-32bURL=…openrouter.ai/api/v1PROVIDER=kakaocorpTYPE=localMODEL_NAME=kanana-2-30b-a3bURL=pps-kanana-2-30b:8000게이트웨이는 (purpose, kind)로 모델을 고른다. hwpx 외 purpose도 같은 레지스트리를 공유한다.
| purpose | kind | 사용처 |
|---|---|---|
hwpx-extract | llm | premium HWPX 텍스트 추출 |
hwpx-vision | vision | premium 제품 이미지 분석 |
rfp-structure | llm | RFP 구조화 |
rfp-image-analysis | vision | RFP 이미지 분석 |
rfp-embedding-text | llm | RFP 임베딩용 요약 c50920a 리네임 |
rfp-gen-chatbot | llm | RFP 생성 챗봇 c50920a 리네임 |
rfp-gen-document-assistant | llm | RFP 문서 어시스턴트 c50920a 리네임 |
eval-analysis | llm | 기술평가 교차분석 |
eval-chatbot | llm | 평가 챗봇 |
semantic-search | embedding | RFP 벡터 검색 (KURE-v1) |
type=local 미등록 provider(kakaocorp)는 generic vLLM(QwenEngine)로 폴백<think> 제거 → 마크다운 펜스 제거 → 괄호짝맞춤 → 잘림보정 → 최종 {}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)
provider=kakaocorp인데 실제로는 QwenEngine(ChatML)로 처리. kanana가 Qwen ChatML 포맷과 호환된다는 암묵 가정에 의존 — tool_call/<think> 포맷이 다르면 파싱 깨질 수 있음.
HWPX_LLM_MODEL이 어딘가 남아있으면 새 MODEL_CONFIG를 덮어써 SSOT를 무력화. staging hwpx env에 해당 키가 안 보였던 점과 일관 — 정리 필요.
| 커밋 | 변경 |
|---|---|
916360e | type=local 미등록 provider도 generic vLLM 어댑터로 fallback — kakaocorp/kanana도 QwenEngine 라우팅 가능 |
c50920a | PURPOSE 3건 rfp-* 네임스페이스 정합 (embedding-text→rfp-embedding-text, chatbot→rfp-gen-chatbot, document-assistant→rfp-gen-document-assistant) |
5f15722 | 모델 정체성을 MODEL_CONFIG_* 컨벤션에서 도출 — 벤더 리더 추가, config.from_env() 리팩토링, 레거시 무회귀 |