diff --git a/python/sglang/srt/entrypoints/openai/serving_chat.py b/python/sglang/srt/entrypoints/openai/serving_chat.py index c183edab232c..549e67ac7f15 100644 --- a/python/sglang/srt/entrypoints/openai/serving_chat.py +++ b/python/sglang/srt/entrypoints/openai/serving_chat.py @@ -1667,7 +1667,9 @@ def _apply_jinja_template( reasoning_effort=v4_reasoning_effort, reasoning_effort_profile=reasoning_effort_profile, ) - prompt_ids = self.tokenizer_manager.tokenizer.encode(real_input) + prompt_ids = parallel_prompt_encode( + self.tokenizer_manager.tokenizer, real_input + ) elif is_dsv41: if request.task is not None: encoding_dsv41.attach_task_to_last_user_message( @@ -1694,12 +1696,16 @@ def _apply_jinja_template( self.tokenizer_manager.image_token_id ), ) - prompt_ids = self.tokenizer_manager.tokenizer.encode(real_input) + prompt_ids = parallel_prompt_encode( + self.tokenizer_manager.tokenizer, real_input + ) else: real_input = encoding_dsv32.encode_messages( messages, thinking_mode=thinking_mode ) - prompt_ids = self.tokenizer_manager.tokenizer.encode(real_input) + prompt_ids = parallel_prompt_encode( + self.tokenizer_manager.tokenizer, real_input + ) # Append assistant prefix if continue_final_message is enabled if assistant_prefix: diff --git a/test/registered/unit/entrypoints/openai/test_serving_chat.py b/test/registered/unit/entrypoints/openai/test_serving_chat.py index e777b459b5dd..ce88091b8b20 100644 --- a/test/registered/unit/entrypoints/openai/test_serving_chat.py +++ b/test/registered/unit/entrypoints/openai/test_serving_chat.py @@ -1572,6 +1572,34 @@ def test_dsv_tool_choice_none_omits_tools_from_prompt(self): prompt = self.tm.tokenizer.encode.call_args.args[0] self.assertEqual("calculator" in prompt, expects_tools) + def test_native_encoders_tokenize_through_parallel_prompt_encode(self): + # dsv4 / dsv41 / dsv32 render the whole conversation to one string and + # then tokenize it on the HTTP event loop. Agent prompts reach several + # hundred thousand tokens, so these paths must use the same chunked + # encoder as the Jinja path instead of a single tokenizer.encode call. + self.template_manager.chat_template_name = None + self.template_manager.jinja_template_content_format = "string" + self.chat._dsv4_reasoning_effort_profile = "preview" + + for chat_encoding_spec in ("dsv4", "dsv32", "dsv41"): + with self.subTest(chat_encoding_spec=chat_encoding_spec): + self.chat.chat_encoding_spec = chat_encoding_spec + request = ChatCompletionRequest( + model="x", + messages=[{"role": "user", "content": "Solve 100 / 7."}], + ) + with patch( + "sglang.srt.entrypoints.openai.serving_chat.parallel_prompt_encode", + return_value=[7, 8, 9], + ) as encode_mock: + result = self.chat._process_messages(request, is_multimodal=False) + + encode_mock.assert_called_once() + tokenizer, prompt = encode_mock.call_args.args + self.assertIs(tokenizer, self.tm.tokenizer) + self.assertIn("Solve 100 / 7.", prompt) + self.assertEqual(result.prompt_ids, [7, 8, 9]) + def test_kimi_k3_encoder_receives_wire_request_fields(self): self.template_manager.chat_template_name = None self.chat.chat_encoding_spec = "kimi_k3"