AI Sparkup

최신 AI 쉽게 깊게 따라잡기⚡

에이전트 위해 CLI를 JSON으로 바꾸면, 비용이 11배 뛴다

요즘 개발자 커뮤니티에 도는 조언이 하나 있습니다. CLI 인자를 낱개로 받지 말고 --json 페이로드 하나로 통일하라는 겁니다. 에이전트는 어차피 구조화된 형식으로 사고하니, 중첩된 데이터를 JSON으로 넘기는 쪽이 자연스럽다는 논리죠.

말이 되는 가설입니다. 그런데 Microsoft가 직접 실측해보니, 데이터는 다른 이야기를 하고 있었습니다.

사진 출처: Microsoft for Developers

Microsoft 개발자 애드보케이트 Waldek Mastykarz가 podctl이라는 합성 CLI를 만들어 args 방식과 JSON 방식을 나란히 테스트했습니다. Claude Haiku 4.5, Sonnet 4.6, Sonnet 5, GPT-5.3-Codex, MAI-Code-1-Flash까지 5개 모델로 각각 다섯 번씩 돌린 결과, JSON은 정확도에서 이득이 없었고 비용은 최대 11배까지 뛰었습니다.

출처: Don’t rewrite your CLI for agents – Microsoft for Developers

30개가 넘는 값을 담은 배포 명세로 테스트

테스트 시나리오는 일부러 복잡하게 설계했습니다. 독립적인 설정을 가진 서비스 두 개, 3단계로 중첩된 구조, 배열과 혼합 타입, 서비스 간 상호 참조까지 넣어 값이 30개를 넘도록 만들었습니다.

args 방식은 이렇게 생겼습니다. 옵션 하나하나를 플래그로 나열합니다.

podctl create --name "api-gateway-prod" --env production \
  --service-name auth --service-port 8080 --service-cpu-request 250m \
  --service-name gateway --service-port 443 --service-depends-on auth \
  --scaling-min 2 --scaling-max 10

--service-name이 두 번 등장하는 게 보이시나요. 두 번째 이후 나오는 플래그가 auth가 아니라 gateway에 속한다는 걸, 에이전트가 등장 순서만으로 스스로 알아내야 합니다.

JSON 방식은 같은 내용을 이렇게 구조화된 객체 하나로 넘깁니다.

podctl create --json '{
  "name": "api-gateway-prod",
  "services": [
    { "name": "auth", "port": 8080, "resources": {"cpu": {"request": "250m"}} },
    { "name": "gateway", "port": 443, "dependsOn": ["auth"] }
  ],
  "scaling": { "min": 2, "max": 10 }
}'

각 서비스가 별개의 객체로 분리돼 있으니, 어느 필드가 어디에 속하는지 헷갈릴 일이 없습니다. 서면으로만 보면 JSON 쪽이 더 다루기 쉬워 보이는 이유입니다.

args만 받는 CLI와 JSON만 받는 CLI를 따로 만들고, 둘 다 같은 검증 로직을 공유하도록 설계했습니다. 테스트 시점엔 어느 쪽이든 이름을 podctl로 통일해서, 에이전트가 CLI 자체를 처음 보는 것처럼 만들었습니다.

정확도는 args가 완승, JSON은 작은 모델에서 무너졌다

args 방식은 다섯 개 모델 전부, 다섯 번의 시도 전부에서 완벽한 정확도를 기록했습니다. 이 테스트에서 가장 작은 모델인 Haiku 4.5조차 Sonnet 5만큼 안정적으로 정확한 배포를 만들어냈습니다.

JSON에서는 결과가 갈렸습니다. 상위권 세 모델은 args와 똑같이 5/5를 기록했지만, Haiku 4.5는 다섯 번 중 두 번만 성공했고 MAI-Code-1-Flash는 세 번만 성공했습니다. args는 입력 공간을 좁혀서 작은 모델도 일관되게 정확한 결과를 내놓게 만들지만, JSON은 모델이 스스로 구조를 관리해야 하니 그만큼의 능력을 요구한다는 뜻입니다.

