← Writing

Plan Do! 자연어 라우팅 — 로컬 LLM과 GPT를 100회 붙여보고

Plan Do!자연어 명령을 라우팅하는 데 gpt-4o-mini를 씁니다. “내일 3시에 회의” 같은 문장을 받아서 어떤 action인지, 어떤 슬롯인지 추출하는 일이에요.
호출이 늘면 비용이 늘어나니 로컬 LLM으로 옮기면 어떨까 검토해본 기록입니다.

시작 — 그럼 비용이 얼마라고?

1인 운영하는 입장에선 AI 호출이 무섭게 느껴져요. 실제로 청구되는 액수와 체감하는 무서움은 다른 종류의 숫자입니다.

그래서 로컬 LLM이 매력적으로 보였어요. 호스팅만 되어 있으면 호출 비용이 0이고, 내가 모델을 가지고 있다는 통제감도 있고요.

테스트 환경은 Mac에 Ollama + qwen2.5-coder:7b (Q4_K_M, 4.4GB)를 띄워두고, Plan Do!에서 실제 쓰는 라우팅 프롬프트 그대로 두 모델에 같은 100개 문장을 던졌습니다.

환경 — 공정 비교를 만든 것

비교가 의미 있으려면 모델 외 모든 변수를 똑같이 두어야 해요.

  • 같은 시스템 프롬프트 — Plan Do! 실제 라우팅 프롬프트 (~700토큰)
  • 같은 100개 입력create_task, search_tasks, update_task, delete_task, need_more_info, chat 6개 action을 골고루
  • 같은 temperature=0 — 결정적 출력
  • 둘 다 JSON 강제 — gpt는 response_format=json_object, ollama는 format=json
  • 같은 채점 — JSON에서 action 필드만 추출해서 정답과 일치 여부

100개 케이스의 정답 라벨은 제가 직접 적었습니다. 100개 중 라벨링 자체가 모호한 케이스도 있는데, 그 얘기는 뒤에서 다시 합니다.

결과 — 표

gpt-4o-miniqwen2.5-coder:7b
라우팅 정확도80 / 100 (80.0%)85 / 100 (85.0%)
평균 응답0.79s1.23s
호출당 비용$0.000087 (약 0.12원)$0 (전기료 별도)
월 비용 추정 (사용자 10명, 호출 1,500회)약 180원Lightsail 업그레이드 +27,000원

첫 줄에서 이미 의외입니다. qwen이 5%p 더 정확했어요.

qwen이 이긴 이유 — GPT의 과한 신중함

GPT가 틀린 케이스 대부분이 명확한 일정 추가need_more_info로 보내는 패턴이었어요.

입력gpt-4o-miniqwen2.5-coder:7b
”다섯시 헬스장”need_more_infocreate_task
”정오에 미팅”need_more_infocreate_task
”이번달 25일 결제 일정”need_more_infocreate_task
”주말 가족 모임 일정 추가”need_more_infocreate_task
”방금 끝난 회의 정리 등록해줘”need_more_infocreate_task
”다음주 화요일 점심 약속”need_more_infocreate_task

GPT는 “이게 진짜 일정 추가인지 더 물어볼래?” 모드에 가깝고, qwen은 “동사가 명확하면 그냥 추가” 모드에 가까웠습니다.

사용자 입장에서 보면 GPT 쪽이 짜증나는 방향으로 틀립니다. “일정 추가하자고 했는데 왜 또 물어봐?”가 됩니다.

그런데 qwen이 위험하게 틀린다

다만 qwen이 틀리는 케이스를 보면 정확도가 함정이라는 느낌을 받았어요.

입력정답qwen 답위험도
”삭제” (대상 없음)need_more_infodelete_task매우 위험
”지워”need_more_infodelete_task매우 위험
”회의 일정 삭제해줘” (어떤 회의인지 모호)need_more_infodelete_task위험
”3번 끝났어”update_task (DONE)delete_task데이터 손실

qwen은 적극적으로 실행하다가 대상이 명확하지 않은 삭제 명령까지 delete_task로 보냅니다. GPT는 같은 케이스에서 모두 need_more_info로 보냈어요.

운영 관점에서 보면 명백히 차이가 납니다.

  • GPT의 실패 — 사용자가 한 번 더 입력해야 함 (UX 부담)
  • qwen의 실패 — 의도하지 않은 데이터 변경/삭제 (복구 비용)

정확도가 5%p 높은 게 이쪽 위험을 감수할 만한 차이는 아니라고 봤습니다.

라벨링 자체가 모호한 케이스도 있었다

100개 중 사람도 정답을 단언하기 어려운 케이스가 있었어요.

