openai/openai-python

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.40.4

Branches

Tags

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

Clone

HTTPS

Download ZIP

tests/lib/chat/test_completions.py

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