mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(chatgpt): preserve OAuth identity during realtime signaling
This commit is contained in:
parent
023f3e93e7
commit
8339ebcc4d
4 changed files with 55 additions and 11 deletions
|
|
@ -4,6 +4,8 @@ Constants and helpers for ChatGPT subscription OAuth.
|
|||
|
||||
import os
|
||||
import platform
|
||||
from collections.abc import Mapping
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Final
|
||||
from uuid import uuid4
|
||||
|
||||
|
|
@ -105,6 +107,12 @@ You are producing plain text that will later be styled by the CLI. Follow these
|
|||
"""
|
||||
|
||||
|
||||
def without_oauth_identity_headers(headers: Mapping[str, object]) -> Mapping[str, object]:
|
||||
return MappingProxyType(
|
||||
{key: value for key, value in headers.items() if key.lower() not in ("authorization", "chatgpt-account-id")}
|
||||
)
|
||||
|
||||
|
||||
class ChatGPTAuthError(BaseLLMException):
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from litellm.types.llms.openai import AllMessageValues, FileTypes
|
|||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
from .authenticator import Authenticator
|
||||
from .common_utils import without_oauth_identity_headers
|
||||
from .responses.transformation import ChatGPTResponsesAPIConfig
|
||||
|
||||
|
||||
|
|
@ -49,12 +50,6 @@ def encode_reference(
|
|||
}
|
||||
|
||||
|
||||
def without_image_identity_headers(headers: Mapping[str, object]) -> Mapping[str, object]:
|
||||
return MappingProxyType(
|
||||
{key: value for key, value in headers.items() if key.lower() not in ("authorization", "chatgpt-account-id")}
|
||||
)
|
||||
|
||||
|
||||
def image_headers(
|
||||
headers: Mapping[str, object], model: str, params: Mapping[str, object]
|
||||
) -> dict[str, object]: # mutable-ok: image handler requires dictionaries
|
||||
|
|
@ -64,7 +59,7 @@ def image_headers(
|
|||
litellm_params=GenericLiteLLMParams.model_validate(params),
|
||||
)
|
||||
return { # mutable-ok: image handler requires dictionaries
|
||||
**without_image_identity_headers(headers),
|
||||
**without_oauth_identity_headers(headers),
|
||||
**auth_headers,
|
||||
"accept": "application/json",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6390,6 +6390,9 @@ class BaseLLMHTTPHandler:
|
|||
- sdp: the SDP offer (text)
|
||||
- session: JSON string with {"type": "realtime", "model": "...", ...}
|
||||
"""
|
||||
from litellm.llms.chatgpt.common_utils import without_oauth_identity_headers
|
||||
from litellm.llms.chatgpt.realtime import ChatGPTRealtimeHTTPConfig
|
||||
|
||||
if client is None or not isinstance(client, AsyncHTTPHandler):
|
||||
async_httpx_client = get_async_httpx_client(
|
||||
llm_provider=litellm.LlmProviders.OPENAI,
|
||||
|
|
@ -6407,7 +6410,11 @@ class BaseLLMHTTPHandler:
|
|||
}
|
||||
|
||||
if extra_headers:
|
||||
headers.update(extra_headers)
|
||||
headers.update(
|
||||
without_oauth_identity_headers(extra_headers)
|
||||
if isinstance(provider_config, ChatGPTRealtimeHTTPConfig)
|
||||
else extra_headers
|
||||
)
|
||||
|
||||
# Build multipart form data: sdp + session JSON
|
||||
session_data: Final = session_config or {}
|
||||
|
|
@ -6641,9 +6648,9 @@ class BaseLLMHTTPHandler:
|
|||
@staticmethod
|
||||
def _image_extra_headers(custom_llm_provider: str, headers: Mapping[str, object]) -> Mapping[str, object]:
|
||||
if custom_llm_provider == "chatgpt":
|
||||
from litellm.llms.chatgpt.images import without_image_identity_headers
|
||||
from litellm.llms.chatgpt.common_utils import without_oauth_identity_headers
|
||||
|
||||
return without_image_identity_headers(headers)
|
||||
return without_oauth_identity_headers(headers)
|
||||
return headers
|
||||
|
||||
def image_edit_handler(
|
||||
|
|
|
|||
|
|
@ -27,7 +27,12 @@ async def test_chatgpt_call_keeps_oauth_and_frameless_session(chatgpt_tokens, ap
|
|||
sdp_body=b"v=0\r\n",
|
||||
session={"model": "chatgpt/gpt-live-1-codex", "audio": {"output": {"voice": "sol"}}},
|
||||
extra_query={"intent": "quicksilver", "architecture": "avas"},
|
||||
extra_headers={"openai-alpha": "quicksilver=v2"},
|
||||
extra_headers={
|
||||
"openai-alpha": "quicksilver=v2",
|
||||
"x-gateway-route": "voice",
|
||||
"aUtHoRiZaTiOn": "Bearer wrong",
|
||||
"CHATGPT-ACCOUNT-ID": "wrong",
|
||||
},
|
||||
client=client,
|
||||
)
|
||||
assert response.extensions["chatgpt_realtime"]["api_base"] == (api_base or "https://api.openai.com/v1")
|
||||
|
|
@ -36,6 +41,9 @@ async def test_chatgpt_call_keeps_oauth_and_frameless_session(chatgpt_tokens, ap
|
|||
assert requests[0].url.path == "/backend-api/codex/realtime/calls"
|
||||
assert requests[0].url.params["architecture"] == "avas"
|
||||
assert requests[0].headers["authorization"] == "Bearer test-token-" + "default"
|
||||
assert requests[0].headers["chatgpt-account-id"] == "test-account-default"
|
||||
assert requests[0].headers["openai-alpha"] == "quicksilver=v2"
|
||||
assert requests[0].headers["x-gateway-route"] == "voice"
|
||||
assert json.loads(requests[0].content) == {
|
||||
"sdp": "v=0\r\n",
|
||||
"session": {"model": "gpt-live-1-codex", "audio": {"output": {"voice": "sol"}}},
|
||||
|
|
@ -43,6 +51,32 @@ async def test_chatgpt_call_keeps_oauth_and_frameless_session(chatgpt_tokens, ap
|
|||
await client.client.aclose()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_openai_call_preserves_explicit_identity_headers():
|
||||
requests = []
|
||||
|
||||
def respond(request):
|
||||
requests.append(request)
|
||||
return httpx.Response(201, text="v=0\r\n")
|
||||
|
||||
client = AsyncHTTPHandler()
|
||||
client.client = httpx.AsyncClient(transport=httpx.MockTransport(respond))
|
||||
try:
|
||||
response = await litellm.arealtime_calls(
|
||||
model="openai/gpt-realtime-1.5",
|
||||
openai_ephemeral_key="original-key",
|
||||
sdp_body=b"v=0\r\n",
|
||||
extra_headers={"Authorization": "Bearer explicit-key", "chatgpt-account-id": "custom-account"},
|
||||
client=client,
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert requests[0].headers["authorization"] == "Bearer explicit-key"
|
||||
assert requests[0].headers["chatgpt-account-id"] == "custom-account"
|
||||
assert requests[0].headers["content-type"].startswith("multipart/form-data")
|
||||
finally:
|
||||
await client.client.aclose()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model,endpoint", [("gpt-realtime-1.5", "realtime"), ("gpt-live-1-codex", "live")])
|
||||
def test_realtime_uses_platform_endpoint_with_oauth_headers(model, endpoint, chatgpt_tokens, local_model_cost_map):
|
||||
handler = ChatGPTRealtime(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue