Claude 코드 모델 선택 시, 소규모의 검증 가능한 변경 사항에는 Haiku를, 일상적인 구현 작업에는 Sonnet을, 불확실성이 크거나 추론이 필요한 작업에는 Opus를 사용하십시오. Anthropic는 현재 Opus 5.5를 폭넓은 출발점으로 권장하고 있습니다. 귀하의 업무 내용, 검토 부담 및 예산에 따라 더 작은 모델이 더 적합할지 여부가 결정됩니다.
모델을 변경하려면 다음을 엽니다. /모델. 누르기 입력 기본값을 저장하려면, 또는 s 현재 세션만 전환하려면. 한 번만 실행하려면 다음을 사용하십시오. 클로드 --모델 오푸스. 이러한 선택지들은 지속 시간이 서로 다르기 때문에, 스위치의 지속 시간을 어떻게 정할지는 모델을 선택하는 것만큼이나 중요합니다.
코딩 작업에는 연구, 설명, 문서화, 창의적인 자산 제작 등도 포함됩니다. GlobalGPT는 선도적인 AI 모델과 다양한 AI 도구를 하나의 합리적인 가격의 대시보드에 통합하며, CLI를 통해 이러한 도구들을 터미널 및 개발 워크플로우와 연동합니다. GlobalGPT로 워크플로를 구축하세요 아이디어에서 실제 작동하는 코드로 발전해 나가는 과정에서 관련 작업들을 한데 묶어두기 위해서입니다.

Claude 코드에서는 어떤 모델을 사용해야 할까요?
먼저 작업의 성격부터 살펴보겠습니다. 명확한 기대 출력을 가진 짧은 함수는 여러 서비스에 걸쳐 발생하는 간헐적인 버그와는 다른 평가가 필요합니다. 중요한 질문은 모델이 합리적인 검토와 반복 과정을 거쳐 수용 가능한 변경 사항을 만들어낼 수 있는지 여부입니다.
| 현재 모델 | 어떤 용도로 설계되었는지 | 실용적인 시작 과제 |
|---|---|---|
| 클로드 하이쿠 4.5 | 범위가 좁은 계약으로 빠르고 효율적으로 업무 수행 | 작은 리팩토링, 반복적인 변환, 또는 간단한 테스트 케이스 |
| 클로드 소네트 5 | 일상적인 코딩에 적합한 지능과 속도의 조화 | 정해진 패턴을 따르는 기능, 일상적인 버그 수정, 또는 코드 설명 |
| Claude Opus 5.5 | 복잡한 추론 및 장시간의 행위자 코딩 작업 | 모호한 실패, 아키텍처 상의 절충, 또는 상호 작용하는 제약 조건이 수반되는 변화 |
| Claude Fable 5.1 | 가장 어렵고 가장 오랫동안 진행된 수사들 | 어려운 작업이 여전히 더 높은 수준의 ‘오푸스’ 노력에도 불구하고 이를 이겨낼 때 발생하는 상황의 악화 |
하이크는 실수를 쉽게 발견할 수 있고 변경 사항이 미미할 때 고려해 볼 가치가 있습니다. 소네트는 일상적인 구현에 더 많은 맥락과 판단이 필요할 때 합리적인 선택지입니다. 오푸스는 문제 이해가 작업의 상당 부분을 차지할 때 고려해 볼 만합니다. 페이블은 까다로운 작업이므로 별도의 검토가 필요하며, 이는 우리가 진행한 세 가지 모델 코딩 테스트의 범위를 벗어난 것이었습니다.
이는 단지 출발점일 뿐, 품질이 자동으로 보장되는 것은 아닙니다. 저렴한 응답도 수차례의 수정이나 광범위한 검토가 필요하면 결국 비용이 많이 들 수 있습니다. 반대로, 이미 검증을 통과한 간단한 변환의 경우 더 큰 모델을 사용한다고 해서 별다른 이점이 없을 수 있습니다. Claude 코드를 넘어 더 폭넓게 활용하려면, Claude 모델 제품군 비교.

그리고 공식 모델 개요 Fable 5.1, Opus 5.5 및 Sonnet 5의 경우 1M 토큰 컨텍스트 윈도우를, Haiku 4.5의 경우 200K 토큰 컨텍스트 윈도우를 나열합니다. 이는 API 사양입니다. 사용자의 Claude 코드 제공자, 계정 및 구성에 따라 세션에서 사용할 수 있는 컨텍스트 옵션이 결정됩니다. 필요한 컨텍스트를 확보하기 위해서는 넓은 창이 유용하지만, 관련 없는 내용으로 채우면 모델에 불필요한 부하를 주게 됩니다.
한 세션에서 모델을 변경하는 방법
실행되는 대화형 세션 내에서 /모델 모델 선택기를 열려면, 모델 행을 선택한 후 s 이 변경 사항이 일회성인 경우, 다음을 누르십시오. 입력 선택한 항목을 사용자 기본값으로 저장합니다. 다음을 입력하면 /모델 소네트 현재 버전에서는 직접 저장도 가능합니다.
변경 사항이 유지될 기간에 따라 명령을 선택하세요
| 여러분의 목표 | 어떻게 해야 할까 | 기본값 변경 사항이 저장되었나요? |
|---|---|---|
| 이 대화형 세션만 전환하기 | 열기 /모델, 모델을 선택한 후, 누르십시오 s. | 아니요. |
| 원하는 모델 저장하기 | 열기 /모델 을 누르고 입력, 또는 다음을 입력하세요 /모델 소네트. | 네, 사용자 설정에 쓰기 권한이 있을 때입니다. |
| Opus에서 단일 세션을 시작하세요 | 시작 클로드 --모델 오푸스. | 아니요. |
| 계정의 런타임 기본값으로 되돌리기 | 사용 /모델 기본값. | 모델 재정의 설정을 초기화합니다. 해결된 기본값은 계정 및 조직 설정에 따라 달라질 수 있습니다. |
| 활성 모델 확인하기 | 달리다 /상태. | 아니요. |
프로젝트 또는 관리 설정, 환경 변수 재정의, 재개된 세션은 다음 실행에 영향을 미칠 수 있습니다. 재현성이 중요한 경우에는 활성 모델을 확인하십시오.
새로운 터미널 세션을 열 때는 시작 시 모델을 선택하세요. 이 명령어들은 VS Code의 내장 터미널에서 Claude Code를 실행할 때도 적용됩니다. 다음 예제에서는 릴리스를 명시적으로 선택합니다:
claude --model claude-opus-5-5
claude --model claude-sonnet-5원하는 세션에 해당하는 명령을 실행하세요. 예시들은 여러 가지 대안입니다. 서로 다른 --모델 공유된 저장된 기본값을 반복적으로 변경하지 않고도 값을 설정할 수 있습니다. 다음을 사용하십시오. /상태 활성 모델을 확인하려면.
다음과 같은 성씨들 opus 그리고 소네트 더 새로운 버전으로 전환할 수 있습니다. 해상도는 제공업체에 따라 달라집니다. Anthropic API는 현재 이를 Opus 5.5 및 Sonnet 5에 매핑하지만, 클라우드 제공업체별 매핑은 다를 수 있습니다. 재현 가능한 실행이 필요한 경우에는 전체 모델 ID를 사용하고, 환경에서 제공업체별 이름이 필요한 경우 해당 이름을 확인하십시오.
Opus 5.5를 사용하려면 Claude 코드 v2.1.280 이상이 필요하며, Sonnet 5를 사용하려면 v2.1.197 이상이 필요합니다. 이 모델 구성 설명서 현재 별칭, 버전 요구 사항 및 공급자 매핑에 대한 참고 자료입니다. 여기에 소개된 명령어는 문서 내 예시일 뿐이며, 실습 섹션에서는 생성된 코드를 별도로 평가합니다.

