# 채팅 템플릿

[채팅 기본](./conversations) 가이드에서는 [TextGenerationPipeline](/docs/transformers/main/ko/main_classes/pipelines#transformers.TextGenerationPipeline)을 사용하여 채팅 히스토리를 저장하고 채팅 모델에서 텍스트를 생성하는 방법을 다룹니다.

이 가이드는 보다 고급 사용자를 대상으로 하며, 모델과 대화할 때 실제로 일어나는 과정을 이해하는 데 필요한 핵심 개념과 함께, 내부적으로 사용되는 클래스와 메서드를 다룹니다.

채팅 모델을 이해하는 데 필요한 핵심 통찰은 다음과 같습니다. 채팅으로 학습되었든 아니든, 모든 causal LM은 토큰 시퀀스를 계속 생성합니다. causal LM을 학습할 때, 보통 방대한 텍스트 코퍼스에서 "pre-training"을 수행하여 "base" 모델을 만듭니다. 이후 이러한 base 모델은 채팅용으로 "fine-tune"되는 경우가 많으며, 이는 메시지를 시퀀스로 포맷팅된 데이터로 모델을 학습시키는 것을 의미합니다. 하지만 채팅 역시 결국 토큰 시퀀스일 뿐입니다! 채팅 모델에 전달하는 `role`과 `content` 딕셔너리 목록은 `<|user|>`나 `<|assistant|>`, `<|end_of_message|>`와 같은 제어 토큰을 사용하여 토큰 시퀀스로 변환되며, 이를 통해 모델이 채팅 구조를 인식할 수 있습니다. 가능한 채팅 포맷은 다양하며, 동일한 base 모델에서 fine-tune되었더라도 모델에 따라 다른 포맷이나 제어 토큰을 사용할 수 있습니다!

하지만 걱정하지 마세요. 채팅 모델을 사용하려면 모든 가능한 채팅 형식을 외울 필요가 없습니다. 채팅 모델에는 채팅이 어떤 형식으로 기대되는지를 나타내는 **채팅 템플릿**이 함께 제공됩니다. `apply_chat_template` 메서드를 통해 이를 접근할 수 있습니다. 두 가지 예를 살펴보겠습니다. 이 두 모델은 모두 동일한 `Mistral-7B` 기반 모델에서 파인튜닝되었습니다.

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1")
chat = [
  {"role": "user", "content": "Hello, how are you?"},
  {"role": "assistant", "content": "I'm doing great. How can I help you today?"},
  {"role": "user", "content": "I'd like to show off how chat templating works!"},
]

tokenizer.apply_chat_template(chat, tokenize=False)
```

```md
<s>[INST] Hello, how are you? [/INST]I'm doing great. How can I help you today?</s> [INST] I'd like to show off how chat templating works! [/INST]
```

```py
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")
chat = [
  {"role": "user", "content": "Hello, how are you?"},
  {"role": "assistant", "content": "I'm doing great. How can I help you today?"},
  {"role": "user", "content": "I'd like to show off how chat templating works!"},
]

tokenizer.apply_chat_template(chat, tokenize=False)
```

```md
<|user|>\nHello, how are you?</s>\n<|assistant|>\nI'm doing great. How can I help you today?</s>\n<|user|>\nI'd like to show off how chat templating works!</s>\n
```

Mistral-7B-Instruct는 `[INST]`와 `[/INST]` 토큰을 사용하여 사용자 메시지의 시작과 끝을 나타내며, Zephyr-7B는 `<|user|>`와 `<|assistant|>` 토큰을 사용하여 화자 역할을 나타냅니다. 이것이 채팅 템플릿이 중요한 이유입니다. 잘못된 제어 토큰을 사용하면 이러한 모델의 성능이 크게 저하됩니다.

## `apply_chat_template` 사용

`apply_chat_template`의 입력은 `role`과 `content` 키를 가진 사전의 리스트로 구성해야 합니다. `role` 키는 화자를 지정하고, `content` 키에는 메시지가 포함됩니다. 일반적인 역할은 다음과 같습니다:

- 사용자로부터의 메시지에 대해 `user`
- 모델에서 온 메시지에 대한 `assistant`
- 지시사항에 대해 모델이 어떻게 행동해야 하는지 안내하는 `system` (보통 채팅의 처음에 배치됨)

`apply_chat_template`는 이 목록을 받아서 포맷된 시퀀스를 반환합니다. 시퀀스를 토큰화하려면 `tokenize=True`로 설정하세요.

```py
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")
model = AutoModelForCausalLM.from_pretrained("HuggingFaceH4/zephyr-7b-beta", device_map="auto", dtype=torch.bfloat16)

messages = [
    {"role": "system", "content": "You are a friendly chatbot who always responds in the style of a pirate",},
    {"role": "user", "content": "How many helicopters can a human eat in one sitting?"},
 ]
tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to(model.device)
print(tokenizer.decode(tokenized_chat["input_ids"][0]))
```

```md
<|system|>
You are a friendly chatbot who always responds in the style of a pirate</s>
<|user|>
How many helicopters can a human eat in one sitting?</s>
<|assistant|>
```

토큰화된 채팅을 [generate()](/docs/transformers/main/ko/main_classes/text_generation#transformers.GenerationMixin.generate)에 전달하여 응답을 생성합니다.

```py
outputs = model.generate(**tokenized_chat, max_new_tokens=128)
print(tokenizer.decode(outputs[0]))
```

```md
<|system|>
You are a friendly chatbot who always responds in the style of a pirate</s>
<|user|>
How many helicopters can a human eat in one sitting?</s>
<|assistant|>
Matey, I'm afraid I must inform ye that humans cannot eat helicopters. Helicopters are not food, they are flying machines. Food is meant to be eaten, like a hearty plate o' grog, a savory bowl o' stew, or a delicious loaf o' bread. But helicopters, they be for transportin' and movin' around, not for eatin'. So, I'd say none, me hearties. None at all.
```

> [!WARNING]
> 일부 토크나이저는 `<bos>`와 `<eos>`와 같은 특수 토큰을 추가합니다. 채팅 템플릿에는 이미 필요한 모든 특수 토큰이 포함되어 있어야 하며, 추가적인 특수 토큰을 넣으면 중복되거나 잘못된 결과가 생겨 모델 성능에 악영향을 줄 수 있습니다. `apply_chat_template(tokenize=False)`로 텍스트를 포맷팅한 후 나중에 토크나이징을 수행하는 경우, 이러한 토큰의 중복을 방지하기 위해 `add_special_tokens=False`를 설정해야 합니다. `apply_chat_template(tokenize=True)`를 사용하면 이러한 문제가 발생하지 않으므로, 일반적으로 더 안전한 선택지입니다!

### add_generation_prompt

위 예제에서 [add_generation_prompt](/docs/transformers/main/ko/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.add_generation_prompt) 인수를 확인하셨을 것입니다. 이 인수는 채팅 끝에 `assistant` 응답의 시작을 나타내는 토큰을 추가합니다. 기억하세요: 모든 채팅 추상화 아래에서 채팅 모델은 여전히 토큰 시퀀스를 계속하는 언어 모델일 뿐입니다! 이제 `assistant` 응답 중임을 알려주는 토큰을 포함하면 모델이 올바르게 응답을 작성하지만, 이러한 토큰을 포함하지 않으면 모델이 혼란을 겪고 사용자의 메시지를 답장하는 대신 **계속**하는 등 이상한 동작을 할 수 있습니다!

`add_generation_prompt`가 실제로 무엇을 하는지 이해하기 위해 예시를 살펴보겠습니다. 먼저, `add_generation_prompt` 없이 채팅을 포맷팅해 보겠습니다:

```py
tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False)
tokenized_chat
```

```md
<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>
```

이제 `add_generation_prompt=True`를 사용하여 동일한 채팅을 포맷팅해 보겠습니다:

```py
tokenized_chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
tokenized_chat
```

```md
<|im_start|>user
Hi there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>
<|im_start|>user
Can I ask a question?<|im_end|>
<|im_start|>assistant

```

`add_generation_prompt=True`인 경우, `assistant` 메시지의 시작을 나타내기 위해 끝에 `<|im_start|>assistant`가 추가됩니다. 이를 통해 모델은 다음으로 `assistant` 응답이 이어진다는 것을 알 수 있습니다.

모든 모델에 생성 프롬프트가 필요한 것은 아니며, [Llama](./model_doc/llama)와 같은 일부 모델은 `assistant` 응답 앞에 특수 토큰이 없습니다. 이러한 경우 [add_generation_prompt](/docs/transformers/main/ko/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.add_generation_prompt)는 효과가 없습니다.

### continue_final_message

[continue_final_message](/docs/transformers/main/ko/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.continue_final_message) 매개변수는 채팅의 마지막 메시지를 새로운 메시지로 시작하는 대신 계속할지 여부를 제어합니다. 시퀀스 종료 토큰을 제거하여 모델이 마지막 메시지부터 생성을 계속하도록 합니다.

이 기능은 모델 응답을 “prefill”하는 데 유용합니다. 아래 예시에서 모델은 새로운 메시지를 시작하는 대신 JSON 문자열을 이어가는 텍스트를 생성합니다. 응답의 시작 방식을 알고 있는 경우, 지시사항 준수 정확도를 높이는 데 매우 유용할 수 있습니다.

```py
chat = [
    {"role": "user", "content": "Can you format the answer in JSON?"},
    {"role": "assistant", "content": '{"name": "'},
]

