Skip to content

Commit 27c9609

Browse files
authored
fix: ChatAzureOpenAI() and ChatDatabricks() now work without a OPENAI_API_KEY set (#185)
* Close #154: ChatAzureOpenAI() and ChatDatabricks() now work without a OPENAI_API_KEY set * Update changelog * Update types
1 parent 1eea4e7 commit 27c9609

File tree

9 files changed

+92
-58
lines changed

9 files changed

+92
-58
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
77
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88
-->
99

10+
## [UNRELEASED]
11+
12+
### Bug fixes
13+
14+
* `ChatAzureOpenAI()` and `ChatDatabricks()` now work as expected when a `OPENAI_API_KEY` environment variable isn't present. (#185)
15+
1016
## [0.13.1] - 2025-09-18
1117

1218
### Bug fixes

chatlas/_provider_databricks.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,13 @@ def __init__(
106106
import httpx
107107
from openai import AsyncOpenAI
108108

109-
super().__init__(name=name, model=model)
109+
super().__init__(
110+
name=name,
111+
model=model,
112+
# The OpenAI() constructor will fail if no API key is present.
113+
# However, a dummy value is fine -- WorkspaceClient() handles the auth.
114+
api_key="not-used",
115+
)
110116

111117
self._seed = None
112118

chatlas/_provider_openai.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,13 @@ def __init__(
884884
model: Optional[str] = "UnusedValue",
885885
kwargs: Optional["ChatAzureClientArgs"] = None,
886886
):
887-
super().__init__(name=name, model=deployment_id)
887+
super().__init__(
888+
name=name,
889+
model=deployment_id,
890+
# The OpenAI() constructor will fail if no API key is present.
891+
# However, a dummy value is fine -- AzureOpenAI() handles the auth.
892+
api_key=api_key or "not-used",
893+
)
888894

889895
self._seed = seed
890896

chatlas/types/anthropic/_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# ---------------------------------------------------------
44

55

6-
from typing import Mapping, Optional, TypedDict, Union
6+
from typing import Mapping, Optional, TypedDict
77

88
import anthropic
99
import httpx
@@ -13,7 +13,7 @@ class ChatClientArgs(TypedDict, total=False):
1313
api_key: str | None
1414
auth_token: str | None
1515
base_url: str | httpx.URL | None
16-
timeout: Union[float, anthropic.Timeout, None, anthropic.NotGiven]
16+
timeout: float | anthropic.Timeout | None | anthropic.NotGiven
1717
max_retries: int
1818
default_headers: Optional[Mapping[str, str]]
1919
default_query: Optional[Mapping[str, object]]

chatlas/types/anthropic/_submit.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,24 @@ class SubmitInputArgs(TypedDict, total=False):
4747
],
4848
str,
4949
]
50-
service_tier: Union[Literal["auto", "standard_only"], anthropic.NotGiven]
51-
stop_sequences: Union[Sequence[str], anthropic.NotGiven]
52-
stream: Union[Literal[False], Literal[True], anthropic.NotGiven]
50+
service_tier: Union[Literal["auto", "standard_only"], anthropic.Omit]
51+
stop_sequences: Union[Sequence[str], anthropic.Omit]
52+
stream: Union[Literal[False], Literal[True], anthropic.Omit]
5353
system: Union[
54-
str,
55-
Iterable[anthropic.types.text_block_param.TextBlockParam],
56-
anthropic.NotGiven,
54+
str, Iterable[anthropic.types.text_block_param.TextBlockParam], anthropic.Omit
5755
]
58-
temperature: float | anthropic.NotGiven
56+
temperature: float | anthropic.Omit
5957
thinking: Union[
6058
anthropic.types.thinking_config_enabled_param.ThinkingConfigEnabledParam,
6159
anthropic.types.thinking_config_disabled_param.ThinkingConfigDisabledParam,
62-
anthropic.NotGiven,
60+
anthropic.Omit,
6361
]
6462
tool_choice: Union[
6563
anthropic.types.tool_choice_auto_param.ToolChoiceAutoParam,
6664
anthropic.types.tool_choice_any_param.ToolChoiceAnyParam,
6765
anthropic.types.tool_choice_tool_param.ToolChoiceToolParam,
6866
anthropic.types.tool_choice_none_param.ToolChoiceNoneParam,
69-
anthropic.NotGiven,
67+
anthropic.Omit,
7068
]
7169
tools: Union[
7270
Iterable[
@@ -79,10 +77,10 @@ class SubmitInputArgs(TypedDict, total=False):
7977
anthropic.types.web_search_tool_20250305_param.WebSearchTool20250305Param,
8078
]
8179
],
82-
anthropic.NotGiven,
80+
anthropic.Omit,
8381
]
84-
top_k: int | anthropic.NotGiven
85-
top_p: float | anthropic.NotGiven
82+
top_k: int | anthropic.Omit
83+
top_p: float | anthropic.Omit
8684
extra_headers: Optional[Mapping[str, Union[str, anthropic.Omit]]]
8785
extra_query: Optional[Mapping[str, object]]
8886
extra_body: object | None