비용 차이의 진짜 원인은 셸 이스케이핑

토큰 소비를 입력, 캐시, 출력으로 나눠서 비용을 계산해보니, JSON 모드는 모델 전부에서 4배에서 11배 더 비쌌습니다. 원인은 재시도였습니다. 에이전트가 만든 JSON이 CLI에서 거부되면, 에러를 읽고 수정을 시도한 뒤 다시 보내는 과정이 반복됩니다. 구문 오류와 잘못된 중첩 구조만으로도 실패할 방법이 많은데, 여기에 셸 이스케이핑 문제까지 겹칩니다.

명령줄에 JSON 페이로드를 넘기려면 셸을 위해 따옴표를 이스케이프해야 합니다. 셸마다 중첩된 따옴표를 처리하는 규칙이 다르고, 그 규칙 중 어느 것도 단순하지 않습니다. 실제로 모델들은 문법적으로 올바른 JSON을 만들고도 셸 이스케이핑 단계에서 깨지는 경우가 잦았습니다. 같은 실험을 macOS의 Bash 환경에서 Sonnet 4.6으로 반복했더니, PowerShell에서 9배였던 비용 격차가 1.5배로 줄었습니다. 같은 모델, 같은 페이로드, 같은 정확도인데 셸 하나 바꿨을 뿐인데 차이가 이만큼 났다는 뜻입니다. args는 이 흔들림 자체가 없었습니다. PowerShell에서 0.05달러, Bash에서 0.07달러로 셸에 상관없이 안정적이었습니다.

자유로운 형식보다 제약이 에이전트를 돕는다

이 결과가 보여주는 더 깊은 지점은 제약이 모델에게 하는 역할입니다. args는 --help 출력이 유효한 입력의 전체 집합을 정의합니다. 각 옵션에 이름과 타입이 있고, 모델은 작업 설명을 특정 옵션 이름과 값으로 매핑하기만 하면 됩니다.

JSON은 다릅니다. 모델이 스키마를 스스로 추론하거나 안내받은 뒤, 그 스키마를 만족하는 자유 형식 객체를 직접 구성해야 합니다. 구문도 맞아야 하고, 중첩 구조도 맞아야 하고, 필드 이름과 타입도 맞아야 하고, 셸 이스케이핑까지 통과해야 합니다. 이 각각이 args에서는 애초에 존재하지 않는 실패 지점입니다.

사람에게도 비슷한 원리가 적용됩니다. 빈 종이를 주고 “알아서 써보세요”라고 하면 실수할 여지가 많지만, 채울 항목이 정해진 양식을 주면 실수가 줄어듭니다. args는 옵션 이름과 타입이 미리 정해진 양식이고, JSON은 매번 새로 채워야 하는 빈 종이인 셈입니다.

큰 모델은 결국 JSON도 맞게 만들어내지만, 그 “결국”이 상당한 비용을 요구합니다. 실패를 겪고 복구하는 과정 자체가 args보다 4배에서 11배 더 비쌉니다. 작은 모델은 같은 실패를 겪고도 복구하지 못해서 정확도가 떨어집니다. 입력 공간을 좁히는 것이 모델의 부족한 능력을 메워준다는 뜻이죠.

JSON이 더 나은 결과를 낼 거라는 직관은 서면상으로는 그럴듯합니다. 하지만 실제 셸에서, 실제 운영체제 위에서 돌아가는 실제 모델 앞에서는 그 직관이 버텨내지 못했습니다. 그럴듯한 조언과 실측된 결과는 다른 것이고, 둘이 어긋날 때는 측정값이 이깁니다.


AI Sparkup 구독하기

최신 게시물 요약과 더 심층적인 정보를 이메일로 받아 보세요! (무료)

Comments

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다