표준화된 도구 통합 및 외부 API 활용 설계 - 100-hours-a-week/KTB4-1st-wiki GitHub Wiki

단계 6. 표준화된 도구 통합 및 외부 API 활용 설계

1. 외부 연동 시나리오

연동 대상 호출 기능 목적
Claude API (sonnet) 1, 3, 4 이미지 분석, 웹검색 기반 시세조사, RAG 응답
Claude API (sonnet, 초기 구현) 5 후보 물건 순위 판단 — 고도화 이후 로컬 임베딩으로 전환 (3번 참고)
Claude API (haiku) 2 (2단계) 텍스트검증 캐스케이드의 최종 판단(사기성 문구·물건 혼재·가격언급)
Claude web_search 도구 3 실시간 쇼핑몰 시세 검색
Claude API(haiku, 초기 구현) → vLLM via Modal(고도화 이후) 6 채팅다듬기 — 아래 3번 참고

아래 "로컬 모델"은 네트워크 호출이 없는 AI 서버 내부 추론이라 이 표(외부 연동)에서 제외했다. 텍스트검증 1단계(규칙 기반 필터링)는 모델이 아니라 정규식·금칙어 템플릿으로 처리하는 순수 코드 로직이라, 이 표에도 아래 "로컬 모델" 표에도 해당하지 않는다.

참고 — 로컬 모델 (외부 연동 아님)

모델 적용 기능 비고
dragonkue/multilingual-e5-small-ko-v2 (임베딩) 4, 5(고도화 이후) 정책 문서 검색(RAG), 물건 검색 랭킹 — CPU 추론

2. MCP 도입 여부

결론: 미도입

  • Claude API와 (고도화 이후의) vLLM 서버 모두 그 자체로 이미 REST 기반 인터페이스를 제공한다. MCP는 이 위에 추가되는 "표준화된 도구 탐색/호출" 레이어인데, 우리는 그 레이어가 필요한 상황이 아니다.
  • MCP가 유리한 조건(① LLM이 런타임에 여러 도구 중 무엇을 쓸지 동적으로 판단, ② 다수의 이기종 도구를 표준 규격으로 묶어야 함, ③ 여러 외부 MCP 클라이언트가 같은 도구를 재사용) 중 우리 서비스에 해당하는 게 없다 — 각 기능이 어떤 provider를 쓸지 이미 코드에 고정돼 있고, 외부 의존성도 사실상 Claude API 하나뿐이다.
  • 기능3의 web_search도 예외가 아니다. Claude가 검색 여부를 스스로 판단하긴 하지만, 이는 Claude API 호출 시 tools 파라미터 하나로 전달하는 것뿐이며 Anthropic 서버 쪽에서 처리된다. 우리가 직접 관리해야 할 "도구"가 아니라 MCP 도입 근거가 되지 않는다.
  • 따라서 REST(각 서비스의 네이티브 인터페이스를 직접 호출) + 자체 provider 추상화(shared/llm/base_provider.py)로 충분하다.

3. 자체 구축 vs API 사용 분석

기능 선택 이유
1 Claude API 유지 이미 1콜 구조로 설계되어 있어(4.1 참고) 이미지 중복 전송이라는 별도 비용 발생 지점 자체가 없음
3 Claude API 유지 결과 캐싱으로 캐시 히트 시 호출 자체가 생략되어 실제 호출량이 작아짐
4 Claude API 유지 애초에 self-hosting 전환 검토 대상이 아니었고(단계2 최적화 대상은 2·3·5·6), 단계5의 RAG(top-k 검색+프롬프트 캐싱)로 비용이 이미 통제됨
2 Claude API(haiku) 유지 캐스케이드 2단계 중 최종 단계만 호출, 호출량 자체가 이미 최소화됨
5 (스마트검색) 초기: Claude API(sonnet) → 고도화: 로컬 임베딩 전환 아래 참고
6 (채팅다듬기) 초기: Claude API(haiku) → 고도화: vLLM(Modal 서버리스 서빙) 아래 참고