chatlas/types/openai/_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ChatClientArgs(TypedDict, total=False):
1616
webhook_secret: str | None
1717
base_url: str | httpx.URL | None
1818
websocket_base_url: str | httpx.URL | None
19-
timeout: Union[float, openai.Timeout, None, openai.NotGiven]
19+
timeout: float | openai.Timeout | None | openai.NotGiven
2020
max_retries: int
2121
default_headers: Optional[Mapping[str, str]]
2222
default_query: Optional[Mapping[str, object]]

chatlas/types/openai/_submit.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -108,61 +108,61 @@ class SubmitInputArgs(TypedDict, total=False):
108108
audio: Union[
109109
openai.types.chat.chat_completion_audio_param.ChatCompletionAudioParam,
110110
None,
111-
openai.NotGiven,
111+
openai.Omit,
112112
]
113-
frequency_penalty: Union[float, None, openai.NotGiven]
113+
frequency_penalty: Union[float, None, openai.Omit]
114114
function_call: Union[
115115
Literal["none", "auto"],
116116
openai.types.chat.chat_completion_function_call_option_param.ChatCompletionFunctionCallOptionParam,
117-
openai.NotGiven,
117+
openai.Omit,
118118
]
119119
functions: Union[
120-
Iterable[openai.types.chat.completion_create_params.Function], openai.NotGiven
120+
Iterable[openai.types.chat.completion_create_params.Function], openai.Omit
121121
]
122-
logit_bias: Union[dict[str, int], None, openai.NotGiven]
123-
logprobs: Union[bool, None, openai.NotGiven]
124-
max_completion_tokens: Union[int, None, openai.NotGiven]
125-
max_tokens: Union[int, None, openai.NotGiven]
126-
metadata: Union[dict[str, str], None, openai.NotGiven]
127-
modalities: Union[list[Literal["text", "audio"]], None, openai.NotGiven]
128-
n: Union[int, None, openai.NotGiven]
129-
parallel_tool_calls: bool | openai.NotGiven
122+
logit_bias: Union[dict[str, int], None, openai.Omit]
123+
logprobs: Union[bool, None, openai.Omit]
124+
max_completion_tokens: Union[int, None, openai.Omit]
125+
max_tokens: Union[int, None, openai.Omit]
126+
metadata: Union[dict[str, str], None, openai.Omit]
127+
modalities: Union[list[Literal["text", "audio"]], None, openai.Omit]
128+
n: Union[int, None, openai.Omit]
129+
parallel_tool_calls: bool | openai.Omit
130130
prediction: Union[
131131
openai.types.chat.chat_completion_prediction_content_param.ChatCompletionPredictionContentParam,
132132
None,
133-
openai.NotGiven,
133+
openai.Omit,
134134
]
135-
presence_penalty: Union[float, None, openai.NotGiven]
136-
prompt_cache_key: str | openai.NotGiven
135+
presence_penalty: Union[float, None, openai.Omit]
136+
prompt_cache_key: str | openai.Omit
137137
reasoning_effort: Union[
138-
Literal["minimal", "low", "medium", "high"], None, openai.NotGiven
138+
Literal["minimal", "low", "medium", "high"], None, openai.Omit
139139
]
140140
response_format: Union[
141141
openai.types.shared_params.response_format_text.ResponseFormatText,
142142
openai.types.shared_params.response_format_json_schema.ResponseFormatJSONSchema,
143143
openai.types.shared_params.response_format_json_object.ResponseFormatJSONObject,
144-
openai.NotGiven,
144+
openai.Omit,
145145
]
146-
safety_identifier: str | openai.NotGiven
147-
seed: Union[int, None, openai.NotGiven]
146+
safety_identifier: str | openai.Omit
147+
seed: Union[int, None, openai.Omit]
148148
service_tier: Union[
149-
Literal["auto", "default", "flex", "scale", "priority"], None, openai.NotGiven
149+
Literal["auto", "default", "flex", "scale", "priority"], None, openai.Omit
150150
]
151-
stop: Union[str, None, Sequence[str], openai.NotGiven]
152-
store: Union[bool, None, openai.NotGiven]
153-
stream: Union[Literal[False], None, Literal[True], openai.NotGiven]
151+
stop: Union[str, None, Sequence[str], openai.Omit]
152+
store: Union[bool, None, openai.Omit]
153+
stream: Union[Literal[False], None, Literal[True], openai.Omit]
154154
stream_options: Union[
155155
openai.types.chat.chat_completion_stream_options_param.ChatCompletionStreamOptionsParam,
156156
None,
157-
openai.NotGiven,
157+
openai.Omit,
158158
]
159-
temperature: Union[float, None, openai.NotGiven]
159+
temperature: Union[float, None, openai.Omit]
160160
tool_choice: Union[
161161
Literal["none", "auto", "required"],
162162
openai.types.chat.chat_completion_allowed_tool_choice_param.ChatCompletionAllowedToolChoiceParam,
163163
openai.types.chat.chat_completion_named_tool_choice_param.ChatCompletionNamedToolChoiceParam,
164164
openai.types.chat.chat_completion_named_tool_choice_custom_param.ChatCompletionNamedToolChoiceCustomParam,
165-
openai.NotGiven,
165+
openai.Omit,
166166
]
167167
tools: Union[
168168
Iterable[
@@ -171,14 +171,14 @@ class SubmitInputArgs(TypedDict, total=False):
171171
openai.types.chat.chat_completion_custom_tool_param.ChatCompletionCustomToolParam,
172172
]
173173
],
174-
openai.NotGiven,
174+
openai.Omit,
175175
]
176-
top_logprobs: Union[int, None, openai.NotGiven]
177-
top_p: Union[float, None, openai.NotGiven]
178-
user: str | openai.NotGiven
179-
verbosity: Union[Literal["low", "medium", "high"], None, openai.NotGiven]
176+
top_logprobs: Union[int, None, openai.Omit]
177+
top_p: Union[float, None, openai.Omit]
178+
user: str | openai.Omit
179+
verbosity: Union[Literal["low", "medium", "high"], None, openai.Omit]
180180
web_search_options: (
181-
openai.types.chat.completion_create_params.WebSearchOptions | openai.NotGiven
181+
openai.types.chat.completion_create_params.WebSearchOptions | openai.Omit
182182
)
183183
extra_headers: Optional[Mapping[str, Union[str, openai.Omit]]]
184184
extra_query: Optional[Mapping[str, object]]