기본 모델 설정 방법
개인 채무 불이행을 선택하려면 다음에서 모델을 선택하십시오. /모델 을 누르고 입력, 또는 다음과 같은 선택 항목을 입력하세요. /모델 소네트. Claude 코드는 다음을 기록합니다. 모델 해당 파일에 쓰기 권한이 있을 때 사용자 설정의 해당 필드를 확인하세요. 새 세션을 열고 활성 모델을 확인하여 결과를 확인하세요.
저장된 기본 설정은 다른 모든 설정을 덮어쓰지는 않습니다. 시작 플래그는, ANTHROPIC_MODEL 셸, 프로젝트 또는 관리 설정에서, 혹은 사용자 선택을 무시하도록 구성된 조직 기본 설정에 따라 실행 시 다른 항목이 선택될 수 있습니다. 재개된 세션의 경우 저장된 기록에 있는 모델이 유지될 수도 있습니다. 시작 정보를 확인하고 /상태 그 선택이 무산되었다고 단정 짓기 전에.
/모델 기본값 모델 재정의 설정을 지우고, 해당 계정 및 구성에 대한 런타임 기본값으로 되돌립니다. 'word'라는 단어는 기본값 이는 재설정 값일 뿐, 특정 Claude 모델의 이름이 아닙니다. 재현 가능한 모델 식별자로 사용해서는 안 됩니다.
고급 설정을 하려면, ANTHROPIC_DEFAULT_MODEL v2.1.236부터 대체 초기 선택값을 제공합니다. 이 옵션은 명시적인 실행, 환경, 설정 및 조직 선택 항목 아래에 위치하며, 다음을 포함한 값들은 무시합니다. 하이쿠 그리고 opusplan. 일반적인 개인적인 선호도의 경우, 피커를 통해 확인하는 것이 더 쉽습니다. 참조: Claude 코드 설정 공유 구성이 관련된 경우.
Opusplan이 실제로 하는 일
opusplan 계획 모드에서는 Opus를 사용하고, 실행 단계에서는 Sonnet을 사용합니다. 그 매력은 간단명료합니다. 더 강력한 추론 모델을 활용해 접근 방식을 수립한 다음, 일상적인 코딩 모델을 통해 그 계획을 실행에 옮기는 것입니다.
Opusplan은 다음과 같은 작업 방식을 따릅니다.
기록된 라우팅 동작. 이러한 제품군 이름 뒤에 표시된 버전은 사용 중인 제공업체 및 구성에 따라 달라집니다.
이는 모드에 따라 달라지는 규칙입니다. 모든 프롬프트를 검토하여 가장 저렴한 적합한 모델군을 자동으로 선택하지는 않습니다. 기본 모델에는 여전히 제공자 매핑 및 접근 제한이 적용됩니다. 또한 모델명이 비용 절감을 보장하는 것도 아닙니다. 총 비용은 해당 작업에 필요한 계획, 구현, 수정 및 컨텍스트 처리의 양에 따라 달라집니다.
이번에는 디자인을 더 심도 있게 분석하고 구현을 명확한 계획에 따라 진행하는 방식을 고려해 보세요. 사소한 수정이라면, 그런 추가적인 계획은 불필요할 수 있습니다.
두 가지 코딩 과제를 통해 드러난 점은 무엇이었을까
모델 선택에 대한 구체적인 사례를 제시하기 위해, 상호 연관된 요구 사항을 가진 소규모 유니코드 리팩토링과 라우트 선택기 수정 작업을 테스트했습니다. 그 결과, 단순한 점수보다 올바른 함수와 요청된 결과를 완벽하게 구현하는 것 사이의 차이가 더 중요한 것으로 나타났습니다.
코딩 결과물을 어떻게 테스트했는지
2026년 9월 28일, 우리는 Claude Haiku 4.5, Claude Sonnet 5, Claude Opus 5.5에 GlobalGPT까지 동일한 두 가지 자바스크립트 과제를 부여했습니다. 각 모델은 소스 코드를 반환했으며, 저희는 해당 코드를 검토하고 고정된 로컬 검사를 실행했습니다. 이 가이드의 다른 부분에 있는 Claude Code의 모델 전환 지침은 해당 모델의 공식 문서에서 발췌한 것입니다.
각 요청은 4,096 토큰의 출력 제한이 적용되었으며, 도구 사용 및 대화 내역은 없었고, 모델 설정은 기본값을 사용했습니다. 불완전한 응답을 포함하여 평가 가능한 첫 번째 응답을 채택했습니다. Sonnet의 라우팅 작업에서 발생한 한 건의 서비스 오류에 대해 허용된 재시도 횟수 한 번을 적용했으며, 총 7건의 요청으로 6건의 출력이 생성되었습니다.
이 두 가지 간단한 작업은 반환된 코드와 전달 완료 여부를 측정합니다. 이 작업들은 Claude Code의 에이전트 실행, 리포지토리 탐색 또는 전체 개발 세션을 측정하지 않습니다. 실제 요금은 보고되지 않았습니다. 작업당 단 한 번의 관찰 결과만으로는 전반적인 속도나 신뢰성 순위를 판단할 수 없습니다.
정확한 모델 ID 및 평가 조건 확인하기
요청된 ID: claude-haiku-4-5-20251001, 클로드-소네트-5, 및 클로드-작품 5-5. 로컬 테스트는 Node.js v24.19.0에서 실행되었습니다. 안내 문구는 Node.js 18 이상을 대상으로 하며, Node.js 18 자체는 테스트되지 않았습니다.
슬러그 과제의 경우 8개의 행동 그룹이, 경로 선택 과제의 경우 12개의 행동 그룹이 고정되었습니다. 형식 준수 여부와 완전한 제출 여부는 별도로 보고됩니다. 이후 진행된 코드 검토 결과는 별도로 표기되며, 해당 집계 수치는 변경되지 않습니다. 우리는 제출된 소스 코드를 통과시키기 위해 수정하지 않았습니다.
과제 1: 작은 슬러그 리팩토링에서 유니코드 유지하기
이 함수는 비라틴 문자와 결합 기호를 유지하고, 분해된 악센트를 정규화하며, 기존의 ASCII 동작을 그대로 유지해야 했습니다.
| 모델 | 행동 점검 | 응답 형식 | 요청된 배송 완료 |
|---|---|---|---|
| 클로드 하이쿠 4.5 | 8/8 | 오류: 마크다운 래퍼 | 예: 함수 완료 |
| 클로드 소네트 5 | 8/8 | 패스: 원시 JSON | 예: 함수 완료 |
| Claude Opus 5.5 | 8/8 | 패스: 원시 JSON | 예: 함수 완료 |
관측된 결과: 세 기능 모두 동일한 여덟 그룹을 통과했습니다. Sonnet과 Opus 역시 원시 JSON 응답 요건을 준수했습니다. Haiku는 응답을 마크다운으로 감쌌으며, 검토 노트에서 NFC를 문자를 분해하는 것으로 잘못 설명했지만, 코드에서는 NFC를 올바르게 호출했습니다.
이번 제한적인 리팩토링의 경우, 업그레이드 후에도 테스트 대상 함수의 동작이 개선되지 않았습니다. 서식 요건과 함께 제공된 설명의 정확성에 대해서는 여전히 검토가 필요했습니다.
공유된 프롬프트 전체와 입력 코드를 확인하세요
SYSTEM
You are completing a bounded JavaScript coding task. Return only the requested JSON object with complete source files and brief review notes. Do not claim to have edited files, run commands, or used tools. You have no tool access. Do not include hidden reasoning.
USER
Implement the following Node.js 18+ CommonJS function from its current source. Return complete replacement source for src/slugify.js. Do not add dependencies, imports, I/O, or other files.
Required behavior:
1. slugify(value) returns an empty string for every non-string input.
2. Normalize string input to Unicode NFC, then lowercase it using JavaScript toLowerCase().
3. Preserve Unicode letters, numbers, and combining marks. Replace each consecutive run of all other characters with one ASCII hyphen.
4. Trim leading and trailing hyphens. Preserve the CommonJS export { slugify }.
5. Existing ASCII behavior must continue to work. Examples: Hello, World! -> hello-world; two words -> two-words; null -> empty string.
6. Additional examples: Café déjà vu -> café-déjà-vu; decomposed Cafe\u0301 -> café; 中文 测试 42 -> 中文-测试-42; **hi___there** -> hi-there; only punctuation or whitespace -> empty string.
Current src/slugify.js:
```javascript
function slugify(value) {
if (typeof value !== "string") return "";
return value
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
module.exports = { slugify };
```
Return exactly one JSON object with this shape:
{"files":{"src/slugify.js":"complete JavaScript source as a JSON string"},"review_notes":["at most four brief implementation or assumption notes"]}
No Markdown wrapper. You are returning code for someone else to evaluate locally; do not say that tests passed or that you ran them.
반환된 Claude Haiku 4.5의 전체 응답 보기
```json
{
"files": {
"src/slugify.js": "function slugify(value) {\n if (typeof value !== \"string\") return \"\";\n \n // Normalize to NFC and lowercase\n const normalized = value.normalize(\"NFC\").toLowerCase();\n \n // Replace consecutive runs of non-letter, non-number, non-combining-mark characters with a single hyphen\n // Unicode letter categories: L* (Lu, Ll, Lt, Lm, Lo)\n // Unicode number categories: N* (Nd, Nl, No)\n // Unicode combining mark categories: M* (Mn, Mc, Me)\n const slugged = normalized.replace(/[^\\p{L}\\p{N}\\p{M}]+/gu, \"-\");\n \n // Trim leading and trailing hyphens\n return slugged.replace(/^-+|-+$/g, \"\");\n}\n\nmodule.exports = { slugify };"
},
"review_notes": [
"Uses String.prototype.normalize(\"NFC\") to decompose composed characters like é into base+combining mark, then toLowerCase() applies to normalized string",
"Unicode property escapes (\\p{L}, \\p{N}, \\p{M}) with /u flag preserve all Unicode letters, numbers, and combining marks while replacing other consecutive characters with single hyphens",
"Existing ASCII behavior is preserved: punctuation and whitespace are replaced with hyphens, then leading/trailing hyphens are trimmed",
"Non-string inputs return empty string as required; normalized decomposed characters (e.g., Cafe\\u0301) are handled correctly by NFC normalization"
]
}
```반환된 Claude Sonnet 5의 전체 응답을 확인하세요
{"files":{"src/slugify.js":"function slugify(value) {\n if (typeof value !== \"string\") return \"\";\n return value\n .normalize(\"NFC\")\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}\\p{M}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nmodule.exports = { slugify };\n"},"review_notes":["Used Unicode property escapes (\\p{L}, \\p{N}, \\p{M}) with the u flag to preserve letters, numbers, and combining marks across scripts.","NFC normalization ensures decomposed sequences like Cafe + combining acute compose to café before lowercasing.","Runs of any other characters (punctuation, whitespace, symbols) collapse to a single hyphen, then leading/trailing hyphens are trimmed."]}반환된 Claude Opus 5.5의 전체 응답을 확인하세요
{"files":{"src/slugify.js":"function slugify(value) {\n if (typeof value !== \"string\") return \"\";\n return value\n .normalize(\"NFC\")\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}\\p{M}]+/gu, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\nmodule.exports = { slugify };\n"},"review_notes":["The input is normalized to NFC before lowercasing, so decomposed input like Cafe\\u0301 composes to café.","A Unicode property regex with the u flag keeps letters (\\p{L}), numbers (\\p{N}) and combining marks (\\p{M}). Each run of any other characters, including underscores and whitespace, becomes one hyphen.","The explicit trim() was removed because the final step strips leading and trailing hyphens, which covers surrounding whitespace.","Lowercasing happens after NFC normalization, as the spec orders. toLowerCase can occasionally produce non-NFC output for rare characters, and it is not re-normalized here."]}과제 2: 경로 선택기를 수정하고 관련 테스트를 제출하세요.
이 함수는 최장 접두사 일치, 연산자 우선순위, 세그먼트 경계, 형식이 잘못된 규칙, 불변 입력 등을 처리해야 했습니다. 요청된 응답에는 새로운 테스트 파일도 포함되어 있었습니다.
| 모델 | 행동 점검 | 응답 형식 | 요청된 배송 완료 |
|---|---|---|---|
| 클로드 하이쿠 4.5 | 12/12는 완전 함수에 한함 | 오류: JSON이 잘림 | 아니요: 테스트 파일이 잘렸습니다. |
| 클로드 소네트 5 | 12/12는 완전 함수에 한함 | 오류: JSON이 잘림 | 아니요: 테스트 파일이 잘렸습니다. |
| Claude Opus 5.5 | 12/12는 완전 함수에 한함 | 오류: JSON이 잘림 | 아니요: 테스트 파일이 잘렸습니다. |
관측된 결과: 어떤 모델도 지정된 출력 한도 내에서 요청된 두 개의 파일 응답을 완료하지 못했습니다. 세 모델 모두 4,096개의 완료 토큰을 보고한 시점에서 중단되었습니다. 각 모델은 이미 12개의 고정된 행동 그룹을 통과한 완전한 라우터 함수를 반환한 상태였으며, 새로운 테스트 파일은 불완전하여 실행되지 않았습니다.
우리는 완전히 닫힌 소스 코드 문자열만 추출했습니다. 원본의 잘린 응답 내용은 아래에 그대로 유지되어 있습니다. 따라서 함수 통과 점수가 나왔다고 해서 전체 요청이 성공한 것은 아닙니다.
코드 검토에서 발견된 추가 사항: 동점(루트 값이 같은 경우)
정기 점검을 마친 후, 하이쿠가 정규화되었다는 사실을 확인했습니다. /// ... 대신 빈 문자열로 /. 별도의 로컬 검사에서는 접두사가 포함된 두 개의 GET 규칙을 사용했으며, /// 그리고 /, 대상은 먼저 그리고 두 번째 그 순서대로. ~의 경우 /무엇이든, 계약에 따르면 먼저.
하이쿠가 돌아왔다 두 번째; 소넷과 오푸스가 돌아왔다 먼저. 이 추가 검사는 코드 검토 과정에서 제기된 것으로, 테스트 전에 설정된 12가지 검사 항목과는 별개입니다. 이 검사는 해당 항목들의 점수에 영향을 미치지 않습니다.
Sonnet은 허용된 재시도 횟수 내에 표시된 잘린 응답을 반환하기 전에 한 번의 서비스 오류를 겪었습니다. 이 서비스 오류는 코드 품질과는 별개의 문제입니다. 어떤 모델도 답변을 개선하거나 완성하기 위한 추가 시도를 받지 못했습니다.
공유된 프롬프트 전체와 입력 코드를 확인하세요
SYSTEM
You are completing a bounded JavaScript coding task. Return only the requested JSON object with complete source files and brief review notes. Do not claim to have edited files, run commands, or used tools. You have no tool access. Do not include hidden reasoning.
USER
Repair a Node.js 18+ CommonJS route selector. Return complete replacement src/router.js and an additional test/router-added.test.js using node:test and node:assert/strict. Do not add dependencies or edit existing tests. Do not use I/O other than the test runner, network, child processes, timers, or any files beyond the two requested outputs.
Contract for chooseRoute(request, rules):
1. Invalid request or rules returns null, without throwing. Request must be a non-null, non-array object; method must be a nonempty ASCII-letter string; path must be a string beginning with /. Rules must be an array.
2. Before matching, remove the request path's query and fragment starting at the earliest ? or #. Do not URL-decode or collapse interior slashes.
3. Ignore invalid rules. A valid rule is a non-null, non-array object with method '*' or a nonempty ASCII-letter string, prefix a string starting with / containing neither ? nor #, and target a nonempty string.
4. Compare methods case-insensitively. Prefer any matching exact-method rule over all wildcard-method '*' rules, even if a wildcard has a longer prefix. Use wildcard rules only if no exact-method route matches the path.
5. Remove trailing slashes from a rule prefix, except that an all-slash prefix normalizes to /. A prefix matches the path only at a segment boundary: /api matches /api and /api/users, never /apix. Root / matches every valid request path.
6. Within the chosen method group, choose the longest normalized matching prefix. On equal prefix length, choose the earlier rule in the original input array. Return its target. Return null if none matches.
7. Do not mutate the request, rules array, or rule objects, including order or prefix strings. Preserve module.exports = { chooseRoute }.
8. Add tests for segment boundaries, query/fragment removal, method precedence, ties, malformed inputs and immutability. Existing simple GET /api/users and POST-no-match cases must still pass.
Current src/router.js:
```javascript
function chooseRoute(request, rules) {
if (!request || !Array.isArray(rules)) return null;
for (const rule of rules) {
if (request.method === rule.method && request.path.startsWith(rule.prefix)) {
return rule.target;
}
}
return null;
}
module.exports = { chooseRoute };
```
Existing regression cases (unchanged): rules [{method:'GET',prefix:'/api',target:'api'},{method:'GET',prefix:'/api/admin',target:'admin'}]; GET /api/users returns 'api'; POST /api/users returns null.
Return exactly one JSON object with this shape:
{"files":{"src/router.js":"complete JavaScript source as a JSON string","test/router-added.test.js":"complete JavaScript tests as a JSON string"},"review_notes":["at most four brief implementation or assumption notes"]}
No Markdown wrapper. You are returning code for someone else to evaluate locally; do not say you edited files, ran tests, or observed passing results.
반환된 Claude Haiku 4.5의 전체 응답 보기
이것이 수신된 응답의 전체 내용입니다. 제한에 도달하여 출력 도중에 끝났으며, 누락된 내용은 채워지지 않았습니다.
```json
{
"files": {
"src/router.js": "function chooseRoute(request, rules) {\n // Validate request\n if (request === null || Array.isArray(request) || typeof request !== 'object') {\n return null;\n }\n if (typeof request.method !== 'string' || request.method.length === 0 || !/^[a-zA-Z]+$/.test(request.method)) {\n return null;\n }\n if (typeof request.path !== 'string' || !request.path.startsWith('/')) {\n return null;\n }\n\n // Validate rules\n if (!Array.isArray(rules)) {\n return null;\n }\n\n // Remove query and fragment from path\n const cleanPath = request.path.split(/[?#]/)[0];\n\n // Filter and validate rules\n const validRules = [];\n for (const rule of rules) {\n if (rule === null || Array.isArray(rule) || typeof rule !== 'object') {\n continue;\n }\n \n const method = rule.method;\n if (typeof method !== 'string' || (method !== '*' && (method.length === 0 || !/^[a-zA-Z]+$/.test(method)))) {\n continue;\n }\n \n const prefix = rule.prefix;\n if (typeof prefix !== 'string' || !prefix.startsWith('/') || prefix.includes('?') || prefix.includes('#')) {\n continue;\n }\n \n const target = rule.target;\n if (typeof target !== 'string' || target.length === 0) {\n continue;\n }\n \n validRules.push(rule);\n }\n\n // Normalize prefixes and partition into exact-method and wildcard groups\n const exactMethodRules = [];\n const wildcardRules = [];\n \n for (const rule of validRules) {\n const normalizedPrefix = rule.prefix === '/' ? '/' : rule.prefix.replace(/\/+$/, '');\n \n const entry = {\n originalRule: rule,\n normalizedPrefix: normalizedPrefix,\n method: rule.method\n };\n \n if (rule.method === '*') {\n wildcardRules.push(entry);\n } else {\n exactMethodRules.push(entry);\n }\n }\n\n const requestMethod = request.method.toUpperCase();\n\n // Helper to check if a prefix matches the path at a segment boundary\n function prefixMatches(prefix, path) {\n if (prefix === '/') {\n return true;\n }\n if (!path.startsWith(prefix)) {\n return false;\n }\n // Check segment boundary: next character must be '/' or end of string\n const nextChar = path[prefix.length];\n return nextChar === undefined || nextChar === '/';\n }\n\n // Try exact-method rules first\n let bestMatch = null;\n let bestLength = -1;\n \n for (const entry of exactMethodRules) {\n if (entry.method.toUpperCase() === requestMethod) {\n if (prefixMatches(entry.normalizedPrefix, cleanPath)) {\n if (entry.normalizedPrefix.length > bestLength) {\n bestMatch = entry.originalRule;\n bestLength = entry.normalizedPrefix.length;\n }\n }\n }\n }\n \n if (bestMatch !== null) {\n return bestMatch.target;\n }\n\n // Try wildcard rules only if no exact-method match\n bestMatch = null;\n bestLength = -1;\n \n for (const entry of wildcardRules) {\n if (prefixMatches(entry.normalizedPrefix, cleanPath)) {\n if (entry.normalizedPrefix.length > bestLength) {\n bestMatch = entry.originalRule;\n bestLength = entry.normalizedPrefix.length;\n }\n }\n }\n \n if (bestMatch !== null) {\n return bestMatch.target;\n }\n\n return null;\n}\n\nmodule.exports = { chooseRoute };",
"test/router-added.test.js": "const test = require('node:test');\nconst assert = require('node:assert/strict');\nconst { chooseRoute } = require('../src/router.js');\n\n// Regression: existing test cases\ntest('GET /api/users matches /api prefix (exact method)', () => {\n const rules = [\n { method: 'GET', prefix: '/api', target: 'api' },\n { method: 'GET', prefix: '/api/admin', target: 'admin' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users' }, rules), 'api');\n});\n\ntest('POST /api/users returns null (no matching method)', () => {\n const rules = [\n { method: 'GET', prefix: '/api', target: 'api' },\n { method: 'GET', prefix: '/api/admin', target: 'admin' }\n ];\n assert.equal(chooseRoute({ method: 'POST', path: '/api/users' }, rules), null);\n});\n\n// Invalid request handling\ntest('null request returns null', () => {\n assert.equal(chooseRoute(null, []), null);\n});\n\ntest('array request returns null', () => {\n assert.equal(chooseRoute([], []), null);\n});\n\ntest('request with missing method returns null', () => {\n assert.equal(chooseRoute({ path: '/api' }, []), null);\n});\n\ntest('request with empty method returns null', () => {\n assert.equal(chooseRoute({ method: '', path: '/api' }, []), null);\n});\n\ntest('request with non-letter method returns null', () => {\n assert.equal(chooseRoute({ method: 'GET-POST', path: '/api' }, []), null);\n});\n\ntest('request with numeric method returns null', () => {\n assert.equal(chooseRoute({ method: '123', path: '/api' }, []), null);\n});\n\ntest('request with missing path returns null', () => {\n assert.equal(chooseRoute({ method: 'GET' }, []), null);\n});\n\ntest('request with path not starting with / returns null', () => {\n assert.equal(chooseRoute({ method: 'GET', path: 'api' }, []), null);\n});\n\ntest('null rules returns null', () => {\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, null), null);\n});\n\ntest('non-array rules returns null', () => {\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, { method: 'GET' }), null);\n});\n\n// Query and fragment removal\ntest('query string is removed before matching', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users?foo=bar' }, rules), 'found');\n});\n\ntest('fragment is removed before matching', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users#section' }, rules), 'found');\n});\n\ntest('earliest ? or # is used as boundary', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users?foo#bar' }, rules), 'found');\n});\n\ntest('fragment before query is used as boundary', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users#foo?bar' }, rules), 'found');\n});\n\n// Segment boundary matching\ntest('prefix /api matches /api/users at segment boundary', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users' }, rules), 'found');\n});\n\ntest('prefix /api does not match /apix (no segment boundary)', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/apix' }, rules), null);\n});\n\ntest('prefix /api matches /api exactly at segment boundary', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'found');\n});\n\ntest('root / matches any valid path', () => {\n const rules = [{ method: 'GET', prefix: '/', target: 'root' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/anything/here' }, rules), 'root');\n});\n\ntest('root / matches /', () => {\n const rules = [{ method: 'GET', prefix: '/', target: 'root' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/' }, rules), 'root');\n});\n\n// Prefix normalization (trailing slash removal)\ntest('trailing slash is removed from prefix', () => {\n const rules = [{ method: 'GET', prefix: '/api/', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users' }, rules), 'found');\n});\n\ntest('multiple trailing slashes are removed', () => {\n const rules = [{ method: 'GET', prefix: '/api///', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users' }, rules), 'found');\n});\n\ntest('all-slash prefix normalizes to single /', () => {\n const rules = [{ method: 'GET', prefix: '///', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'found');\n});\n\n// Method case-insensitivity\ntest('method comparison is case-insensitive', () => {\n const rules = [{ method: 'get', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'found');\n});\n\ntest('request method case does not affect matching', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'found' }];\n assert.equal(chooseRoute({ method: 'get', path: '/api' }, rules), 'found');\n});\n\n// Exact-method vs wildcard precedence\ntest('exact-method rule preferred over wildcard with longer prefix', () => {\n const rules = [\n { method: '*', prefix: '/api/admin/users', target: 'wildcard' },\n { method: 'GET', prefix: '/api', target: 'exact' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/test' }, rules), 'exact');\n});\n\ntest('wildcard rule used when no exact-method matches', () => {\n const rules = [\n { method: '*', prefix: '/api', target: 'wildcard' },\n { method: 'GET', prefix: '/other', target: 'exact' }\n ];\n assert.equal(chooseRoute({ method: 'POST', path: '/api/users' }, rules), 'wildcard');\n});\n\n// Longest prefix matching\ntest('longest matching prefix is chosen', () => {\n const rules = [\n { method: 'GET', prefix: '/api', target: 'short' },\n { method: 'GET', prefix: '/api/admin', target: 'long' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/admin/users' }, rules), 'long');\n});\n\n// Tie-breaking (first rule wins on equal prefix length)\ntest('first rule wins on equal prefix length', () => {\n const rules = [\n { method: 'GET', prefix: '/api', target: 'first' },\n { method: 'GET', prefix: '/api', target: 'second' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users' }, rules), 'first');\n});\n\n// Invalid rules are ignored\ntest('null rule is ignored', () => {\n const rules = [\n null,\n { method: 'GET', prefix: '/api', target: 'found' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'found');\n});\n\ntest('array rule is ignored', () => {\n const rules = [\n [],\n { method: 'GET', prefix: '/api', target: 'found' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'found');\n});\n\ntest('rule with invalid method is ignored', () => {\n const rules = [\n { method: '', prefix: '/api', target: 'invalid' },\n { method: 'GET', prefix: '/api', target: 'valid' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'valid');\n});\n\ntest('rule with non-letter method (if not wildcard) is ignored', () => {\n const rules = [\n { method: 'G3T', prefix: '/api', target: 'invalid' },\n { method: 'GET', prefix: '/api', target: 'valid' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'valid');\n});\n\ntest('rule with prefix not starting with / is ignored', () => {\n const rules = [\n { method: 'GET', prefix: 'api', target: 'invalid' },\n { method: 'GET', prefix: '/api', target: 'valid' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'valid');\n});\n\ntest('rule with query in prefix is ignored', () => {\n const rules = [\n { method: 'GET', prefix: '/api?foo=bar', target: 'invalid' },\n { method: 'GET', prefix: '/api', target: 'valid' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'valid');\n});\n\ntest('rule with fragment in prefix is ignored', () => {\n const rules = [\n { method: 'GET', prefix: '/api#section', target: 반환된 Claude Sonnet 5의 전체 응답을 확인하세요
이것이 수신된 응답의 전체 내용입니다. 제한에 도달하여 출력 도중에 끝났으며, 누락된 내용은 채워지지 않았습니다.
{"files":{"src/router.js":"function isValidRequest(request) {\n return (\n request !== null &&\n typeof request === 'object' &&\n !Array.isArray(request) &&\n typeof request.method === 'string' &&\n request.method.length > 0 &&\n /^[A-Za-z]+$/.test(request.method) &&\n typeof request.path === 'string' &&\n request.path.startsWith('/')\n );\n}\n\nfunction isValidRule(rule) {\n if (rule === null || typeof rule !== 'object' || Array.isArray(rule)) {\n return false;\n }\n const { method, prefix, target } = rule;\n\n if (typeof method !== 'string') return false;\n if (method !== '*' && !(method.length > 0 && /^[A-Za-z]+$/.test(method))) {\n return false;\n }\n\n if (\n typeof prefix !== 'string' ||\n !prefix.startsWith('/') ||\n prefix.includes('?') ||\n prefix.includes('#')\n ) {\n return false;\n }\n\n if (typeof target !== 'string' || target.length === 0) return false;\n\n return true;\n}\n\nfunction normalizePrefix(prefix) {\n if (/^\/+$/.test(prefix)) return '/';\n return prefix.replace(/\/+$/, '');\n}\n\nfunction prefixMatches(normalizedPrefix, path) {\n if (normalizedPrefix === '/') return true;\n if (path === normalizedPrefix) return true;\n return path.startsWith(normalizedPrefix + '/');\n}\n\nfunction stripQueryAndFragment(path) {\n let cutIndex = path.length;\n for (let i = 0; i < path.length; i++) {\n const c = path[i];\n if (c === '?' || c === '#') {\n cutIndex = i;\n break;\n }\n }\n return path.slice(0, cutIndex);\n}\n\nfunction chooseRoute(request, rules) {\n if (!isValidRequest(request) || !Array.isArray(rules)) return null;\n\n const path = stripQueryAndFragment(request.path);\n const reqMethodLower = request.method.toLowerCase();\n\n let bestExact = null; // { len, idx, target }\n let bestWildcard = null;\n\n for (let idx = 0; idx < rules.length; idx++) {\n const rule = rules[idx];\n if (!isValidRule(rule)) continue;\n\n const normPrefix = normalizePrefix(rule.prefix);\n if (!prefixMatches(normPrefix, path)) continue;\n\n const len = normPrefix.length;\n\n if (rule.method === '*') {\n if (\n bestWildcard === null ||\n len > bestWildcard.len ||\n (len === bestWildcard.len && idx < bestWildcard.idx)\n ) {\n bestWildcard = { len, idx, target: rule.target };\n }\n } else if (rule.method.toLowerCase() === reqMethodLower) {\n if (\n bestExact === null ||\n len > bestExact.len ||\n (len === bestExact.len && idx < bestExact.idx)\n ) {\n bestExact = { len, idx, target: rule.target };\n }\n }\n }\n\n if (bestExact !== null) return bestExact.target;\n if (bestWildcard !== null) return bestWildcard.target;\n return null;\n}\n\nmodule.exports = { chooseRoute };\n","test/router-added.test.js":"const test = require('node:test');\nconst assert = require('node:assert/strict');\nconst { chooseRoute } = require('../src/router');\n\ntest('segment boundary: prefix matches at slash boundary only', () => {\n const rules = [\n { method: 'GET', prefix: '/api', target: 'api' },\n ];\n assert.equal(\n chooseRoute({ method: 'GET', path: '/api/users' }, rules),\n 'api'\n );\n assert.equal(\n chooseRoute({ method: 'GET', path: '/api' }, rules),\n 'api'\n );\n assert.equal(\n chooseRoute({ method: 'GET', path: '/apix' }, rules),\n null\n );\n});\n\ntest('root prefix matches every valid path', () => {\n const rules = [{ method: 'GET', prefix: '/', target: 'root' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/' }, rules), 'root');\n assert.equal(\n chooseRoute({ method: 'GET', path: '/anything/here' }, rules),\n 'root'\n );\n});\n\ntest('trailing slashes in rule prefix are normalized', () => {\n const rules = [{ method: 'GET', prefix: '/api///', target: 'api' }];\n assert.equal(\n chooseRoute({ method: 'GET', path: '/api/users' }, rules),\n 'api'\n );\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'api');\n});\n\ntest('all-slash prefix normalizes to /', () => {\n const rules = [{ method: 'GET', prefix: '////', target: 'root' }];\n assert.equal(\n chooseRoute({ method: 'GET', path: '/whatever' }, rules),\n 'root'\n );\n});\n\ntest('query and fragment are stripped before matching', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'api' }];\n assert.equal(\n chooseRoute({ method: 'GET', path: '/api/users?x=1&y=2' }, rules),\n 'api'\n );\n assert.equal(\n chooseRoute({ method: 'GET', path: '/api/users#section' }, rules),\n 'api'\n );\n assert.equal(\n chooseRoute({ method: 'GET', path: '/api/users?x=1#frag' }, rules),\n 'api'\n );\n});\n\ntest('query/fragment stripping does not decode or collapse slashes', () => {\n const rules = [{ method: 'GET', prefix: '/a//b', target: 'weird' }];\n assert.equal(\n chooseRoute({ method: 'GET', path: '/a//b?x=1' }, rules),반환된 Claude Opus 5.5의 전체 응답을 확인하세요
이것이 수신된 응답의 전체 내용입니다. 제한에 도달하여 출력 도중에 끝났으며, 누락된 내용은 채워지지 않았습니다.
{"files":{"src/router.js":"'use strict';\n\nconst LETTERS = /^[A-Za-z]+$/;\n\nfunction isPlainObject(value) {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isMethodName(value) {\n return typeof value === 'string' && value.length > 0 && LETTERS.test(value);\n}\n\nfunction stripQueryAndFragment(path) {\n const q = path.indexOf('?');\n const h = path.indexOf('#');\n let cut = -1;\n if (q !== -1 && h !== -1) cut = Math.min(q, h);\n else if (q !== -1) cut = q;\n else if (h !== -1) cut = h;\n return cut === -1 ? path : path.slice(0, cut);\n}\n\nfunction normalizePrefix(prefix) {\n let end = prefix.length;\n while (end > 0 && prefix[end - 1] === '/') end -= 1;\n return end === 0 ? '/' : prefix.slice(0, end);\n}\n\nfunction prefixMatches(path, prefix) {\n if (prefix === '/') return true;\n return path === prefix || path.startsWith(prefix + '/');\n}\n\nfunction chooseRoute(request, rules) {\n try {\n if (!isPlainObject(request) || !Array.isArray(rules)) return null;\n const method = request.method;\n const rawPath = request.path;\n if (!isMethodName(method)) return null;\n if (typeof rawPath !== 'string' || rawPath[0] !== '/') return null;\n\n const path = stripQueryAndFragment(rawPath);\n const upperMethod = method.toUpperCase();\n\n let bestExact = null;\n let bestWildcard = null;\n\n const count = rules.length;\n for (let i = 0; i < count; i += 1) {\n const rule = rules[i];\n if (!isPlainObject(rule)) continue;\n const ruleMethod = rule.method;\n const prefix = rule.prefix;\n const target = rule.target;\n const isWildcard = ruleMethod === '*';\n if (!isWildcard && !isMethodName(ruleMethod)) continue;\n if (typeof prefix !== 'string' || prefix[0] !== '/') continue;\n if (prefix.indexOf('?') !== -1 || prefix.indexOf('#') !== -1) continue;\n if (typeof target !== 'string' || target.length === 0) continue;\n\n const normalized = normalizePrefix(prefix);\n if (!prefixMatches(path, normalized)) continue;\n\n const candidate = { length: normalized.length, target };\n if (isWildcard) {\n if (bestWildcard === null || candidate.length > bestWildcard.length) bestWildcard = candidate;\n } else if (ruleMethod.toUpperCase() === upperMethod) {\n if (bestExact === null || candidate.length > bestExact.length) bestExact = candidate;\n }\n }\n\n if (bestExact !== null) return bestExact.target;\n if (bestWildcard !== null) return bestWildcard.target;\n return null;\n } catch (err) {\n return null;\n }\n}\n\nmodule.exports = { chooseRoute };\n","test/router-added.test.js":"'use strict';\n\nconst test = require('node:test');\nconst assert = require('node:assert/strict');\nconst { chooseRoute } = require('../src/router.js');\n\nconst baseRules = [\n { method: 'GET', prefix: '/api', target: 'api' },\n { method: 'GET', prefix: '/api/admin', target: 'admin' }\n];\n\ntest('existing regression cases still hold', () => {\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users' }, baseRules), 'api');\n assert.equal(chooseRoute({ method: 'POST', path: '/api/users' }, baseRules), null);\n});\n\ntest('longest prefix wins within method group', () => {\n assert.equal(chooseRoute({ method: 'GET', path: '/api/admin/x' }, baseRules), 'admin');\n assert.equal(chooseRoute({ method: 'GET', path: '/api/admin' }, baseRules), 'admin');\n});\n\ntest('segment boundaries', () => {\n const rules = [{ method: 'GET', prefix: '/api', target: 'api' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'api');\n assert.equal(chooseRoute({ method: 'GET', path: '/api/' }, rules), 'api');\n assert.equal(chooseRoute({ method: 'GET', path: '/api/users' }, rules), 'api');\n assert.equal(chooseRoute({ method: 'GET', path: '/apix' }, rules), null);\n assert.equal(chooseRoute({ method: 'GET', path: '/ap' }, rules), null);\n});\n\ntest('trailing slashes in prefixes are normalized, all-slash is root', () => {\n const rules = [{ method: 'GET', prefix: '/api///', target: 'api' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api' }, rules), 'api');\n assert.equal(chooseRoute({ method: 'GET', path: '/apix' }, rules), null);\n const rootRules = [{ method: 'GET', prefix: '///', target: 'root' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/' }, rootRules), 'root');\n assert.equal(chooseRoute({ method: 'GET', path: '/anything/else' }, rootRules), 'root');\n assert.equal(chooseRoute({ method: 'GET', path: '/x' }, [{ method: 'GET', prefix: '/', target: 'r' }]), 'r');\n});\n\ntest('query and fragment are removed at earliest ? or #', () => {\n assert.equal(chooseRoute({ method: 'GET', path: '/api?next=/api/admin' }, baseRules), 'api');\n assert.equal(chooseRoute({ method: 'GET', path: '/api#/admin' }, baseRules), 'api');\n assert.equal(chooseRoute({ method: 'GET', path: '/apix?/api' }, baseRules), null);\n assert.equal(chooseRoute({ method: 'GET', path: '/api/admin#frag?q' }, baseRules), 'admin');\n assert.equal(chooseRoute({ method: 'GET', path: '/api?q#/admin' }, baseRules), 'api');\n});\n\ntest('no URL decoding and no slash collapsing', () => {\n const rules = [{ method: 'GET', prefix: '/api/admin', target: 'admin' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/api%2Fadmin' }, rules), null);\n assert.equal(chooseRoute({ method: 'GET', path: '/api//admin' }, rules), null);\n assert.equal(chooseRoute({ method: 'GET', path: '/api//admin' }, baseRules), 'api');\n});\n\ntest('methods compare case-insensitively', () => {\n const rules = [{ method: 'get', prefix: '/a', target: 'lower' }];\n assert.equal(chooseRoute({ method: 'GET', path: '/a' }, rules), 'lower');\n assert.equal(chooseRoute({ method: 'gEt', path: '/a/b' }, rules), 'lower');\n});\n\ntest('exact method beats longer wildcard; wildcard used only as fallback', () => {\n const rules = [\n { method: '*', prefix: '/api/admin', target: 'wild-admin' },\n { method: 'GET', prefix: '/', target: 'get-root' },\n { method: 'POST', prefix: '/other', target: 'post-other' },\n { method: '*', prefix: '/api', target: 'wild-api' }\n ];\n assert.equal(chooseRoute({ method: 'GET', path: '/api/admin/x' }, rules), 'get-root');\n assert.equal(chooseRoute({ method: 'POST', path: '/api/admin/x' }, rules), 'wild-admin');\n assert.equal(chooseRoute({ method: 'POST', path: '/api/x' }, rules), 'wild-api');\n assert.equal(chooseRoute({ method: 'POST', path: '/other/1' }, rules), 'post-other');\n assert.equal(chooseRoute({ method: 'PUT', path: '/nothing' }, rules), null);\n});\n\ntest('ties go to earlier rule in input order', () => {\n const rules = [\n { method: 'GET', pref4,096 토큰이라는 상한선은 이번 테스트를 위해 정해진 출력 예산일 뿐, 특정 모델의 공식적으로 명시된 최대값은 아닙니다. 라우팅 결과를 보면, 더 큰 규모의 요청을 보내기 전에 실용적인 점검이 필요함을 시사합니다. 즉, 반환될 것으로 예상되는 구현 결과, 테스트 결과 및 설명의 크기를 미리 추정해 보아야 합니다. 결과물을 분할하거나 출력 공간을 더 확보하는 것이 적절할 수 있습니다. 저희는 다른 설정으로 이 출력 결과를 재실행하지는 않았습니다.
자신의 과제를 수행할 때는 다음 세 가지를 별도로 평가하십시오. 응답이 완전한지, 코드가 유용한 검증을 통과하는지, 그리고 검토 과정에서 해당 검증에서 놓친 누락 사항이 발견되는지 여부입니다. 12개의 함수 검증을 통과했다고 해서, 내용이 축약된 두 개의 파일로 구성된 응답 중 어느 것도 완성된 결과물로 간주될 수는 없습니다.
어떤 모델을 선택하느냐에 따라 비용이 어떻게 달라지는가
Claude Code는 코딩 도구이며, Haiku, Sonnet, Opus, Fable은 지원되는 환경 내에서 선택할 수 있는 모델입니다. 인증 및 결제 방식 또한 선택 사항입니다. 모델을 변경한다고 해서 구독이 API 계정으로 전환되거나 모든 사용 제한이 사라지는 것은 아닙니다.
| 청구 경로 | 볼거리 | 모델 선택을 어떻게 해석해야 할까 |
|---|---|---|
| Claude 구독 이용 권한 | 포함된 요금제 사용량 및 별도로 활성화된 모든 사용량 크레딧 | 모델에 따라 할당량 소모 방식이 다를 수 있으므로, 해당 요금제의 이용 가능한 모델과 한도를 확인하시기 바랍니다. |
| Anthropic API 액세스 | 입력/출력 토큰, 캐싱 및 적용되는 API 요금 | 현재 요금표와 실제 사용량을 바탕으로 계산한 후, 청구 콘솔에서 요금을 확인하십시오. |
| 클라우드 제공업체 또는 게이트웨이 | 귀하의 서비스 제공자가 지원하는 ID, 요금 및 청구 내역 | Anthropic의 정가 예시는 귀하의 서비스 제공업체가 청구하는 금액과 다를 수 있습니다. |
Claude 코드에서, /사용 세션 토큰 정보와, 해당되는 경우 구독 사용량 막대를 표시합니다. 표시된 금액은 목록 또는 구성된 요율을 기준으로 로컬에서 산출된 예상치이며, 청구서가 아닙니다. Pro 및 Max 구독자의 경우, 해당 세션 수치가 구독에 추가 API 요금이 부과되었음을 의미하지는 않습니다. 자세한 내용은 Claude 코드 가격 책정 및 청구 경로 결제 방법을 결정 중이라면.
현재 API 가격 및 동등 토큰 예시
Anthropic의 입력/출력 토큰 100만 개당 기본 요율은 다음과 같습니다. 하이쿠 4.5: $1/$5; Sonnet 5: $2/$10; Opus 5.5: $4/$20; Fable 5.1: $10/$50, 2026년 9월 28일 확인. 다음 차트는 계산 비교를 용이하게 하기 위해 토큰 거래량을 일정하게 유지한 것입니다.
동일한 토큰 거래량, 네 가지 다른 API 비용
예시: 캐시에 저장되지 않은 입력 토큰 100,000개 + 출력 토큰 10,000개, 2026년 9월 28일 기준으로 확인된 Anthropic의 일반 API 정가를 기준으로 합니다.
요금 및 계산 방법 보기
| 모델 | 입력 / 1m | 출력 / 1m | 예시 합계 |
|---|---|---|---|
| 하이쿠 4.5 | $1 | $5 | $0.15 |
| 소네트 5 | $2 | $10 | $0.30 |
| 작품 5.5 | $4 | $20 | $0.60 |
| Fable 5.1 | $10 | $50 | $1.50 |
계산식: 0.1 × 입력 비율 + 0.01 × 출력 비율. Opus 5.5의 경우: 0.1 × $4 + 0.01 × $20 = $0.60.
이것은 고정 토큰을 가정한 예시입니다. 실제 작업에서는 소비되는 토큰 총량이 다를 수 있으며, 캐시 쓰기 및 읽기, 재시도, 도구 사용, 제공업체 청구, 구독 한도 등은 별도로 계산해야 합니다.
Source: Anthropic API 가격.

실제 Claude 코드 작업은 일련의 요청으로 이루어집니다. 이전의 컨텍스트가 다시 전송될 수도 있고, 모델이 더 심층적으로 분석할 수도 있으며, 도구나 추가 에이전트가 더 많은 작업을 생성할 수도 있습니다. 프롬프트 캐싱 역시 쓰기 속도와 읽기 속도가 서로 다릅니다. 따라서 예시에서 가장 낮은 막대가 승인된 변경 사항당 최저 비용을 나타내는 것은 아닙니다.
업무의 질을 떨어뜨리지 않으면서 비용을 관리하는 5가지 방법
- 모델을 선택하기 전에 ‘수용’의 정의를 명확히 하십시오. 예상되는 동작, 예외적인 경우, 그리고 기존의 제약 사항을 제시하십시오. 명확하고 간단한 작업일수록 비용 효율적으로 위임하기 쉽고 검토하기도 더 수월합니다.
- 관련된 맥락을 제시해 주세요. 모델에 필요한 파일과 오류 세부 정보를 포함해 주세요. 관련 없는 작업의 경우, 단순히 대화 내역이 존재한다는 이유만으로 긴 내역을 그대로 이어가기보다는 새로운 대화를 시작해 주세요.
- ‘능력’과 ‘노력’을 구분하라. Anthropic는 모델의 기본 노력 수준부터 시작할 것을 권장합니다. 모델이 문맥을 올바르게 파악했음에도 문제를 해결하지 못했다면, 더 강력한 모델을 사용하는 것이 도움이 될 수 있습니다. 모델이 읽기 또는 검증 단계를 건너뛴 경우, 작업 설정과 노력 수준 선택을 다시 확인해 보십시오.
- 답변 전체에 대한 예산. 소스 코드, 테스트, 설명 모두를 담을 공간이 필요합니다. 두 번째 과제를 통해, 완성된 함수만으로는 요청된 결과물이 예산에 부합한다는 증거가 되지 못하는 이유를 알 수 있습니다.
- 채택된 작품을 비교해 보세요. 대표적인 작업에 대해 유용한 출력 결과, 재시도 횟수, 검토 시간 및 실제 청구 데이터를 기록하십시오. 제공자 오류와 모델 품질 관련 오류를 별도로 구분하십시오.
이러한 전략은 다음의 내용과 일치합니다. 공식 비용 안내서 그리고 Anthropic의 모델 및 노력 지침. 모델 전환 후에도 계획 전체에 적용되는 주간 또는 세션 제한은 계속 유효할 수 있지만, 모델별 제한은 상황이 다릅니다. 반복적으로 전환하기 전에 실제 제한 사항을 확인하십시오.
코드, 연구, 글쓰기, 미디어 등 다양한 분야를 아우르는 작업을 하고 있다면, 비용을 지불하고 이용하는 워크플로우도 비교해 보세요. GlobalGPT의 다중 모델 작업 공간 이러한 기능들을 하나의 대시보드에 통합하고, 개발 작업을 위한 CLI 액세스 기능을 제공합니다. 이러한 포괄적인 워크플로우의 가치는 사용량 기반 API 경로와 함께 고려할 때 유용합니다. 위의 API 차트는 GlobalGPT 구독 요금표가 아닙니다.
선택한 모델이 제대로 정착되지 않을 수 있는 이유
| 증상 | 아마도 확인 | 실질적인 다음 단계 |
|---|---|---|
| 다음 세션에서는 기존 모델을 사용합니다. | 해당 스위치는 세션 전용이었을 수도 있습니다 | 피커의 ‘Enter’ 동작을 사용하여 저장한 다음, 새 세션을 선택하세요. |
| 설정이 저장되었지만 다른 모델이 실행됩니다. | ‘출시’, ‘셸’, ‘프로젝트’, ‘관리’ 또는 ‘조직’ 설정이 우선 적용될 수 있습니다. | 시동 정보를 확인하고, 해당 구성 항목을 소유자와 함께 점검하십시오. |
| 다시 시작된 대화는 다른 모델을 갖습니다. | 저장된 세션은 이전 모델을 복원할 수 있습니다. | /status를 확인하고, 작업을 재개할 때 사용할 모델을 신중하게 선택하십시오. |
| 모델이 없거나 거부되었습니다. | 서비스 제공자의 이용 가능 여부, 조직 규정 또는 구버전의 Claude 코드 | 지원되는 모델 ID, 계정 액세스 권한 및 버전 요구 사항을 확인하십시오. |
| 같은 성이라도 다른 곳에서는 다르게 표기됩니다 | 별칭과 공급자 매핑이 다릅니다 | 공개 설정이 고정되어야 할 경우, 해당 제공자의 정확한 전체 ID를 사용하십시오. |
| 스위칭을 해도 사용 한도는 초기화되지 않습니다 | 해당 제한 사항은 전체 플랜에 적용될 수 있습니다. | 가족용 스위치만으로도 충분하다고 단정 짓기보다는 사용 안내 메시지를 확인하고 기간을 재설정하십시오. |
Claude 코드가 사용자 설정을 저장할 수 없는 경우, 대화형 선택 사항은 현재 세션 동안만 유지되고 영구적으로 저장되지는 않습니다. 관리되거나 생성된 설정 파일의 경우, 다시 선택기를 실행하기보다는 소유자가 직접 변경해야 할 수도 있습니다. 단순히 옵션을 표시하기 위해 조직의 제한 사항을 해제하는 일은 피하십시오.
GlobalGPT를 활용해 더 폭넓은 워크플로우를 구축하세요
모델 선택은 워크플로우의 한 부분일 뿐입니다. 라이브러리를 조사하거나, 함수를 초안으로 작성하거나, 팀원에게 변경 사항을 설명하거나, 문서를 작성하거나, 시각적 자료를 준비해야 할 수도 있습니다. GlobalGPT는 여러 선도적인 모델과 AI 기능을 하나의 합리적인 가격의 작업 공간에 통합하므로, 이러한 작업들을 모두 동일한 대시보드에서 수행할 수 있습니다.

터미널 작업의 경우, GlobalGPT CLI 플랫폼의 도구를 개발 환경과 연결합니다. 문서화된 설정 절차에서는 사용자의 GlobalGPT 계정 로그인 정보와 일반 계정 잔액을 사용합니다. CLI는 보다 폭넓은 코딩 및 제작 워크플로를 지원할 수 있는 반면, Claude Code 고유의 모델은 모델 제어 기능을 통해 의도적으로 선택된 방식으로 유지됩니다.
예를 들어, 구현안을 작성하고 검토한 다음, 간결한 설명을 위해 다른 모델을 활용하고, 마지막으로 릴리스에 포함될 문서나 자산을 제작합니다. 다음 가이드를 따라 GlobalGPT CLI를 Claude 코드에 연결 해당 도구 통합이 필요할 때. 코딩 과제에 따라 Claude 코드 추론 모델을 별도로 설정하십시오.
시작 모델을 선택하고, 성공을 판단하는 기준을 정리한 뒤, 작업 상황에 따라 단계적으로 발전해 나가세요. 작업을 완료했다고 선언하기 전에 전체 결과물을 검토하세요. GlobalGPT로 워크플로를 구축하세요 일상 업무 전반에 걸쳐 모델 선택, 연구, 코딩 및 창의적인 도구를 유기적으로 연계하고 싶을 때.
자주 묻는 질문
Claude 코드에서 모델을 변경하려면 어떻게 해야 하나요?
/model을 실행하여 선택기를 엽니다. s 키를 누르면 현재 세션만 전환하고, Enter 키를 누르면 선택한 모델을 기본값으로 저장합니다. 새로운 단일 세션에 사용할 모델을 선택하려면 claude –model 뒤에 모델 이름을 입력하여 실행하세요.
/model 명령어가 저장된 기본 설정을 변경하나요?
현재의 대화형 버전에서는 Enter 키를 눌러 모델을 선택하거나 /model sonnet을 입력하면, 쓰기 권한이 있는 경우 해당 선택 사항이 사용자 설정에 저장됩니다. 선택기에서 s 키를 누르는 것은 해당 세션에서만 적용됩니다. 다음 실행 시에는 다른 설정이 여전히 우선 적용될 수 있습니다.
기본 Claude 코드 모델은 무엇입니까?
실행 시 기본값은 계정, 제공업체 및 구성에 따라 달라집니다. 이 기본값은 모델 재정의 설정을 초기화하는 것이며, 고정된 단일 모델 ID가 아닙니다. 모든 계정이 동일한 릴리스 버전으로 시작된다고 가정하지 말고, /status 명령어를 통해 활성 모델을 확인하고 피커를 살펴보시기 바랍니다.
오푸스플랜은 어떤 기능을 하나요?
이 모델은 계획 모드에서는 Opus를, 실행 모드에서는 Sonnet을 사용합니다. 각 프롬프트에 대해 가장 비용이 저렴한 모델을 자동으로 선택하는 것이 아니라, 모드 전환에 따라 모델을 선택합니다. 실제 비용은 각 단계에서 수행되는 작업에 따라 달라집니다.
왜 선택기에서 모델이 하나 빠져 있나요?
사용 가능 여부는 서비스 제공업체, 계정, 조직의 제한 사항 및 Claude 코드 버전에 따라 달라질 수 있습니다. 현재 모델의 설명서와 해당 환경에서 허용되는 모델을 확인하십시오. 또한, 패밀리 별칭이 다른 서비스 제공업체에서는 다른 릴리스로 연결될 수도 있습니다.
더 저렴한 모델이 항상 작업 비용을 낮추는 것일까요?
아닙니다. 토큰 가격은 여러 요인 중 하나일 뿐입니다. 총 사용량, 불완전한 답변, 재시도 횟수, 검토에 드는 노력 등이 허용 가능한 변경에 드는 비용에 영향을 미칩니다. 우리가 진행한 소규모 리팩토링에서는 세 가지 함수 모두 통과했지만, 두 개의 파일로 구성된 세 가지 대규모 응답은 모두 공유 출력 제한으로 인해 잘렸습니다.
출처
- Claude 코드 모델 구성 — 모델 선택, 기본값, 별칭, 제공자 간 차이, 그리고 opusplan.
- Claude 코드 비용 — 사용량 추정, 가입자 할당량, 캐싱 및 비용 관리.
- Anthropic 모델 개요 그리고 API 가격 — 현재 모델의 사양 및 정가.
- 모델 및 노력 지침 — 역량, 맥락, 노력 간의 실질적인 차이.
- GlobalGPT CLI 설명서 — 터미널 워크플로우 및 계정 기반 설정.







