openai/openai-python

Public

mirrored from https://github.com/openai/openai-pythonAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.41.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

tests/lib/chat/test_completions.py

689lines · modecode

1from __future__ import annotations
2
3import os
4import json
5from enum import Enum
6from typing import Any, Callable
7from typing_extensions import Literal, TypeVar
8
9import httpx
10import pytest
11from respx import MockRouter
12from pydantic import Field, BaseModel
13from inline_snapshot import snapshot
14
15import openai
16from openai import OpenAI, AsyncOpenAI
17from openai._utils import assert_signatures_in_sync
18from openai._compat import PYDANTIC_V2
19
20from ._utils import print_obj
21from ...conftest import base_url
22from ..schema_types.query import Query
23
24_T = TypeVar("_T")
25
26# all the snapshots in this file are auto-generated from the live API
27#
28# you can update them with
29#
30# `OPENAI_LIVE=1 pytest --inline-snapshot=fix`
31
32
33@pytest.mark.respx(base_url=base_url)
34def test_parse_nothing(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
35 completion = _make_snapshot_request(
36 lambda c: c.beta.chat.completions.parse(
37 model="gpt-4o-2024-08-06",
38 messages=[
39 {
40 "role": "user",
41 "content": "What's the weather like in SF?",
42 },
43 ],
44 ),
45 content_snapshot=snapshot(
46 '{"id": "chatcmpl-9tXjSozlYq8oGdlRH3vgLsiUNRg8c", "object": "chat.completion", "created": 1723024734, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "I\'m unable to provide real-time weather updates. To find out the current weather in San Francisco, please check a reliable weather website or app.", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 14, "completion_tokens": 28, "total_tokens": 42}, "system_fingerprint": "fp_845eaabc1f"}'
47 ),
48 mock_client=client,
49 respx_mock=respx_mock,
50 )
51
52 assert print_obj(completion, monkeypatch) == snapshot(
53 """\
54ParsedChatCompletion[NoneType](
55 choices=[
56 ParsedChoice[NoneType](
57 finish_reason='stop',
58 index=0,
59 logprobs=None,
60 message=ParsedChatCompletionMessage[NoneType](
61 content="I'm unable to provide real-time weather updates. To find out the current weather in San
62Francisco, please check a reliable weather website or app.",
63 function_call=None,
64 parsed=None,
65 refusal=None,
66 role='assistant',
67 tool_calls=[]
68 )
69 )
70 ],
71 created=1723024734,
72 id='chatcmpl-9tXjSozlYq8oGdlRH3vgLsiUNRg8c',
73 model='gpt-4o-2024-08-06',
74 object='chat.completion',
75 service_tier=None,
76 system_fingerprint='fp_845eaabc1f',
77 usage=CompletionUsage(completion_tokens=28, prompt_tokens=14, total_tokens=42)
78)
79"""
80 )
81
82
83@pytest.mark.respx(base_url=base_url)
84def test_parse_pydantic_model(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
85 class Location(BaseModel):
86 city: str
87 temperature: float
88 units: Literal["c", "f"]
89
90 completion = _make_snapshot_request(
91 lambda c: c.beta.chat.completions.parse(
92 model="gpt-4o-2024-08-06",
93 messages=[
94 {
95 "role": "user",
96 "content": "What's the weather like in SF?",
97 },
98 ],
99 response_format=Location,
100 ),
101 content_snapshot=snapshot(
102 '{"id": "chatcmpl-9tXjTNupyDe7nL1Z8eOO6BdSyrHAD", "object": "chat.completion", "created": 1723024735, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":56,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 17, "completion_tokens": 14, "total_tokens": 31}, "system_fingerprint": "fp_2a322c9ffc"}'
103 ),
104 mock_client=client,
105 respx_mock=respx_mock,
106 )
107
108 assert print_obj(completion, monkeypatch) == snapshot(
109 """\
110ParsedChatCompletion[Location](
111 choices=[
112 ParsedChoice[Location](
113 finish_reason='stop',
114 index=0,
115 logprobs=None,
116 message=ParsedChatCompletionMessage[Location](
117 content='{"city":"San Francisco","temperature":56,"units":"f"}',
118 function_call=None,
119 parsed=Location(city='San Francisco', temperature=56.0, units='f'),
120 refusal=None,
121 role='assistant',
122 tool_calls=[]
123 )
124 )
125 ],
126 created=1723024735,
127 id='chatcmpl-9tXjTNupyDe7nL1Z8eOO6BdSyrHAD',
128 model='gpt-4o-2024-08-06',
129 object='chat.completion',
130 service_tier=None,
131 system_fingerprint='fp_2a322c9ffc',
132 usage=CompletionUsage(completion_tokens=14, prompt_tokens=17, total_tokens=31)
133)
134"""
135 )
136
137
138@pytest.mark.respx(base_url=base_url)
139def test_parse_pydantic_model_enum(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
140 class Color(Enum):
141 """The detected color"""
142
143 RED = "red"
144 BLUE = "blue"
145 GREEN = "green"
146
147 class ColorDetection(BaseModel):
148 color: Color
149 hex_color_code: str = Field(description="The hex color code of the detected color")
150
151 if not PYDANTIC_V2:
152 ColorDetection.update_forward_refs(**locals()) # type: ignore
153
154 completion = _make_snapshot_request(
155 lambda c: c.beta.chat.completions.parse(
156 model="gpt-4o-2024-08-06",
157 messages=[
158 {"role": "user", "content": "What color is a Coke can?"},
159 ],
160 response_format=ColorDetection,
161 ),
162 content_snapshot=snapshot(
163 '{"id": "chatcmpl-9vK4UZVr385F2UgZlP1ShwPn2nFxG", "object": "chat.completion", "created": 1723448878, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"color\\":\\"red\\",\\"hex_color_code\\":\\"#FF0000\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 18, "completion_tokens": 14, "total_tokens": 32}, "system_fingerprint": "fp_845eaabc1f"}'
164 ),
165 mock_client=client,
166 respx_mock=respx_mock,
167 )
168
169 assert print_obj(completion.choices[0], monkeypatch) == snapshot(
170 """\
171ParsedChoice[ColorDetection](
172 finish_reason='stop',
173 index=0,
174 logprobs=None,
175 message=ParsedChatCompletionMessage[ColorDetection](
176 content='{"color":"red","hex_color_code":"#FF0000"}',
177 function_call=None,
178 parsed=ColorDetection(color=<Color.RED: 'red'>, hex_color_code='#FF0000'),
179 refusal=None,
180 role='assistant',
181 tool_calls=[]
182 )
183)
184"""
185 )
186
187
188@pytest.mark.respx(base_url=base_url)
189def test_parse_pydantic_model_multiple_choices(
190 client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch
191) -> None:
192 class Location(BaseModel):
193 city: str
194 temperature: float
195 units: Literal["c", "f"]
196
197 completion = _make_snapshot_request(
198 lambda c: c.beta.chat.completions.parse(
199 model="gpt-4o-2024-08-06",
200 messages=[
201 {
202 "role": "user",
203 "content": "What's the weather like in SF?",
204 },
205 ],
206 n=3,
207 response_format=Location,
208 ),
209 content_snapshot=snapshot(
210 '{"id": "chatcmpl-9tXjUrNFyyjSB2FJ842TMDNRM6Gen", "object": "chat.completion", "created": 1723024736, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":58,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}, {"index": 1, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":58,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}, {"index": 2, "message": {"role": "assistant", "content": "{\\"city\\":\\"San Francisco\\",\\"temperature\\":63,\\"units\\":\\"f\\"}", "refusal": null}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 17, "completion_tokens": 42, "total_tokens": 59}, "system_fingerprint": "fp_845eaabc1f"}'
211 ),
212 mock_client=client,
213 respx_mock=respx_mock,
214 )
215
216 assert print_obj(completion.choices, monkeypatch) == snapshot(
217 """\
218[
219 ParsedChoice[Location](
220 finish_reason='stop',
221 index=0,
222 logprobs=None,
223 message=ParsedChatCompletionMessage[Location](
224 content='{"city":"San Francisco","temperature":58,"units":"f"}',
225 function_call=None,
226 parsed=Location(city='San Francisco', temperature=58.0, units='f'),
227 refusal=None,
228 role='assistant',
229 tool_calls=[]
230 )
231 ),
232 ParsedChoice[Location](
233 finish_reason='stop',
234 index=1,
235 logprobs=None,
236 message=ParsedChatCompletionMessage[Location](
237 content='{"city":"San Francisco","temperature":58,"units":"f"}',
238 function_call=None,
239 parsed=Location(city='San Francisco', temperature=58.0, units='f'),
240 refusal=None,
241 role='assistant',
242 tool_calls=[]
243 )
244 ),
245 ParsedChoice[Location](
246 finish_reason='stop',
247 index=2,
248 logprobs=None,
249 message=ParsedChatCompletionMessage[Location](
250 content='{"city":"San Francisco","temperature":63,"units":"f"}',
251 function_call=None,
252 parsed=Location(city='San Francisco', temperature=63.0, units='f'),
253 refusal=None,
254 role='assistant',
255 tool_calls=[]
256 )
257 )
258]
259"""
260 )
261
262
263@pytest.mark.respx(base_url=base_url)
264def test_pydantic_tool_model_all_types(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
265 completion = _make_snapshot_request(
266 lambda c: c.beta.chat.completions.parse(
267 model="gpt-4o-2024-08-06",
268 messages=[
269 {
270 "role": "user",
271 "content": "look up all my orders in may of last year that were fulfilled but not delivered on time",
272 },
273 ],
274 tools=[openai.pydantic_function_tool(Query)],
275 response_format=Query,
276 ),
277 content_snapshot=snapshot(
278 '{"id": "chatcmpl-9tXjVJVCLTn7CWFhpjETixvvApCk3", "object": "chat.completion", "created": 1723024737, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_Un4g0IXeQGOyqKBS3zhqNCox", "type": "function", "function": {"name": "Query", "arguments": "{\\"table_name\\":\\"orders\\",\\"columns\\":[\\"id\\",\\"status\\",\\"expected_delivery_date\\",\\"delivered_at\\",\\"shipped_at\\",\\"ordered_at\\"],\\"conditions\\":[{\\"column\\":\\"ordered_at\\",\\"operator\\":\\">=\\",\\"value\\":\\"2022-05-01\\"},{\\"column\\":\\"ordered_at\\",\\"operator\\":\\"<=\\",\\"value\\":\\"2022-05-31\\"},{\\"column\\":\\"status\\",\\"operator\\":\\"=\\",\\"value\\":\\"fulfilled\\"},{\\"column\\":\\"delivered_at\\",\\"operator\\":\\">\\",\\"value\\":{\\"column_name\\":\\"expected_delivery_date\\"}}],\\"order_by\\":\\"asc\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 195, "completion_tokens": 114, "total_tokens": 309}, "system_fingerprint": "fp_845eaabc1f"}'
279 ),
280 mock_client=client,
281 respx_mock=respx_mock,
282 )
283
284 assert print_obj(completion.choices[0], monkeypatch) == snapshot(
285 """\
286ParsedChoice[Query](
287 finish_reason='tool_calls',
288 index=0,
289 logprobs=None,
290 message=ParsedChatCompletionMessage[Query](
291 content=None,
292 function_call=None,
293 parsed=None,
294 refusal=None,
295 role='assistant',
296 tool_calls=[
297 ParsedFunctionToolCall(
298 function=ParsedFunction(
299 arguments='{"table_name":"orders","columns":["id","status","expected_delivery_date","delivered_at","
300shipped_at","ordered_at"],"conditions":[{"column":"ordered_at","operator":">=","value":"2022-05-01"},{"column":"ordered_
301at","operator":"<=","value":"2022-05-31"},{"column":"status","operator":"=","value":"fulfilled"},{"column":"delivered_at
302","operator":">","value":{"column_name":"expected_delivery_date"}}],"order_by":"asc"}',
303 name='Query',
304 parsed_arguments=Query(
305 columns=[
306 <Column.id: 'id'>,
307 <Column.status: 'status'>,
308 <Column.expected_delivery_date: 'expected_delivery_date'>,
309 <Column.delivered_at: 'delivered_at'>,
310 <Column.shipped_at: 'shipped_at'>,
311 <Column.ordered_at: 'ordered_at'>
312 ],
313 conditions=[
314 Condition(column='ordered_at', operator=<Operator.ge: '>='>, value='2022-05-01'),
315 Condition(column='ordered_at', operator=<Operator.le: '<='>, value='2022-05-31'),
316 Condition(column='status', operator=<Operator.eq: '='>, value='fulfilled'),
317 Condition(
318 column='delivered_at',
319 operator=<Operator.gt: '>'>,
320 value=DynamicValue(column_name='expected_delivery_date')
321 )
322 ],
323 order_by=<OrderBy.asc: 'asc'>,
324 table_name=<Table.orders: 'orders'>
325 )
326 ),
327 id='call_Un4g0IXeQGOyqKBS3zhqNCox',
328 type='function'
329 )
330 ]
331 )
332)
333"""
334 )
335
336
337@pytest.mark.respx(base_url=base_url)
338def test_parse_max_tokens_reached(client: OpenAI, respx_mock: MockRouter) -> None:
339 class Location(BaseModel):
340 city: str
341 temperature: float
342 units: Literal["c", "f"]
343
344 with pytest.raises(openai.LengthFinishReasonError):
345 _make_snapshot_request(
346 lambda c: c.beta.chat.completions.parse(
347 model="gpt-4o-2024-08-06",
348 messages=[
349 {
350 "role": "user",
351 "content": "What's the weather like in SF?",
352 },
353 ],
354 max_tokens=1,
355 response_format=Location,
356 ),
357 content_snapshot=snapshot(
358 '{"id": "chatcmpl-9tXjYACgVKixKdMv2nVQqDVELkdSF", "object": "chat.completion", "created": 1723024740, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": "{\\"", "refusal": null}, "logprobs": null, "finish_reason": "length"}], "usage": {"prompt_tokens": 17, "completion_tokens": 1, "total_tokens": 18}, "system_fingerprint": "fp_2a322c9ffc"}'
359 ),
360 mock_client=client,
361 respx_mock=respx_mock,
362 )
363
364
365@pytest.mark.respx(base_url=base_url)
366def test_parse_pydantic_model_refusal(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
367 class Location(BaseModel):
368 city: str
369 temperature: float
370 units: Literal["c", "f"]
371
372 completion = _make_snapshot_request(
373 lambda c: c.beta.chat.completions.parse(
374 model="gpt-4o-2024-08-06",
375 messages=[
376 {
377 "role": "user",
378 "content": "How do I make anthrax?",
379 },
380 ],
381 response_format=Location,
382 ),
383 content_snapshot=snapshot(
384 '{"id": "chatcmpl-9tXm7FnIj3hSot5xM4c954MIePle0", "object": "chat.completion", "created": 1723024899, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "refusal": "I\'m very sorry, but I can\'t assist with that request."}, "logprobs": null, "finish_reason": "stop"}], "usage": {"prompt_tokens": 17, "completion_tokens": 13, "total_tokens": 30}, "system_fingerprint": "fp_845eaabc1f"}'
385 ),
386 mock_client=client,
387 respx_mock=respx_mock,
388 )
389
390 assert print_obj(completion.choices, monkeypatch) == snapshot(
391 """\
392[
393 ParsedChoice[Location](
394 finish_reason='stop',
395 index=0,
396 logprobs=None,
397 message=ParsedChatCompletionMessage[Location](
398 content=None,
399 function_call=None,
400 parsed=None,
401 refusal="I'm very sorry, but I can't assist with that request.",
402 role='assistant',
403 tool_calls=[]
404 )
405 )
406]
407"""
408 )
409
410
411@pytest.mark.respx(base_url=base_url)
412def test_parse_pydantic_tool(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
413 class GetWeatherArgs(BaseModel):
414 city: str
415 country: str
416 units: Literal["c", "f"] = "c"
417
418 completion = _make_snapshot_request(
419 lambda c: c.beta.chat.completions.parse(
420 model="gpt-4o-2024-08-06",
421 messages=[
422 {
423 "role": "user",
424 "content": "What's the weather like in Edinburgh?",
425 },
426 ],
427 tools=[
428 openai.pydantic_function_tool(GetWeatherArgs),
429 ],
430 ),
431 content_snapshot=snapshot(
432 '{"id": "chatcmpl-9tXjbQ9V0l5XPlynOJHKvrWsJQymO", "object": "chat.completion", "created": 1723024743, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_EEaIYq8aTdiDWro8jILNl3XK", "type": "function", "function": {"name": "GetWeatherArgs", "arguments": "{\\"city\\":\\"Edinburgh\\",\\"country\\":\\"GB\\",\\"units\\":\\"c\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 76, "completion_tokens": 24, "total_tokens": 100}, "system_fingerprint": "fp_2a322c9ffc"}'
433 ),
434 mock_client=client,
435 respx_mock=respx_mock,
436 )
437
438 assert print_obj(completion.choices, monkeypatch) == snapshot(
439 """\
440[
441 ParsedChoice[NoneType](
442 finish_reason='tool_calls',
443 index=0,
444 logprobs=None,
445 message=ParsedChatCompletionMessage[NoneType](
446 content=None,
447 function_call=None,
448 parsed=None,
449 refusal=None,
450 role='assistant',
451 tool_calls=[
452 ParsedFunctionToolCall(
453 function=ParsedFunction(
454 arguments='{"city":"Edinburgh","country":"GB","units":"c"}',
455 name='GetWeatherArgs',
456 parsed_arguments=GetWeatherArgs(city='Edinburgh', country='GB', units='c')
457 ),
458 id='call_EEaIYq8aTdiDWro8jILNl3XK',
459 type='function'
460 )
461 ]
462 )
463 )
464]
465"""
466 )
467
468
469@pytest.mark.respx(base_url=base_url)
470def test_parse_multiple_pydantic_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
471 class GetWeatherArgs(BaseModel):
472 """Get the temperature for the given country/city combo"""
473
474 city: str
475 country: str
476 units: Literal["c", "f"] = "c"
477
478 class GetStockPrice(BaseModel):
479 ticker: str
480 exchange: str
481
482 completion = _make_snapshot_request(
483 lambda c: c.beta.chat.completions.parse(
484 model="gpt-4o-2024-08-06",
485 messages=[
486 {
487 "role": "user",
488 "content": "What's the weather like in Edinburgh?",
489 },
490 {
491 "role": "user",
492 "content": "What's the price of AAPL?",
493 },
494 ],
495 tools=[
496 openai.pydantic_function_tool(GetWeatherArgs),
497 openai.pydantic_function_tool(
498 GetStockPrice, name="get_stock_price", description="Fetch the latest price for a given ticker"
499 ),
500 ],
501 ),
502 content_snapshot=snapshot(
503 '{"id": "chatcmpl-9tXjcnIvzZDXRfLfbVTPNL5963GWw", "object": "chat.completion", "created": 1723024744, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_ECSuZ8gcNPPwgt24me91jHsJ", "type": "function", "function": {"name": "GetWeatherArgs", "arguments": "{\\"city\\": \\"Edinburgh\\", \\"country\\": \\"UK\\", \\"units\\": \\"c\\"}"}}, {"id": "call_Z3fM2sNBBGILhMtimk5Y3RQk", "type": "function", "function": {"name": "get_stock_price", "arguments": "{\\"ticker\\": \\"AAPL\\", \\"exchange\\": \\"NASDAQ\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 149, "completion_tokens": 60, "total_tokens": 209}, "system_fingerprint": "fp_845eaabc1f"}'
504 ),
505 mock_client=client,
506 respx_mock=respx_mock,
507 )
508
509 assert print_obj(completion.choices, monkeypatch) == snapshot(
510 """\
511[
512 ParsedChoice[NoneType](
513 finish_reason='tool_calls',
514 index=0,
515 logprobs=None,
516 message=ParsedChatCompletionMessage[NoneType](
517 content=None,
518 function_call=None,
519 parsed=None,
520 refusal=None,
521 role='assistant',
522 tool_calls=[
523 ParsedFunctionToolCall(
524 function=ParsedFunction(
525 arguments='{"city": "Edinburgh", "country": "UK", "units": "c"}',
526 name='GetWeatherArgs',
527 parsed_arguments=GetWeatherArgs(city='Edinburgh', country='UK', units='c')
528 ),
529 id='call_ECSuZ8gcNPPwgt24me91jHsJ',
530 type='function'
531 ),
532 ParsedFunctionToolCall(
533 function=ParsedFunction(
534 arguments='{"ticker": "AAPL", "exchange": "NASDAQ"}',
535 name='get_stock_price',
536 parsed_arguments=GetStockPrice(exchange='NASDAQ', ticker='AAPL')
537 ),
538 id='call_Z3fM2sNBBGILhMtimk5Y3RQk',
539 type='function'
540 )
541 ]
542 )
543 )
544]
545"""
546 )
547
548
549@pytest.mark.respx(base_url=base_url)
550def test_parse_strict_tools(client: OpenAI, respx_mock: MockRouter, monkeypatch: pytest.MonkeyPatch) -> None:
551 completion = _make_snapshot_request(
552 lambda c: c.beta.chat.completions.parse(
553 model="gpt-4o-2024-08-06",
554 messages=[
555 {
556 "role": "user",
557 "content": "What's the weather like in SF?",
558 },
559 ],
560 tools=[
561 {
562 "type": "function",
563 "function": {
564 "name": "get_weather",
565 "parameters": {
566 "type": "object",
567 "properties": {
568 "city": {"type": "string"},
569 "state": {"type": "string"},
570 },
571 "required": [
572 "city",
573 "state",
574 ],
575 "additionalProperties": False,
576 },
577 "strict": True,
578 },
579 }
580 ],
581 ),
582 content_snapshot=snapshot(
583 '{"id": "chatcmpl-9tXjfjETDIqeYvDjsuGACbwdY0xsr", "object": "chat.completion", "created": 1723024747, "model": "gpt-4o-2024-08-06", "choices": [{"index": 0, "message": {"role": "assistant", "content": null, "tool_calls": [{"id": "call_7ZZPctBXQWexQlIHSrIHMVUq", "type": "function", "function": {"name": "get_weather", "arguments": "{\\"city\\":\\"San Francisco\\",\\"state\\":\\"CA\\"}"}}], "refusal": null}, "logprobs": null, "finish_reason": "tool_calls"}], "usage": {"prompt_tokens": 48, "completion_tokens": 19, "total_tokens": 67}, "system_fingerprint": "fp_2a322c9ffc"}'
584 ),
585 mock_client=client,
586 respx_mock=respx_mock,
587 )
588
589 assert print_obj(completion.choices, monkeypatch) == snapshot(
590 """\
591[
592 ParsedChoice[NoneType](
593 finish_reason='tool_calls',
594 index=0,
595 logprobs=None,
596 message=ParsedChatCompletionMessage[NoneType](
597 content=None,
598 function_call=None,
599 parsed=None,
600 refusal=None,
601 role='assistant',
602 tool_calls=[
603 ParsedFunctionToolCall(
604 function=ParsedFunction(
605 arguments='{"city":"San Francisco","state":"CA"}',
606 name='get_weather',
607 parsed_arguments={'city': 'San Francisco', 'state': 'CA'}
608 ),
609 id='call_7ZZPctBXQWexQlIHSrIHMVUq',
610 type='function'
611 )
612 ]
613 )
614 )
615]
616"""
617 )
618
619
620def test_parse_non_strict_tools(client: OpenAI) -> None:
621 with pytest.raises(
622 ValueError, match="`get_weather` is not strict. Only `strict` function tools can be auto-parsed"
623 ):
624 client.beta.chat.completions.parse(
625 model="gpt-4o-2024-08-06",
626 messages=[],
627 tools=[
628 {
629 "type": "function",
630 "function": {
631 "name": "get_weather",
632 "parameters": {},
633 },
634 }
635 ],
636 )
637
638
639@pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"])
640def test_parse_method_in_sync(sync: bool, client: OpenAI, async_client: AsyncOpenAI) -> None:
641 checking_client: OpenAI | AsyncOpenAI = client if sync else async_client
642
643 assert_signatures_in_sync(
644 checking_client.chat.completions.create,
645 checking_client.beta.chat.completions.parse,
646 exclude_params={"response_format", "stream"},
647 )
648
649
650def _make_snapshot_request(
651 func: Callable[[OpenAI], _T],
652 *,
653 content_snapshot: Any,
654 respx_mock: MockRouter,
655 mock_client: OpenAI,
656) -> _T:
657 live = os.environ.get("OPENAI_LIVE") == "1"
658 if live:
659
660 def _on_response(response: httpx.Response) -> None:
661 # update the content snapshot
662 assert json.dumps(json.loads(response.read())) == content_snapshot
663
664 respx_mock.stop()
665
666 client = OpenAI(
667 http_client=httpx.Client(
668 event_hooks={
669 "response": [_on_response],
670 }
671 )
672 )
673 else:
674 respx_mock.post("/chat/completions").mock(
675 return_value=httpx.Response(
676 200,
677 content=content_snapshot._old_value,
678 headers={"content-type": "application/json"},
679 )
680 )
681
682 client = mock_client
683
684 result = func(client)
685
686 if live:
687 client.close()
688
689 return result
690