Decision Model을 활용하여 LLM 라우터를 강화하기: Cloudflare Workers에서 Clef 사용
요약
본 글은 LLM 라우터의 분류기를 강화하기 위해 'decision models'을 활용하는 방법을 다룹니다. Cloudflare Workers AI에 도입된 Clef와 Clef-flash는 텍스트 생성 없이 선택지(choice)를 직접 반환하여, 코딩 질문과 일반 질문을 정확하게 분리할 수 있는 효율적인 라우팅 기능을 제공합니다.
핵심 포인트
- Decision model은 답변 대신 선택지를 토큰 단위로 직접 반환한다.
- Clef/Clef-flash는 LLM 라우터의 분류기(classifier)를 개선하는 데 적합하다.
- Cloudflare Workers AI에서 Clef와 Clef-flash 사용 및 테스트 방법을 제시한다.
part 1에서는 약 50줄의 코드로 Cloudflare Worker에 분류 라우터를 구축했습니다. 이 라우터는 각 프롬프트를 분류하여 AI Gateway를 통해 전송합니다: 코딩 질문은 Claude Sonnet으로, 나머지 모든 것은 저렴한 Workers AI 모델로 보냅니다. 분류기(classifier)는 작은 LLM인 Llama 4 Scout였으며, 한 줄짜리 시스템 프롬프트가 있었습니다: "Reply with only that single word."
그 이후로 이 정확히 같은 작업을 위해 새로운 종류의 모델이 등장했습니다: decision models입니다. TypeSafe에서 Jev를 소개했고, Cloudflare는 10월 1일 Workers AI에 Clef and Clef-flash을 게시했습니다. decision model은 답변을 작성하지 않습니다. 대신 콘텐츠와 유형화된 질문(typed question)을 받아 토큰 단위로 생성할 텍스트 없이 직접 선택지(choice)를 반환합니다. coding 또는 simple과 같이 단 하나의 단어만 필요로 하는 라우터에게는 완벽하게 맞는 것 같습니다.
그래서 이 포스트에서는 Clef와 Clef-flash를 기존 라우터에 드롭인 분류기(drop-in classifiers)로 연결한 다음, 동일한 레이블이 지정된 프롬프트들을 Llama로 테스트하여 어느 것이 각 프롬프트를 올바른 모델로 라우팅하는지 검증할 것입니다:
공개 고지: 저는 Cloudflare에서 근무합니다. 이것은 개인적인 사이드 프로젝트이며, 의견과 측정값은 저의 것입니다.
분류기를 테스트 가능하게 만들기
분류기를 변경하기 전에, 이를 점수화할 수 있는 방법이 필요했습니다. 두 가지 작은 추가 사항으로 그것이 가능해졌습니다.
1. /classify 엔드포인트. POST /classify는 분류기만 실행하고 그 결정을 JSON으로 반환합니다. Claude를 호출하지 않으므로, 테스트 비용은 분류기 자체 외에는 발생하지 않습니다:
curl https://prompt.demolabs.fyi/classify \
-H
npm run bench --
--url https://prompt-router.<your-subdomain>.workers.dev
--classifier llama-4-scout,clef --runs 10
The `x-classifier` 요청 헤더는 요청마다 분류기(classifier)를 지정하므로, 하나의 배포판으로 여러 모델을 동일한 프롬프트에 대해 비교할 수 있습니다.
벤치마크 스크립트의 출력 예시:
npm run bench -- --url https://prompt.demolabs.fyi --classifier llama-4-scout,clef,clef-flash --runs 10
Endpoint: https://prompt.demolabs.fyi/classify
...
## LLM 분류기가 잘못 판단한 것들
Llama 4 Scout의 첫 번째 실행 정확도는 **75%**였습니다. 네 개의 프롬프트 중 하나가 잘못된 모델로 전송되었습니다. 실패는 두 가지 종류에서 발생했습니다.
**'단어 하나'를 무시함.** 일부 코딩 프롬프트에 대해 Llama는 챗봇처럼 답변했습니다:
// "Explain useEffect vs useLayoutEffect"
raw: "coding\n\n## Explanation\n\nIn..."
...
제 파서는 정확히 `coding`만 예상했기 때문에, 두 프롬프트 모두 저렴한 모델로 전송되었습니다. 이 두 프롬프트는 Sonnet이 가장 필요했던 것들입니다. 또한 이는 라우터가 아무도 읽지 않는 단락을 생성하는 데 시간을 썼다는 의미이기도 합니다.
**자신만만하게 틀림.** 세 가지 일반적인 프롬프트가 깨끗한 `coding`으로 반환되었습니다:
- "Good morning, how are you?를 독일어로 번역해 주세요."
- "햄릿의 줄거리를 두 문장으로 요약해 주세요."
- "내일 솔루션 아키텍트 역할 면접이 있어요..."
이것들은 비싼 실수는 아니지만 (Sonnet 가격만 들었을 뿐), 매번 반복되었습니다.
### 패치하는 것에는 한계가 있다
명백한 해결책은 더 안전한 기본값입니다. 명시적인 `simple`만 저렴한 모델로 보내고, 그 외의 모든 것(장황함, 공백, 예상치 못한 내용)은 강력한 모델로 보내는 것입니다.
const isSimple = raw.trim().toLowerCase() === "simple";
const task: Task = isSimple ? "simple" : "coding";
이것으로 Llama의 정확도는 **85%**까지 향상되었습니다. 장황한 답변들은 이제 올바르게 라우팅됩니다. 하지만 세 가지 자신만만한 실수는 매번 남아있었습니다. 안전한 기본값은 확신하지 못하거나 형식이 맞지 않는 모델을 잡아낼 수 있습니다. 하지만 확실하고 틀린 모델은 잡아낼 수 없습니다.
## 의사결정 모델(Decision models): 텍스트가 아닌 답변으로
챗 모델에게 분류 작업을 요청하고 그 결과가 지켜지기를 기대합니다. [Clef](https://developers.cloudflare.com/workers-ai/models/clef/)는 의사결정 모델입니다. 콘텐츠와 입력된 질문을 제공하면, 이 모델은 입력된 답변을 반환합니다. 생성되는 텍스트가 없기 때문에 장황하게 늘어놓거나 파싱할 것이 없습니다.
요청은 두 부분으로 구성됩니다:
- **`state`**: 판단해야 할 콘텐츠 (여기서는 사용자의 프롬프트).
- **`questions`**: 최대 64개의 명명된 질문이며, 각 질문은 다음 세 가지 유형 중 하나입니다: `noul` (예/아니오, 확률 반환), `choice` (집합에서 하나의 옵션 선택), 또는 `score` (순서화된 척도에 배치).
라우팅의 경우, `choice` 질문 하나만으로 충분합니다. 옵션들은 객체 키이며, 각 값은 해당 옵션이 적용되는 시점을 설명합니다:
const CLEF_PROMPT = "Which model tier should handle this user request?";
const CLEF_CRITERIA = {
coding:
...
답변은 선택된 키, 각 옵션별 확률, 그리고 0과 1 사이의 `confidence`로 돌아옵니다. 구조상 `coding` 또는 `simple` 중 하나여야 합니다. 또한 동일한 API를 가진 더 작고 빠른 변형인 **Clef-flash**도 있습니다.
이 기준 설명은 제 시스템 프롬프트가 실패했던 작업을 수행합니다. '번역'과 '작성'은 `simple` 아래에 명시적으로 나열되어 있는데, 이는 Llama가 잘못 처리했던 바로 그 부분입니다.
## 스왑(The swap)
모델들을 공정하게 비교하기 위해 분류기(classifier)는 레지스트리가 되었습니다. 모든 항목은 프롬프트를 받아 동일한 작은 형태인 `{ raw, confidence? }`를 반환하며, 라우팅에 대한 파싱은 한 곳에서 이루어집니다:
interface ClassifierOutput {
raw: string;
confidence?: number; // 하나의 세트를 보고하는 분류기만 해당
...
Clef 항목은 약 15줄입니다:
"clef": {
model: "@cf/cloudflare/clef",
async run(env, prompt) {
...
제가 겪은 두 가지 주의사항이 있습니다. 텍스트는 `messages` 배열에 들어가는 것이 아니라 `state`에 들어가야 합니다. 이 부분을 제외하면 Clef가 판단할 근거가 없습니다. 그리고 본문(body)의 `model` 필드는 모델 ID와 일치해야 합니다. `@cf/cloudflare/clef-flash`를 사용하면서 `model: "clef"`로 호출하면, 예외 처리(catch)하지 않는 한 단순히 'error code: 1101'로 표시되는 오류가 발생합니다.
### Clef가 확신하지 못할 때, Sonnet을 사용하세요
두 가지 가능한 실수는 비용이 동일하지 않습니다. 저렴한 모델에 보내는 코딩 질문은 잘못된 답변을 받기 쉬우며, 사용자들은 이를 알아차립니다. 반면, Sonnet에 간단한 질문을 보내는 것은 약간 더 많은 비용이 들지만 여전히 잘 답변됩니다. 따라서 신뢰도가 낮은(low confidence) 요청은 강력한 모델로 라우팅됩니다:
const CONFIDENCE_THRESHOLD = 0.7;
const lowConfidence =
...
결과는 `fallback` 플래그를 포함하므로, 로그를 통해 안전장치(safety net)가 얼마나 자주 작동했는지 확인할 수 있습니다. 만약 이 빈도가 높다면, 임계값(threshold)이 너무 엄격하여 간단한 프롬프트에 대해서도 Sonnet 가격을 지불하고 있다는 의미입니다. Llama는 신뢰도를 보고하지 않으므로, Llama의 경우 규칙은 이전 단계에서 설정된 안전 기본값으로 축소됩니다.
라우터는 `x-classifier` 헤더, 그 다음 `CLASSIFIER` 변수, 그리고 코드 내의 기본값을 순서대로 분류기(classifier)를 선택합니다. 이것이 재배포 없이도 나란히 비교하는 벤치마크가 가능했던 이유입니다.
## 결과
두 Clef 모델 모두 200개의 테스트 요청을 올바르게 라우팅했습니다. 패치된 Llama는 여전히 20개 프롬프트 중 3개를 매번 잘못 라우팅했습니다.
| 분류기 (Classifier) | 정확도 (Accuracy) | 오분류된 프롬프트 수 (20개 중) |
| :--- | :--- | :--- |
| Llama 4 Scout, 엄격한 파싱 | 75% (150/200) | 5 |
| ... |
테스트 방법: 라벨링된 프롬프트 20개 × 10회 실행 = 분류기당 200개 요청을 `/classify`를 통해 전송했습니다:
`npm run bench -- --url https://prompt.demolabs.fyi --classifier llama-4-scout,clef,clef-flash --runs 20`
실수했던 프롬프트는 모든 실행에서 동일했으므로, 이는 무작위 노이즈가 아니라 각 모델이 어떤 프롬프트를 틀리는지에 대한 문제입니다.
몇 가지 눈에 띄는 점들이 있습니다:
모델 평가 기준 및 프롬프트 라우터 개선점
- **기준 설명이 중요하다는 점.** Llama의 남은 세 가지 실수는 번역, 요약, 면접 준비였다. 이 세 항목 모두 Clef의 기준에서 `simple`로 명명되어 있으며, Clef는 이를 정확하게 처리했다.
- **Clef 대 Clef-flash는 이 작업에서 동점이었다.** 둘 다 100%를 기록했다. 양방향 코딩/simple 분할에서는 어느 쪽이든 작동하지만, 더 어렵고 여러 카테고리로 구성된 작업일수록 더 큰 모델이 제 역할을 해야 한다.
- **20개의 프롬프트는 작은 테스트 세트이다.** 여기서 100%라는 것은 "이 20개에 대한 실수가 없었다"를 의미하며, "절대 틀리지 않는다"는 뜻은 아니다. 신뢰도(confidence)가 낮은 경우를 대비한 폴백(fallback) 장치가 존재한다.
## 주요 시사점 (Takeaways)
- **라우터뿐만 아니라 분류기(classifier)를 테스트해야 한다.** 수동 테스트에서는 라우팅이 괜찮아 보였다. 레이블이 지정된 프롬프트 세트와 작은 벤치스크립트를 사용했을 때, 네 개의 프롬프트 중 하나가 잘못된 모델로 전송되는 것을 확인했다.
- **텍스트 생성 모델에게 레이블을 요청해서는 안 된다.** "한 단어로 답해라(Reply with one word)"는 요청일 뿐 보장이 아니다. 의사 결정 모델은 사용자가 정의한 옵션 중 하나만 반환할 수 있다.
- **'불확실함(unsure)'의 실패 처리를 품질 쪽으로 유도해야 한다.** 명시적인 `simple`만이 저렴하게 작동한다. 낮은 신뢰도를 포함하여 나머지 모든 것은 강력한 모델로 전송되어야 한다.
- **Llama에게 공정하기 위해서:** Clef는 옵션별 설명이 있는 더 나은 프롬프트를 받았다. Llama의 프롬프트가 좀 더 정교해진다면, 일부 자신감 넘치는 실수(mistakes)는 수정될 수 있겠지만, 형식에 맞지 않는 답변(off-format answers)까지는 해결하지 못할 것이다.
## 직접 사용해 보기 (Try it)
코드는 GitHub에 있다: [palermo-777/prompt-router](https://github.com/asazhin-dev/prompt-router/tree/part-2). part 1 버전은 여전히 [`part-1` 태그](https://github.com/palermo-777/prompt-router/tree/part-1)에 있다.
git clone -b part-2 https://github.com/palermo-777/prompt-router.git
cd prompt-router && npm install
...
워커(Worker)가 호출자를 인증하지 않기 때문에, URL을 아는 사람은 누구나 당신의 Anthropic 크레딧을 사용할 수 있다. 공유하기 전에 Cloudflare Access나 베어러 토큰 체크를 앞에 두어야 한다.
## 다음 단계 (What's next)
- **더 많은 라우트.** Clef는 호출당 최대 64개의 질문을 처리할 수 있으므로, `reasoning`, `creative` 또는 `needs-tools`를 옵션으로 추가하거나 별도의 질문으로 추가하여 모두 동일한 호출에서 답변받을 수 있습니다.
- **더 어려운 테스트 세트.** 애매모호한 프롬프트(
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기