Skip to content

Commit 96f090b

Browse files
committed
fix: properly handle extra_body parameter to match OpenAI SDK behavior
When users pass extra_body={'a': 'b'} to API methods, the contents should be merged at the top level of the request body, not nested under an 'extra_body' key. Previously, the SDK was passing kwargs directly to the OpenAI SDK's extra_body parameter, which meant extra_body={'a': 'b'} would result in the request body containing {'extra_body': {'a': 'b'}} instead of just {'a': 'b'}. This fix: - Adds extract_extra_params() helper in utils.py that properly extracts and merges extra_body, extra_headers, extra_query, and timeout from kwargs - Updates 11 API files to use the new helper: - chat_complete.py - complete.py - embeddings.py - moderations.py - models.py - audio.py - images.py - batches.py - fine_tuning.py - uploads.py - evals.py The fix ensures that: - extra_body={'a': 'b'} adds 'a' at the top level of the request body - Additional kwargs are also merged into the request body - extra_headers, extra_query, and timeout are properly passed through
1 parent 1d9c53e commit 96f090b

12 files changed

Lines changed: 218 additions & 216 deletions

File tree

portkey_ai/api_resources/apis/audio.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from portkey_ai.api_resources.apis.api_resource import APIResource, AsyncAPIResource
44
from portkey_ai.api_resources.global_constants import AUDIO_FILE_DURATION_HEADER
55
from portkey_ai.api_resources.get_audio_duration import get_audio_file_duration
6+
from portkey_ai.api_resources.utils import extract_extra_params
67
from ..._vendor.openai._types import Omit, omit, FileTypes
78
from portkey_ai.api_resources.client import AsyncPortkey, Portkey
89
import typing
@@ -43,11 +44,13 @@ def create(
4344
stream: Union[bool, Omit] = omit,
4445
**kwargs,
4546
) -> Union[Transcription, TranscriptionVerbose, str]:
46-
extra_headers = kwargs.pop("extra_headers", {})
47+
extra_params = extract_extra_params(kwargs)
48+
extra_headers = extra_params.get("extra_headers") or {}
4749
if file.name and self._client.calculate_audio_duration:
4850
duration = get_audio_file_duration(file.name)
4951
if duration is not None:
5052
extra_headers[AUDIO_FILE_DURATION_HEADER] = duration
53+
extra_params["extra_headers"] = extra_headers if extra_headers else None
5154
if stream:
5255
return self.openai_client.audio.transcriptions.create(
5356
file=file,
@@ -58,8 +61,7 @@ def create(
5861
temperature=temperature,
5962
timestamp_granularities=timestamp_granularities,
6063
stream=stream,
61-
extra_headers=extra_headers,
62-
extra_body=kwargs,
64+
**extra_params,
6365
)
6466
else:
6567
response = self.openai_client.with_raw_response.audio.transcriptions.create(
@@ -70,8 +72,7 @@ def create(
7072
response_format=response_format,
7173
temperature=temperature,
7274
timestamp_granularities=timestamp_granularities,
73-
extra_headers=extra_headers,
74-
extra_body=kwargs,
75+
**extra_params,
7576
)
7677

7778
if response_format == "verbose_json":
@@ -101,18 +102,19 @@ def create(
101102
temperature: Union[float, Omit] = omit,
102103
**kwargs,
103104
) -> Union[Translation, TranslationVerbose, str]:
104-
extra_headers = kwargs.pop("extra_headers", {})
105+
extra_params = extract_extra_params(kwargs)
106+
extra_headers = extra_params.get("extra_headers") or {}
105107
if file.name and self._client.calculate_audio_duration: # type: ignore[union-attr]
106108
duration = get_audio_file_duration(file.name) # type: ignore[union-attr]
107109
if duration is not None:
108110
extra_headers[AUDIO_FILE_DURATION_HEADER] = duration
111+
extra_params["extra_headers"] = extra_headers if extra_headers else None
109112
response = self.openai_client.with_raw_response.audio.translations.create(
110113
file=file,
111114
model=model,
112115
prompt=prompt,
113116
temperature=temperature,
114-
extra_headers=extra_headers,
115-
extra_body=kwargs,
117+
**extra_params,
116118
)
117119
data = Translation(**json.loads(response.text))
118120
data._headers = response.headers
@@ -139,15 +141,14 @@ def create(
139141
) -> Any:
140142
if stream is True:
141143
self.openai_client = self.openai_client.with_streaming_response
142-
extra_headers = kwargs.pop("extra_headers", {})
144+
extra_params = extract_extra_params(kwargs)
143145
response = self.openai_client.audio.speech.create(
144146
input=input,
145147
model=model,
146148
voice=voice,
147149
response_format=response_format,
148150
speed=speed,
149-
extra_headers=extra_headers,
150-
extra_body=kwargs,
151+
**extra_params,
151152
)
152153

153154
return response
@@ -181,11 +182,13 @@ async def create(
181182
stream: Union[bool, Omit] = omit,
182183
**kwargs,
183184
) -> Union[Transcription, TranscriptionVerbose, str]:
184-
extra_headers = kwargs.pop("extra_headers", {})
185+
extra_params = extract_extra_params(kwargs)
186+
extra_headers = extra_params.get("extra_headers") or {}
185187
if file.name and self._client.calculate_audio_duration:
186188
duration = get_audio_file_duration(file.name)
187189
if duration is not None:
188190
extra_headers[AUDIO_FILE_DURATION_HEADER] = duration
191+
extra_params["extra_headers"] = extra_headers if extra_headers else None
189192
if stream:
190193
return await self.openai_client.audio.transcriptions.create(
191194
file=file,
@@ -196,8 +199,7 @@ async def create(
196199
temperature=temperature,
197200
timestamp_granularities=timestamp_granularities,
198201
stream=stream,
199-
extra_headers=extra_headers,
200-
extra_body=kwargs,
202+
**extra_params,
201203
)
202204
else:
203205
response = (
@@ -209,8 +211,7 @@ async def create(
209211
response_format=response_format,
210212
temperature=temperature,
211213
timestamp_granularities=timestamp_granularities,
212-
extra_headers=extra_headers,
213-
extra_body=kwargs,
214+
**extra_params,
214215
)
215216
)
216217

@@ -241,18 +242,19 @@ async def create(
241242
temperature: Union[float, Omit] = omit,
242243
**kwargs,
243244
) -> Union[Translation, TranslationVerbose, str]:
244-
extra_headers = kwargs.pop("extra_headers", {})
245+
extra_params = extract_extra_params(kwargs)
246+
extra_headers = extra_params.get("extra_headers") or {}
245247
if file.name and self._client.calculate_audio_duration: # type: ignore[union-attr]
246248
duration = get_audio_file_duration(file.name) # type: ignore[union-attr]
247249
if duration is not None:
248250
extra_headers[AUDIO_FILE_DURATION_HEADER] = duration
251+
extra_params["extra_headers"] = extra_headers if extra_headers else None
249252
response = await self.openai_client.with_raw_response.audio.translations.create(
250253
file=file,
251254
model=model,
252255
prompt=prompt,
253256
temperature=temperature,
254-
extra_headers=extra_headers,
255-
extra_body=kwargs,
257+
**extra_params,
256258
)
257259
data = Translation(**json.loads(response.text))
258260
data._headers = response.headers
@@ -279,15 +281,14 @@ async def create(
279281
) -> Any:
280282
if stream is True:
281283
self.openai_client = await self.openai_client.with_streaming_response
282-
extra_headers = kwargs.pop("extra_headers", {})
284+
extra_params = extract_extra_params(kwargs)
283285
response = await self.openai_client.audio.speech.create(
284286
input=input,
285287
model=model,
286288
voice=voice,
287289
response_format=response_format,
288290
speed=speed,
289-
extra_headers=extra_headers,
290-
extra_body=kwargs,
291+
**extra_params,
291292
)
292293

293294
data = response

portkey_ai/api_resources/apis/batches.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import typing
44
from portkey_ai.api_resources.apis.api_resource import APIResource, AsyncAPIResource
55
from portkey_ai.api_resources.client import AsyncPortkey, Portkey
6+
from portkey_ai.api_resources.utils import extract_extra_params
67
from ..._vendor.openai._types import Omit, omit
78

89
from portkey_ai.api_resources.types.batches_type import Batch, BatchList
@@ -23,27 +24,24 @@ def create(
2324
metadata: Union[Optional[Dict[str, str]], Omit] = omit,
2425
**kwargs,
2526
) -> Batch:
27+
extra_params = extract_extra_params(kwargs)
2628
response = self.openai_client.with_raw_response.batches.create(
2729
completion_window=completion_window,
2830
endpoint=endpoint,
2931
input_file_id=input_file_id,
3032
metadata=metadata,
31-
extra_body=kwargs,
33+
**extra_params,
3234
)
3335
data = Batch(**json.loads(response.text))
3436
data._headers = response.headers
3537

3638
return data
3739

3840
def retrieve(self, batch_id, **kwargs) -> Batch:
39-
if kwargs:
40-
response = self.openai_client.with_raw_response.batches.retrieve(
41-
batch_id=batch_id, extra_body=kwargs
42-
)
43-
else:
44-
response = self.openai_client.with_raw_response.batches.retrieve(
45-
batch_id=batch_id
46-
)
41+
extra_params = extract_extra_params(kwargs)
42+
response = self.openai_client.with_raw_response.batches.retrieve(
43+
batch_id=batch_id, **extra_params
44+
)
4745
data = Batch(**json.loads(response.text))
4846
data._headers = response.headers
4947

@@ -56,17 +54,19 @@ def list(
5654
limit: Union[int, Omit] = omit,
5755
**kwargs,
5856
) -> BatchList:
57+
extra_params = extract_extra_params(kwargs)
5958
response = self.openai_client.with_raw_response.batches.list(
60-
after=after, limit=limit
59+
after=after, limit=limit, **extra_params
6160
)
6261
data = BatchList(**json.loads(response.text))
6362
data._headers = response.headers
6463

6564
return data
6665

6766
def cancel(self, batch_id: str, **kwargs) -> Batch:
67+
extra_params = extract_extra_params(kwargs)
6868
response = self.openai_client.with_raw_response.batches.cancel(
69-
batch_id=batch_id, extra_body=kwargs
69+
batch_id=batch_id, **extra_params
7070
)
7171
data = Batch(**json.loads(response.text))
7272
data._headers = response.headers
@@ -102,27 +102,24 @@ async def create(
102102
metadata: Union[Optional[Dict[str, str]], Omit] = omit,
103103
**kwargs,
104104
) -> Batch:
105+
extra_params = extract_extra_params(kwargs)
105106
response = await self.openai_client.with_raw_response.batches.create(
106107
completion_window=completion_window,
107108
endpoint=endpoint,
108109
input_file_id=input_file_id,
109110
metadata=metadata,
110-
extra_body=kwargs,
111+
**extra_params,
111112
)
112113
data = Batch(**json.loads(response.text))
113114
data._headers = response.headers
114115

115116
return data
116117

117118
async def retrieve(self, batch_id, **kwargs) -> Batch:
118-
if kwargs:
119-
response = await self.openai_client.with_raw_response.batches.retrieve(
120-
batch_id=batch_id, extra_body=kwargs
121-
)
122-
else:
123-
response = await self.openai_client.with_raw_response.batches.retrieve(
124-
batch_id=batch_id
125-
)
119+
extra_params = extract_extra_params(kwargs)
120+
response = await self.openai_client.with_raw_response.batches.retrieve(
121+
batch_id=batch_id, **extra_params
122+
)
126123
data = Batch(**json.loads(response.text))
127124
data._headers = response.headers
128125

@@ -135,17 +132,19 @@ async def list(
135132
limit: Union[int, Omit] = omit,
136133
**kwargs,
137134
) -> BatchList:
135+
extra_params = extract_extra_params(kwargs)
138136
response = await self.openai_client.with_raw_response.batches.list(
139-
after=after, limit=limit
137+
after=after, limit=limit, **extra_params
140138
)
141139
data = BatchList(**json.loads(response.text))
142140
data._headers = response.headers
143141

144142
return data
145143

146144
async def cancel(self, batch_id: str, **kwargs) -> Batch:
145+
extra_params = extract_extra_params(kwargs)
147146
response = await self.openai_client.with_raw_response.batches.cancel(
148-
batch_id=batch_id, extra_body=kwargs
147+
batch_id=batch_id, **extra_params
149148
)
150149
data = Batch(**json.loads(response.text))
151150
data._headers = response.headers

portkey_ai/api_resources/apis/chat_complete.py

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
from portkey_ai.api_resources.apis.api_resource import APIResource, AsyncAPIResource
3131
from portkey_ai.api_resources.types.shared_types import Headers, Metadata, Query
32-
from portkey_ai.api_resources.utils import Body
32+
from portkey_ai.api_resources.utils import Body, extract_extra_params
3333
from ..._vendor.openai._types import NOT_GIVEN, NotGiven, Omit, omit
3434

3535
__all__ = ["ChatCompletion", "AsyncChatCompletion"]
@@ -74,11 +74,7 @@ def stream_create( # type: ignore[return]
7474
store,
7575
**kwargs,
7676
) -> Union[ChatCompletions, Iterator[ChatCompletionChunk]]:
77-
extra_headers = kwargs.pop("extra_headers", None)
78-
extra_query = kwargs.pop("extra_query", None)
79-
timeout = kwargs.pop("timeout", None)
80-
user_extra_body = kwargs.pop("extra_body", None) or {}
81-
merged_extra_body = {**user_extra_body, **kwargs}
77+
extra_params = extract_extra_params(kwargs)
8278
return self.openai_client.chat.completions.create(
8379
model=model,
8480
messages=messages,
@@ -93,10 +89,7 @@ def stream_create( # type: ignore[return]
9389
prediction=prediction,
9490
reasoning_effort=reasoning_effort,
9591
store=store,
96-
extra_headers=extra_headers,
97-
extra_query=extra_query,
98-
extra_body=merged_extra_body,
99-
timeout=timeout,
92+
**extra_params,
10093
)
10194

10295
def normal_create(
@@ -116,11 +109,7 @@ def normal_create(
116109
store,
117110
**kwargs,
118111
) -> ChatCompletions:
119-
extra_headers = kwargs.pop("extra_headers", None)
120-
extra_query = kwargs.pop("extra_query", None)
121-
timeout = kwargs.pop("timeout", None)
122-
user_extra_body = kwargs.pop("extra_body", None) or {}
123-
merged_extra_body = {**user_extra_body, **kwargs}
112+
extra_params = extract_extra_params(kwargs)
124113
response = self.openai_client.with_raw_response.chat.completions.create(
125114
model=model,
126115
messages=messages,
@@ -135,10 +124,7 @@ def normal_create(
135124
prediction=prediction,
136125
reasoning_effort=reasoning_effort,
137126
store=store,
138-
extra_headers=extra_headers,
139-
extra_query=extra_query,
140-
extra_body=merged_extra_body,
141-
timeout=timeout,
127+
**extra_params,
142128
)
143129
data = ChatCompletions(**json.loads(response.text))
144130
data._headers = response.headers
@@ -484,11 +470,7 @@ async def stream_create(
484470
store,
485471
**kwargs,
486472
) -> Union[ChatCompletions, AsyncIterator[ChatCompletionChunk]]:
487-
extra_headers = kwargs.pop("extra_headers", None)
488-
extra_query = kwargs.pop("extra_query", None)
489-
timeout = kwargs.pop("timeout", None)
490-
user_extra_body = kwargs.pop("extra_body", None) or {}
491-
merged_extra_body = {**user_extra_body, **kwargs}
473+
extra_params = extract_extra_params(kwargs)
492474
return await self.openai_client.chat.completions.create(
493475
model=model,
494476
messages=messages,
@@ -503,10 +485,7 @@ async def stream_create(
503485
prediction=prediction,
504486
reasoning_effort=reasoning_effort,
505487
store=store,
506-
extra_headers=extra_headers,
507-
extra_query=extra_query,
508-
extra_body=merged_extra_body,
509-
timeout=timeout,
488+
**extra_params,
510489
)
511490

512491
async def normal_create(
@@ -526,11 +505,7 @@ async def normal_create(
526505
store,
527506
**kwargs,
528507
) -> ChatCompletions:
529-
extra_headers = kwargs.pop("extra_headers", None)
530-
extra_query = kwargs.pop("extra_query", None)
531-
timeout = kwargs.pop("timeout", None)
532-
user_extra_body = kwargs.pop("extra_body", None) or {}
533-
merged_extra_body = {**user_extra_body, **kwargs}
508+
extra_params = extract_extra_params(kwargs)
534509
response = await self.openai_client.with_raw_response.chat.completions.create(
535510
model=model,
536511
messages=messages,
@@ -545,10 +520,7 @@ async def normal_create(
545520
prediction=prediction,
546521
reasoning_effort=reasoning_effort,
547522
store=store,
548-
extra_headers=extra_headers,
549-
extra_query=extra_query,
550-
extra_body=merged_extra_body,
551-
timeout=timeout,
523+
**extra_params,
552524
)
553525
data = ChatCompletions(**json.loads(response.text))
554526
data._headers = response.headers

0 commit comments

Comments
 (0)