@@ -356,10 +356,17 @@ void OfflineRecognizerQwen3ASRImpl::InitPromptTemplateIds() {
356356 SHERPA_ONNX_LOGE (" Failed to tokenize <|audio_pad|> for qwen3-asr prompt" );
357357 SHERPA_ONNX_EXIT (-1 );
358358 }
359+
360+ asr_text_token_id_ = tokenizer_->GetTokenId (" <asr_text>" );
361+ if (asr_text_token_id_ < 0 ) {
362+ SHERPA_ONNX_LOGE (" Failed to locate <asr_text> token id for qwen3-asr" );
363+ SHERPA_ONNX_EXIT (-1 );
364+ }
359365}
360366
361367std::vector<int64_t > OfflineRecognizerQwen3ASRImpl::BuildSourceIds (
362- const std::string &hotwords, int32_t audio_token_len, int32_t *before_len,
368+ const std::string &hotwords, const std::string &language,
369+ int32_t audio_token_len, int32_t *before_len,
363370 int32_t *fake_audio_token_len) const {
364371 const std::string before_utf8 = std::string (kQwen3SystemPromptPrefix ) +
365372 hotwords + kQwen3SystemPromptSuffix ;
@@ -372,11 +379,27 @@ std::vector<int64_t> OfflineRecognizerQwen3ASRImpl::BuildSourceIds(
372379 *fake_audio_token_len = audio_token_len;
373380 }
374381
382+ std::vector<int64_t > prompt_ids_after_with_language;
383+ const std::vector<int64_t > *ids_after = &prompt_ids_after_;
384+ if (!language.empty ()) {
385+ auto language_ids = tokenizer_->Encode (" language " + language);
386+ prompt_ids_after_with_language.reserve (prompt_ids_after_.size () +
387+ language_ids.size () + 1 );
388+ prompt_ids_after_with_language.insert (prompt_ids_after_with_language.end (),
389+ prompt_ids_after_.begin (),
390+ prompt_ids_after_.end ());
391+ prompt_ids_after_with_language.insert (prompt_ids_after_with_language.end (),
392+ language_ids.begin (),
393+ language_ids.end ());
394+ prompt_ids_after_with_language.push_back (asr_text_token_id_);
395+ ids_after = &prompt_ids_after_with_language;
396+ }
397+
375398 std::vector<int64_t > source_ids;
376399 size_t estimated_size =
377400 prompt_ids_before.size () +
378401 static_cast <size_t >(audio_token_len) * audio_pad_ids_.size () +
379- prompt_ids_after_. size ();
402+ ids_after-> size ();
380403 source_ids.reserve (estimated_size);
381404 source_ids.insert (source_ids.end (), prompt_ids_before.begin (),
382405 prompt_ids_before.end ());
@@ -386,8 +409,7 @@ std::vector<int64_t> OfflineRecognizerQwen3ASRImpl::BuildSourceIds(
386409 audio_pad_ids_.end ());
387410 }
388411
389- source_ids.insert (source_ids.end (), prompt_ids_after_.begin (),
390- prompt_ids_after_.end ());
412+ source_ids.insert (source_ids.end (), ids_after->begin (), ids_after->end ());
391413
392414 return source_ids;
393415}
@@ -689,10 +711,15 @@ OfflineRecognitionResult OfflineRecognizerQwen3ASRImpl::GenerateText(
689711 const std::string hotwords = Qwen3FormatHotwordsForPrompt (
690712 stream->HasOption (" hotwords" ) ? stream->GetOption (" hotwords" ) : " " );
691713
714+ std::string language;
715+ if (stream->HasOption (" language" )) {
716+ language = stream->GetOption (" language" );
717+ }
718+
692719 int32_t before_len = 0 ;
693720 int32_t fake_audio_token_len = 0 ;
694721 std::vector<int64_t > source_ids = BuildSourceIds (
695- hotwords, audio_token_len, &before_len, &fake_audio_token_len);
722+ hotwords, language, audio_token_len, &before_len, &fake_audio_token_len);
696723
697724 int32_t context_len = static_cast <int32_t >(source_ids.size ());
698725 if (context_len == 0 ) {
@@ -951,21 +978,37 @@ OfflineRecognitionResult OfflineRecognizerQwen3ASRImpl::GenerateText(
951978 ++cur_len;
952979 }
953980
954- // drop the first 3 tokens which contain things like:
955- // language None<asr_text>
956- if (generated_ids.size () >= 3 ) {
957- generated_ids.erase (generated_ids.begin (), generated_ids.begin () + 3 );
981+ std::vector<int64_t > cleaned_ids = generated_ids;
982+ if (!generated_ids.empty ()) {
983+ const size_t prefix_window = std::min<size_t >(16 , generated_ids.size ());
984+ auto asr_text_it = std::find (generated_ids.begin (),
985+ generated_ids.begin () + prefix_window,
986+ asr_text_token_id_);
987+
988+ // Only strip a leading scaffold prefix recognized by token ID.
989+ if (asr_text_it != generated_ids.begin () + prefix_window &&
990+ asr_text_it != generated_ids.begin ()) {
991+ std::vector<int64_t > prefix_ids (generated_ids.begin (),
992+ std::next (asr_text_it));
993+ std::string prefix_text = tokenizer_->Decode (prefix_ids);
994+ if (prefix_text.rfind (" language " , 0 ) == 0 &&
995+ prefix_text.size () >= 10 &&
996+ prefix_text.compare (prefix_text.size () - 10 , 10 , " <asr_text>" ) ==
997+ 0 ) {
998+ cleaned_ids.assign (std::next (asr_text_it), generated_ids.end ());
999+ }
1000+ }
9581001 }
9591002
960- result.text = tokenizer_->Decode (generated_ids );
1003+ result.text = tokenizer_->Decode (cleaned_ids );
9611004 RemoveUtf8ReplacementChars (&result.text );
9621005
963- if (!generated_ids .empty ()) {
1006+ if (!cleaned_ids .empty ()) {
9641007 std::vector<std::string> all_tokens;
965- all_tokens.reserve (generated_ids .size ());
1008+ all_tokens.reserve (cleaned_ids .size ());
9661009 std::string pending_bytes;
9671010
968- for (int64_t token_id : generated_ids ) {
1011+ for (int64_t token_id : cleaned_ids ) {
9691012 std::string s =
9701013 tokenizer_->GetTokenStringStreaming (token_id, &pending_bytes);
9711014 all_tokens.push_back (std::move (s));
@@ -975,25 +1018,7 @@ OfflineRecognitionResult OfflineRecognizerQwen3ASRImpl::GenerateText(
9751018 all_tokens.back ().append (" \xEF\xBF\xBD " );
9761019 }
9771020
978- std::string concat;
979- size_t skip = 0 ;
980- bool stripped = false ;
981- for (size_t i = 0 ; i < all_tokens.size (); ++i) {
982- concat += all_tokens[i];
983- if (concat.find (" <asr_text>" ) != std::string::npos) {
984- skip = i + 1 ;
985- stripped = true ;
986- break ;
987- }
988- }
989- if (stripped && skip <= all_tokens.size ()) {
990- result.tokens .reserve (all_tokens.size () - skip);
991- for (size_t i = skip; i < all_tokens.size (); ++i) {
992- result.tokens .push_back (std::move (all_tokens[i]));
993- }
994- } else {
995- result.tokens = std::move (all_tokens);
996- }
1021+ result.tokens = std::move (all_tokens);
9971022 }
9981023
9991024 return result;
0 commit comments