본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 16. 22:43

프롬프트 엔지니어링을 이해할 수 없습니다. 우리는 이제 토큰으로 도박을 하고 있는 건가요?

요약

결정론적인 프로그래밍 작업(정렬, 필터링 등)을 LLM 호출로 대체하는 현재의 잘못된 개발 관행을 비판합니다. 이는 비용, 지연 시간, 환각 문제 등 비효율성을 초래하며 운영 환경에서 매우 위험할 수 있음을 경고합니다.

핵심 포인트

  • 단순 정렬과 같은 결정론적 연산을 LLM으로 처리하는 것은 매우 비효율적임
  • LLM 사용 시 높은 API 비용, 지연 시간, 비결정론적 결과(환각)가 발생함
  • 실제 운영 환경(Production)에서 LLM의 불안정성은 서비스 품질을 저해함
  • 전통적인 알고리즘(sort, filter, map)이 훨씬 빠르고 정확하고 저렴함

좋아요, 이 이야기를 꼭 털어놓아야겠습니다.

저는 몇몇 하이프 사이클(hype cycles)이 오고 가는 것을 지켜볼 만큼 이 업계에 오래 있었습니다. 저는 "블록체인이 모든 것을 해결할 것이다"라는 시대에서 살아남았습니다. 마이크로서비스(microservices)가 모놀리스(monoliths)를 집어삼키는 것을 보았고, 그 후 모두가 조용히 모놀리스로 다시 기어 들어가는 것도 보았습니다. 저는 모든 사람이 갑자기 무언가에 동의할 때, 먼지가 가라앉을 때까지 일단 앉아서 입을 다물고 있어야 한다는 것을 알 만큼 충분히 경험했습니다.

하지만 이번에는 다릅니다. 이번에 모두가 동의하는 것은... 모든 것에 LLM(Large Language Models)을 사용하는 것인가요? 그리고 저는 진심으로, 깊이, 그것을 이해할 수 없습니다.

제가 무슨 말을 하는지 보여드리겠습니다

몇 주 전, 저는 자금력이 풍부한 스타트업의 코드베이스를 살펴보고 있었습니다. 이름을 밝히지는 않겠습니다. 중요하지 않으니까요. 중요한 것은 그들이 운영 환경(production)에서 다음과 같은 코드를 사용하고 있었다는 점입니다:

