diff --git a/src/interfaze/_client.py b/src/interfaze/_client.py index 128ead9..c0a83b3 100644 --- a/src/interfaze/_client.py +++ b/src/interfaze/_client.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Union from openai import AsyncOpenAI, OpenAI @@ -46,6 +46,17 @@ def _build_headers( return headers +# OpenAI() fills `organization` / `project` from OPENAI_ORG_ID / OPENAI_PROJECT_ID and sends them +# as OpenAI-Organization / OpenAI-Project headers. Those are OpenAI account ids, so keep them off +# Interfaze requests unless they were passed in explicitly. +# This only covers the client built here: one derived via client.openai.with_options(...) or +# .copy() goes back through the constructor with organization/project None, so it reads the +# env vars again and sends them. +def _drop_openai_env_ids(client: Union[OpenAI, AsyncOpenAI], kwargs: Dict[str, Any]) -> None: + client.organization = kwargs.get("organization") + client.project = kwargs.get("project") + + class Interfaze: def __init__( self, @@ -68,6 +79,7 @@ def __init__( ), **kwargs, ) + _drop_openai_env_ids(self.openai, kwargs) self.chat = Chat(self.openai) self.models = self.openai.models self.tasks = Tasks(self.chat.completions) @@ -95,6 +107,7 @@ def __init__( ), **kwargs, ) + _drop_openai_env_ids(self.openai, kwargs) self.chat = AsyncChat(self.openai) self.models = self.openai.models self.tasks = AsyncTasks(self.chat.completions) diff --git a/tests/test_chat.py b/tests/test_chat.py index c072e21..0e8b7ea 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -146,6 +146,33 @@ def test_default_headers_omit_control_flags_when_unset(): assert "x-admin-key" not in h +@respx.mock +def test_openai_org_and_project_env_not_sent(monkeypatch): + monkeypatch.setenv("OPENAI_ORG_ID", "org-from-env") + monkeypatch.setenv("OPENAI_PROJECT_ID", "proj_from_env") + route = mock_json(BASIC) + Interfaze(api_key="t").chat.completions.create(messages=[{"role": "user", "content": "x"}]) + h = last_headers(route) + assert "openai-organization" not in h and "openai-project" not in h + + asyncio.run( + AsyncInterfaze(api_key="t").chat.completions.create(messages=[{"role": "user", "content": "x"}]) + ) + h = last_headers(route) + assert "openai-organization" not in h and "openai-project" not in h + + +@respx.mock +def test_explicit_openai_org_and_project_still_sent(monkeypatch): + monkeypatch.setenv("OPENAI_ORG_ID", "org-from-env") + route = mock_json(BASIC) + Interfaze(api_key="t", organization="org-explicit", project="proj_explicit").chat.completions.create( + messages=[{"role": "user", "content": "x"}] + ) + h = last_headers(route) + assert h["openai-organization"] == "org-explicit" and h["openai-project"] == "proj_explicit" + + @respx.mock def test_per_request_extra_headers_override_client_default(): route = mock_json(BASIC)