formatted_chat = tokenizer.apply_chat_template(chat, tokenize=True, return_dict=True, continue_final_message=True)
model.generate(**formatted_chat)
```

> [!WARNING]
> [add_generation_prompt](/docs/transformers/main/ko/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.add_generation_prompt)와 [continue_final_message](/docs/transformers/main/ko/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.continue_final_message)를 함께 사용해서는 안 됩니다. 전자는 새 메시지를 시작하는 토큰을 추가하고, 후자는 시퀀스 종료 토큰을 제거합니다. 함께 사용하면 오류가 반환됩니다.

`content` 대신 해당 필드를 미리 채우려면 필드 이름을 문자열로 전달하세요. 추론 모델은 별도의 `reasoning_content` 필드에서 자신의 추론을 읽습니다. `content`를 미리 채우면 생성이 시작되기 전에 추론 블록이 닫히므로 모델이 그 안에서 계속할 수 없습니다. `reasoning_content`를 직접 미리 채우면 블록이 열린 상태로 유지됩니다.

```py
chat = [
    {"role": "user", "content": "Explain 1+1"},
    {"role": "assistant", "reasoning_content": "The user wants a simple addition. ", "content": ""},
]

formatted_chat = tokenizer.apply_chat_template(chat, tokenize=False, continue_final_message="reasoning_content")
```

이름이 지정된 필드는 최종 메시지에 존재해야 하며, 채팅 템플릿에 의해 참조되어야 합니다. 두 가지 검사 중 하나라도 실패하면 오류가 발생합니다.

[TextGenerationPipeline](/docs/transformers/main/ko/main_classes/pipelines#transformers.TextGenerationPipeline)는 기본적으로 [add_generation_prompt](/docs/transformers/main/ko/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.add_generation_prompt)을 `True`로 설정하여 새 메시지를 시작합니다. 그러나 채팅의 마지막 메시지가 `assistant` 역할을 가진 경우, 해당 메시지를 prefill로 간주하고 `continue_final_message=True`로 전환합니다. 이는 대부분의 모델이 연속된 여러 assistant 메시지를 지원하지 않기 때문입니다. 이 동작을 덮어쓰려면 [continue_final_message](/docs/transformers/main/ko/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.continue_final_message) 인수를 파이프라인에 명시적으로 전달하세요.

## 모델 학습

채팅 템플릿을 사용하여 모델을 학습시키는 것은 템플릿이 모델이 학습된 토큰과 일치하는지 확인하는 좋은 방법입니다. 데이터셋에 채팅 템플릿을 전처리 단계로 적용하세요. 어시스턴트 응답을 유도하는 추가 토큰은 학습 중에 도움이 되지 않으므로 `add_generation_prompt=False`로 설정하세요.

채팅 템플릿을 사용하여 데이터셋을 전처리하는 예시는 다음과 같습니다.

```py
from transformers import AutoTokenizer
from datasets import Dataset

tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-beta")

chat1 = [
    {"role": "user", "content": "Which is bigger, the moon or the sun?"},
    {"role": "assistant", "content": "The sun."}
]
chat2 = [
    {"role": "user", "content": "Which is bigger, a virus or a bacterium?"},
    {"role": "assistant", "content": "A bacterium."}
]

dataset = Dataset.from_dict({"chat": [chat1, chat2]})
dataset = dataset.map(lambda x: {"formatted_chat": tokenizer.apply_chat_template(x["chat"], tokenize=False, add_generation_prompt=False)})
print(dataset['formatted_chat'][0])
```

```md
<|user|>
Which is bigger, the moon or the sun?</s>
<|assistant|>
The sun.</s>
```

이 단계 이후에는 `formatted_chat` 컬럼을 사용하여 causal language models의 [training recipe](./tasks/language_modeling)를 계속 따를 수 있습니다.