def sort_hotels_by_price(hotels):
    prompt = f"""
    Sort these hotels by price from lowest to highest:
...

저는 이것을 꼬박 1분 동안 쳐다보았습니다. 그러고 나서 웃음이 났습니다. 그러고 나서 약간 슬퍼졌습니다.

우리는 결정론적(deterministic)인 O(n log n) 연산 — 마이크로초 단위로 걸리고 비용이 전혀 들지 않는 작업 — 을 다음과 같이 바꾸고 있습니다:

  1. OpenAI 또는 Anthropic으로의 API 호출 (요청당 $0.001–0.01)
  2. 환각(hallucinate)을 일으킬 수 있는 비결정론적(non-deterministic) 연산
  3. 500ms–2s의 지연 시간(latency) 발생
  4. 깨질 수 있는 취약한 JSON 파싱(parse)

리스트를 정렬하기 위해서 말이죠.

그리고 가장 무서운 점은 무엇일까요? 이것은 어떤 주니어 개발자의 사이드 프로젝트가 아니라는 점입니다. 이것은 실제 사용자를 대상으로 서비스하며, 실제 돈이 흐르고 있는 운영 환경(production) 코드입니다.

밤잠을 설치게 만드는 수학적 계산

계산을 좀 해봅시다. 왜냐하면 숫자가 정말 말도 안 되기 때문입니다.

일일 활성 사용자(DAU)가 10,000명인 여행 앱을 만든다고 가정해 봅시다. 각 사용자는 세션당 검색, 필터링, 정렬, 추천, 결과 파싱, 설명 생성 등등 약 50회의 LLM 호출을 발생시킵니다.

그것은 하루에 500,000회의 LLM 호출입니다.

호출당 $0.002(저렴한 모델, 짧은 프롬프트)라고 하면, 이는 하루에 $1,000입니다. 즉, 한 달에 $30,000입니다.

무엇을 위해서 말입니까? 1970년대부터 sort(), filter(), 그리고 map()으로 할 수 있었던 일들을 위해서 말입니다.

그리고 진짜 문제는 이겁니다. 월 3만 달러를 쓴다고 해도 당신의 리스트가 올바르게 정렬되어 돌아온다는 보장이 없다는 것이죠. 저는 LLM이 리스트를 "정렬"하다가 아이템 3개를 빼먹는 것을 보았습니다. 중복된 값을 반환하거나, 완전히 다른 데이터 구조를 반환하기도 합니다. 심지어 "그것은 도와드릴 수 없습니다"라며 처리를 거부하기도 하죠.

sort([3, 1, 2])[1, 2]를 반환했던 게 마지막으로 언제였습니까?

우리가 실제로 하는 일

저는 단순히 불평을 하는 것이 아닙니다. 우리는 실제 사용자가 있는 실제 제품을 구축해 왔으며, 이러한 선택들을 직접 내려야만 했습니다.

GoChatTravel은 채팅 우선(chat-first) 여행 예약 플랫폼입니다. 사용자는 WhatsApp, Telegram, Messenger 등 무엇이든 사용하여 저희 봇과 대화하며 호텔, 항공편, 투어, 기차를 예약합니다. "모든 것에 그냥 LLM을 사용하자"는 유혹이 압도적인 종류의 제품입니다. 모든 단계가 프롬프트(prompt)가 될 수 있을 것처럼 느껴지기 때문입니다.

하지만 우리는 다른 선택을 했고, 솔직히 말해서 그 이야기는 너무 지루하게 느껴질 정도입니다.

LLM은 대화를 처리합니다

그게 전부입니다. 그것이 LLM의 역할입니다. 자연어(natural language)를 통해 사용자가 무엇을 원하는지 이해하고, 친근한 인간의 응답을 생성하며, 잡담과 예외 상황(edge cases)을 처리합니다. LLM은 그 일을 정말 잘합니다. 우리는 LLM이 그 한 가지 일만 하도록 둡니다.

코드는 말 그대로 그 외의 모든 것을 처리합니다

그리고 "그 외의 모든 것"이란 말은, _정말 많은 것_을 의미합니다. 사용자가 "다음 주에 토론토에서 도쿄로 가는 항공편을 찾아줘"라고 말할 때 실제로 일어나는 일은 다음과 같습니다:

// 1. LLM이 의도(intent)를 추출합니다 (이 부분이 LLM이 빛을 발하는 지점입니다)
let intent = llm.parse_intent("Find me flights from Toronto to Tokyo next week");
// 반환값: { action: "search", from: "YYZ", to: "NRT", dates: "..." }
...

그 후, 사용자가 항공편을 선택하면 다시 코드가 제어권을 가져옵니다. Stripe를 통한 결제 처리, PCI 준수(PCI compliance), 예약 확인, 티켓팅, 변경 및 취소를 위한 사후 관리 흐름(after-sales flows), 지원 팀과의 통합 등 말입니다. 이 중 그 어떤 것도 LLM을 건드리지 않습니다.

Real chat interaction showing natural language input from the user, with all flight search and booking logic handled deterministically by our Rust backend.
](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6mfdbz808upsx2stjkd6.jpg)

이것이 작동하는 이유

항공편에서 일어나고 있는 일을 주목해 보세요. 약 1,000개의 옵션이 있지만, 우리는 LLM에 단 3개만 보냅니다. 왜일까요?

  • 토큰 효율성 (Token efficiency). 10,000개의 항공편(일부 왕복 여정의 경우 더 많을 수 있음)을 LLM에 보내는 것은 엄청난 비용이 들 것이며, 어쨌든 컨텍스트 제한 (context limits)을 초과할 가능성이 높습니다. 세 가지 스마트한 선택은 비용이 거의 들지 않습니다.
  • 더 나은 UX (Better UX). 사용자들은 채팅창에서 10,000개의 항공편을 스크롤하고 싶어 하지 않습니다. 그들은 큐레이션된 3가지 옵션을 원합니다.
  • 결정론적 큐레이션 (Deterministic curation). "가장 저렴한", "가장 빠른", "최적의" 선택은 LLM의 추측이 아니라 코드를 통해 이루어집니다. 실제로는 가장 비싼 옵션인, 환각 (hallucinated)된 "최고의 가치" 같은 것은 없습니다.
  • 탈출구 (Escape hatch). 만약 사용자가 정말로 모든 것을 보고 싶어 한다면, 우리는 웹 페이지로 연결되는 일회용 링크를 제공합니다. 이 링크는 15분 후에 만료되므로, 우리는 무한한 비교 페이지를 호스팅할 필요가 없습니다.

LLM은 원시 데이터 (raw data)를 절대 보지 않습니다. LLM은 단지 다음과 같이 봅니다: "여기 3가지 옵션이 있습니다. 나머지는 이 링크를 확인하세요. 사용자에게 말하세요."

그것이 핵심입니다. LLM은 데이터 처리 계층 (data processing layer)이 아니라 대화 계층 (conversation layer)입니다.

지루한 결과

우리는 이 엔진을 적당한 사양의 하드웨어 — 2 vCPU, 2GB RAM — 에서 부하 테스트 (load tested)를 진행했습니다. 이 엔진은 p99 지연 시간 (latency) 50ms 미만으로 초당 약 8,000개의 요청을 처리합니다.

임계 경로 (critical path)에 있는 LLM으로 이 작업을 시도해 보세요. 감히 도전해 보라고 말하고 싶군요.

하지만 여기서부터 솔직해지겠습니다

보세요, 저는 "모든 것에 LLM을 사용하는" 접근 방식에 대해 상당히 비판적이었습니다. 그리고 저는 그 의견의 대부분을 고수합니다. 하지만 저 자신에게 진실된 질문을 던져보고 싶기도 합니다: 내가 무언가를 놓치고 있는 것은 아닐까?

어쩌면 그럴지도 모릅니다. 저 자신에 대해 악마의 변호인 (devil's advocate) 역할을 해보겠습니다.

내가 놓치고 있을지도 모르는 것

유연성 (Flexibility). 요구 사항이 변경될 때, 여러분은 그저 프롬프트 (prompt)를 업데이트하기만 하면 됩니다. 코드를 사용할 때는 배포 (deploy)가 필요합니다. 코드베이스를 건드리지 않고 동작을 미세하게 조정할 수 있다는 점에는 분명 유혹적인 면이 있습니다.

예외 케이스 처리 (Handling edge cases). LLM은 정말로 기묘하고 모호한 것들을 잘 다룹니다. 사용자가 "너무 화려하지도 않지만 그렇다고 쓰레기 같지도 않은 호텔을 원해요"라고 말한다면, 이를 필터 규칙 (filter rule)으로 인코딩하는 것은 매우 어렵겠지만, LLM은 이를 자연스럽게 처리합니다.

더 빠른 프로토타이핑 (Faster prototyping). MVP (최소 기능 제품)를 만들 때, 프롬프팅 (prompting)을 통해 로직을 구현하는 것은 매우 빠릅니다. 제대로 코딩하려면 몇 주가 걸릴 일을 단 며칠 만에 출시할 수 있습니다.

대규모 개인화 (Personalization at scale). LLM은 규칙 (rules)으로 구현하기에는 고통스러운 방식으로 개별 사용자에게 맞춰 응답을 조정할 수 있습니다.

더 풍부한 비교 (Richer comparisons). 이론적으로 LLM은 수십 가지 요소를 고려하여 왜 한 항공편이 다른 항공편보다 더 나은지를 자연어 (natural language)로 설명할 수 있습니다. 우리는 우리의 "최적 (optimal)" 점수 함수를 통해 이를 수동으로 수행하지만, 이는 LLM이 할 수 있는 것보다 필연적으로 단순합니다. 우리는 정교함 (sophistication)을 예측 가능성 (predictability)과 맞바꾸고 있는 것입니다.

하지만 제가 계속해서 되돌아오게 되는 지점은 이겁니다

결정론적 코드 (deterministic code)로 처리할 수 있는 일에 LLM을 사용하려고 시도할 때마다, 저는 후회해 왔습니다. 즉각적인 후회는 아닙니다. 보통 3~6개월 뒤에 다음과 같은 상황이 닥칠 때 후회합니다:

  • LLM이 미묘하게 틀린 결과를 반환하기 시작하고, 그 이유를 알 수 없을 때
  • 청구서가 날아왔는데 예상보다 3배나 더 많이 나왔을 때
  • 사용자가 일관성 없는 동작에 대해 불평하지만, 이를 재현할 수 없을 때
  • 새벽 2시에 무언가를 디버깅(debug)해야 하는데, 왜 쓰레기 같은 결과가 나오는지 알아내기 위해 프롬프트 (prompt)를 뚫어지게 쳐다보고 있을 때

LLM은 확률적 (probabilistic) 도구입니다. 언어, 의도, 창의성과 같이 본질적으로 모호한 것들을 다루는 데는 놀랍도록 뛰어납니다. 하지만 매번 정확히 (exactly right, every single time) 무언가가 이루어져야 한다면, 당신은 코드를 원하게 될 것입니다.

저의 경험칙 (현재 기준)

LLM이 나쁘다는 뜻이 아닙니다. 그것들은 놀라운 도구입니다. 우리는 매일 그것들을 사용합니다. 하지만 저는 우리가 "모든 것에 LLM을 사용하는" 방향으로 너무 과하게 치우쳤다고 생각하며, 몇 년 뒤에 sort() 함수가 처리할 수 있었던 일들에 토큰 (tokens) 비용으로 얼마나 많은 돈을 태웠는지 돌아보며 이불킥을 하게 될 것이라고 생각합니다.

저의 경험칙은 간단합니다:

연산에 단 하나의 정답이 있다면, 코드를 사용하세요. 만약 유효한 답이 여러 개라면, LLM을 사용하세요.

정렬 (Sorting)에는 단 하나의 정답이 있습니다. 코드를 사용하세요. \n"너무 화려하지도 않지만, 너무 형편없지도 않게"라는 말이 무엇을 의미하는지 이해하는 것? 유효한 답이 많습니다. LLM을 사용하세요.

제가 틀렸을까요? 그럴 수도 있습니다. 저는 실제로 프로덕션 환경에서 LLM 중심의 아키텍처 (LLM-heavy architectures)를 성공적으로 운영하고 있는 분들의 의견을 진심으로 듣고 싶습니다. 수치를 보여주세요. 제가 무언가를 놓치고 있다는 것을 증명해 주세요.

아니면 여러분도 sort()가 프롬프트 (prompt)로 구현되는 것을 보는 데 지쳤다면, 댓글을 남겨주세요. 함께 한탄해 봅시다.

추신: 만약 채팅 기반 애플리케이션을 구축 중이며 저희가 이 아키텍처를 Rust에서 어떻게 구현했는지 알고 싶다면, GoChatTravel을 확인해 보세요. 저희는 작은 캐나다 스타트업입니다. 아키텍처에 대해 이야기하고 싶거나, LLM에 대해 논쟁하고 싶거나, 혹은 그저 저희가 틀렸다고 말하고 싶다면 언제든 채팅으로 연락해 주세요. 😉

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0