[Frontend] Expose logprob_token_ids on Python OpenAI endpoints (#43463)

Signed-off-by: Lang Zhao <lang.zhao@galileo.ai> Co-authored-by: Claude <noreply@anthropic.com>

langzhao-netizen committed Jul 13, 2026 at 14:40 UTC 8b8af2caf739a7537b9ca848b836733c6533bf20
9 files changed +401 -7
tests/entrypoints/openai/chat_completion/test_batched_chat_completions.py
+34
@@ -187,3 +187,37 @@ async def test_batched_chat_completions_return_tokens_as_token_ids(
187 content = data["choices"][0]["logprobs"]["content"]
188 assert content
189 assert all(entry["token"].startswith("token_id:") for entry in content)
190 +
191 +
192 +@pytest.mark.asyncio
193 +async def test_batched_chat_completions_logprob_token_ids(
194 + server: RemoteOpenAIServer,
195 +) -> None:
196 + conversations = [[{"role": "user", "content": "Hello"}]]
197 +
198 + async with httpx.AsyncClient() as http_client:
199 + response = await http_client.post(
200 + f"{server.url_for('v1/chat/completions/batch')}",
201 + json={
202 + "model": MODEL_NAME,
203 + "messages": conversations,
204 + "max_tokens": 1,
205 + "temperature": 0,
206 + "logprobs": True,
207 + "top_logprobs": 5,
208 + "logprob_token_ids": [100, 1000, 5000],
209 + "return_tokens_as_token_ids": True,
210 + },
211 + timeout=60,
212 + )
213 +
214 + assert response.status_code == 200, response.text
215 + content = response.json()["choices"][0]["logprobs"]["content"]
216 + assert content
217 + sampled_token = content[0]["token"]
218 + assert {entry["token"] for entry in content[0]["top_logprobs"]} == {
219 + "token_id:100",
220 + "token_id:1000",
221 + "token_id:5000",
222 + sampled_token,
223 + }
tests/entrypoints/openai/chat_completion/test_logprob_token_ids.py new
+208
@@ -0,0 +1,208 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""End-to-end tests for the `logprob_token_ids` field on the OpenAI-compat
4 +chat-completion endpoint.
5 +
6 +`logprob_token_ids` lets a caller pin the set of vocab ids whose logprobs
7 +should appear in the response, independent of where those ids would rank in
8 +the natural top-k distribution. This is the primitive that multilabel
9 +scoring postprocessors use to gather logprobs at a fixed small label
10 +vocabulary (e.g. PII detection where each label corresponds to a known
11 +digit-token vocab id).
12 +"""
13 +
14 +import math
15 +
16 +import pytest
17 +from pydantic import ValidationError
18 +
19 +from tests.utils import RemoteOpenAIServer
20 +from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
21 +from vllm.entrypoints.openai.completion.protocol import CompletionRequest
22 +
23 +MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct"
24 +
25 +
26 +@pytest.fixture(scope="module")
27 +def server():
28 + args = [
29 + "--max-model-len",
30 + "1024",
31 + "--max-num-seqs",
32 + "8",
33 + "--enforce-eager",
34 + ]
35 + with RemoteOpenAIServer(MODEL_NAME, args) as remote_server:
36 + yield remote_server
37 +
38 +
39 +def _logprob_entries(resp) -> list:
40 + """Return the top_logprobs list from the first generated position."""
41 + return resp.choices[0].logprobs.content[0].top_logprobs
42 +
43 +
44 +def _token_id(token: str) -> int:
45 + assert token.startswith("token_id:"), (
46 + "expected return_tokens_as_token_ids=True to yield the "
47 + f"`token_id:<int>` form, got {token!r}"
48 + )
49 + return int(token.removeprefix("token_id:"))
50 +
51 +
52 +def _top_logprob_token_ids(resp) -> list[int]:
53 + return [_token_id(entry.token) for entry in _logprob_entries(resp)]
54 +
55 +
56 +def test_chat_request_decouples_top_k_from_explicit_token_ids():
57 + request = ChatCompletionRequest(
58 + model=MODEL_NAME,
59 + messages=[{"role": "user", "content": "Hello"}],
60 + logprobs=True,
61 + top_logprobs=5,
62 + logprob_token_ids=[5000],
63 + )
64 +
65 + sampling_params = request.to_sampling_params(
66 + max_tokens=1, default_sampling_params={}
67 + )
68 +
69 + assert sampling_params.logprobs is None
70 + assert sampling_params.logprob_token_ids == [5000]
71 + assert sampling_params.num_logprobs == 1
72 +
73 +
74 +def test_completion_request_decouples_top_k_from_explicit_token_ids():
75 + request = CompletionRequest(
76 + model=MODEL_NAME,
77 + prompt="Hello",
78 + logprobs=5,
79 + logprob_token_ids=[5000],
80 + )
81 +
82 + sampling_params = request.to_sampling_params(max_tokens=1)
83 +
84 + assert sampling_params.logprobs is None
85 + assert sampling_params.logprob_token_ids == [5000]
86 + assert sampling_params.num_logprobs == 1
87 +
88 +
89 +def test_completion_rejects_explicit_token_ids_without_generated_tokens():
90 + with pytest.raises(ValidationError, match="no output tokens are generated"):
91 + CompletionRequest(
92 + model=MODEL_NAME,
93 + prompt="Hello",
94 + echo=True,
95 + max_tokens=0,
96 + logprobs=5,
97 + logprob_token_ids=[5000],
98 + )
99 +
100 +
101 +def test_requests_reject_explicit_token_ids_with_beam_search():
102 + with pytest.raises(ValidationError, match="not supported with beam search"):
103 + ChatCompletionRequest(
104 + model=MODEL_NAME,
105 + messages=[{"role": "user", "content": "Hello"}],
106 + logprobs=True,
107 + logprob_token_ids=[5000],
108 + use_beam_search=True,
109 + )
110 +
111 + with pytest.raises(ValidationError, match="not supported with beam search"):
112 + CompletionRequest(
113 + model=MODEL_NAME,
114 + prompt="Hello",
115 + logprobs=5,
116 + logprob_token_ids=[5000],
117 + use_beam_search=True,
118 + )
119 +
120 +
121 +@pytest.mark.asyncio
122 +@pytest.mark.parametrize(
123 + ("requested_ids", "top_logprobs", "sampled_id"),
124 + [
125 + pytest.param([5000], 5, 42, id="original-issue-shape"),
126 + pytest.param([100, 1000, 5000], 3, 42, id="sampled-outside-set"),
127 + pytest.param([100, 1000, 5000], 3, 5000, id="sampled-inside-set"),
128 + ],
129 +)
130 +async def test_logprob_token_ids_returns_requested_ids(
131 + server, requested_ids, top_logprobs, sampled_id
132 +):
133 + async with server.get_async_client() as client:
134 + resp = await client.chat.completions.create(
135 + model=MODEL_NAME,
136 + messages=[{"role": "user", "content": "Hello"}],
137 + max_tokens=1,
138 + temperature=0,
139 + logprobs=True,
140 + top_logprobs=top_logprobs,
141 + extra_body={
142 + "logprob_token_ids": requested_ids,
143 + "allowed_token_ids": [sampled_id],
144 + "return_tokens_as_token_ids": True,
145 + },
146 + )
147 + entries = _logprob_entries(resp)
148 + returned_token_ids = _top_logprob_token_ids(resp)
149 +
150 + assert _token_id(resp.choices[0].logprobs.content[0].token) == sampled_id
151 + assert set(returned_token_ids) == {*requested_ids, sampled_id}
152 + assert len(returned_token_ids) == len(set(returned_token_ids))
153 +
154 + for e in entries:
155 + assert isinstance(e.logprob, float)
156 + assert not math.isnan(e.logprob)
157 + assert not math.isinf(e.logprob)
158 +
159 +
160 +@pytest.mark.asyncio
161 +async def test_logprob_token_ids_stream_with_no_top_k(server):
162 + async with server.get_async_client() as client:
163 + stream = await client.chat.completions.create(
164 + model=MODEL_NAME,
165 + messages=[{"role": "user", "content": "Hello"}],
166 + max_tokens=1,
167 + temperature=0,
168 + logprobs=True,
169 + top_logprobs=None,
170 + stream=True,
171 + extra_body={
172 + "logprob_token_ids": [5000],
173 + "allowed_token_ids": [42],
174 + "return_tokens_as_token_ids": True,
175 + },
176 + )
177 +
178 + returned_token_ids: list[int] = []
179 + async for chunk in stream:
180 + if not chunk.choices or chunk.choices[0].logprobs is None:
181 + continue
182 + for content in chunk.choices[0].logprobs.content:
183 + returned_token_ids.extend(
184 + _token_id(entry.token) for entry in content.top_logprobs
185 + )
186 +
187 + assert set(returned_token_ids) == {42, 5000}
188 +
189 +
190 +@pytest.mark.asyncio
191 +async def test_logprob_token_ids_default_behavior_unchanged(server):
192 + """Without `logprob_token_ids`, the response carries the natural top-k
193 + most-likely tokens. This guards against the new field accidentally
194 + changing the default-path output."""
195 + async with server.get_async_client() as client:
196 + resp = await client.chat.completions.create(
197 + model=MODEL_NAME,
198 + messages=[{"role": "user", "content": "Hello"}],
199 + max_tokens=1,
200 + temperature=0,
201 + logprobs=True,
202 + top_logprobs=5,
203 + )
204 + entries = _logprob_entries(resp)
205 + assert len(entries) == 5
206 + # Default mode emits log_softmax, so all values are <= 0.
207 + for e in entries:
208 + assert e.logprob <= 0.0
tests/entrypoints/openai/completion/test_completion.py
+55
@@ -175,6 +175,61 @@ async def test_some_logprobs(client: openai.AsyncOpenAI, model_name: str):
175 assert 5 <= len(choice.logprobs.top_logprobs[0]) <= 6
176
177
178 +@pytest.mark.asyncio
179 +async def test_logprob_token_ids(client: openai.AsyncOpenAI):
180 + completion = await client.completions.create(
181 + model=MODEL_NAME,
182 + prompt="Hello",
183 + max_tokens=1,
184 + temperature=0.0,
185 + logprobs=5,
186 + extra_body={
187 + "logprob_token_ids": [5000],
188 + "allowed_token_ids": [42],
189 + "return_tokens_as_token_ids": True,
190 + },
191 + )
192 +
193 + choice = completion.choices[0]
194 + assert choice.logprobs is not None
195 + assert choice.logprobs.tokens == ["token_id:42"]
196 + assert choice.logprobs.top_logprobs is not None
197 + assert set(choice.logprobs.top_logprobs[0]) == {
198 + "token_id:42",
199 + "token_id:5000",
200 + }
201 +
202 +
203 +@pytest.mark.asyncio
204 +async def test_logprob_token_ids_stream(client: openai.AsyncOpenAI):
205 + stream = await client.completions.create(
206 + model=MODEL_NAME,
207 + prompt="Hello",
208 + max_tokens=1,
209 + temperature=0.0,
210 + logprobs=5,
211 + stream=True,
212 + extra_body={
213 + "logprob_token_ids": [5000],
214 + "allowed_token_ids": [42],
215 + "return_tokens_as_token_ids": True,
216 + },
217 + )
218 +
219 + returned_top_logprobs: list[dict[str, float]] = []
220 + async for chunk in stream:
221 + logprobs = chunk.choices[0].logprobs
222 + if logprobs is not None and logprobs.top_logprobs is not None:
223 + returned_top_logprobs.extend(
224 + top_logprobs
225 + for top_logprobs in logprobs.top_logprobs
226 + if top_logprobs is not None
227 + )
228 +
229 + assert len(returned_top_logprobs) == 1
230 + assert set(returned_top_logprobs[0]) == {"token_id:42", "token_id:5000"}
231 +
232 +
233 @pytest.mark.asyncio
234 @pytest.mark.parametrize(
235 "model_name",
vllm/entrypoints/openai/chat_completion/batch_serving.py
+4 -1
@@ -251,12 +251,15 @@ class OpenAIServingChatBatch(OpenAIServingChat):
251 for output in final_res.outputs:
252 self._raise_if_error(output.finish_reason, request_id)
253
254 - if request.logprobs and request.top_logprobs is not None:
254 + if request.logprobs and (
255 + request.top_logprobs is not None or request.logprob_token_ids
256 + ):
257 assert output.logprobs is not None, "Did not output logprobs"
258 logprobs = self._create_chat_logprobs(
259 token_ids=output.token_ids,
260 top_logprobs=output.logprobs,
261 num_output_top_logprobs=request.top_logprobs,
262 + logprob_token_ids=request.logprob_token_ids,
263 tokenizer=tokenizer,
264 return_as_token_id=request.return_tokens_as_token_ids,
265 )
vllm/entrypoints/openai/chat_completion/protocol.py
+42 -1
@@ -270,6 +270,18 @@ class ChatCompletionRequest(OpenAIBaseModel):
270 ),
271 )
272 prompt_logprobs: int | None = None
273 + logprob_token_ids: list[int] | None = Field(
274 + default=None,
275 + description=(
276 + "Specific vocab token IDs to return logprobs for at each generated "
277 + "position, in addition to the sampled token. More efficient than "
278 + "`top_logprobs=-1` when only a small fixed label set is needed "
279 + "(e.g. multilabel scoring "
280 + "where each label corresponds to a known vocab id). When set, "
281 + "this explicit token selection takes precedence over the natural "
282 + "top-k selected by `top_logprobs`. Requires `logprobs=True`."
283 + ),
284 + )
285 allowed_token_ids: list[int] | None = None
286 bad_words: list[str] = Field(default_factory=list)
287 # --8<-- [end:chat-completion-sampling-params]
@@ -690,8 +702,13 @@ class ChatCompletionRequest(OpenAIBaseModel):
702 seed=self.seed,
703 stop=self.stop,
704 stop_token_ids=stop_token_ids,
693 - logprobs=self.top_logprobs if self.logprobs else None,
705 + logprobs=(
706 + self.top_logprobs
707 + if self.logprobs and not self.logprob_token_ids
708 + else None
709 + ),
710 prompt_logprobs=prompt_logprobs,
711 + logprob_token_ids=self.logprob_token_ids or None,
712 ignore_eos=self.ignore_eos,
713 max_tokens=max_tokens,
714 min_tokens=self.min_tokens,
@@ -756,6 +773,18 @@ class ChatCompletionRequest(OpenAIBaseModel):
773 @model_validator(mode="before")
774 @classmethod
775 def check_logprobs(cls, data):
776 + if data.get("logprob_token_ids") and data.get("use_beam_search"):
777 + raise VLLMValidationError(
778 + "`logprob_token_ids` is not supported with beam search.",
779 + parameter="logprob_token_ids",
780 + )
781 +
782 + if data.get("logprob_token_ids") and not data.get("logprobs"):
783 + raise VLLMValidationError(
784 + "when using `logprob_token_ids`, `logprobs` must be set to true.",
785 + parameter="logprob_token_ids",
786 + )
787 +
788 if (prompt_logprobs := data.get("prompt_logprobs")) is not None:
789 if data.get("stream") and (prompt_logprobs > 0 or prompt_logprobs == -1):
790 raise VLLMValidationError(
@@ -1009,6 +1038,14 @@ class BatchChatCompletionRequest(OpenAIBaseModel):
1038 logit_bias: dict[str, float] | None = None
1039 logprobs: bool | None = False
1040 top_logprobs: int | None = 0
1041 + logprob_token_ids: list[int] | None = Field(
1042 + default=None,
1043 + description=(
1044 + "Specific vocab token IDs to return logprobs for at each generated "
1045 + "position, in addition to the sampled token. Requires "
1046 + "`logprobs=True`."
1047 + ),
1048 + )
1049 max_tokens: int | None = None
1050 max_completion_tokens: int | None = None
1051 n: int | None = 1
@@ -1058,6 +1095,10 @@ class BatchChatCompletionRequest(OpenAIBaseModel):
1095 "Batch chat completions do not support beam search. "
1096 "Please set `use_beam_search` to False."
1097 )
1098 + if data.get("logprob_token_ids") and not data.get("logprobs"):
1099 + raise ValueError(
1100 + "when using `logprob_token_ids`, `logprobs` must be set to true."
1101 + )
1102 response_format = data.get("response_format")
1103 rf_type = (
1104 response_format.get("type")
vllm/entrypoints/openai/chat_completion/serving.py
+14 -3
@@ -568,13 +568,16 @@ class OpenAIServingChat(GenerateBaseServing):
568 if finish_reason_sent[i]:
569 continue
570
571 - if request.logprobs and request.top_logprobs is not None:
571 + if request.logprobs and (
572 + request.top_logprobs is not None or request.logprob_token_ids
573 + ):
574 assert output.logprobs is not None, "Did not output logprobs"
575 logprobs = self._create_chat_logprobs(
576 token_ids=output.token_ids,
577 top_logprobs=output.logprobs,
578 tokenizer=tokenizer,
579 num_output_top_logprobs=request.top_logprobs,
580 + logprob_token_ids=request.logprob_token_ids,
581 return_as_token_id=request.return_tokens_as_token_ids,
582 )
583 else:
@@ -862,12 +865,15 @@ class OpenAIServingChat(GenerateBaseServing):
865 token_ids = output.token_ids
866 out_logprobs = output.logprobs
867
865 - if request.logprobs and request.top_logprobs is not None:
868 + if request.logprobs and (
869 + request.top_logprobs is not None or request.logprob_token_ids
870 + ):
871 assert out_logprobs is not None, "Did not output logprobs"
872 logprobs = self._create_chat_logprobs(
873 token_ids=token_ids,
874 top_logprobs=out_logprobs,
875 num_output_top_logprobs=request.top_logprobs,
876 + logprob_token_ids=request.logprob_token_ids,
877 tokenizer=tokenizer,
878 return_as_token_id=request.return_tokens_as_token_ids,
879 )
@@ -1101,6 +1107,7 @@ class OpenAIServingChat(GenerateBaseServing):
1107 top_logprobs: int | None,
1108 tokenizer: TokenizerLike | None,
1109 should_return_as_token_id: bool,
1110 + return_all: bool = False,
1111 ) -> list[ChatCompletionLogProb]:
1112 return [
1113 ChatCompletionLogProb(
@@ -1116,7 +1123,9 @@ class OpenAIServingChat(GenerateBaseServing):
1123 bytes=list(token.encode("utf-8", errors="replace")),
1124 )
1125 for i, p in enumerate(logprobs.items())
1119 - if (top_logprobs and i < top_logprobs or top_logprobs == -1)
1126 + if return_all
1127 + or top_logprobs == -1
1128 + or (top_logprobs is not None and i < top_logprobs)
1129 ]
1130
1131 def _create_chat_logprobs(
@@ -1125,6 +1134,7 @@ class OpenAIServingChat(GenerateBaseServing):
1134 top_logprobs: GenericSequence[dict[int, Logprob] | None],
1135 tokenizer: TokenizerLike | None,
1136 num_output_top_logprobs: int | None = None,
1137 + logprob_token_ids: list[int] | None = None,
1138 return_as_token_id: bool | None = None,
1139 ) -> ChatCompletionLogProbs:
1140 """Create OpenAI-style logprobs."""
@@ -1177,6 +1187,7 @@ class OpenAIServingChat(GenerateBaseServing):
1187 num_output_top_logprobs,
1188 tokenizer,
1189 should_return_as_token_id,
1190 + return_all=bool(logprob_token_ids),
1191 ),
1192 )
1193 )
vllm/entrypoints/openai/completion/protocol.py
+38 -1
@@ -95,6 +95,19 @@ class CompletionRequest(OpenAIBaseModel):
95 )
96 allowed_token_ids: list[int] | None = None
97 prompt_logprobs: int | None = None
98 + logprob_token_ids: list[int] | None = Field(
99 + default=None,
100 + description=(
101 + "Specific vocab token IDs to return logprobs for at each generated "
102 + "position, in addition to the sampled token. More efficient than "
103 + "requesting the full vocab when only a small fixed label set is "
104 + "needed (e.g. multilabel "
105 + "scoring where each label corresponds to a known vocab id). When "
106 + "set, this explicit token selection takes precedence over the "
107 + "natural top-k selected by `logprobs`. Requires `logprobs` to be "
108 + "set."
109 + ),
110 + )
111 bad_words: list[str] = Field(default_factory=list)
112 # --8<-- [end:completion-sampling-params]
113
@@ -368,11 +381,12 @@ class CompletionRequest(OpenAIBaseModel):
381 seed=self.seed,
382 stop=self.stop,
383 stop_token_ids=stop_token_ids,
371 - logprobs=self.logprobs,
384 + logprobs=None if self.logprob_token_ids else self.logprobs,
385 ignore_eos=self.ignore_eos,
386 max_tokens=max_tokens if not echo_without_generation else 1,
387 min_tokens=self.min_tokens,
388 prompt_logprobs=prompt_logprobs,
389 + logprob_token_ids=self.logprob_token_ids or None,
390 skip_special_tokens=self.skip_special_tokens,
391 spaces_between_special_tokens=self.spaces_between_special_tokens,
392 include_stop_str_in_output=self.include_stop_str_in_output,
@@ -459,6 +473,29 @@ class CompletionRequest(OpenAIBaseModel):
473 @model_validator(mode="before")
474 @classmethod
475 def check_logprobs(cls, data):
476 + if data.get("logprob_token_ids") and data.get("use_beam_search"):
477 + raise VLLMValidationError(
478 + "`logprob_token_ids` is not supported with beam search.",
479 + parameter="logprob_token_ids",
480 + )
481 +
482 + if (
483 + data.get("logprob_token_ids")
484 + and data.get("echo")
485 + and data.get("max_tokens") == 0
486 + ):
487 + raise VLLMValidationError(
488 + "`logprob_token_ids` is not supported when `echo=True` and "
489 + "`max_tokens=0` because no output tokens are generated.",
490 + parameter="logprob_token_ids",
491 + )
492 +
493 + if data.get("logprob_token_ids") and data.get("logprobs") is None:
494 + raise VLLMValidationError(
495 + "when using `logprob_token_ids`, `logprobs` must be set.",
496 + parameter="logprob_token_ids",
497 + )
498 +
499 if (prompt_logprobs := data.get("prompt_logprobs")) is not None:
500 if data.get("stream") and (prompt_logprobs > 0 or prompt_logprobs == -1):
501 raise VLLMValidationError(
vllm/entrypoints/openai/completion/serving.py
+4 -1
@@ -386,6 +386,7 @@ class OpenAIServingCompletion(GenerateBaseServing):
386 top_logprobs=out_logprobs,
387 num_output_top_logprobs=request.logprobs,
388 tokenizer=tokenizer,
389 + logprob_token_ids=request.logprob_token_ids,
390 initial_text_offset=previous_text_lens[i],
391 return_as_token_id=request.return_tokens_as_token_ids,
392 )
@@ -560,6 +561,7 @@ class OpenAIServingCompletion(GenerateBaseServing):
561 top_logprobs=out_logprobs,
562 tokenizer=tokenizer,
563 num_output_top_logprobs=request.logprobs,
564 + logprob_token_ids=request.logprob_token_ids,
565 return_as_token_id=request.return_tokens_as_token_ids,
566 )
567 else:
@@ -653,6 +655,7 @@ class OpenAIServingCompletion(GenerateBaseServing):
655 top_logprobs: GenericSequence[dict[int, Logprob] | None],
656 num_output_top_logprobs: int,
657 tokenizer: TokenizerLike | None,
658 + logprob_token_ids: list[int] | None = None,
659 initial_text_offset: int = 0,
660 return_as_token_id: bool | None = None,
661 ) -> CompletionLogProbs:
@@ -717,7 +720,7 @@ class OpenAIServingCompletion(GenerateBaseServing):
720 return_as_token_id=should_return_as_token_id,
721 ): max(top_lp[1].logprob, -9999.0)
722 for i, top_lp in enumerate(step_top_logprobs.items())
720 - if num_output_top_logprobs >= i
723 + if logprob_token_ids or num_output_top_logprobs >= i
724 }
725 )
726
vllm/sampling_params.py
+2
@@ -383,6 +383,7 @@ class SamplingParams(
383 extra_args: dict[str, Any] | None = None,
384 skip_clone: bool = False,
385 repetition_detection: RepetitionDetectionParams | None = None,
386 + logprob_token_ids: list[int] | None = None,
387 ) -> "SamplingParams":
388 if logit_bias is not None:
389 # Fast path uses a dict comprehension; on failure we iterate once
@@ -433,6 +434,7 @@ class SamplingParams(
434 min_tokens=min_tokens,
435 logprobs=logprobs,
436 prompt_logprobs=prompt_logprobs,
437 + logprob_token_ids=logprob_token_ids,
438 detokenize=detokenize,
439 skip_special_tokens=skip_special_tokens,
440 spaces_between_special_tokens=spaces_between_special_tokens,