tests/test_provider_azure.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
pytest.skip("Skipping Azure tests", allow_module_level=True)
1010

1111

12-
def test_azure_simple_request():
13-
chat = ChatAzureOpenAI(
14-
system_prompt="Be as terse as possible; no punctuation",
12+
def chat_func(system_prompt: str = "Be as terse as possible; no punctuation"):
13+
return ChatAzureOpenAI(
14+
system_prompt=system_prompt,
1515
endpoint="https://chatlas-testing.openai.azure.com",
1616
deployment_id="gpt-4o-mini",
1717
api_version="2024-08-01-preview",
1818
)
1919

20+
21+
def test_azure_simple_request():
22+
chat = chat_func()
2023
response = chat.chat("What is 1 + 1?")
2124
assert "2" == response.get_content()
2225
turn = chat.get_last_turn()
@@ -27,15 +30,20 @@ def test_azure_simple_request():
2730

2831
@pytest.mark.asyncio
2932
async def test_azure_simple_request_async():
30-
chat = ChatAzureOpenAI(
31-
system_prompt="Be as terse as possible; no punctuation",
32-
endpoint="https://chatlas-testing.openai.azure.com",
33-
deployment_id="gpt-4o-mini",
34-
api_version="2024-08-01-preview",
35-
)
33+
chat = chat_func()
3634

3735
response = await chat.chat_async("What is 1 + 1?")
3836
assert "2" == await response.get_content()
3937
turn = chat.get_last_turn()
4038
assert turn is not None
4139
assert turn.tokens == (27, 2, 0)
40+
41+
42+
def test_connect_without_openai_key(monkeypatch):
43+
# Ensure OPENAI_API_KEY is not set
44+
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
45+
46+
# This should not raise an error
47+
chat = chat_func()
48+
assert chat is not None
49+

tests/test_provider_databricks.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
23
from chatlas import ChatDatabricks
34

45
from .conftest import assert_turns_existing, assert_turns_system
@@ -82,3 +83,12 @@ def test_anthropic_empty_response():
8283
# def test_openai_pdf():
8384
# chat_fun = ChatDatabricks
8485
# assert_pdf_local(chat_fun)
86+
87+
88+
def test_connect_without_openai_key(monkeypatch):
89+
# Ensure OPENAI_API_KEY is not set
90+
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
91+
92+
# This should not raise an error
93+
chat = ChatDatabricks()
94+
assert chat is not None

0 commit comments

Comments
 (0)