Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

146 lines
5.3 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
from copy import deepcopy
from dataclasses import dataclass, field
from transformers import PreTrainedTokenizerBase
from typing import List, Optional, Type, Union
from .base import Template
from .utils import Prompt, Word
@dataclass
class TemplateMeta:
"""
Examples:
chatml (with bos):
prefix: <s>
prompt: <|im_start|>user\n{{QUERY}}<|im_end|>\n<|im_start|>assistant\n
chat_sep: <|im_end|>\n
suffix: <|im_end|>
system_prefix: <s><|im_start|>system\n{{SYSTEM}}<|im_end|>\n
<s><|im_start|>system # prefix or system_prefix
{{SYSTEM}}<|im_end|>
<|im_start|>user # prompt
{{QUERY}}<|im_end|>
<|im_start|>assistant
{{RESPONSE}}<|im_end|> # chat_sep
<|im_start|>user # prompt
{{QUERY}}<|im_end|>
<|im_start|>assistant
{{RESPONSE}}<|im_end|> # suffix
"""
template_type: str
prefix: Prompt
prompt: Prompt
chat_sep: Optional[Prompt]
suffix: Prompt = field(default_factory=lambda: [['eos_token_id']])
template_cls: Type[Template] = Template
system_prefix: Optional[Prompt] = None
default_system: Optional[str] = None
auto_add_bos: bool = False
stop_words: List[Word] = field(default_factory=list)
agent_template: Optional[str] = None
# thinking
is_thinking: bool = False # Automatically remove think content
thinking_prefix: str = ''
non_thinking_prefix: str = '' # Automatically add non_thinking_prefix for hybrid thinking models
# During encoding, historical thinking content will be removed.
# This parameter represents the prefix for the historical part.
history_thinking_prefix: str = ''
def to_generate_template_meta(self) -> 'TemplateMeta':
self = deepcopy(self)
return TemplateMeta(
self.template_type,
prefix=[],
prompt=['{{QUERY}}'],
chat_sep=None,
template_cls=self.template_cls,
auto_add_bos=True,
stop_words=self.stop_words,
)
@staticmethod
def _has_system(prefix_or_prompt: Prompt) -> bool:
return any(['{{SYSTEM}}' in p for p in prefix_or_prompt])
@staticmethod
def _replace_system(prefix: Prompt) -> Prompt:
return [p.replace('{{SYSTEM}}', '') if isinstance(p, str) else p for p in prefix]
def _check_template_meta(self):
# check
for x in [self.prefix, self.prompt, self.suffix]:
assert isinstance(x, list)
for x in [self.chat_sep, self.system_prefix]:
assert x is None or isinstance(x, list)
def __post_init__(self):
# system
if self._has_system(self.prefix):
assert self.system_prefix is None, 'The prefix already contains {{SYSTEM}}.'
self.system_prefix = self.prefix
self.prefix = self._replace_system(self.prefix)
self.is_post_system = self._has_system(self.prompt) # mistral_nemo
if self.is_post_system:
self.system_prompt = self.prompt
self.prompt = [context for context in self.prompt if '{{SYSTEM}}' not in context]
if self.system_prefix is None and not self.is_post_system:
self.support_system = False
else:
self.support_system = True
self.check_system(self.default_system)
self.support_multi_round = self.chat_sep is not None
@staticmethod
def _token_attr_to_id(tokenizer: PreTrainedTokenizerBase, value: Optional[Prompt]) -> Optional[Prompt]:
"""Turn `eos_token_id` to token id
e.g. [['eos_token_id']] -> [[2]]
"""
if value is None:
return None
res_value = []
for v in value:
if isinstance(v, list):
v = [getattr(tokenizer, sub_v) if isinstance(sub_v, str) else sub_v for sub_v in v]
res_value.append(v)
return res_value
def init(self, tokenizer: PreTrainedTokenizerBase) -> None:
for key in ['prefix', 'prompt', 'chat_sep', 'suffix', 'system_prefix']:
value = getattr(self, key)
value = self._token_attr_to_id(tokenizer, value)
setattr(self, key, value)
suffix_stop = self.suffix[-1] if self.suffix else None
if isinstance(suffix_stop, str):
suffix_stop = suffix_stop.strip()
self.suffix_stop = suffix_stop
if suffix_stop and suffix_stop not in self.stop_words:
self.stop_words.append(suffix_stop)
if tokenizer.eos_token not in self.stop_words:
self.stop_words.append(tokenizer.eos_token)
self.stop_token_id = tokenizer.eos_token_id
if suffix_stop:
if isinstance(suffix_stop, str):
stop_token_id = tokenizer.convert_tokens_to_ids(suffix_stop)
elif isinstance(suffix_stop, list) and len(suffix_stop) == 1:
stop_token_id = suffix_stop[0]
else:
stop_token_id = None
if stop_token_id is not None:
self.stop_token_id = stop_token_id
def check_system(self, system: Optional[str]) -> None:
if system is not None:
assert self.support_system, (
f'The template does not support `system`, template_type: {self.template_type}, system: {system}')