OpenAI: opt-in server-side web search (web_search_options)#146
Conversation
Mirrors the Anthropic server-tool plumbing on the OpenAI side. When
WebSearch is on, the OpenAI provider emits "web_search_options": {}
as a top-level Chat Completions request field. Only OpenAI's
search-capable models honour the field — currently gpt-5-search-api
(and the deprecated gpt-4o-search-preview / gpt-4o-mini-search-preview,
shutdown 2026-07-23). The operator picks the model via Cfg.DefaultModel
or per-call; we don't auto-rewrite it.
Opt-in via config.json:
"openai_server_tools": {
"web_search": true
}
Off by default. Third-party OpenAI-compatible endpoints (Groq,
OpenRouter, Together, vLLM, Ollama) silently ignore web_search_options
— flipping the flag while pointed at one of them is harmless but
pointless.
Unlike Anthropic's tools-array entry, web_search_options is a
top-level parameter, not a tool entry, so it does NOT collide with a
user-defined web_search function tool. Both can be active in the same
request without dropping anything.
Optional refinements supported by the Chat Completions surface
(user_location, etc.) and Responses-API-only knobs (search_context_size,
domain filters) are not exposed yet — operators who need them can drop
to the Responses API directly.
Ref: https://developers.openai.com/api/docs/guides/tools-web-search
Test plan:
- make test-openai-server-tools — three wire-shape cases (off, on,
coexistence with a user-defined function tool of the same name)
- make test-anthropic-server-tools — Anthropic suite unchanged
- make smoke — every top-level CLI command still launches
- Full FPC build clean — no new warnings on touched units
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23270b5d5a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| OAIServerTools.WebSearch := Cfg.OpenAIServerTools.WebSearch; | ||
| Provider := TOpenAIProvider.Create(APIKey, Base, Model, Kind, Spec.Auth, | ||
| OAIServerTools); |
There was a problem hiding this comment.
Gate OpenAI-only search options to OpenAI providers
When openai_server_tools.web_search is enabled, this copies the flag into every pfOpenAI provider constructed by the factory, including OpenAI-compatible providers such as Groq, OpenRouter, Ollama, vLLM, and LiteLLM from BuildCatalog. In configs with an OpenAI primary and an OpenAI-compatible fallback (or when the default provider is one of those), the fallback request will also emit the OpenAI-specific web_search_options field added in BuildOAIRequest, which those providers do not implement as OpenAI server-side web search and may reject or mishandle. Please gate this to Kind = 'openai' or make the toggle provider-specific.
Useful? React with 👍 / 👎.
Flip openai_server_tools.web_search default from False to True. The
field is silently ignored on every non-search-capable model
(gpt-4o, gpt-4o-mini, ...) and on third-party OpenAI-compatible
endpoints (Groq, OpenRouter, vLLM, Ollama), so leaving it on costs
nothing there. Operators who pick gpt-5-search-api as their model
get server-side search for free with no extra config step.
ToJSON now always emits the openai_server_tools section so that an
operator setting web_search: false in config.json round-trips. The
previous "emit only when True" path was fine while the default was
False, but with default True an explicit false would silently
revert on the next load — the section would be absent on save and
the default would win on the next FromJSON.
Test rename: TestNoServerTools → TestServerToolsExplicitlyOff. The
"by default" wording in the assertion message was misleading after
the flip; the test still exercises the off-sentinel (NoOpenAIServerTools)
because BuildOAIRequest is pure and doesn't know about TConfig defaults.
Smoke check confirms a fresh config.json now carries:
"openai_server_tools": { "web_search": true }
Codex P2: the factory was copying Cfg.OpenAIServerTools.WebSearch into every pfOpenAI-family provider — including Groq, OpenRouter, Ollama, vLLM, LiteLLM, DeepSeek, Mistral, Together, and the generic "openai-compat" entry. Those backends speak the OpenAI wire shape but don't implement server-side web search, and some reject unknown top-level fields outright. With the default flipped to True in the previous commit, every operator with an OpenAI-compatible fallback (or one of those as their primary) would suddenly start sending web_search_options to backends that don't know what to do with it. Fix: gate the flag on the operator's RAW config Kind (or Name when Kind is empty), matching only the catalog's genuine 'openai' entry. NOT on NormalizeProviderKind(Kind), because that helper collapses 'openai-compat' to 'openai' for spec lookup — correct for inheriting OpenAI's Bearer auth shape, but wrong for "send an OpenAI-only request field," since openai-compat configs intentionally point at non-OpenAI backends (self-hosted proxies, vLLM, etc.). IsGenuineOpenAI is exposed in PasClaw.Providers.Factory's interface so the gating boundary has direct test coverage. New test cases: - kind=openai / OpenAI / " openai " → True - kind empty + name=openai → True - kind=openai-compat → False (the trap that prompted this fix) - kind=groq/openrouter/ollama/vllm/litellm/deepseek/mistral/together → False - kind=groq + name=openai → False (explicit Kind wins)
Summary
Mirrors the Anthropic server-tool plumbing landed in #145 on the OpenAI side. When the flag is on, the OpenAI provider emits
"web_search_options": {}as a top-level Chat Completions request field. OpenAI's search-capable model (currentlygpt-5-search-api, and the deprecatedgpt-4o-search-preview/gpt-4o-mini-search-previewshutting down 2026-07-23) then runs the query on OpenAI's side and stitches results into the response.Opt-in via
config.json:Off by default. Operators picking a search-capable model in
Cfg.DefaultModel(or per-call) get the server-side path; on every other OpenAI model — and on third-party OpenAI-compatible endpoints (Groq, OpenRouter, Together, vLLM, Ollama) — the field is silently ignored, so leaving the flag off costs nothing.Implementation notes
tools[](and collides with user function tools of the same name), OpenAI'sweb_search_optionsis a top-level request field. A user-definedweb_searchfunction tool coexists intools[]without conflict — both stay in the request, OpenAI picks whichever path makes sense.web_fetchequivalent. Chat Completions only exposesweb_search_options; URL fetching lives on the Responses API.max_uses/search_context_size/user_location. Chat Completions accepts an optionaluser_locationrefinement but the other Responses-API knobs aren't supported there. Empty-object opt-in is the documented minimal form; richer config can land later if anyone needs it.TOpenAIProvider.Create(APIKey, APIBase, DefaultModel)keeps every existing call site working (used by embedders / sample apps); the catalog-aware overload (used by the factory) gained the newServerToolsrecord.Ref: https://developers.openai.com/api/docs/guides/tools-web-search
Test plan
make test-openai-server-tools— three wire-shape cases (off, on, coexistence with a user-definedweb_searchfunction tool)make test-anthropic-server-tools— Anthropic suite unchangedmake smoke— every top-level CLI command still launchesOPENAI_API_KEY, flipopenai_server_tools.web_search: trueinconfig.json, set the model togpt-5-search-api, and runpasclaw agent "what's the latest CVE on CVE.org?"— confirm response text references current resultsNot in this PR (follow-ups)
pasclaw onboarddoesn't yet prompt for the toggle — editconfig.jsondirectly.user_locationrefinement: easy to add (sub-object insideweb_search_options); skipped until someone asks.https://claude.ai/code/session_01TBcLtmpj7dqA5tyFbGnQon
Generated by Claude Code