main
py 830 lines 25.3 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 import openai # use the official client for correctness check
6 import pytest
7 import pytest_asyncio
8 import regex as re
9 from openai import BadRequestError
10
11 from tests.utils import RemoteOpenAIServer
12 from vllm.entrypoints.openai.completion.protocol import CompletionRequest
13 from vllm.sampling_params import SamplingParams
14 from vllm.tokenizers import get_tokenizer
15
16 # any model with a chat template should work here
17 MODEL_NAME = "facebook/opt-125m"
18
19
20 @pytest.fixture(scope="module")
21 def default_server_args():
22 return [
23 "--dtype",
24 "float32",
25 "--max-model-len",
26 "2048",
27 "--max-num-seqs",
28 "128",
29 "--enforce-eager",
30 "--enable-prompt-tokens-details",
31 "--no-enable-prefix-caching",
32 ]
33
34
35 @pytest.fixture(scope="module")
36 def server(default_server_args):
37 with RemoteOpenAIServer(MODEL_NAME, default_server_args) as remote_server:
38 yield remote_server
39
40
41 @pytest_asyncio.fixture
42 async def client(server):
43 async with server.get_async_client() as async_client:
44 yield async_client
45
46
47 @pytest.mark.asyncio
48 @pytest.mark.parametrize(
49 "model_name",
50 [MODEL_NAME],
51 )
52 async def test_single_completion(client: openai.AsyncOpenAI, model_name: str) -> None:
53 completion = await client.completions.create(
54 model=model_name, prompt="Hello, my name is", max_tokens=5, temperature=0.0
55 )
56
57 assert completion.id is not None
58 assert completion.choices is not None and len(completion.choices) == 1
59
60 choice = completion.choices[0]
61 assert len(choice.text) >= 5
62 assert choice.finish_reason == "length"
63 assert completion.usage is not None
64 assert completion.usage.completion_tokens == 5
65 assert completion.usage.prompt_tokens == 6
66 assert completion.usage.total_tokens == 11
67 assert completion.usage.prompt_tokens_details is not None
68 assert completion.usage.prompt_tokens_details.cached_tokens == 0
69
70 # test using token IDs
71 completion = await client.completions.create(
72 model=model_name,
73 prompt=[0, 0, 0, 0, 0],
74 max_tokens=5,
75 temperature=0.0,
76 )
77 assert len(completion.choices[0].text) >= 1
78 assert completion.choices[0].prompt_logprobs is None
79
80
81 @pytest.mark.asyncio
82 @pytest.mark.parametrize(
83 "model_name",
84 [MODEL_NAME],
85 )
86 async def test_completion_truncation_side_controls_prompt_truncation(
87 client: openai.AsyncOpenAI, model_name: str
88 ) -> None:
89 prompt_token_ids = list(range(8))
90
91 right_completion = await client.completions.create(
92 model=model_name,
93 prompt=prompt_token_ids,
94 max_tokens=1,
95 temperature=0.0,
96 extra_body={
97 "return_token_ids": True,
98 "truncate_prompt_tokens": 4,
99 "truncation_side": "right",
100 },
101 )
102 assert right_completion.choices[0].prompt_token_ids == prompt_token_ids[:4]
103
104 left_completion = await client.completions.create(
105 model=model_name,
106 prompt=prompt_token_ids,
107 max_tokens=1,
108 temperature=0.0,
109 extra_body={
110 "return_token_ids": True,
111 "truncate_prompt_tokens": 4,
112 "truncation_side": "left",
113 },
114 )
115 assert left_completion.choices[0].prompt_token_ids == prompt_token_ids[-4:]
116
117
118 @pytest.mark.asyncio
119 @pytest.mark.parametrize(
120 "model_name",
121 [MODEL_NAME],
122 )
123 async def test_no_logprobs(client: openai.AsyncOpenAI, model_name: str):
124 # test using token IDs
125 completion = await client.completions.create(
126 model=model_name,
127 prompt=[0, 0, 0, 0, 0],
128 max_tokens=5,
129 temperature=0.0,
130 logprobs=None,
131 )
132 choice = completion.choices[0]
133 assert choice.logprobs is None
134
135
136 @pytest.mark.asyncio
137 @pytest.mark.parametrize(
138 "model_name",
139 [MODEL_NAME],
140 )
141 async def test_zero_logprobs(client: openai.AsyncOpenAI, model_name: str):
142 # test using token IDs
143 completion = await client.completions.create(
144 model=model_name,
145 prompt=[0, 0, 0, 0, 0],
146 max_tokens=5,
147 temperature=0.0,
148 logprobs=0,
149 )
150 choice = completion.choices[0]
151 assert choice.logprobs is not None
152 assert choice.logprobs.token_logprobs is not None
153 assert choice.logprobs.top_logprobs is not None
154 assert len(choice.logprobs.top_logprobs[0]) == 1
155
156
157 @pytest.mark.asyncio
158 @pytest.mark.parametrize(
159 "model_name",
160 [MODEL_NAME],
161 )
162 async def test_some_logprobs(client: openai.AsyncOpenAI, model_name: str):
163 # test using token IDs
164 completion = await client.completions.create(
165 model=model_name,
166 prompt=[0, 0, 0, 0, 0],
167 max_tokens=5,
168 temperature=0.0,
169 logprobs=5,
170 )
171 choice = completion.choices[0]
172 assert choice.logprobs is not None
173 assert choice.logprobs.token_logprobs is not None
174 assert choice.logprobs.top_logprobs is not None
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",
236 [MODEL_NAME],
237 )
238 async def test_too_many_completion_logprobs(
239 client: openai.AsyncOpenAI, model_name: str
240 ) -> None:
241 with pytest.raises(
242 (openai.BadRequestError, openai.APIError)
243 ): # test using token IDs
244 await client.completions.create(
245 model=model_name,
246 prompt=[0, 0, 0, 0, 0],
247 max_tokens=5,
248 temperature=0.0,
249 # vLLM has higher default max_logprobs (20 instead of 5) to support
250 # both Completion API and Chat Completion API
251 logprobs=21,
252 )
253 ...
254 with pytest.raises(
255 (openai.BadRequestError, openai.APIError)
256 ): # test using token IDs
257 stream = await client.completions.create(
258 model=model_name,
259 prompt=[0, 0, 0, 0, 0],
260 max_tokens=5,
261 temperature=0.0,
262 # vLLM has higher default max_logprobs (20 instead of 5) to support
263 # both Completion API and Chat Completion API
264 logprobs=30,
265 stream=True,
266 )
267 async for chunk in stream:
268 ...
269
270 # the server should still work afterwards
271 completion = await client.completions.create(
272 model=model_name,
273 prompt=[0, 0, 0, 0, 0],
274 max_tokens=5,
275 temperature=0.0,
276 )
277 assert len(completion.choices[0].text) >= 0
278
279
280 @pytest.mark.asyncio
281 @pytest.mark.parametrize(
282 "model_name, prompt_logprobs",
283 [(MODEL_NAME, -1), (MODEL_NAME, 0), (MODEL_NAME, 1), (MODEL_NAME, None)],
284 )
285 async def test_prompt_logprobs_completion(
286 client: openai.AsyncOpenAI, model_name: str, prompt_logprobs: int | None
287 ):
288 params: dict = {
289 "prompt": ["A robot may not injure another robot", "My name is"],
290 "model": model_name,
291 }
292 if prompt_logprobs is not None:
293 params["extra_body"] = {"prompt_logprobs": prompt_logprobs}
294
295 if prompt_logprobs is not None and prompt_logprobs < 0:
296 with pytest.raises(BadRequestError):
297 await client.completions.create(**params)
298 else:
299 completion = await client.completions.create(**params)
300 if prompt_logprobs is not None:
301 assert completion.choices[0].prompt_logprobs is not None
302 assert len(completion.choices[0].prompt_logprobs) > 0
303
304 assert completion.choices[1].prompt_logprobs is not None
305 assert len(completion.choices[1].prompt_logprobs) > 0
306
307 else:
308 assert completion.choices[0].prompt_logprobs is None
309
310
311 @pytest.mark.asyncio
312 @pytest.mark.parametrize(
313 "model_name",
314 [MODEL_NAME],
315 )
316 async def test_completion_streaming(
317 client: openai.AsyncOpenAI, model_name: str
318 ) -> None:
319 prompt = "What is an LLM?"
320
321 single_completion = await client.completions.create(
322 model=model_name,
323 prompt=prompt,
324 max_tokens=5,
325 temperature=0.0,
326 )
327 single_output = single_completion.choices[0].text
328 stream = await client.completions.create(
329 model=model_name, prompt=prompt, max_tokens=5, temperature=0.0, stream=True
330 )
331 chunks: list[str] = []
332 finish_reason_count = 0
333 async for chunk in stream:
334 chunks.append(chunk.choices[0].text)
335 if chunk.choices[0].finish_reason is not None:
336 finish_reason_count += 1
337 # finish reason should only return in last block
338 assert finish_reason_count == 1
339 assert chunk.choices[0].finish_reason == "length"
340 assert chunk.choices[0].text
341 assert "".join(chunks) == single_output
342
343
344 @pytest.mark.asyncio
345 @pytest.mark.parametrize(
346 "model_name",
347 [MODEL_NAME],
348 )
349 async def test_parallel_no_streaming(client: openai.AsyncOpenAI, model_name: str):
350 """Parallel sampling without streaming.
351 A single request output contains a list of completions.
352 """
353 prompt = "What is an LLM?"
354 n = 3
355 max_tokens = 50
356
357 # High temperature to maximize chance of unique completions.
358 completion = await client.completions.create(
359 model=model_name,
360 prompt=prompt,
361 max_tokens=max_tokens,
362 n=n,
363 temperature=1.0,
364 stream=False,
365 logprobs=0,
366 seed=42,
367 )
368
369 # Assert `n` completions
370 num_completions = len(completion.choices)
371 assert num_completions == n, f"Num completions {num_completions} but expected {n}."
372 completion_repeats: dict[str, int] = {}
373 for idx, choice in enumerate(completion.choices):
374 # Assert correct completion index & some finish reason.
375 assert choice.index == idx, f"Index {choice.index} but expected {idx}."
376 assert choice.finish_reason is not None, "None finish_reason is invalid."
377 text = choice.text
378 completion_repeats[text] = completion_repeats.get(text, 0) + 1
379 # Assert `n` unique completions
380 num_unique = len(completion_repeats)
381 if num_unique != n:
382 repeats = {txt: num for (txt, num) in completion_repeats.items() if num > 1}
383 raise AssertionError(
384 f"Expected {n} unique completions, got {num_unique}; repeats: {repeats}."
385 )
386
387
388 @pytest.mark.asyncio
389 @pytest.mark.parametrize(
390 "model_name",
391 [MODEL_NAME],
392 )
393 async def test_parallel_streaming(client: openai.AsyncOpenAI, model_name: str):
394 """Streaming for parallel sampling.
395 The tokens from multiple samples, are flattened into a single stream,
396 with an index to indicate which sample the token belongs to.
397 """
398 prompt = "What is an LLM?"
399 n = 3
400 max_tokens = 50
401
402 stream = await client.completions.create(
403 model=model_name,
404 prompt=prompt,
405 max_tokens=max_tokens,
406 n=n,
407 temperature=1.0,
408 stream=True,
409 seed=42,
410 )
411 chunks: list[list[str]] = [[] for _ in range(n)]
412 finish_reason_count = 0
413 async for chunk in stream:
414 index = chunk.choices[0].index
415 text = chunk.choices[0].text
416 chunks[index].append(text)
417 if chunk.choices[0].finish_reason is not None:
418 finish_reason_count += 1
419 # Assert `n` completions with correct finish reasons
420 assert finish_reason_count == n, (
421 f"Expected {n} completions with valid indices and finish_reason."
422 )
423 completion_repeats: dict[str, int] = {}
424 for chunk in chunks:
425 chunk_len = len(chunk)
426 # Assert correct number of completion tokens
427 assert chunk_len <= max_tokens, (
428 f"max_tokens={max_tokens} but chunk len is {chunk_len}."
429 )
430 text = "".join(chunk)
431 completion_repeats[text] = completion_repeats.get(text, 0) + 1
432 print(text)
433 # Assert `n` unique completions
434 num_unique = len(completion_repeats)
435 if num_unique != n:
436 repeats = {txt: num for (txt, num) in completion_repeats.items() if num > 1}
437 raise AssertionError(
438 f"{num_unique} unique completions, expected {n}; repeats: {repeats}"
439 )
440
441
442 @pytest.mark.asyncio
443 @pytest.mark.parametrize(
444 "model_name",
445 [MODEL_NAME],
446 )
447 async def test_completion_stream_options(client: openai.AsyncOpenAI, model_name: str):
448 prompt = "What is the capital of France?"
449
450 # Test stream=True, stream_options=
451 # {"include_usage": False, "continuous_usage_stats": False}
452 stream = await client.completions.create(
453 model=model_name,
454 prompt=prompt,
455 max_tokens=5,
456 temperature=0.0,
457 stream=True,
458 stream_options={
459 "include_usage": False,
460 "continuous_usage_stats": False,
461 },
462 )
463
464 async for chunk in stream:
465 assert chunk.usage is None
466
467 # Test stream=True, stream_options=
468 # {"include_usage": False, "continuous_usage_stats": True}
469 stream = await client.completions.create(
470 model=model_name,
471 prompt=prompt,
472 max_tokens=5,
473 temperature=0.0,
474 stream=True,
475 stream_options={
476 "include_usage": False,
477 "continuous_usage_stats": True,
478 },
479 )
480 async for chunk in stream:
481 assert chunk.usage is None
482
483 # Test stream=True, stream_options=
484 # {"include_usage": True, "continuous_usage_stats": False}
485 stream = await client.completions.create(
486 model=model_name,
487 prompt=prompt,
488 max_tokens=5,
489 temperature=0.0,
490 stream=True,
491 stream_options={
492 "include_usage": True,
493 "continuous_usage_stats": False,
494 },
495 )
496 async for chunk in stream:
497 if chunk.choices[0].finish_reason is None:
498 assert chunk.usage is None
499 else:
500 assert chunk.usage is None
501 final_chunk = await anext(stream)
502 assert final_chunk.usage is not None
503 assert final_chunk.usage.prompt_tokens > 0
504 assert final_chunk.usage.completion_tokens > 0
505 assert final_chunk.usage.total_tokens == (
506 final_chunk.usage.prompt_tokens + final_chunk.usage.completion_tokens
507 )
508 assert final_chunk.choices == []
509
510 # Test stream=True, stream_options=
511 # {"include_usage": True, "continuous_usage_stats": True}
512 stream = await client.completions.create(
513 model=model_name,
514 prompt=prompt,
515 max_tokens=5,
516 temperature=0.0,
517 stream=True,
518 stream_options={
519 "include_usage": True,
520 "continuous_usage_stats": True,
521 },
522 )
523 async for chunk in stream:
524 assert chunk.usage is not None
525 assert chunk.usage.prompt_tokens > 0
526 assert chunk.usage.completion_tokens > 0
527 assert chunk.usage.total_tokens == (
528 chunk.usage.prompt_tokens + chunk.usage.completion_tokens
529 )
530 if chunk.choices[0].finish_reason is not None:
531 final_chunk = await anext(stream)
532 assert final_chunk.usage is not None
533 assert final_chunk.usage.prompt_tokens > 0
534 assert final_chunk.usage.completion_tokens > 0
535 assert final_chunk.usage.total_tokens == (
536 final_chunk.usage.prompt_tokens + final_chunk.usage.completion_tokens
537 )
538 assert final_chunk.choices == []
539
540 # Test stream=True, stream_options={}
541 stream = await client.completions.create(
542 model=model_name,
543 prompt=prompt,
544 max_tokens=5,
545 temperature=0.0,
546 stream=True,
547 stream_options={},
548 )
549 async for chunk in stream:
550 assert chunk.usage is None
551
552 # Test stream=False, stream_options=
553 # {"include_usage": None}
554 with pytest.raises(BadRequestError):
555 await client.completions.create(
556 model=model_name,
557 prompt=prompt,
558 max_tokens=5,
559 temperature=0.0,
560 stream=False,
561 stream_options={"include_usage": None},
562 )
563
564 # Test stream=False, stream_options=
565 # {"include_usage": True}
566 with pytest.raises(BadRequestError):
567 await client.completions.create(
568 model=model_name,
569 prompt=prompt,
570 max_tokens=5,
571 temperature=0.0,
572 stream=False,
573 stream_options={"include_usage": True},
574 )
575
576 # Test stream=False, stream_options=
577 # {"continuous_usage_stats": None}
578 with pytest.raises(BadRequestError):
579 await client.completions.create(
580 model=model_name,
581 prompt=prompt,
582 max_tokens=5,
583 temperature=0.0,
584 stream=False,
585 stream_options={"continuous_usage_stats": None},
586 )
587
588 # Test stream=False, stream_options=
589 # {"continuous_usage_stats": True}
590 with pytest.raises(BadRequestError):
591 await client.completions.create(
592 model=model_name,
593 prompt=prompt,
594 max_tokens=5,
595 temperature=0.0,
596 stream=False,
597 stream_options={"continuous_usage_stats": True},
598 )
599
600
601 @pytest.mark.asyncio
602 @pytest.mark.parametrize(
603 "model_name",
604 [MODEL_NAME],
605 )
606 async def test_batch_completions(client: openai.AsyncOpenAI, model_name: str):
607 # test both text and token IDs
608 for prompts in (["Hello, my name is"] * 2, [[0, 0, 0, 0, 0]] * 2):
609 # test simple list
610 batch = await client.completions.create(
611 model=model_name,
612 prompt=prompts,
613 max_tokens=5,
614 temperature=0.0,
615 )
616 assert len(batch.choices) == 2
617 assert batch.choices[0].text == batch.choices[1].text
618
619 # test n = 2
620 batch = await client.completions.create(
621 model=model_name,
622 prompt=prompts,
623 n=2,
624 max_tokens=5,
625 temperature=0.0,
626 extra_body=dict(
627 # NOTE: this has to be true for n > 1 in vLLM, but
628 # not necessary for official client.
629 use_beam_search=True
630 ),
631 )
632 assert len(batch.choices) == 4
633 assert batch.choices[0].text != batch.choices[1].text, (
634 "beam search should be different"
635 )
636 assert batch.choices[0].text == batch.choices[2].text, (
637 "two copies of the same prompt should be the same"
638 )
639 assert batch.choices[1].text == batch.choices[3].text, (
640 "two copies of the same prompt should be the same"
641 )
642
643 # test streaming
644 batch = await client.completions.create(
645 model=model_name,
646 prompt=prompts,
647 max_tokens=5,
648 temperature=0.0,
649 stream=True,
650 )
651 texts = [""] * 2
652 async for chunk in batch:
653 assert len(chunk.choices) == 1
654 choice = chunk.choices[0]
655 texts[choice.index] += choice.text
656 assert texts[0] == texts[1]
657
658
659 @pytest.mark.asyncio
660 @pytest.mark.parametrize(
661 "model_name",
662 [MODEL_NAME],
663 )
664 @pytest.mark.parametrize("logprobs_arg", [1, 0])
665 async def test_echo_logprob_completion(
666 client: openai.AsyncOpenAI, model_name: str, logprobs_arg: int
667 ):
668 tokenizer = get_tokenizer(tokenizer_name=MODEL_NAME)
669 # test using text and token IDs
670 for prompt in ("Hello, my name is", [0, 0, 0, 0, 0]):
671 completion = await client.completions.create(
672 model=model_name,
673 prompt=prompt,
674 max_tokens=5,
675 temperature=0.0,
676 echo=True,
677 logprobs=logprobs_arg,
678 )
679
680 prompt_text = tokenizer.decode(prompt) if isinstance(prompt, list) else prompt
681 assert re.search(r"^" + prompt_text, completion.choices[0].text)
682 logprobs = completion.choices[0].logprobs
683 assert logprobs is not None
684 assert len(logprobs.text_offset) > 5
685 assert len(logprobs.token_logprobs) > 5 and logprobs.token_logprobs[0] is None
686 assert len(logprobs.top_logprobs) > 5 and logprobs.top_logprobs[0] is None
687 for top_logprobs in logprobs.top_logprobs[1:]:
688 assert max(logprobs_arg, 1) <= len(top_logprobs) <= logprobs_arg + 1
689 assert len(logprobs.tokens) > 5
690
691
692 @pytest.mark.asyncio
693 @pytest.mark.parametrize(
694 "model_name",
695 [MODEL_NAME],
696 )
697 async def test_invalid_json_schema(client: openai.AsyncOpenAI, model_name: str) -> None:
698 invalid_json_schema = {
699 "$defs": {
700 "CarType": {
701 "enum": ["sedan", "SUV", "Truck", "Coupe"],
702 "title": "CarType",
703 "type": "string",
704 }
705 },
706 "properties": {
707 "brand": {"title": "Brand", "type": "string"},
708 "model": {"title": "Model", "type": "string"},
709 "car_type": {"$ref": "#/$defs/CarType"},
710 "foo": "bar",
711 },
712 "required": ["brand", "model", "car_type"],
713 "title": "CarDescription",
714 "type": "object",
715 }
716 prompt = (
717 "Generate a JSON with the brand, model and car_type of"
718 "the most iconic car from the 90's"
719 )
720 with pytest.raises((openai.BadRequestError, openai.APIError)):
721 await client.completions.create(
722 model=model_name,
723 prompt=prompt,
724 extra_body={"structured_outputs": {"json": invalid_json_schema}},
725 )
726
727
728 @pytest.mark.asyncio
729 @pytest.mark.parametrize(
730 "model_name",
731 [MODEL_NAME],
732 )
733 async def test_invalid_regex(client: openai.AsyncOpenAI, model_name: str):
734 prompt = (
735 "Generate an email address for Alan Turing, who works in Enigma."
736 "End in .com and new line. Example result:"
737 "alan.turing@enigma.com\n"
738 )
739
740 with pytest.raises((openai.BadRequestError, openai.APIError)):
741 await client.completions.create(
742 model=model_name,
743 prompt=prompt,
744 extra_body={"structured_outputs": {"regex": r"[.*"}, "stop": ["\n"]},
745 )
746
747
748 @pytest.mark.asyncio
749 @pytest.mark.parametrize(
750 "model_name",
751 [MODEL_NAME],
752 )
753 async def test_invalid_grammar(client: openai.AsyncOpenAI, model_name: str):
754 invalid_simplified_sql_grammar = """
755 root ::= select_statementinvalidsyntax
756
757 select_statement ::= "SELECT " column " from " table " where " condition
758
759 column ::= "col_1 " | "col_2 "
760
761 table ::= "table_1 " | "table_2 "
762
763 condition ::= column "= " number
764
765 number ::= "1 " | "2 "
766 """
767
768 prompt = (
769 "Generate an SQL query to show the 'username' and 'email'"
770 "from the 'users' table."
771 )
772 with pytest.raises((openai.BadRequestError, openai.APIError)):
773 await client.completions.create(
774 model=model_name,
775 prompt=prompt,
776 extra_body={
777 "structured_outputs": {"grammar": invalid_simplified_sql_grammar}
778 },
779 )
780
781
782 # Unit tests for bad_words in CompletionRequest.to_sampling_params()
783 def test_completion_request_bad_words_to_sampling_params():
784 """bad_words should be forwarded to SamplingParams (parity with chat)."""
785 request = CompletionRequest(
786 model="test-model",
787 prompt="Hello",
788 bad_words=["foo", "bar"],
789 max_tokens=10,
790 )
791
792 sampling_params = request.to_sampling_params(
793 max_tokens=10,
794 default_sampling_params={},
795 )
796
797 assert isinstance(sampling_params, SamplingParams)
798 assert sampling_params.bad_words == ["foo", "bar"]
799
800
801 def test_completion_request_bad_words_default_empty():
802 """bad_words defaults to an empty list, matching the chat endpoint."""
803 request = CompletionRequest(
804 model="test-model",
805 prompt="Hello",
806 max_tokens=10,
807 )
808
809 assert request.bad_words == []
810 sampling_params = request.to_sampling_params(
811 max_tokens=10,
812 default_sampling_params={},
813 )
814 assert sampling_params.bad_words == []
815
816
817 def test_completion_request_forwards_routed_experts_prompt_start():
818 request = CompletionRequest(
819 model="test-model",
820 prompt="Hello",
821 max_tokens=10,
822 routed_experts_prompt_start=3,
823 )
824
825 sampling_params = request.to_sampling_params(
826 max_tokens=10,
827 default_sampling_params={},
828 )
829
830 assert sampling_params.routed_experts_prompt_start == 3