Transformers documentation
Viết một mẫu trò chuyện
Viết một mẫu trò chuyện
Chat template là một Jinja template được lưu trong thuộc tính ~PreTrainedTokenizer.chat_template của tokenizer. Jinja là một ngôn ngữ template cho phép bạn viết mã và cú pháp giống Python.
{%- for message in messages %}
{{- '<|' + message['role'] + '|>\n' }}
{{- message['content'] + eos_token }}
{%- endfor %}
{%- if add_generation_prompt %}
{{- '<|assistant|>\n' }}
{%- endif %}Nếu bạn nhìn kỹ một lúc, bạn sẽ nhận ra rằng điều này thực sự rất giống Python, mặc dù có một số cú pháp {%- kỳ lạ. Mẫu lặp qua một danh sách các tin nhắn, và với mỗi tin nhắn, nó in ra vai trò và nội dung của tin nhắn đó, sau đó là token kết thúc chuỗi. Nếu add_generation_prompt=True, nó sẽ thêm tiêu đề bắt đầu cho một tin nhắn của trợ lý vào cuối cuộc trò chuyện.
Nạp template đã viết dưới dạng chuỗi và gán nó cho thuộc tính chat_template của tokenizer. Một khi đã được thiết lập, template sẽ được sử dụng mỗi khi bạn gọi apply_chat_template(). Nó cũng được lưu cùng tokenizer mỗi khi save_pretrained() hoặc push_to_hub() được gọi. Template được lưu trong tệp chat_template.jinja trong thư mục tokenizer. Bạn có thể chỉnh sửa trực tiếp tệp này để thay đổi template, điều này thường dễ dàng hơn so với việc thao tác trên chuỗi template. Xem Lưu và nạp chat templates bên dưới để biết các định dạng trên đĩa khác mà Transformers hỗ trợ.
Mẹo viết template
Cách dễ nhất để bắt đầu viết các mẫu Jinja là tham khảo các mẫu có sẵn. Sử dụng print(tokenizer.chat_template) trên bất kỳ mô hình chat nào để xem mẫu mà mô hình đó đang dùng. Hãy bắt đầu với các mô hình đơn giản, không gọi công cụ nào hoặc không hỗ trợ RAG, vì các mô hình sử dụng công cụ có thể có các mẫu rất phức tạp. Cuối cùng, hãy xem tài liệu Jinja để biết thêm chi tiết về định dạng và cú pháp.
Tuy nhiên, có một số mẹo và bẫy cụ thể mà bạn có thể gặp phải khi viết các mẫu chat, và phần này sẽ đi sâu hơn vào một số trong số đó.
Viết các mẫu chat đa phương thức
Đối với các template đa phương thức, thuộc tính chat_template được đặt trên processor, không phải tokenizer. Khóa content của một message thường là một danh sách các dict nội dung, thay vì chỉ là một chuỗi duy nhất. Bạn có thể muốn kiểm tra kiểu của từng mục nội dung trong danh sách và xử lý tương ứng.
Nhìn chung, template không nên truy cập trực tiếp vào dữ liệu ảnh hoặc video. Việc này thường được xử lý bởi processor sau khi quá trình render template hoàn tất. Thay vào đó,
template của bạn nên phát ra một token đặc biệt duy nhất như <|image|> hoặc <|video|> khi gặp nội dung ảnh hoặc video. Processor sẽ
mở rộng token đặc biệt duy nhất đó thành một chuỗi các token ảnh hoặc video sau đó. Các token cụ thể cần phát ra phụ thuộc vào model bạn đang làm việc. Chúng tôi khuyến nghị mạnh mẽ việc tải một processor đa phương thức có sẵn để xem cách nó xử lý dữ liệu.
Mẫu ví dụ bên dưới xử lý nội dung kết hợp giữa hình ảnh và văn bản.
{%- for message in messages %}
{%- if loop.index0 == 0 %}
{{- bos_token }}
{%- endif %}
{{- '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n' }}
{%- if message['content'] is string %}
{{- message['content'] }}
{%- else %}
{%- for content in message['content'] %}
{%- if content['type'] == 'image' %}
{{- '<|image|>' }}
{%- elif content['type'] == 'text' %}
{{- content['text'] }}
{%- endif %}
{%- endfor %}
{%- endif %}
{{- '<|eot_id|>' }}
{%- endfor %}
{%- if add_generation_prompt %}
{{- '<|start_header_id|>assistant<|end_header_id|>\n\n' }}
{%- endif %}Mẫu đa phương thức này rất giống với mẫu đơn giản hơn ở trên, nhưng nó kiểm tra các danh sách content
và lặp qua chúng để hiển thị các token <|image|> khi cần thiết. Điều này cho phép chèn hình ảnh “vào dòng chảy”
của văn bản người dùng.
Không phải tất cả các model đều hoạt động theo cách này - một số có thể di chuyển tất cả các hình ảnh đến cuối tin nhắn của người dùng, chẳng hạn. Chat template luôn phải khớp với định dạng mà model được huấn luyện.
Cắt bỏ khoảng trắng
Jinja in ra mọi khoảng trắng trước hoặc sau một khối văn bản. Điều này có thể gây vấn đề cho các mẫu chat vì việc thêm khoảng trắng thừa không có trong quá trình huấn luyện mô hình có thể làm giảm hiệu suất. Để loại bỏ khoảng trắng, hãy thêm - vào cú pháp dòng Jinja. Điều này cho phép bạn viết mẫu với cách thụt đầu dòng và xuống dòng theo phong cách Python, mà không vô tình in ra khoảng thụt đầu dòng trong kết quả hiển thị.
Mẫu ví dụ bên dưới không sử dụng -, dẫn đến việc in ra khoảng trắng thừa trong kết quả.
{% for message in messages %}
{{ message['role'] + message['content'] }}
{% endfor %}Chúng tôi rất khuyến khích sử dụng - để đảm bảo chỉ nội dung dự kiến được in ra.
{%- for message in messages %}
{{- message['role'] + message['content'] }}
{%- endfor %}Biến đặc biệt và các đối tượng có thể gọi
Các hằng số duy nhất trong một template là biến messages và boolean add_generation_prompt. Tuy nhiên, bạn có
truy cập bất kỳ các đối số từ khóa khác nào được truyền vào phương thức apply_chat_template().
Điều này mang lại sự linh hoạt và cho phép hỗ trợ các trường hợp sử dụng mà chúng ta có thể chưa nghĩ đến khi thiết kế đặc tả. Biến bổ sung phổ biến nhất là tools, chứa một danh sách các công cụ ở định dạng JSON schema. Mặc dù bạn có thể sử dụng bất kỳ tên biến nào mình thích, chúng tôi rất khuyến khích tuân theo quy ước và sử dụng tools cho mục đích này. Điều này giúp các template tương thích hơn với API chuẩn.
Bạn cũng có thể truy cập bất kỳ token nào chứa trong tokenizer.special_tokens_map, thường bao gồm các token đặc biệt như bos_token và eos_token. Truy cập chúng trực tiếp theo tên, ví dụ {{- bos_token }}.
Có hai hàm có thể gọi được dành cho bạn. Để gọi chúng, hãy sử dụng {{- function_name(argument) }}.
raise_exception(msg)sẽ gây ra mộtTemplateException. Điều này hữu ích để gỡ lỗi hoặc cảnh báo người dùng về việc sử dụng template không chính xác.strftime_now(format_str)lấy ngày và giờ hiện tại theo một định dạng cụ thể, điều thường được yêu cầu trong các thông báo hệ thống. Nó tương đương với datetime.now().strftime(format_str) trong Python.
Tương thích với Jinja không phải Python
Jinja được triển khai bằng nhiều ngôn ngữ và chúng thường có cùng cú pháp. Việc viết template bằng Python cho phép bạn sử dụng các phương thức Python như lower trên chuỗi hoặc items trên dict. Tuy nhiên, điều này sẽ không hoạt động nếu template được sử dụng trong một triển khai không phải Python, ví dụ như khi triển khai với Javascript hoặc Rust.
Thực hiện các thay đổi bên dưới để đảm bảo tính tương thích trên tất cả các triển khai Jinja.
- Thay thế các phương thức Python bằng các bộ lọc Jinja. Ví dụ, thay
string.lower()bằngstring|lowerhoặcdict.items()bằngdict|dictitems. Hầu hết các thay đổi đều tuân theo cùng một mẫu, ngoại trừstring.strip(), được thay bằngstring|trim. Tham khảo danh sách các bộ lọc tích hợp sẵn để có danh sách đầy đủ các bộ lọc. - Thay thế
True,FalsevàNone(đặc thù của Python) lần lượt bằngtrue,falsevànone. - Việc render trực tiếp một dict hoặc list có thể cho ra kết quả khác nhau ở các implementation khác. Ví dụ, các entry dạng string có thể bị đổi từ dấu nháy đơn sang dấu nháy kép. Để tránh điều này, hãy thêm filter tojson để duy trì tính nhất quán.
Các mẫu lớn
Các mô hình mới hơn hoặc các mô hình có các tính năng như tool-calling và RAG yêu cầu các template lớn hơn, có thể dài hơn 100 dòng. Việc viết các template lớn trong một tệp riêng có thể dễ dàng hơn. Số dòng trong tệp riêng tương ứng chính xác với số dòng trong các lỗi phân tích hoặc thực thi template, giúp việc gỡ lỗi các vấn đề tiềm ẩn trở nên dễ dàng hơn.
Viết template trong một tệp riêng và trích xuất nó vào chat template.
open("template.jinja", "w").write(tokenizer.chat_template)Bạn cũng có thể nạp lại một mẫu đã chỉnh sửa vào tokenizer.
tokenizer.chat_template = open("template.jinja").read()Lưu trữ và nạp các mẫu chat
Chat templates được lưu trên đĩa theo một số định dạng khác nhau. Các checkpoint hiện đại lưu template dưới dạng tệp .jinja độc lập, trong khi các checkpoint cũ nhúng chúng trong cấu hình tokenizer hoặc processor.
Định dạng lưu trữ
Các mẫu có thể được lưu trữ ở bất kỳ định dạng nào sau đây.
chat_template.jinja(khuyến nghị). Một tệp Jinja độc lập ở gốc của kho, chứa một chat template duy nhất. Đây là thứ mà save_pretrained() ghi theo mặc định. Việc lưu trữ template trong tệp riêng giúp dễ kiểm tra, chỉnh sửa và so sánh. Cả tokenizer và processor đều nạpchat_template.jinjatheo cùng một cách.additional_chat_templates/<name>.jinja. Một thư mục chứa các tệp Jinja độc lập, được sử dụng khi một model đi kèm nhiều template có tên riêng (ví dụ, một templatedefaultvà một templatetool_useriêng biệt). Templatedefaultvẫn được đặt trongchat_template.jinjaở gốc repo, nhưng mọi template có tên khác đều được đặt trongadditional_chat_templates/<name>.jinja, trong đó phần thân của tên tệp chính là tên của template.
Các định dạng cũ bên dưới được giữ lại chỉ để tải tương thích ngược. Không viết mẫu chat vào bất kỳ định dạng nào trong số đó.
trường
chat_templatetrongtokenizer_config.json. Định dạng legacy chỉ dùng để nạp, được sử dụng trước khi có các tệp.jinjađộc lập. Mẫu được nhúng dưới dạng chuỗi JSON trongtokenizer_config.json. Khi một mô hình có nhiều mẫu được đặt tên, trường này là một danh sách các dict{"name": ..., "template": ...}thay vì một chuỗi duy nhất. Các kho lưu trữ hiện có sử dụng định dạng này vẫn tiếp tục được nạp, nhưng save_pretrained() sẽ ghi định dạng.jinjahiện đại thay thế.chat_template.json. Định dạng cũ chỉ dùng để nạp, được sử dụng bởi các checkpoint bộ xử lý đa phương thức đời trước. Đây là một tệp JSON có dạng{"chat_template": "<template string>"}. Các kho lưu trữ hiện có sử dụng định dạng này vẫn tiếp tục được nạp, nhưng save_pretrained() sẽ ghi ra định dạng.jinjahiện đại thay thế. Một kho lưu trữ bộ xử lý trộn lẫn tệpchat_template.jsoncũ với các tệp.jinjahiện đại sẽ gây ra lỗi khi nạp.
Ưu tiên tải
Khi gọi from_pretrained(), Transformers giải quyết các định dạng lưu trữ theo một thứ tự ưu tiên cố định. Các tệp .jinja độc lập có ưu tiên cao hơn so với các mẫu được nhúng trong cấu hình. Trình nạp:
- Đọc bất kỳ trường
chat_templatenào có trongtokenizer_config.json(hoặc, đối với bộ xử lý,chat_template.jsoncũ). - Reads
chat_template.jinjaat the repo root if it exists, and uses it as thedefaulttemplate, overriding step 1. - Đọc mọi tệp
.jinjatrongadditional_chat_templates/, dùng phần thân tên tệp làm khóa, và gộp chúng vào.
Nếu kết quả chỉ có một default template, ~PreTrainedTokenizer.chat_template sẽ được đặt thành chuỗi đó. Nếu có nhiều template đặt tên, chat_template sẽ trở thành một dict của {name: template_string}. Trong trường hợp đó, apply_chat_template() sẽ chọn mục tool_use khi có tools được truyền vào, và default trong các trường hợp còn lại.
Lưu
save_pretrained() và push_to_hub() ghi định dạng .jinja theo mặc định. Một chuỗi template duy nhất sẽ trở thành chat_template.jinja. Một dict chứa các template có tên sẽ ghi mục default vào chat_template.jinja và một tệp cho mỗi mục còn lại dưới additional_chat_templates/. Trường chat_template sẽ bị xóa khỏi tokenizer_config.json để tránh trùng lặp.
Không có cách được hỗ trợ để lưu trữ một template trong một trong các định dạng cũ. Chúng chỉ được giữ lại để tải các kho lưu trữ cũ hơn.
Cập nhật kho lưu trữ cũ
Chuyển đổi một mẫu nhúng trong tokenizer_config.json hoặc chat_template.json sang định dạng .jinja được khuyến nghị bằng cách tải và lưu lại nó.
Bước load chuẩn hóa bất kỳ định dạng cũ nào mà kho sử dụng trong chat_template, và ~PushToHubMixin.push_to_hub trả về tệp chat_template.jinja.
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("your-org/your-model")
tokenizer.push_to_hub("your-org/your-model")Mẫu cho các công cụ
Không có định dạng cụ thể nào cho việc viết template cho các công cụ, nhưng tốt nhất là nên tuân theo API chuẩn. Điều này đảm bảo template có thể được sử dụng rộng rãi trên nhiều model khác nhau mà không yêu cầu người dùng phải viết mã tùy chỉnh để sử dụng công cụ với model của bạn.
Định dạng như khoảng trắng và các token đặc biệt phụ thuộc vào từng model. Hãy đảm bảo mọi thứ khớp chính xác với định dạng mà model đã được huấn luyện.
Phần dưới đây liệt kê các thành phần của API chuẩn để viết các mẫu (templates) cho các công cụ.
Định nghĩa công cụ
Công cụ được truyền dưới dạng các hàm Python hoặc một schema JSON. Khi các hàm được truyền vào, một schema JSON sẽ được tạo tự động và truyền cho template. Khi một template truy cập biến tools, nó luôn là một danh sách các schema JSON.
Mặc dù một template luôn nhận tools dưới dạng JSON schema, bạn có thể cần thay đổi triệt để định dạng này khi render để khớp với định dạng mà model được huấn luyện. Ví dụ, Command-R được huấn luyện với tools được định nghĩa bằng các Python function headers. Template sẽ chuyển đổi các kiểu JSON schema bên trong và render các input tools thành các Python headers.
Ví dụ dưới đây cho thấy cách một công cụ được định nghĩa ở định dạng JSON schema.
{
"type": "function",
"function": {
"name": "multiply",
"description": "A function that multiplies two numbers",
"parameters": {
"type": "object",
"properties": {
"a": {
"type": "number",
"description": "The first number to multiply"
},
"b": {
"type": "number",
"description": "The second number to multiply"
}
},
"required": ["a", "b"]
}
}
}Một ví dụ về cách xử lý định nghĩa công cụ trong một mẫu chat được hiển thị bên dưới. Các token và bố cục cụ thể nên được thay đổi để khớp với những gì mô hình đã được huấn luyện.
{%- if tools %}
{%- for tool in tools %}
{{- '<tool>' + tool['function']['name'] + '\n' }}
{%- for argument in tool['function']['parameters']['properties'] %}
{{- argument + ': ' + tool['function']['parameters']['properties'][argument]['description'] + '\n' }}
{%- endfor %}
{{- '\n</tool>' }}
{%- endfor %}
{%- endif %}Gọi công cụ
Ngoài việc hiển thị các định nghĩa công cụ, bạn cũng cần hiển thị các cuộc gọi công cụ và các phản hồi công cụ trong template.
Các lời gọi công cụ thường được truyền trong khóa tool_calls của một thông điệp "assistant”. Đây luôn là một danh sách, mặc dù hầu hết các mô hình gọi công cụ chỉ hỗ trợ một lời gọi công cụ duy nhất, điều này có nghĩa là danh sách thường chỉ chứa một phần tử.
{
"role": "assistant",
"tool_calls": [
{
"type": "function",
"function": {
"name": "multiply",
"arguments": {
"a": 5,
"b": 6
}
}
}
]
}Một mẫu phổ biến để xử lý các cuộc gọi công cụ được minh họa bên dưới. Bạn có thể sử dụng mẫu này làm điểm khởi đầu, nhưng hãy đảm bảo rằng mẫu của bạn thực sự khớp với định dạng mà mô hình đã được huấn luyện!
{%- if message['role'] == 'assistant' and 'tool_calls' in message %}
{%- for tool_call in message['tool_calls'] %}
{{- '<tool_call>' + tool_call['function']['name'] + '\n' + tool_call['function']['arguments']|tojson + '\n</tool_call>' }}
{%- endfor %}
{%- endif %}Phản hồi từ công cụ
Phản hồi công cụ là các dict tin nhắn có vai trò tool. Chúng đơn giản hơn nhiều so với các cuộc gọi công cụ và thường chỉ chứa các khóa role, name và content.
{
"role": "tool",
"name": "multiply",
"content": "30"
}Một số template có thể thậm chí không cần đến khóa name, trong trường hợp đó, bạn có thể viết template của mình để chỉ đọc khóa content.
{%- if message['role'] == 'tool' %}
{{- "<tool_result>" + message['content'] + "</tool_result>" }}
{%- endif %}Đóng góp
Khi một template đã sẵn sàng, hãy đặt nó vào thuộc tính chat_template trong tokenizer và kiểm tra bằng apply_chat_template(). Nếu hoạt động như mong đợi, hãy tải nó lên Hub bằng push_to_hub().
Ngay cả khi bạn không phải là chủ sở hữu của model, việc thêm một template cho model có chat template trống hoặc không chính xác vẫn rất hữu ích. Hãy mở một pull request trên kho lưu trữ của model để thêm template!
tokenizer.chat_template = template
tokenizer.push_to_hub("amazing_company/cool_model", commit_message="Add chat template", create_pr=True)