최종 통합 설계 및 회고 - 100-hours-a-week/KTB4-1st-wiki GitHub Wiki
%%{init: {'flowchart': {'nodeSpacing': 80, 'rankSpacing': 110, 'curve': 'monotoneY', 'diagramPadding': 30}}}%%
graph TD
subgraph Internal["AI 서버 (직접 운영)"]
subgraph GroupA["등록 파이프라인"]
F1["기능 1<br/>이미지 검열·식별·초안생성"]
F2["기능 2<br/>텍스트 검열"]
F3["기능 3<br/>시세조사·가치산정"]
REJECT["반려 처리"]
DONE["등록 완료"]
F1 --> F2
F2 -->|검열 통과| F3
F2 -->|검열 실패| REJECT
F3 --> DONE
end
subgraph GroupB["독립 기능"]
F4["기능 4<br/>정책•네비게이션 챗봇"]
F5["기능 5<br/>스마트 검색"]
F6["기능 6<br/>채팅다듬기"]
end
EMBED["임베딩 모델<br/>(dragonkue/multilingual-e5-small-ko-v2)"]
subgraph Infra["인프라"]
VECTOR[("벡터스토어(ChromaDB)<br/>정책문서 인덱스, 인메모리 기본")]
end
end
CLAUDE["Claude API<br/>(외부 서비스)"]
DONE ~~~ CLAUDE
F1 & F2 & F3 & F4 & F5 & F6 --> CLAUDE
F4 --> EMBED
F4 --> VECTOR
classDef claudeapi fill:#e6f1fb,stroke:#185fa5,color:#0c447c;
classDef embed fill:#e1f5ee,stroke:#0f6e56,color:#085041;
classDef success fill:#eaf3de,stroke:#3b6d11,color:#27500a;
classDef fail fill:#fcebeb,stroke:#a32d2d,color:#791f1f;
classDef infra fill:#faeeda,stroke:#854f0b,color:#412402;
classDef subgraphStyle fill:transparent,stroke:#8c8c8c,stroke-width:1px,color:#cccccc;
class F1,F2,F3,F5,F6,CLAUDE claudeapi;
class F4,EMBED embed;
class DONE success;
class REJECT fail;
class VECTOR infra;
class GroupA,GroupB,Infra,Internal subgraphStyle;
%%{init: {'flowchart': {'nodeSpacing': 80, 'rankSpacing': 110, 'curve': 'monotoneY', 'diagramPadding': 30}}}%%
graph TD
subgraph Internal["AI 서버 (직접 운영)"]
subgraph GroupA["등록 파이프라인 (POST /api/registration/process)"]
F1["기능 1<br/>이미지 검열·식별·초안생성"]
ORCH["등록 오케스트레이터<br/>(기능2→기능3 체이닝)"]
F2["기능 2<br/>텍스트 검열<br/>(규칙 기반 필터링→LLM 2단 캐스케이드)"]
F3["기능 3<br/>시세조사·가치산정"]
REJECT["반려 처리"]
DONE["등록 완료"]
F1 --> ORCH
ORCH --> F2
F2 -->|검열 통과| F3
F2 -->|검열 실패| REJECT
F3 --> DONE
end
subgraph GroupB["독립 기능"]
F4["기능 4<br/>정책•네비게이션 챗봇"]
F5["기능 5<br/>스마트 검색<br/>(요청 시 즉석 임베딩+코사인유사도)"]
F6["기능 6<br/>채팅다듬기"]
end
subgraph Infra["인프라"]
VECTOR[("벡터스토어(ChromaDB)<br/>정책문서 인덱스, 인스턴스별 인메모리")]
CACHE[("가격 캐시<br/>(Backend DB)")]
end
EMBED["임베딩 모델<br/>(dragonkue/multilingual-e5-small-ko-v2)"]
end
CLAUDE["Claude API<br/>(외부 서비스)"]
MODAL["Modal 서버리스 + vLLM<br/>(Qwen2.5-7B-Instruct, AWQ INT4,<br/>prefix caching)<br/>(외부 서비스)"]
DONE ~~~ CLAUDE
DONE ~~~ MODAL
F1 & F2 & F3 & F4 --> CLAUDE
F3 -.->|조회/저장| CACHE
F4 & F5 --> EMBED
F4 --> VECTOR
F6 --> MODAL
classDef claudeapi fill:#e6f1fb,stroke:#185fa5,color:#0c447c;
classDef embed fill:#e1f5ee,stroke:#0f6e56,color:#085041;
classDef selfhost fill:#eeedfe,stroke:#534ab7,color:#3c3489;
classDef success fill:#eaf3de,stroke:#3b6d11,color:#27500a;
classDef fail fill:#fcebeb,stroke:#a32d2d,color:#791f1f;
classDef infra fill:#faeeda,stroke:#854f0b,color:#412402;
classDef orch fill:#fdf6e3,stroke:#a67c00,color:#5c4400;
classDef subgraphStyle fill:transparent,stroke:#8c8c8c,stroke-width:1px,color:#cccccc;
class F1,F2,F3,F4,CLAUDE claudeapi;
class F5,EMBED embed;
class F6,MODAL selfhost;
class DONE success;
class REJECT fail;
class VECTOR,CACHE infra;
class ORCH orch;
class GroupA,GroupB,Infra,Internal subgraphStyle;
범례: 파랑 = Claude API 사용 / 청록 = 임베딩 모델 사용 / 보라 = Modal 서버리스+vLLM 사용 / 주황 = 인프라(벡터스토어·캐시) / 노랑 = 등록 오케스트레이터 / 초록·빨강 = 등록 파이프라인 종료 상태 / 테두리 안(Internal) = 직접 운영하는 AI 서버 구성요소, 테두리 밖 = 우리가 관리하지 않고 API로 호출만 하는 외부 서비스
| 기능 | v1 (초안) | v2 (고도화) | 변경 이유 |
|---|---|---|---|
| 1. 이미지처리 | Claude API 멀티모달 단일호출 | 동일 (변경 없음) | 이미 적절해 고도화 대상 아님 |
| 2. 텍스트검열 | Claude API 단일호출로 전체 검열 | 규칙 기반 필터링(정규식+금칙어 템플릿: 연락처·스팸·욕설혐오·금지품목) → Claude API(사기성 문구·물건혼재·가격언급) 2단 캐스케이드, early exit | 명백한 위반은 앞 단계에서 즉시 종료되어 LLM 호출 자체가 생략됨 |
| 3. 가격검색 | 매 요청 web_search+Claude API 호출 | Backend DB의 캐시 우선조회 → 미스 시에만 web_search+Claude API 호출 | 반복 검색 비용·지연 절감 |
| 4. 정책•네비게이션챗봇 | 임베딩 RAG검색 + Claude API 액션분기 | 동일 (변경 없음) | 애초에 임베딩 기반 구조라 고도화 대상 아님 |
| 5. 스마트검색 | Claude API가 후보 전체를 직접 랭킹 | 검색 요청 시 후보(Backend가 넘긴 title·description)와 취향 텍스트를 그 자리에서 임베딩해 코사인 유사도로 랭킹 | 후보 수 증가 시 비용·속도 개선 |
| 6. 채팅다듬기 | Claude API 직접 호출 | Qwen2.5-7B-Instruct를 Modal 서버리스+vLLM으로 서빙, AWQ INT4 양자화, prefix caching 적용 | API 비용 절감, 간헐적 트래픽에 맞는 종량 과금, 서빙 효율화 |
-
등록 오케스트레이터:
POST /api/registration/process가 등록 파이프라인의 주 진입점. 사용자가 "등록하기"를 누르면 기능2(텍스트 검열)를 먼저 실행하고, Backend에 별도 요청이나 확인 신호를 보내지 않은 채 검열 통과 시 바로 기능3(가격검색)을 이어서 실행한다. 6개 독립 기능 모듈은 서로 호출하지 않는다는 원칙을 유지하기 위해, 이 체이닝은 기능2·기능3 모듈 코드 내부가 아니라 별도의 오케스트레이터 모듈에 둔다. 기존moderation·price의 개별 엔드포인트(router.py)는 이번 버전에서 유지하되, 오케스트레이션 방식이 팀 논의를 거쳐 최종 확정되면 존치 여부를 재검토한다(아래 폴더구조 주석 참고). -
기능 1 (이미지 검열·식별·초안생성): 이미지 1~3장을 받아 하나의 멀티모달 호출(Claude API)로 처리. ① 동일 물건 여부, ② 물건 유무, ③ 거래금지 품목 여부를 판별하고, 통과 시 제목(간결)·상세설명(풍부)을 생성. confidence가 낮아도 명확한 거부 사유(금지품목/물건혼합)가 아니면 일단 초안을 생성해 사용자 확인 단계로 넘김.
-
기능 2 (텍스트 검열, 2단 캐스케이드): 사용자가 확인·수정한 최종 제목·내용을 검열.
- 1단계(규칙 기반 필터링, 정규식+금칙어 템플릿): 연락처·스팸·욕설혐오·금지품목 패턴 매칭 → 걸리면 즉시 거부, LLM 호출 없음
- 2단계(Claude API, haiku): 앞 단계에서 걸러지지 않은 텍스트만 대상으로 사기성 문구·물건 혼재·가격언급 여부 판단
- Early exit 구조라 명백한 위반은 뒷 단계 호출이 생략되지만, 정상 텍스트는 2단계까지 모두 거치므로 이득은 위반 케이스 비율만큼만 발생한다는 한계가 있음
- 2단계를 자체 서빙이 아닌 Claude API로 유지하는 이유: (1) 1단계가 대부분의 트래픽을 흡수해 호출량 자체가 적음, (2) 사기 문구 분류기 학습용 라벨 데이터셋이 아직 없음, (3) 사기 문구·가격언급 등 우회 표현은 계속 적대적으로 변형되어 범용 추론이 가능한 큰 모델이 novel 패턴에 더 강함. 호출량 증가·라벨 데이터 확보 시 자체 분류기 전환을 재검토 예정
-
기능 3 (시세조사·가치산정): 등록 오케스트레이터에 의해 검열 통과 시에만 실행. Backend DB의 캐시를 먼저 조회(물건명 정규화 키 기준, TTL 예: 24시간)하고, 히트 시 web_search·LLM 호출 없이 즉시 응답. 미스 시에만 기존과 동일하게 web_search+Claude API로 기준 가치를 산출하고, 그 결과를 Backend가 캐시에 저장한다. 이후 사용자가 등록 시 입력한 선호도 가중치(0~1, 빠른 거래 희망도/낮은 가치 수용도)를 별도 계산 로직(비-LLM)으로 반영해 하한가를 산출. 두 값 모두 저장하되 사용자에게 직접 노출하지 않음(추후 가치 비교 기능에서 활용).
-
반려 처리 / 등록 완료: 기능 2의 검열 결과에 따른 파이프라인 종료 상태.
-
기능 4 (정책•네비게이션 챗봇): 사용자 메시지를
dragonkue/multilingual-e5-small-ko-v2임베딩 모델로 벡터화해, ChromaDB(인스턴스별 인메모리, 문서량이 늘면PersistentClient로 디스크 영속화 전환 가능)에 저장된 정책 문서 12개 섹션(전체 13개 중 내부 팀 메모 1개는 사용자 노출용이 아니므로 임베딩 대상에서 제외)에서 상위 2개를 검색(RAG)하고, 검색 결과를 바탕으로 Claude API가 구조화된 출력으로 "정책답변" 또는 "화면이동" 액션을 스스로 판단. -
기능 5 (스마트 검색): Backend가 가격범위로 필터링해 넘긴 후보 물건 목록(제목+설명)과, 사용자의 취향 텍스트(최근 검색어·과거 요청 이력·거래완료 이력·찜 목록·자연어 입력)를 검색 요청이 올 때마다 그 자리에서 함께 임베딩하고, 코사인 유사도로 순위를 산출. 후보가 최대 50개로 제한돼 있고 경량 모델(CPU 추론)이라 이 방식으로도 충분히 빠름. 임베딩 모델은 기능4와 동일한
dragonkue/multilingual-e5-small-ko-v2를 재사용. v1에서 쓰던 Claude API 직접 랭킹 프롬프트는 삭제하지 않고prompts.py에 남겨두되, v2 런타임에서는 호출하지 않는다(아래 폴더 구조 참고). -
기능 6 (채팅다듬기): 사용자가 입력한 메시지의 의미는 유지한 채, 나와 상대방의 프로필 데이터(대화/거래 스타일)를 참고해 표현만 변환. Qwen2.5-7B-Instruct 모델을 Modal 서버리스 위에서 vLLM으로 서빙하며, AWQ INT4 양자화로 가중치를 압축하고, vLLM 내장 prefix caching으로 반복되는 시스템 프롬프트 부분의 KV 캐시를 재사용. 트래픽이 간헐적인 팀 프로젝트 특성상 상시가동 비용 대신 사용한 만큼만 과금되는 Modal을 채택.
ai-server/
├── features/ # 기능별 독립 모듈
│ ├── registration/ # 등록 파이프라인 오케스트레이터
│ │ ├── __init__.py
│ │ ├── router.py # POST /api/registration/process
│ │ ├── service.py # moderation·price의 service 함수를 순서대로 호출
│ │ └── schemas.py # 요청/응답 스키마
│ ├── vision/ # 기능1: 사진 등록
│ │ ├── __init__.py
│ │ ├── router.py # 엔드포인트 정의
│ │ ├── service.py # 처리 로직
│ │ ├── schemas.py # 요청/응답 스키마
│ │ ├── prompts.py # 시스템 프롬프트
│ │ └── exceptions.py # 도메인 에러 (prohibited_item, unrelated_photo, multiple_items_mixed)
│ ├── moderation/ # 기능2: 텍스트검열 (2단 캐스케이드)
│ │ ├── __init__.py
│ │ ├── router.py # 엔드포인트 정의 (오케스트레이션 최종 확정 시 존치 여부 재검토 — 현재는 유지)
│ │ ├── service.py # 2단계 오케스트레이션 (early exit)
│ │ ├── schemas.py # 요청/응답 스키마
│ │ ├── prompts.py # 2단계: LLM 사기·물건혼재·가격언급 판별 프롬프트
│ │ ├── rule_filter.py # 1단계: 정규식+금칙어 템플릿 (연락처·스팸·욕설혐오·금지품목)
│ │ └── exceptions.py # 도메인 에러 7종 (offensive_language, prohibited_item, fraud_language, personal_contact, price_mention, spam_content, multiple_items_mixed)
│ ├── price/ # 기능3: 가격검색
│ │ ├── __init__.py
│ │ ├── router.py # 엔드포인트 정의 (오케스트레이션 최종 확정 시 존치 여부 재검토 — 현재는 유지)
│ │ ├── service.py # 캐시 조회 → 미스 시 web_search+LLM 호출 → 선호도 가중치 계산 → 응답 구성
│ │ ├── schemas.py # 요청/응답 스키마
│ │ └── prompts.py # 시세조사 프롬프트
│ ├── chatbot/ # 기능4: 정책RAG•네비게이션 챗봇
│ │ ├── __init__.py
│ │ ├── router.py # 엔드포인트 정의
│ │ ├── service.py # 검색→답변생성 오케스트레이션
│ │ ├── schemas.py # 요청/응답 스키마
│ │ ├── prompts.py # 정책답변/화면이동 액션분기 프롬프트
│ │ └── retriever.py # ChromaDB 컬렉션 검색 (dragonkue/multilingual-e5-small-ko-v2 임베딩)
│ ├── search/ # 기능5: 스마트검색
│ │ ├── __init__.py
│ │ ├── router.py # 엔드포인트 정의
│ │ ├── service.py # 후보·취향 텍스트 즉석 임베딩 + 코사인 유사도 랭킹 (v2 런타임 경로)
│ │ ├── schemas.py # 요청/응답 스키마
│ │ └── prompts.py # v1 Claude API 직접 랭킹 프롬프트 — v2에서는 service.py가 호출하지 않음, 롤백 대비 보관용으로 삭제하지 않고 유지
│ └── message_refine/ # 기능6: 채팅다듬기
│ ├── __init__.py
│ ├── router.py # 엔드포인트 정의
│ ├── service.py # Modal(vLLM) 호출
│ ├── schemas.py # 요청/응답 스키마
│ └── prompts.py # 채팅다듬기 시스템 프롬프트
│
├── shared/ # 2개 이상 기능이 실제로 겹쳐 쓰는 것만
│ ├── llm/
│ │ ├── base_provider.py # 공통 인터페이스 정의
│ │ ├── claude_provider.py # Claude API 호출 (기능1,2 2단계,3,4)
│ │ └── modal_provider.py # Modal 서버리스+vLLM 호출 (기능6, AWQ·prefix caching 설정 포함)
│ ├── embedding/
│ │ └── embedding_provider.py # dragonkue/multilingual-e5-small-ko-v2 (기능4 RAG검색, 기능5 유사도 공용)
│ └── errors/
│ ├── internal_error.py # Claude/Modal 호출 실패(요청한도·지연·파싱실패) 공통 예외 → internal_error
│ └── formatter.py # 모든 예외 → {"error":..., "message":...} 표준 JSON 변환
│
├── infra/ # 저장소성 인프라
│ └── vector_store/
│ └── policy_docs_index/ # 기능4 RAG용 ChromaDB 컬렉션 (인스턴스별 인메모리, 필요 시 PersistentClient로 디스크 영속화)
│ # (기능3 가격캐시는 Backend DB 관리 대상이라 AI 서버 범위 밖.
│ # 기능5는 요청 시 즉석 임베딩 방식이라 별도 저장소 불필요)
│
├── core/
│ └── config.py # 환경설정
│
└── main.py # 각 기능의 router.py를 앱에 등록
폴더 구성 규칙: 모든 기능은 router.py(엔드포인트) + service.py(오케스트레이션) + schemas.py(요청/응답 계약)를 기본으로 갖는다. prompts.py는 LLM을 호출하는 기능에 추가된다. exceptions.py는 도메인 에러(422 등)가 2개 이상인 기능(vision, moderation)에만 추가하고, 그 외 개별 파일(rule_filter.py, retriever.py)은 해당 기능이 실제로 감싸는 외부 도구·모델 수만큼만 추가한다. Claude/Modal 호출 실패(429·timeout·파싱실패)는 기능별로 따로 정의하지 않고 shared/errors/internal_error.py의 공통 예외 하나로 통일해 던진다. registration 모듈은 예외적으로 moderation·price 모듈의 service 함수를 호출한다 — 이는 등록 파이프라인 오케스트레이션이라는 명시적 목적을 위한 것이며, 6개 독립 기능 모듈 간에는 여전히 상호 호출이 없다. search/prompts.py는 단계3 설계 당시엔 완전 임베딩 전환으로 불필요하다고 판단했으나, 롤백 리스크에 대비해 이번 최종 버전에서 보관용으로 되살렸다(service.py는 호출하지 않음). 이처럼 현재 버전의 service.py가 참조하지 않는 파일이 남아있는 경우, 삭제 대신 보관하는 이유를 파일 옆 주석으로 명시한다.
AI 서버(FastAPI)와 ChromaDB는 하나의 서버 안에 함께 둬서 별도 통신 없이 바로 사용한다. Claude API와 Modal(기능6 v2)은 우리가 직접 운영하지 않는 외부 서비스라서 API로 호출만 하는 형태로 연동한다. 프로젝트 규모와 예산(36만원/3달)을 고려해 서버는 여러 대로 늘리지 않고 1대로 운영하며, 트래픽이 늘어나 병목이 생긴다면 서버 인프라보다는 Claude API 호출 횟수 자체를 줄이는 방식(캐스케이드, 캐싱)으로 먼저 대응한다. 모니터링은 응답 속도, 에러 발생률, Claude API 호출 횟수와 비용, 캐시 적중률, ChromaDB 검색 속도, Modal 콜드스타트, 서버 CPU·메모리 사용률을 지표로 삼고, 각각 클라우드 기본 모니터링 도구, Anthropic 콘솔, 자체 로그, Modal 대시보드 등으로 확인한다. 부하 테스트는 아직 API 키가 없어 계획만 세워둔 상태로, 이후 실제 테스트를 통해 서버 1대가 감당할 수 있는 트래픽 한계를 확인하고, 부족하면 그때 서버를 여러 대로 늘리는 방안을 검토한다.
| 기능 | v1 (초기 설계) | v2 (고도화) | 주요 변경사항 | 기대 효과 |
|---|---|---|---|---|
| 1. 이미지처리 | Claude API 멀티모달 단일호출 | 동일 (변경 없음) | 없음 | 이미 단일호출로 검열·식별·초안생성을 통합 처리하고 있어 추가 고도화 실익이 낮다고 판단, 우선순위에서 제외 |
| 2. 텍스트검열 | Claude API 단일호출로 전체 검열 | 규칙 기반 필터링→Claude API 2단 캐스케이드 | 연락처·스팸·욕설혐오·금지품목을 1단계(정규식+금칙어 템플릿)에서 선제 차단, 나머지만 2단계 LLM으로 사기성 문구·물건혼재·가격언급 판단 | 명백한 위반은 LLM 호출 없이 즉시 처리 → 평균 응답속도·비용 개선 |
| 3. 가격검색 | 매 요청 web_search+Claude API 호출 | Backend DB 캐시 우선조회 후 미스 시에만 호출 | 물건명 정규화 키+TTL 기반 캐싱 도입, 캐시는 Backend가 관리(AI 서버 무상태 유지) | 반복 검색 시 API 호출·지연시간 절감 |
| 4. 정책•네비게이션챗봇 | 임베딩 RAG검색 + Claude API 액션분기 | 동일 (변경 없음) | 없음 | 애초 설계부터 임베딩 기반 RAG 구조를 채택해 별도 고도화가 필요하지 않았음 |
| 5. 스마트검색 | Claude API가 후보 전체를 직접 랭킹 | 검색 요청 시 후보·취향 텍스트를 즉석 임베딩 후 코사인 유사도 랭킹 | LLM 직접 랭킹 → 임베딩 벡터 유사도 계산으로 교체(v1 프롬프트는 롤백 대비 보관) | 후보 수가 늘어나도 비용·속도가 선형적으로 급증하지 않음 |
| 6. 채팅다듬기 | Claude API 직접 호출 | Qwen2.5-7B-Instruct, Modal 서버리스+vLLM, AWQ INT4, prefix caching | 관리형 API 호출 → 자체 오픈소스 모델 서빙(경량화+캐싱 포함)으로 전환 | API 비용 절감, 간헐적 트래픽에 맞는 종량 과금, 반복 프롬프트 재계산 생략 |
별도 부하테스트·정밀 측정 환경을 아직 구축하지 못해, 아래는 실측 데이터가 아닌 설계 근거에 기반한 이론적 추정입니다. 실측은 향후 개선 계획(5번 항목)에서 다룹니다.
- 기능 2 (2단 캐스케이드): 연락처·스팸·욕설혐오·금지품목은 1단계 규칙 기반 필터링(정규식+금칙어 템플릿)으로 대부분 사전 검출이 가능한 유형이라, 해당 케이스에서는 Claude API 호출 자체가 생략됩니다. 이 비율만큼 평균 응답시간과 호출 비용이 줄어드는 구조로, 위반 패턴이 명확할수록 개선폭이 커지는 특성을 가집니다. 다만 정상 텍스트는 2단계까지 모두 거쳐야 하므로 이득이 전체 트래픽이 아닌 위반 케이스 비율에 한정된다는 한계가 있습니다.
- 기능 3 (결과 캐싱): 동일 상품군은 짧은 기간 내 시세 변동이 크지 않다는 전제 하에, 캐시 히트 시 web_search·LLM 호출이 완전히 생략되어 해당 요청은 이론상 지연시간이 캐시 조회 수준까지 낮아집니다. 다만 캐시 미스율이 높으면(예: 희귀 품목 위주) 개선폭이 작아질 수 있습니다.
- 기능 5 (즉석 임베딩 랭킹): 검색 요청이 올 때마다 후보 물건들의 제목·설명과 취향 텍스트를 그 자리에서 임베딩해 코사인 유사도로 비교합니다. 후보가 최대 50개로 제한돼 있고 경량 모델(CPU 추론)이라, LLM으로 후보 전체를 매번 추론하던 방식보다 후보 수 증가에 따른 비용·지연 증가폭이 훨씬 완만할 것으로 예상됩니다.
- 기능 6 (vLLM+AWQ+prefix caching): AWQ 양자화로 모델 크기가 약 4배 줄어 서빙 비용과 추론 속도가 개선되고, prefix caching으로 반복되는 시스템 프롬프트 연산이 생략되어 응답 지연이 추가로 줄어들 것으로 예상됩니다. Modal 서버리스 특성상 트래픽이 없을 때 비용이 발생하지 않아 총 운영비용 절감이 기대되나, 콜드스타트로 인한 첫 요청 지연은 상시가동 대비 트레이드오프로 남아있습니다.
- 품질 측면: 정확도(예: 검열 오탐/미탐률, 정책챗봇 액션분기 정확도)는 아직 정량 측정 전이며, 현재는 팀 내 정성적 샘플 확인 수준에 머물러 있습니다.
이번 설계 과정에서 가장 고민이 많았던 부분은 **파이프라인 순서 설계(기능2→기능3 게이트 방식)**였습니다. 두 기능을 병렬로 실행할지, 순차적으로 실행하면서 검열 실패 시 뒤 단계를 생략할지를 두고 트레이드오프를 따져봤습니다. 결과적으로 "검열 실패 시 비용이 큰 시세조사(web_search)를 아예 실행하지 않는" 순차 게이트 방식을 택했는데, 이 과정에서 단순히 기능을 나열하는 것을 넘어 각 기능의 실행 비용·실패 가능성까지 고려해 순서를 설계해야 한다는 점을 배웠습니다.
이 오케스트레이션을 실제로 어느 레이어에 둘지 정하는 과정에서, "AI 서버 내부의 6개 기능 모듈은 서로 호출하지 않는다"는 기존 원칙과 "등록하기 버튼 한 번으로 검열→가격조사가 끊김없이 이어져야 한다"는 새 요구사항이 충돌하는 걸 발견했습니다. 두 원칙을 다 지키기 위해, 6개 기능 모듈과는 별개로 이 둘만 순서대로 호출하는 전용 오케스트레이터 모듈을 신설하는 방식으로 해결했습니다. 설계 원칙은 고정된 규칙이 아니라, 새로운 요구사항이 생길 때마다 그 원칙의 "의도"(모듈 간 결합도 최소화)를 지키는 선에서 예외를 명시적으로 설계해야 한다는 걸 배운 지점이었습니다.
또한 6개 기능 각각에 어떤 방식으로 LLM을 적용할지, 그리고 이를 어떻게 고도화할지 결정하는 과정 자체가 큰 학습이었습니다. 처음에는 모든 기능을 동일하게 "LLM 호출 하나"로 단순하게 설계했지만, 기능별 특성(반복 요청이 많은지, 후보 수가 늘어나는 구조인지, 명확한 룰로 걸러지는 케이스가 있는지)에 따라 캐스케이드·캐싱·임베딩 유사도 전환·자체 서빙(vLLM/AWQ/prefix caching) 등 서로 다른 고도화 전략이 필요하다는 걸 알게 됐습니다. 이 과정에서 MCP와 Tool Use(Function Calling)의 차이, AWQ 양자화의 원리처럼 정확히 알지 못했던 개념들을 다시 짚어보며 아키텍처 표현을 바로잡기도 했습니다.
정량적 성능 지표는 실측 없이 이론적 추정에 그쳤습니다. 시간과 리소스 제약상 이번 단계에서는 설계와 근거 마련에 집중했고, 검증은 다음 단계 과제로 남기게 됐습니다.
- 정량 평가 체계 구축: 각 기능의 응답시간·호출 비용·정확도(검열 오탐/미탐률, 챗봇 액션분기 정확도 등)를 실제로 측정할 수 있는 로깅·모니터링을 추가하고, 캐스케이드·캐싱 적용 전후를 A/B로 비교해 이번 문서의 이론적 추정치를 실측치로 교체한다.
- 캐시 유효기간·무효화 조건 설계: 기능3 캐시의 TTL(예: 24시간) 외에, 물건 재등록 등 이벤트 기반 무효화 조건도 Backend와 함께 설계한다.
- 기능 5 하이브리드 구조 확장: 현재는 임베딩 유사도로만 순위를 매기지만, 이후 "임베딩으로 1차 후보 축소 → LLM으로 상위 후보만 정교하게 재랭킹"하는 retrieval+rerank 하이브리드로 확장해 품질과 비용을 동시에 잡는다.
- 기능 4 신뢰도 기반 처리: 정책챗봇의 액션분기(정책답변/화면이동) confidence가 낮은 경우, 바로 답을 내지 않고 "잘 모르겠다"는 안내나 사람 문의 연결로 넘기는 fallback 로직을 추가해 오답 리스크를 줄인다.
- 기능 2 사기판별 자체 분류기 전환 검토: 2단계(Claude API) 호출량이 늘거나 라벨링된 사기 문구 데이터셋이 쌓이면, 자체 경량 분류기로 전환해 비용을 추가로 절감할지 재검토한다.
- 기능 6 콜드스타트 최적화: Modal의 GPU 메모리 스냅샷 등 콜드스타트 단축 옵션을 적용해, 서버리스 전환으로 얻은 비용 이점을 지연시간 저하 없이 유지한다.
현재 아키텍처는 6개 기능을 단일 책임 원칙에 따라 독립 모듈로 분리하고, 등록 파이프라인처럼 여러 기능이 순서대로 이어져야 하는 경우에는 전용 오케스트레이터를 별도로 두어 원칙과 실제 요구사항을 동시에 충족시켰습니다. 그 위에서 서로 다른 특성(반복성, 확장성, 트래픽 패턴)에 맞춰 Claude API(외부 API)·임베딩 모델(자체 CPU 서빙)·Modal 서버리스(GPU)라는 세 가지 서빙 방식을 선택적으로 조합했다는 점에서 타당하다고 판단합니다.
모든 기능을 획일적으로 하나의 방식으로 처리하지 않고, 캐스케이드·캐싱·임베딩 유사도 전환처럼 기능별 병목 지점을 분석해 차별화된 고도화를 적용한 점, 그리고 오케스트레이션·캐시 저장 위치처럼 설계 중 발견된 원칙 간 충돌을 회피하지 않고 명시적으로 해결한 점이 이 설계의 핵심 강점입니다. 정량적 검증이 아직 이론적 추정 단계에 머물러 있다는 한계는 남아 있으나, 이는 다음 단계에서 실측으로 채워나갈 명확한 계획으로 이어져 있습니다.