ChatML vs XML 태그 vs JSONL — 모델마다 프롬프트 포맷이 다른 이유
여러 LLM을 다뤄본 사람이라면 "프롬프트 포맷"이라는 말이 회사마다 조금씩 다른 걸 가리킨다는 걸 눈치챘을 겁니다. GPT 쪽 문서에서는 ChatML이 나오고, Claude 문서에서는 XML 태그를 쓰라고 하고, 파인튜닝 얘기가 나오면 갑자기 JSONL이 등장합니다. 이 셋은 층위가 다른 개념인데 종종 "프롬프트 포맷"이라는 한 단어로 뭉뚱그려져서 혼란을 만듭니다. 이 가이드는 셋을 층위별로 나눠서 정리합니다.
1. 겉으로 보이는 API 구조는 이미 수렴했다
오늘날 주요 LLM API(OpenAI Chat Completions, Anthropic Messages API 등)는 겉모습이 상당히 비슷합니다. {"role":"system","content":"..."}, {"role":"user","content":"..."}, {"role":"assistant","content":"..."} 식으로 역할(role)과 내용(content)을 가진 JSON 객체 배열을 요청 본문으로 보내는 방식이 사실상 업계 공통 관례로 자리잡았습니다. 개발자 입장에서는 이 구조만 알면 여러 제공사의 API를 비슷한 코드로 호출할 수 있습니다. 하지만 이 "겉모습의 수렴"이 "내부적으로 완전히 같은 방식으로 처리된다"는 뜻은 아닙니다.
2. ChatML: OpenAI 계열이 실제 학습에 쓴 내부 포맷
ChatML(Chat Markup Language)은 OpenAI가 GPT 계열 모델을 채팅용으로 학습시킬 때 사용한, <|im_start|>role\n내용<|im_end|> 형태의 특수 토큰 기반 구조입니다. 사용자가 API로 보내는 역할별 JSON 메시지 배열은, 모델에 실제로 입력되기 전에 서버 쪽에서 이런 내부 토큰 형식으로 다시 조립되는 것으로 알려져 있습니다. 즉 JSON 배열은 개발자 편의를 위한 "겉 포장"이고, ChatML은 그 안에 있는 "실제 학습된 구조"에 가깝습니다. ChatML 방식은 이후 Mistral, LLaMA 계열, Phi 등 여러 오픈소스 모델도 채팅 파인튜닝 포맷으로 채택하면서 사실상 업계에서 널리 쓰이는 규약 중 하나가 됐습니다.
3. Claude의 XML 태그: API 구조가 아니라 "본문 안의 관례"
Anthropic의 공식 프롬프트 엔지니어링 가이드는 프롬프트 본문(content) 안에 <document>, <instructions>, <example> 같은 XML 스타일 태그를 써서 내용을 구획하라고 권장합니다. 여기서 헷갈리기 쉬운 지점이 있습니다 — 이건 Messages API의 role 기반 구조(system/user/assistant)를 대체하는 게 아니라, 그 구조 안의 content 텍스트를 어떻게 조직할지에 대한 별도의 권장 사항입니다. Claude는 학습 과정에서 XML 태그로 구분된 구간을 강한 구조적 신호로 인식하도록 훈련되었기 때문에, 긴 문서·복잡한 지시사항·예시를 태그로 감싸주면 모델이 각 부분의 역할(참고 문서인지, 지시문인지, few-shot 예시인지)을 더 명확히 구분한다는 것이 공식적으로 권장되는 이유입니다.
4. JSONL: 실시간 프롬프트가 아니라 파인튜닝 데이터셋 포맷
JSONL(JSON Lines)은 지금까지의 둘과 층위가 다릅니다. 이건 대화 한 번을 어떻게 구조화할지가 아니라, 수천~수만 개의 대화 예시를 파일 하나에 담아 모델 파인튜닝에 제출하기 위한 데이터셋 포맷입니다. 한 줄에 JSON 객체 하나씩({"messages":[{"role":"...","content":"..."}]} 형태)을 담아, 줄 단위로 스트리밍 파싱이 가능하게 만든 것이 핵심입니다. OpenAI를 비롯한 여러 제공사가 파인튜닝용 학습 데이터 업로드 포맷으로 JSONL을 요구합니다. 즉 ChatML·XML 태그가 "모델에게 지금 이 순간 무엇을 시킬지"를 다루는 반면, JSONL은 "모델을 미리 어떻게 재학습시킬지"를 위한 배치용 포맷입니다.
5. 실전 결론: role 구조는 공통, 본문 안 조직법은 모델마다 다르다
정리하면 이렇습니다. system/user/assistant라는 역할 기반 JSON 구조는 이제 제공사를 넘나드는 이식 가능한 공통 관례에 가깝습니다. 하지만 그 안의 content 텍스트를 어떻게 조직하느냐(XML 태그를 쓸지, 마크다운 헤더를 쓸지, 그냥 평문으로 쓸지)는 각 모델이 학습 과정에서 어떤 구조적 신호에 더 강하게 반응하도록 훈련됐는지에 따라 달라집니다. 그래서 "Claude에는 XML 태그가 잘 먹힌다"는 조언과 "GPT에는 마크다운 헤더나 명확한 번호 목록이 잘 먹힌다"는 조언이 동시에 존재하는 것이며, 이는 모순이 아니라 각 모델의 학습 습관을 반영한 실전 팁입니다.
ChatML: OpenAI/오픈소스 계열의 실제 토큰 구조(자동 변환, 직접 안 씀) · XML 태그: Claude 프롬프트 본문 안에서 직접 쓰는 구조화 관례 · JSONL: 실시간 대화가 아닌 파인튜닝 학습 데이터셋 파일 포맷
6. 이 도구가 하는 일
모두의 툴의 Prompt Formatter는 줄바꿈(빈 줄)으로 구분한 원본 텍스트를 입력하면, ChatML·XML 태그·Markdown·JSONL·Plain 5가지 포맷 중 선택한 형식으로 즉시 변환해 보여줍니다. system 메시지와 각 메시지의 역할(User prefix)을 지정할 수 있어, API 호출용 텍스트를 만들거나 여러 포맷의 결과물을 눈으로 비교하며 감을 잡는 데 유용합니다.
자주 묻는 질문
Q. ChatML을 직접 API 요청에 써야 하나요?
A. 아니요. 대부분의 경우 OpenAI Chat Completions API는 역할 기반 JSON 배열({"role":...,"content":...})을 받고, 내부적으로 ChatML 유사 구조로 변환하는 것으로 알려져 있습니다. 직접 ChatML 토큰 문자열을 조립해 보낼 일은 거의 없고, 파인튜닝 데이터를 준비하거나 오픈소스 모델을 로컬에서 직접 다룰 때 참고할 개념에 가깝습니다.
Q. Claude에 XML 태그를 안 쓰면 응답 품질이 떨어지나요?
A. 반드시 그런 건 아닙니다. 짧고 단순한 요청은 평문으로도 충분히 잘 작동합니다. 다만 긴 문서를 참조시키거나, 여러 개의 지시사항·예시를 한 프롬프트에 섞어 넣는 복잡한 경우에는 XML 태그로 구간을 나눠주면 모델이 각 부분을 혼동 없이 구분하는 데 도움이 된다고 공식적으로 권장됩니다.
Q. JSONL 파일은 어떻게 만드나요?
A. 파인튜닝용 대화 예시 각각을 {"messages":[{"role":"system",...},{"role":"user",...},{"role":"assistant",...}]} 형태의 JSON 객체로 만들고, 그 객체 하나를 한 줄에 하나씩 텍스트 파일에 이어 붙이면 됩니다. 줄바꿈이 구분자 역할을 하므로 각 줄 자체는 완전한 JSON이어야 합니다.
Q. 여러 모델에 같은 프롬프트를 쓰려면 어떻게 해야 하나요?
A. system/user/assistant 역할 구조는 대부분의 제공사가 공통으로 지원하므로 그대로 재사용할 수 있습니다. 다만 본문 안의 세부 구조(XML 태그 유무, 마크다운 사용 여부)는 모델별로 다르게 튜닝하는 것이 안전하며, 하나의 "만능 포맷"을 기대하기보다는 모델별 공식 프롬프트 가이드를 참고하는 편이 결과가 안정적입니다.