기능5(스마트검색) — 단계적 전환

  • 초기 구현: Claude API(sonnet)가 후보 물건 목록과 취향 신호를 직접 읽고 의미 기반으로 순위를 매김
  • 고도화 이후: 검색 요청이 올 때마다 Backend가 넘겨준 후보 물건들의 제목·설명과 취향 텍스트를 그 자리에서 임베딩해 코사인 유사도로 랭킹하는 방식으로 전환. 별도의 사전 저장·인덱싱 없이 요청 단위로 즉시 계산되며, 후보가 최대 50개로 제한돼 있고 경량 모델(CPU 추론)이라 이 방식으로도 충분히 빠름
  • 이 전환에 쓰이는 임베딩 모델은 기능4(정책 챗봇 RAG)에서 채택한 dragonkue/multilingual-e5-small-ko-v2를 그대로 재사용

기능6(채팅다듬기) — 단계적 전환

  • 초기 구현: Claude API(haiku)로 구현. 별도로 검토했으나 채택하지 않은 초안메시지 생성 기능이 빠지면서 실제 호출량이 애초 예상보다 줄어든 상태였고, 이 규모에서 상시 GPU 인스턴스를 먼저 띄우는 건 예산·검증되지 않은 트래픽 양쪽에서 리스크가 크다고 판단
  • 고도화 이후: vLLM + AWQ 양자화 모델을 **Modal(서버리스 GPU)**로 서빙 전환. 초기 API 운영을 통해 실제 호출량이 확인된 뒤 전환하는 것이라, "추정치로 미리 GPU 인프라부터 갖추는" 리스크 없이 근거를 갖고 전환할 수 있음. Modal은 호출이 없을 때 자동으로 0으로 스케일되는 사용량 기반 과금이라, 상시 GPU 인스턴스 대비 예산 부담이 훨씬 적음
  • shared/llm/modal_provider.py가 이 전환의 구현체 — provider 인터페이스만 교체하면 되므로 기능6의 라우터·다른 모듈 코드는 변경 불필요

4. REST 인터페이스 및 예외처리

features/{기능}/service.py
    → shared/llm/base_provider.py (텍스트 생성 공통 인터페이스: generate(prompt, ...))
        ├─ claude_provider.py  → Anthropic Claude API (기능1,2,3,4 상시 / 기능5,6 초기 구현)
        └─ modal_provider.py   → Modal 서버리스 vLLM (기능6, 고도화 이후)

    → shared/embedding/embedding_provider.py (임베딩 공통 인터페이스: embed(text))
        └─ dragonkue/multilingual-e5-small-ko-v2 (기능4 상시 / 기능5, 고도화 이후)
상황 처리
Claude API 429 (rate limit) 1초 고정 대기 후 재시도 1회 → 실패 시 internal_error
Claude API timeout 1초 고정 대기 후 재시도 1회 → 실패 시 internal_error
응답 JSON 파싱 실패 1초 고정 대기 후 재시도 1회 → 실패 시 internal_error

재시도는 1회로 제한되어 있어 "지수 백오프"처럼 대기시간이 늘어날 다음 시도 자체가 없다. 그래서 고정 대기 1초로 단순하게 확정.


5. 확장 계획

새 외부 도구(예: 이미지 생성 API)가 필요해지면 shared/llm/base_provider.py 인터페이스를 구현하는 provider 파일을 하나 추가하고, 해당 기능의 service.py에서 그 provider를 선택하도록 연결한다. 기존 라우터·다른 기능 모듈은 변경할 필요가 없다.


6. 통합 방법이 미치는 영향

  • 확장성: 새 provider 추가가 기존 코드에 영향 없음(모듈화 단계에서 확보한 결합도 0에 가까운 구조 유지). 기능5의 임베딩 전환, 기능6의 Claude API → Modal 전환 모두 이 구조 덕분에 라우터 변경 없이 provider만 교체하면 됨
  • 유지보수성: Claude API 정책 변경 시 provider 파일 하나만 수정
  • 비용 효율성: 캐스케이드(기능2)·로컬 임베딩 전환(기능5)으로 API 호출량 자체를 줄이는 것이 MCP 같은 통합 레이어보다 비용에 직접적 영향. 기능6은 초기에 API로 리스크 없이 시작하고, 고도화 단계에서 실측된 트래픽을 근거로 서버리스 GPU로 전환해 상시 인스턴스 비용 없이 self-hosting 이득만 취하는 구조