Initial commit: FunASR Speech Recognition Toolkit
Update API Documentation / build-api-docs (push) Has been cancelled

Add complete FunASR codebase including models, runtime, and documentation.
This commit is contained in:
freedakgmail
2026-07-09 22:38:58 +08:00
commit 6116b1f3c6
3683 changed files with 990984 additions and 0 deletions
@@ -0,0 +1,569 @@
import json
import logging
import re
import torch
import random
import traceback
import numpy as np
from funasr.register import tables
from funasr.utils.load_utils import extract_fbank, load_audio_text_image_video
@tables.register("dataset_classes", "FunASR")
class FunASR(torch.utils.data.Dataset):
"""
FunASR dataset
"""
def __init__(
self,
path,
index_ds: str = None,
frontend=None,
tokenizer=None,
int_pad_value: int = -1,
float_pad_value: float = 0.0,
**kwargs,
):
"""Initialize FunASR.
Args:
path: TODO.
index_ds: TODO.
frontend: Audio frontend for feature extraction.
tokenizer: Tokenizer instance for text encoding/decoding.
int_pad_value: TODO.
float_pad_value: TODO.
**kwargs: Additional keyword arguments.
"""
super().__init__()
index_ds_class = tables.index_ds_classes.get(index_ds)
self.index_ds = index_ds_class(path, **kwargs)
preprocessor_speech = kwargs.get("preprocessor_speech", None)
if preprocessor_speech:
preprocessor_speech_class = tables.preprocessor_classes.get(preprocessor_speech)
preprocessor_speech = preprocessor_speech_class(
**kwargs.get("preprocessor_speech_conf")
)
self.preprocessor_speech = preprocessor_speech
preprocessor_noise = kwargs.get("preprocessor_noise", None)
if preprocessor_noise:
preprocessor_noise_class = tables.preprocessor_classes.get(preprocessor_noise)
preprocessor_noise = preprocessor_noise_class(**kwargs.get("preprocessor_noise_conf"))
self.preprocessor_noise = preprocessor_noise
prompt_classes_text = kwargs.get("prompt_classes", None)
if prompt_classes_text is not None:
prompt_classes = tables.prompt_classes.get(prompt_classes_text)
prompt_classes = prompt_classes(**kwargs.get("prompt_conf"))
else:
prompt_classes = None
self.prompt_classes = prompt_classes
self.frontend = frontend
self.fs = 16000 if frontend is None else frontend.fs
self.data_type = "sound"
self.tokenizer = tokenizer
self.int_pad_value = int_pad_value
self.float_pad_value = float_pad_value
self.sos = kwargs.get("sos", "<|startoftranscript|>")
self.eos = kwargs.get("eos", "<|endoftext|>")
self.batch_size = kwargs.get("batch_size")
self.batch_type = kwargs.get("batch_type")
self.prompt_ids_len = 0
self.retry = kwargs.get("retry", 100)
self.pattern = re.compile(r"(<\|startofspeech\|>.*?<\|endofspeech\|>)")
# self.kwargs = kwargs
self.max_token_length = kwargs.get("max_token_length", 1500)
self.batch_size_scale_ratio_max = kwargs.get("batch_size_scale_ratio_max", 1.5)
self.batch_size_token_max = kwargs.get("batch_size_token_max", 2500)
self.multiturn_num_max = kwargs.get("multiturn_num_max", 5)
self.max_source_length = kwargs.get("max_source_length", 3000)
self.max_target_length = kwargs.get("max_target_length", 1024)
self.do_think = kwargs.get("do_think", True)
self.sys_prompt = kwargs.get("sys_prompt", True)
# used for dynamic output alignment
self.use_dynamic_output_ratio = kwargs.get("use_dynamic_output_ratio", 0.0)
self.min_output_mask_token_len = kwargs.get("min_mask_token_len", 1)
self.min_output_non_mask_token_len = kwargs.get("min_non_mask_token_len", 6) # [eos]
def get_source_len(self, index):
"""Get source len.
Args:
index: TODO.
"""
item = self.index_ds[index]
return self.index_ds.get_source_len(item)
def get_target_len(self, index):
"""Get target len.
Args:
index: TODO.
"""
item = self.index_ds[index]
return self.index_ds.get_target_len(item)
def get_random_user_prompt(self, item, user_prompt):
"""Get random user prompt.
Args:
item: TODO.
user_prompt: TODO.
"""
tasks = ["语音转写:", "Speech transcription:"]
language = item.get("language", None)
# LID in distill data is fake
language = None
if language is not None:
if language.lower() == "zh":
tasks.append("语音转写成中文:")
tasks.append("Transcribe speech into Chinese:")
elif language.lower() == "en":
tasks.append("语音转写成英文:")
tasks.append("Transcribe speech into English:")
if len(tasks) == 2:
task = random.choice(tasks)
elif len(tasks) == 4:
task = random.choices(tasks, weights=[0.4, 0.4, 0.1, 0.1])[0]
if "语音转写:<|startofspeech|>" in user_prompt:
user_prompt = user_prompt.replace("语音转写:<|startofspeech|>", task + "<|startofspeech|>")
elif "Speech transcription:<|startofspeech|>" in user_prompt:
user_prompt = user_prompt.replace("Speech transcription:<|startofspeech|>", task + "<|startofspeech|>")
return user_prompt
def __len__(self):
"""Internal: len ."""
return len(self.index_ds)
def __getitem__(self, index):
"""Internal: getitem .
Args:
index: TODO.
"""
output = None
for idx in range(self.retry):
if idx > 0:
logging.info(f"retry: {idx}")
badcase_flag = False
if idx == 0:
index_cur = index
else:
index_cur = torch.randint(0, len(self.index_ds), ()).item()
item = self.index_ds[index_cur]
system = item["system"]
user = item["user"]
assistant = item["assistant"]
is_noised = item.get("noised", False)
if len(user) < 1 or len(assistant) < 1:
logging.warning(f"item is error: {item}")
continue
input_ids, labels, fbank, fbank_lens, fbank_mask, fbank_beg, fake_token_len = (
[],
[],
[],
[],
[],
[],
[],
)
for i, (system_prompt, user_prompt, target_out) in enumerate(
zip(system, user, assistant)
):
if i >= self.multiturn_num_max:
break
if len(input_ids) > self.max_token_length:
logging.info(
f"input_ids > max_token_length: {len(input_ids)}>{self.max_token_length}, {item}"
)
break
if self.prompt_classes is not None:
asr_prompt = user_prompt.split("<|startofspeech|>")[0]
language = self.prompt_classes.detect_language(asr_prompt)
user_prompt_all_context = self.prompt_classes.get_prompt(item, language)
else:
user_prompt_all_context = ""
if i == 0:
source_input = f"<|im_start|>system\n{system_prompt}<|im_end|>\n<|im_start|>user\n{user_prompt_all_context}{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
if not self.sys_prompt:
source_input = f"<|im_start|>user\n{user_prompt_all_context}{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
else:
source_input = (
f"<|im_start|>user\n{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
)
if not self.do_think:
source_input += "<think>\n\n</think>\n\n"
splits = self.pattern.split(source_input)
source_ids = []
fbank_i = []
fake_token_len_i = 0
fbank_beg_i = -1
fbank_lens_i = []
speech = []
speech_lengths = []
for k, sub_str in enumerate(splits):
if not sub_str.startswith("<|startofspeech|>"):
sub_token = self.tokenizer.encode(sub_str)
source_ids += sub_token
else:
sub_str = sub_str.replace("<|startofspeech|>", "").replace(
"<|endofspeech|>", ""
)
if sub_str.startswith("!"):
try:
data_src = load_audio_text_image_video(sub_str[1:], fs=self.fs)
if self.preprocessor_noise is not None and not is_noised:
try:
data_src = self.preprocessor_noise(data_src.numpy())
except Exception as e:
logging.error(f"Generate noise audio failed: {e}")
speech, speech_lengths = extract_fbank(
data_src,
data_type=self.data_type,
frontend=self.frontend,
is_final=True,
) # speech: [b, T, d]
if speech_lengths > self.max_source_length:
logging.info(
f"speech_lengths > max_source_length: {speech_lengths}>{self.max_source_length}, {item}"
)
badcase_flag = True
except Exception as e:
logging.warning(
f"Loading wav failed! {str(e)}, {traceback.format_exc()}\n{item}"
)
badcase_flag = True
continue
if True:
olens = 1 + (speech_lengths[0].item() - 3 + 2 * 1) // 2
olens = 1 + (olens - 3 + 2 * 1) // 2
fake_token_len_i = (olens - 1) // 2 + 1
else:
fake_token_len_i = speech_lengths[0].item()
fake_token = [0] * fake_token_len_i
fbank_beg_i = len(source_ids)
source_ids += fake_token
if badcase_flag:
continue
if fbank_beg_i > 0:
fbank_beg += [fbank_beg_i + len(input_ids)]
fake_token_len += [fake_token_len_i]
else:
fbank_beg += [-1]
fake_token_len += [0]
if target_out is not None and any(
isinstance(item, dict) and "prev_content" in item for item in target_out
):
prev_value = next(
(
item["prev_content"]
for item in target_out
if isinstance(item, dict) and "prev_content" in item
),
None,
)
source_ids += self.tokenizer.encode(prev_value)
source_mask = [-100] * len(source_ids)
target_out = f"{target_out[0]}<|im_end|>"
else:
source_mask = [-100] * len(source_ids)
target_out = f"{target_out}<|im_end|>"
target_ids = self.tokenizer.encode(target_out)
if len(target_ids) > self.max_target_length:
logging.info(
f"text_length: {len(target_ids)} > {self.max_target_length}, drop it: {item}"
)
# simulate prev-token fixed output
target_labels = target_ids.copy()
if np.random.rand() < self.use_dynamic_output_ratio:
max_len = len(target_labels)
min_output_mask_token_len = min(self.min_output_mask_token_len, max_len)
min_output_non_mask_token_len = min(self.min_output_non_mask_token_len, max_len)
if max_len - min_output_non_mask_token_len > min_output_mask_token_len:
end_index = np.random.randint(min_output_mask_token_len,
max_len - min_output_non_mask_token_len)
else:
end_index = max_len - min_output_non_mask_token_len
if end_index > 0:
target_labels[:end_index] = [-100] * end_index
input_ids += source_ids + target_ids
labels += source_mask + target_labels
if len(speech) > 0:
fbank.append(speech[0, :, :])
fbank_lens.append(speech_lengths)
if badcase_flag:
continue
input_ids = torch.tensor(input_ids, dtype=torch.int64) # [: self.max_token_length]
attention_mask = torch.tensor([1] * len(input_ids), dtype=torch.int32)
labels = torch.tensor(labels, dtype=torch.int64) # [: self.max_token_length]
fbank_beg = torch.tensor(fbank_beg, dtype=torch.int32)
fake_token_len = torch.tensor(fake_token_len, dtype=torch.int32)
output = {
"fbank_beg": fbank_beg,
"fake_token_len": fake_token_len,
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels_ids": labels,
}
output["item"] = item
if len(fbank) > 0:
output["speech"] = fbank
output["speech_lengths"] = fbank_lens
if len(input_ids) > self.max_token_length:
logging.warning(
f"len(input_ids): {len(input_ids)} > max_token_length: {self.max_token_length}, item: {item}"
)
continue
break
return output
def collator(self, samples: list = None):
"""Collator.
Args:
samples: TODO.
"""
for idx in range(self.retry):
badcase_flag = False
outputs = {}
for sample in samples:
if sample is None:
continue
for key in sample.keys():
if key not in outputs:
outputs[key] = []
if isinstance(sample[key], (list, tuple)):
outputs[key].extend(sample[key])
else:
outputs[key].append(sample[key])
for key, data_list in outputs.items():
if isinstance(data_list[0], torch.Tensor):
if data_list[0].dtype == torch.int64 or data_list[0].dtype == torch.int32:
pad_value = self.int_pad_value
else:
pad_value = self.float_pad_value
outputs[key] = torch.nn.utils.rnn.pad_sequence(
data_list, batch_first=True, padding_value=pad_value
)
if self.batch_type != "example":
b, t = outputs["input_ids"].shape
if b > 1 and b * t > self.batch_size_token_max:
logging.info(
f"Warning, {idx}th, b*t: {b}*{t}={b * t} > batch_size_sample_max: {self.batch_size_token_max}, drop last data"
)
samples = samples[:-1]
continue
break
return outputs
@tables.register("index_ds_classes", "FunASR")
class FunASR(torch.utils.data.Dataset): # torch.utils.data.Dataset
def __init__(self, path: str, **kwargs):
"""Initialize FunASR.
Args:
path: TODO.
**kwargs: Additional keyword arguments.
"""
super().__init__()
self.max_source_length = kwargs.get("max_source_length", 8000)
self.min_source_length = kwargs.get("min_source_length", 10)
self.max_target_length = kwargs.get("max_target_length", 2048)
self.min_target_length = kwargs.get("min_target_length", 0)
# self.max_token_length = kwargs.get("max_token_length", 2200)+
audio_downsample_rate = int(kwargs.get("audio_downsample_rate", 8))
is_training = kwargs.get("is_training", True)
if not (path.endswith(".jsonl") or path.endswith(".json")):
# jsonl list file
data_split_num = kwargs.get("data_split_num", 1)
data_split_i = kwargs.get("data_split_i", 0)
if not is_training:
data_split_num = 1
data_split_i = 0
with open(path, encoding="utf-8") as fin:
file_list_all = fin.readlines()
num_per_slice = (len(file_list_all) - 1) // data_split_num + 1 # 16
file_list = file_list_all[
data_split_i * num_per_slice: (data_split_i + 1) * num_per_slice
]
logging.info(
f"is_training: {is_training}, data_split_num: {data_split_num}, data_split_i: {data_split_i}, \nfile_list: {file_list}, \nfile_list_all: {file_list_all}"
)
else:
file_list = [path]
contents = []
total_whrs = 0.0
total_token_for_llm_B = 0.0
for file_json in file_list:
with open(file_json.strip(), encoding="utf-8") as fin:
for line in fin:
try:
data_dict = json.loads(line.strip())
except Exception as e:
logging.error(
f"drop it, json error: {e}, line: {line}, file_json: {file_json}"
)
continue
data = data_dict["messages"]
if isinstance(data_dict.get("speech_length", 0), (list, tuple)):
speech_length = int(data_dict.get("speech_length", [0])[0])
text_length = int(data_dict.get("text_length", 0)[0])
else:
speech_length = int(data_dict.get("speech_length", 0))
text_length = int(data_dict.get("text_length", 0))
speech_length = int(speech_length)
text_length = int(text_length)
if speech_length > 0 and speech_length < 1:
continue
if text_length < 1:
logging.warning(
f"speech_length: {speech_length}, text_length: {text_length}, data: {data}, file_json: {file_json}"
)
if len(data) > 2:
text_length = len(data[2]['content'])
continue
if speech_length > self.max_source_length:
continue
if speech_length < self.min_source_length:
continue
if text_length > self.max_target_length:
continue
system, user, assistant = [], [], []
for i, item in enumerate(data):
try:
role = item["role"]
content = item["content"]
except KeyError:
logging.error(
f"drop it, KeyError: {item}, file_json: {file_json}"
)
continue
if role == "system":
system.append(content)
elif role == "user":
user.append(content)
elif role == "assistant":
if "prev_content" in item:
prev_content = item["prev_content"]
assistant.append([content, {"prev_content": prev_content}])
else:
assistant.append(content)
if len(system) == 0:
system = ["You are a helpful assistant."]
system = system * len(user)
contents_i = {
"system": system,
"user": user,
"assistant": assistant,
"source_len": speech_length + text_length,
}
if "key" in data_dict:
contents_i["key"] = data_dict["key"] if not isinstance(data_dict.get("key", "key_01234"),
(list, tuple)) else data_dict["key"][0]
if "hist_context" in data_dict:
contents_i["hist_context"] = data_dict["hist_context"]
if "hotwords" in data_dict:
contents_i["hotwords"] = data_dict["hotwords"]
if "asr_hotwords" in data_dict:
contents_i["asr_hotwords"] = data_dict["asr_hotwords"]
if "vad_segs" in data_dict:
contents_i["vad_segs"] = data_dict["vad_segs"]
if "word_list" in data_dict:
contents_i["word_list"] = data_dict["word_list"]
if "one_pass_result" in data_dict:
contents_i["one_pass_result"] = data_dict["one_pass_result"]
if "one_pass_wer" in data_dict:
contents_i["one_pass_wer"] = data_dict["one_pass_wer"]
if "noised" in data_dict:
contents_i["noised"] = data_dict["noised"]
if kwargs.get("save_meta", False):
contents_i["meta"] = data_dict
total_whrs += speech_length / 100.0 / 3600 / 10000 * audio_downsample_rate
total_token_for_llm_B += (text_length + speech_length / 8) / 1000 / 1000 / 1000
contents.append(contents_i)
self.contents = contents
logging.info(
f"\n\ntotal_num of samplers: {len(self.contents)}, total_whrs: {total_whrs:.5f}, total_token_for_llm_B: {total_token_for_llm_B:.5g}, {path}, {file_list}\n\n")
def __len__(self):
"""Internal: len ."""
return len(self.contents)
def __getitem__(self, index):
"""Internal: getitem .
Args:
index: TODO.
"""
data = self.contents[index]
return data
def get_source_len(self, data_dict):
"""Get source len.
Args:
data_dict: TODO.
"""
source_len = data_dict.get("source_len", -1)
if source_len < 0:
source_len = len(data_dict["system"]) + len(data_dict["user"])
return source_len
def get_target_len(self, data_dict):
"""Get target len.
Args:
data_dict: TODO.
"""
return 0
@@ -0,0 +1,371 @@
import numpy as np
from funasr.register import tables
import logging
import random
import re
@tables.register("prompt_classes", "MultiContextPrompt")
class MultiContextPrompt:
CONTEXT_TEMPLATES = {
'en': {
'header': "Please combine the context information provided below to complete the speech transcription task more accurately. If there is no relevant information, we will leave it blank.\n",
'fields': {
'hist_context': "Historical transcription: {hist_context}\n",
'one_pass_result': "One-pass result: {one_pass_result}\n",
'hotwords': "Hotword list: {hotwords}\n"
}
},
'zh': {
'header': "请结合下面提供的上下文信息,更加准确地完成语音转写任务。如果没有相关信息,我们会留空。\n",
'fields': {
'hist_context': "历史转写结果:{hist_context}\n",
'one_pass_result': "一遍解码结果:{one_pass_result}\n",
'hotwords': "热词列表:{hotwords}\n"
}
}
}
def __init__(self,
use_hist=True,
use_one_pass_result=True,
use_hotwords=True,
use_asr_hotwords=True,
use_multi_lingual_prompt=True,
**kwargs):
"""Initialize MultiContextPrompt.
Args:
use_hist: TODO.
use_one_pass_result: TODO.
use_hotwords: TODO.
use_asr_hotwords: TODO.
use_multi_lingual_prompt: TODO.
**kwargs: Additional keyword arguments.
"""
self.use_hist = use_hist
self.use_one_pass_result = use_one_pass_result
self.use_hotwords = use_hotwords
self.use_asr_hotwords = use_asr_hotwords
self.use_multi_lingual_prompt = use_multi_lingual_prompt
self.kwargs = kwargs
chinese_hotwords_list = kwargs.get("chinese_hotwords_list", "")
english_hotwords_list = kwargs.get("english_hotwords_list", "")
if chinese_hotwords_list:
self.chinese_hotwords_list, self.chinese_hotwords_num = self.get_hotwords_list(chinese_hotwords_list)
else:
self.chinese_hotwords_list = None
self.chinese_hotwords_num = 0
logging.info(f"chinese_hotwords_num: {self.chinese_hotwords_num}")
if english_hotwords_list:
self.english_hotwords_list, self.english_hotwords_num = self.get_hotwords_list(english_hotwords_list)
else:
self.english_hotwords_list = None
self.english_hotwords_num = 0
logging.info(f"english_hotwords_num: {self.english_hotwords_num}")
self.max_neg_hotwords_num = kwargs.get("max_neg_hotwords_num", 900)
self.min_neg_hotwords_num = kwargs.get("min_neg_hotwords_num", 0)
def get_hotwords_list(self, hotwords_file):
"""Get hotwords list.
Args:
hotwords_file: TODO.
"""
with open(hotwords_file, "r") as f:
hotwords_list = f.read().strip().split("\n")
return hotwords_list, len(hotwords_list)
def detect_language(self, text):
"""Detect language.
Args:
text: Text tensor or string input.
"""
if isinstance(text, list):
text = " ".join(text)
chinese_pattern = re.compile(
"["
"\u4e00-\u9fff" # CJK Unified Ideographs
"]+"
)
english_pattern = re.compile(r'[A-Za-z]+')
chinese_matches = chinese_pattern.findall(text)
english_matches = english_pattern.findall(text)
chinese_length = sum(len(match) for match in chinese_matches)
english_length = sum(len(match) for match in english_matches)
total_length = len(text)
if total_length == 0:
return 'zh'
if (chinese_length > english_length) and (chinese_length / total_length > 0.3):
return 'zh'
else:
return 'en'
def hotwords_sampling(self, hotwords):
# hotwords_list = hotwords.split(", ")
"""Hotwords sampling.
Args:
hotwords: TODO.
"""
hotwords_list = hotwords
selected_hotwords = []
if self.max_neg_hotwords_num > -1:
max_neg_hotwords_num = min(self.max_neg_hotwords_num, len(hotwords_list))
else:
max_neg_hotwords_num = len(hotwords_list)
if self.min_neg_hotwords_num < max_neg_hotwords_num:
selected_hotwords_num = np.random.randint(self.min_neg_hotwords_num, max_neg_hotwords_num + 1)
else:
selected_hotwords_num = max_neg_hotwords_num
if selected_hotwords_num > 0:
selected_hotwords = np.random.choice(hotwords_list, selected_hotwords_num, replace=False).tolist()
return selected_hotwords, selected_hotwords_num
def get_prompt(self, item, language):
"""Get prompt.
Args:
item: TODO.
language: Language identifier.
"""
template = self.CONTEXT_TEMPLATES[language]
prompt = template['header']
context_lines = []
if self.use_hist and item.get("hist_context"):
context_lines.append(template['fields']['hist_context'].format(hist_context=item["hist_context"]))
if self.use_one_pass_result and item.get("one_pass_result"):
context_lines.append(template['fields']['one_pass_result'].format(one_pass_result=item["one_pass_result"]))
hotwords = None
if self.use_hotwords and item.get("hotwords"):
hotwords = item["hotwords"]
if self.use_asr_hotwords and item.get("asr_hotwords"):
hotwords = item["asr_hotwords"]
if hotwords is not None and hotwords != "":
language = self.detect_language(hotwords)
if language == 'en':
neg_hotwords = self.english_hotwords_list
else:
neg_hotwords = self.chinese_hotwords_list
if neg_hotwords is not None:
selected_neg_hotwords, selected_neg_hotwords_num = self.hotwords_sampling(neg_hotwords)
else:
selected_neg_hotwords = []
if not isinstance(hotwords, list):
pos_hotwords = hotwords.split(", ")
else:
pos_hotwords = hotwords
hotwords = pos_hotwords + selected_neg_hotwords
random.shuffle(hotwords)
hotwords = ", ".join(hotwords)
context_lines.append(template['fields']['hotwords'].format(hotwords=hotwords))
if context_lines:
prompt += ''.join(context_lines)
else:
prompt += "\n\n\n"
return prompt
def get_inference_prompt(self, item, language="zh"):
"""Get inference prompt.
Args:
item: TODO.
language: Language identifier.
"""
template = self.CONTEXT_TEMPLATES[language]
prompt = template['header']
context_lines = []
if self.use_hist and item.get("hist_context"):
context_lines.append(template['fields']['hist_context'].format(hist_context=item["hist_context"]))
if self.use_one_pass_result and item.get("one_pass_result"):
context_lines.append(template['fields']['one_pass_result'].format(one_pass_result=item["one_pass_result"]))
hotwords = None
if self.use_hotwords and item.get("hotwords"):
hotwords = item["hotwords"]
if self.use_asr_hotwords and item.get("asr_hotwords"):
hotwords = item["asr_hotwords"]
if hotwords is not None and hotwords != "":
print(f"hotwords: {hotwords}")
language = self.detect_language(hotwords)
if language == 'en':
neg_hotwords = self.english_hotwords_list
else:
neg_hotwords = self.chinese_hotwords_list
if neg_hotwords is not None:
selected_neg_hotwords, selected_neg_hotwords_num = self.hotwords_sampling(neg_hotwords)
else:
selected_neg_hotwords = []
if not isinstance(hotwords, list):
pos_hotwords = hotwords.split(", ")
else:
pos_hotwords = hotwords
hotwords = pos_hotwords + selected_neg_hotwords
print(f"selected_neg_hotwords_num: {selected_neg_hotwords_num}")
random.shuffle(hotwords)
hotwords = ", ".join(hotwords)
context_lines.append(template['fields']['hotwords'].format(hotwords=hotwords))
if context_lines:
prompt += ''.join(context_lines)
else:
prompt += "\n\n\n"
return prompt
@tables.register("prompt_classes", "MultiContextPromptNew")
class MultiContextPromptNew:
CONTEXT_TEMPLATES = {
'en': {
'header': "Please combine the context information to complete the speech transcription task more accurately. If there is no relevant information, we will leave it blank.\n\n",
'context_header': "**Context:**\n",
'fields': {
'hist_context': "Historical transcription: {hist_context}\n",
'one_pass_result': "One-pass result: {one_pass_result}\n",
'hotwords': "Hotword list: {hotwords}\n"
}
},
'zh': {
'header': "请结合上下文信息,更加准确地完成语音转写任务。如果没有相关信息,我们会留空。\n\n",
'context_header': "**上下文信息:**\n",
'fields': {
'hist_context': "历史转写结果:{hist_context}\n",
'one_pass_result': "一遍解码结果:{one_pass_result}\n",
'hotwords': "热词列表:{hotwords}\n"
}
}
}
def __init__(self,
use_hist=True,
use_one_pass_result=True,
use_hotwords=True,
use_multi_lingual_prompt=True,
**kwargs):
"""Initialize MultiContextPromptNew.
Args:
use_hist: TODO.
use_one_pass_result: TODO.
use_hotwords: TODO.
use_multi_lingual_prompt: TODO.
**kwargs: Additional keyword arguments.
"""
self.use_hist = use_hist
self.use_one_pass_result = use_one_pass_result
self.use_hotwords = use_hotwords
self.use_multi_lingual_prompt = use_multi_lingual_prompt
self.use_full_hotwords_ratio = kwargs.get("use_full_hotwords_ratio", 0.2)
self.max_hotwords_num = kwargs.get("max_hotwords_num", -1)
self.min_hotwords_num = kwargs.get("min_hotwords_num", 15)
def hotwords_sampling(self, hotwords):
"""Hotwords sampling.
Args:
hotwords: TODO.
"""
hotwords_list = hotwords.split(", ")
if self.max_hotwords_num > 0:
max_hotwords_num = min(self.max_hotwords_num, len(hotwords_list))
else:
max_hotwords_num = len(hotwords_list)
if self.min_hotwords_num < max_hotwords_num:
selected_hotwords_num = np.random.randint(self.min_hotwords_num, max_hotwords_num + 1)
else:
selected_hotwords_num = max_hotwords_num
selected_hotwords = np.random.choice(hotwords_list, selected_hotwords_num, replace=False)
hotwords_list = ", ".join(selected_hotwords)
return hotwords_list, selected_hotwords_num
def get_prompt(self, item, language):
"""Get prompt.
Args:
item: TODO.
language: Language identifier.
"""
template = self.CONTEXT_TEMPLATES[language]
prompt = template['header']
context_lines = []
if self.use_hist and item.get("hist_context"):
context_lines.append(template['fields']['hist_context'].format(hist_context=item["hist_context"]))
if self.use_one_pass_result and item.get("one_pass_result"):
context_lines.append(template['fields']['one_pass_result'].format(one_pass_result=item["one_pass_result"]))
if self.use_hotwords and item.get("hotwords"):
hotwords = item["hotwords"]
if np.random.rand() < self.use_full_hotwords_ratio:
hotwords = hotwords
else:
hotwords, selected_hotwords_num = self.hotwords_sampling(hotwords)
context_lines.append(template['fields']['hotwords'].format(hotwords=hotwords))
if context_lines:
prompt += template['context_header'] + ''.join(context_lines)
return prompt
def get_inference_prompt(self, hist_context="", one_pass_result="", hotwords=""):
"""Get inference prompt.
Args:
hist_context: TODO.
one_pass_result: TODO.
hotwords: TODO.
"""
language = 'zh' if self.use_multi_lingual_prompt and np.random.rand() < 0.5 else 'en'
template = self.CONTEXT_TEMPLATES[language]
prompt = template['header']
context_lines = []
if hist_context:
context_lines.append(template['fields']['hist_context'].format(hist_context=hist_context))
if one_pass_result:
context_lines.append(template['fields']['one_pass_result'].format(one_pass_result=one_pass_result))
if hotwords:
context_lines.append(template['fields']['hotwords'].format(hotwords=hotwords))
if context_lines:
prompt += template['context_header'] + ''.join(context_lines)
return prompt