# Шаблоны чата

Руководство по [основам чата](./conversations) описывает, как хранить историю чата и генерировать текст с помощью чат-моделей, используя [TextGenerationPipeline](/docs/transformers/main/ru/main_classes/pipelines#transformers.TextGenerationPipeline).

Это руководство предназначено для более опытных пользователей и описывает базовые классы и методы, а также ключевые концепции, необходимые для понимания того, что происходит при взаимодействии с моделью.

Ключевой момент, необходимый для понимания чат-моделей, заключается в следующем: все каузальные языковые модели, независимо от того, обучены ли они для чата, продолжают последовательность токенов. При обучении каузальных языковых моделей обычно начинается с «предварительного обучения» на огромном корпусе текстов, что приводит к созданию «базовой» модели. Затем эти базовые модели часто «дообучают» для чата, то есть обучают на данных, оформленных в виде последовательности сообщений. При этом чат по-прежнему остаётся просто последовательностью токенов! Список словарей `role` и `content`, которые вы передаёте в чат-модель, преобразуется в последовательность токенов, часто с использованием управляющих токенов, таких как `<|user|>` или `<|assistant|>` или `<|end_of_message|>`, которые позволяют модели видеть структуру чата. Существует множество возможных форматов чата, и разные модели могут использовать различные форматы или управляющие токены, даже если они были дообучены из одной и той же базовой модели!

Не паникуйте: чтобы работать с чат-моделями, не нужно запоминать все возможные форматы чатов. Чат-модели поставляются с **шаблонами чата**, которые указывают, в каком формате они ожидают получать чаты. К ним можно обратиться с помощью метода `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/ru/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/ru/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`, в конце добавляется `<|im_start|>assistant`, чтобы обозначить начало сообщения `assistant`. Это позволяет модели понять, что далее ожидается ответ `assistant`.

Не все модели требуют промпта для генерации, а некоторые модели, такие как [Llama](./model_doc/llama), не содержат специальных токенов перед ответом `assistant`. В таких случаях [add_generation_prompt](/docs/transformers/main/ru/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.add_generation_prompt) не оказывает никакого эффекта.

### continue_final_message

Параметр [continue_final_message](/docs/transformers/main/ru/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.continue_final_message) управляет тем, следует ли продолжать последнее сообщение в чате, а не начинать новое. Он удаляет токены конца последовательности, чтобы модель продолжала генерацию с последнего сообщения.

Это полезно для «предзаполнения» ответа модели. В примере ниже модель генерирует текст, продолжающий строку 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/ru/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.add_generation_prompt) и [continue_final_message](/docs/transformers/main/ru/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/ru/main_classes/pipelines#transformers.TextGenerationPipeline) по умолчанию устанавливает [add_generation_prompt](/docs/transformers/main/ru/internal/tokenization_utils#transformers.PreTrainedTokenizerBase.apply_chat_template.add_generation_prompt) в `True`, чтобы начать новое сообщение. Однако, если последнее сообщение в чате имеет роль `assistant`, считается, что это префилл, и включается режим `continue_final_message=True`. Это связано с тем, что большинство моделей не поддерживают несколько последовательных сообщений от ассистента. Чтобы переопределить это поведение, явно передайте аргумент [continue_final_message](/docs/transformers/main/ru/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>
```

После этого шага вы можете продолжить следование [рецепту обучения](./tasks/language_modeling) для каузальных языковых моделей, используя столбец `formatted_chat`.

