Skip to content

Commit b7ebd02

Browse files
Add gpt-5-search-api model support (#125)
Reference: - https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat
1 parent 3e86212 commit b7ebd02

File tree

3 files changed

+41
-10
lines changed

3 files changed

+41
-10
lines changed

app/openai_constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
GPT_4_1_NANO_MODEL = "gpt-4.1-nano"
2929
GPT_4_1_NANO_2025_04_14_MODEL = "gpt-4.1-nano-2025-04-14"
3030
GPT_5_CHAT_LATEST_MODEL = "gpt-5-chat-latest"
31+
GPT_5_SEARCH_API_MODEL = "gpt-5-search-api"
32+
GPT_5_SEARCH_API_2025_10_14_MODEL = "gpt-5-search-api-2025-10-14"
3133
GPT_5_MODEL = "gpt-5"
3234
GPT_5_MINI_MODEL = "gpt-5-mini"
3335
GPT_5_NANO_MODEL = "gpt-5-nano"
@@ -75,6 +77,7 @@
7577
GPT_4O_MINI_2024_07_18_MODEL: (3, 1),
7678
# GPT-5 chat latest
7779
GPT_5_CHAT_LATEST_MODEL: (3, 1),
80+
GPT_5_SEARCH_API_2025_10_14_MODEL: (3, 1),
7881
# GPT-5 family (dated)
7982
GPT_5_2025_08_07_MODEL: (3, 1),
8083
GPT_5_MINI_2025_08_07_MODEL: (3, 1),
@@ -100,6 +103,7 @@
100103
GPT_5_MODEL: GPT_5_2025_08_07_MODEL,
101104
GPT_5_MINI_MODEL: GPT_5_MINI_2025_08_07_MODEL,
102105
GPT_5_NANO_MODEL: GPT_5_NANO_2025_08_07_MODEL,
106+
GPT_5_SEARCH_API_MODEL: GPT_5_SEARCH_API_2025_10_14_MODEL,
103107
O3_MODEL: O3_2025_04_16_MODEL,
104108
O4_MINI_MODEL: O4_MINI_2025_04_16_MODEL,
105109
}
@@ -130,6 +134,7 @@
130134
GPT_4_1_NANO_2025_04_14_MODEL: 1048576,
131135
# GPT-5 chat latest
132136
GPT_5_CHAT_LATEST_MODEL: 128000,
137+
GPT_5_SEARCH_API_2025_10_14_MODEL: 128000,
133138
# GPT-5 family (dated)
134139
GPT_5_2025_08_07_MODEL: 128000,
135140
GPT_5_MINI_2025_08_07_MODEL: 128000,

app/openai_ops.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ def messages_within_context_window(
8686
def _is_reasoning(model: str) -> bool:
8787
"""Returns True if the model is a reasoning model under Chat Completions.
8888
89-
Excludes chat models like gpt-5-chat-latest. Matches o3*, o4*, and
89+
Excludes chat models like gpt-5-chat-latest and gpt-5-search-api. Matches o3*, o4*, and
9090
non-chat gpt-5* families. Case-insensitive and safe with None/empty.
9191
"""
9292
if not model:
9393
return False
9494
ml = model.lower()
95-
if ml.startswith("gpt-5-chat"):
95+
if ml.startswith("gpt-5-chat") or ml.startswith("gpt-5-search"):
9696
return False
9797
return (
9898
ml.startswith("o1")
@@ -102,6 +102,13 @@ def _is_reasoning(model: str) -> bool:
102102
)
103103

104104

105+
def _is_search_model(model: str) -> bool:
106+
"""Returns True for search-specific chat models."""
107+
if not model:
108+
return False
109+
return model.lower().startswith("gpt-5-search")
110+
111+
105112
def _normalize_base_url(value: Optional[str]) -> Optional[str]:
106113
"""Normalizes falsy/empty base URLs to None for SDK compatibility."""
107114
if value is None:
@@ -159,17 +166,20 @@ def _create_chat_completion(
159166
raise ValueError("timeout_seconds must be None for streaming calls")
160167

161168
is_reasoning = _is_reasoning(model)
169+
is_search = _is_search_model(model)
162170
# Reasoning models use max_completion_tokens; others use max_tokens
163171
token_kwarg = _token_budget_kwarg(model, MAX_TOKENS)
164172

165173
base_kwargs = dict(
166174
model=model,
167175
messages=messages,
168-
n=1,
169176
user=user,
170177
stream=stream,
171178
)
172-
if not is_reasoning:
179+
if not is_search:
180+
base_kwargs["n"] = 1
181+
182+
if not is_reasoning and not is_search:
173183
base_kwargs["temperature"] = temperature
174184
base_kwargs["presence_penalty"] = 0
175185
base_kwargs["frequency_penalty"] = 0

tests/openai_ops_test.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
format_assistant_reply,
44
format_openai_message_content,
55
)
6-
from app.openai_constants import GPT_4O_MODEL
7-
from app.openai_constants import MAX_TOKENS
6+
from app.openai_constants import (
7+
GPT_4O_MODEL,
8+
GPT_5_SEARCH_API_MODEL,
9+
MAX_TOKENS,
10+
)
811
import pytest
912

1013

@@ -152,6 +155,7 @@ def fake_calculate_num_tokens(messages, model=None): # type: ignore[no-redef]
152155
[
153156
(GPT_4O_MODEL, False, 0.7, 12, "U123"),
154157
("o3", True, 1.0, 5, "U234"),
158+
(GPT_5_SEARCH_API_MODEL, False, 0.5, 8, "U345"),
155159
],
156160
)
157161
def test_sync_tokens_and_sampling_behavior(fake_clients, api_type, model, is_reasoning, temperature, timeout, user):
@@ -172,22 +176,34 @@ def test_sync_tokens_and_sampling_behavior(fake_clients, api_type, model, is_rea
172176
)
173177

174178
kwargs = fake_clients["create_kwargs"]
179+
is_search = kwargs.get("model", "").startswith("gpt-5-search")
175180
if is_reasoning:
176181
assert kwargs.get("max_completion_tokens") == MAX_TOKENS
177182
assert "max_tokens" not in kwargs
178183
for k in ("temperature", "presence_penalty", "frequency_penalty", "logit_bias"):
179184
assert k not in kwargs
185+
assert "top_p" not in kwargs
186+
elif is_search:
187+
assert kwargs.get("max_tokens") == MAX_TOKENS
188+
for k in (
189+
"temperature",
190+
"presence_penalty",
191+
"frequency_penalty",
192+
"logit_bias",
193+
"top_p",
194+
):
195+
assert k not in kwargs
180196
else:
181197
assert kwargs.get("max_tokens") == MAX_TOKENS
182198
assert kwargs.get("temperature") == temperature
183199
assert kwargs.get("presence_penalty") == 0
184200
assert kwargs.get("frequency_penalty") == 0
185201
assert isinstance(kwargs.get("logit_bias"), dict)
186-
if is_reasoning:
187-
assert "top_p" not in kwargs
188-
else:
189202
assert kwargs.get("top_p") == 1
190-
assert kwargs.get("n") == 1
203+
if is_search:
204+
assert "n" not in kwargs
205+
else:
206+
assert kwargs.get("n") == 1
191207
assert kwargs.get("user") == user
192208
assert kwargs.get("stream") is False
193209
assert kwargs.get("timeout") == timeout

0 commit comments

Comments
 (0)