입력제가 매긴 정답qwen 답
”이번주 안에 보고서 마감”create_tasksearch_tasks
”이번달 말 보고서 검토”create_tasksearch_tasks
”다음주 금요일까지 PR 리뷰”create_tasksearch_tasks

이 표현들은 *“~까지 ~할 일을 추가해줘”*로도, *“~까지 마감인 일을 보여줘”*로도 읽힙니다. 한국어 자체가 시간 표현 + 명사구만으로는 의도가 안 정해져요.

이런 케이스에서 GPT는 거의 create_task로 갔고, qwen은 search_tasks로 갔습니다. 두 모델의 보수성 자체가 다르다는 신호로 읽었어요.

비용은 — 의도와 정반대

원래 가설은 *“로컬로 옮기면 비용이 줄어든다”*였습니다. 실제로 따져보면 이렇게 됩니다.

사용자 10명 × 월 호출 1,500회 × $0.000087
= $0.13 / 월
= 약 180원 / 월

이걸 줄이려면 모델을 어디든 호스팅해야 해요. 가장 단순한 길은 Plan Do! 백엔드가 도는 Lightsail 인스턴스에 같이 올리는 것인데, 현재 인스턴스는 4GB입니다. qwen2.5-coder:7b Q4 양자화 모델만 4.4GB라 RAM부터 안 맞습니다.

8GB로 업그레이드하면 월 +$20 (약 27,000원). 비용이 150배 늘어요.

Lightsail 4GB에 띄울 수 있나

답은 현실적으로 불가입니다.

  • RAM: OS + Spring Boot + PostgreSQL + Nginx + 정적 파일에 이미 4GB가 빡빡해요. 모델 4.4GB는 들어갈 자리가 없습니다.
  • CPU 추론: Lightsail에는 GPU가 없습니다. CPU만으로 7B 모델 추론하면 토큰당 수백 ms — 100토큰 출력에 10~30초 정도가 자연스럽습니다. UX 부담이 큰 영역이에요.
  • 공유 부하: 추론 중에는 다른 서비스도 영향을 받습니다. 1인 운영의 복구 비용이 올라가요.

더 작은 모델(1.5B-3B)로 가면 RAM은 가능하지만, 그쪽은 한국어 라우팅 정확도가 7B보다 더 떨어집니다.

그래서 안 옮겼다

항목gpt-4o-mini 유지qwen 로컬 도입
월 비용약 180원+27,000원 (인스턴스 업그레이드)
라우팅 정확도80%85% (5%p ↑)
위험한 오작동거의 없음데이터 손실 패턴 존재
응답 속도~0.8sLightsail CPU에서 10~30s 추정
운영 책임OpenAI에 위임직접 운영

어떤 축으로 봐도 옮겨야 할 이유가 없었습니다.

진짜 줄여야 할 것 — 프롬프트

100개 중 두 모델 모두 틀린 케이스를 보면 신기했어요.

  • “3번 끝났어” → 둘 다 delete_task (정답: update_task + DONE)
  • “안 한 것만 보여줘” → 둘 다 need_more_info (정답: search_tasks + status=TODO)

이건 모델의 한계보다 프롬프트의 한계입니다.

  • “끝났어/완료”가 상태 변경인지 삭제인지 프롬프트가 명확히 구분 안 함
  • 상태 필터 검색이 프롬프트 예시에 부족

프롬프트를 다듬으면 두 모델 모두 5~10%p 정도 상승할 여지가 보였어요. 모델 교체보다 프롬프트 개선이 훨씬 큰 ROI라는 게 솔직한 결론입니다.

배운 것

  • 1인 운영의 비용 본능은 실제 수치와 자주 어긋난다 — 직접 계산해보는 게 가장 빠르다
  • 정확도만 보면 함정이다. 운영에선 틀리는 방향이 더 중요하다
  • “도입 = 정답”이 아니라 *“도입 트리거 기준”*을 정해두는 게 정답이다
  • 시스템 프롬프트는 모델보다 더 큰 레버리지다 — 모델 교체 검토 전에 프롬프트부터

다음에 해볼 것

  • 프롬프트 보강 후 재시도 — 위에 적은 두 모델 공통 실패 패턴부터
  • 도입 트리거 기준 명시 — 월 호출 N회 이상, 평균 응답 N초 이상 등 언제 다시 검토할지 적어두기
  • 민감 액션 가드 — qwen이 보인 “대상 없는 delete” 패턴 같은 데이터 손실 동작은 모델과 별개로 백엔드 단에서 한 번 더 막기

지금 단계에선 바꾸는 것보다 안 바꾸는 것이 더 큰 의사결정이었어요. “막상 따져보니 의미 없었다”를 솔직하게 적어두는 게 다음의 저에게 가장 도움이 될 것 같습니다.