feat(web): add xAI Web Search provider plugin#29036
Closed
Jaaneek wants to merge 1 commit into
Closed
Conversation
Adds a new bundled web search provider plugin backed by xAI's agentic Web Search tool (server-side `web_search` on the Responses API). Slots in alongside the existing Firecrawl / Tavily / Exa / Brave / SearXNG / DDGS providers; opt in via `web.backend: xai` (or auto-selected by the registry's single-provider shortcut when it's the only available web provider, matching every other backend's behavior). Reuses the existing xAI HTTP credential plumbing (`tools/xai_http.py`) so it works with both `hermes auth login xai-oauth` (SuperGrok OAuth) and `XAI_API_KEY` — no new credential paths, no new env vars, no new setup-wizard prompts. The existing `xai_grok` post_setup hook handles credential collection. Reference: https://docs.x.ai/developers/tools/web-search Provider behavior ----------------- - Sends a structured prompt to Grok with `tools=[{"type": "web_search"}]` enabled and `include=["no_inline_citations"]`, then parses results from a `{"results": [...]}` JSON block (primary), falling back to `url_citation` annotations (secondary) and the top-level `citations` list (last-ditch). Annotation fallback falls through to citations when no rows are extractable, so future annotation types xAI may add don't silently mask real data. - HTTP 200 + `{"error": {...}}` envelopes (model-overload, refusal) are surfaced as failures rather than masked as success-with-empty- results. - HTTP 401 on the OAuth path triggers a single `force_refresh=True` retry — closes two gaps the resolver's proactive JWT-exp shortcut doesn't cover: opaque (non-JWT) access tokens and mid-window revocation. Env-var (`XAI_API_KEY`) credentials never retry; they can't be refreshed and an immediate retry would just burn quota. - `is_available()` is a cheap probe (env var OR auth.json read), never invokes the OAuth resolver — required by the ABC contract because it runs on every `hermes tools` repaint and at tool-registration time. - Class docstring documents the LLM-in-a-trench-coat trust model so callers piping untrusted input into `web_search` know returned URLs are model-generated and should be validated before fetching. Config (`config.yaml`): web: backend: xai xai: model: grok-4.3 # optional, defaults to grok-4.3 allowed_domains: # optional, max 5 — mutex with excluded_domains - arxiv.org excluded_domains: # optional, max 5 - example-spam.com timeout: 90 # optional, seconds Files ----- - plugins/web/xai/plugin.yaml (new) plugin manifest - plugins/web/xai/__init__.py (new) register(ctx) hook - plugins/web/xai/provider.py (new) XAIWebSearchProvider impl - tools/xai_http.py (+47) has_xai_credentials() cheap-probe helper + keyword-only force_refresh arg on resolve_xai_http_ credentials() (backwards compatible; all 9 other call sites unaffected) - tools/web_tools.py (+11) "xai" added to configured- backend set + branch in _is_backend_available() - tests/tools/test_web_providers_xai.py (new, 39 tests) covers identity, cheap-probe semantics, JSON / annotation / citations parse paths, request payload shape, error envelopes, OAuth force-refresh-on-401 retry, env-var-no-retry guard, 500-not- retried guard, refresh-returns- same-token guard, OAuth runtime resolution, and backend wiring. Tests ----- - 39 xai-suite passes - 79 sibling web-provider tests (brave-free, ddgs, searxng, base) pass - 119 cross-suite tests for other xai_http callers (transcription, x_search, tts) pass — verifies the new keyword-only arg is BC - scripts/check-windows-footguns.py: clean on all 5 modified files No edits to run_agent.py, cli.py, gateway/, toolsets, config schema, plugin core, or auth core.
Contributor
|
Merged via #29042 — your commit was cherry-picked onto current main with authorship preserved in |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Adds a new bundled web search provider plugin backed by xAI's agentic Web Search tool on the Responses API.
Why this approach:
tools/xai_http.pyfor the xAI image-gen / TTS / STT providers (OAuth-first viahermes auth login xai-oauth, env-var fallback viaXAI_API_KEY). No new auth paths, no new env vars, no new setup wizard — the existingxai_grokpost_setuphook handles credential prompts.plugins/web/xai/mirroring theplugins/web/brave_free/layout, registers via the standardregister(ctx)plugin hook, and touches only 11 lines oftools/web_tools.py(one set-element + one if-branch in_is_backend_available).is_available(). The ABC contract requiresis_available()to be safe to call on everyhermes toolsrepaint and at tool-registration time. The provider uses a newhas_xai_credentials()helper that does an env-var check + singleauth.jsonread — never invokes the OAuth resolver (which can trigger a network token refresh).Related Issue
Fixes #
Type of Change
Changes Made
plugins/web/xai/plugin.yamlkind: backend,provides_web_providers: [xai]plugins/web/xai/__init__.pyregister(ctx)registration hookplugins/web/xai/provider.pyXAIWebSearchProvider(WebSearchProvider)implementationtools/xai_http.pyhas_xai_credentials()cheap-probe helpertools/web_tools.py"xai"added to configured-backend set +_is_backend_availablebranchtests/tools/test_web_providers_xai.pyProvider behavior:
tools=[{"type": "web_search"}]enabled andinclude=["no_inline_citations"]so URLs come back from annotations / citations rather than inline markdown.{"results": [...]}JSON block fromoutput[*].content[*].text. Tolerates leading/trailing prose because reasoning models occasionally narrate.url_citationannotations + surrounding-text descriptions.citationslist (URLs only).{"error": {...}}envelopes (model-overload, refusal) are surfaced as failures rather than masked as success-with-empty-results.allowed_domains/excluded_domainsconfig keys map to xAI's domain filters; capped at 5 each per the API; mutual-exclusion enforced locally to skip a known-bad request.Config (
config.yaml):How to Test
Run the new test suite:
Expected: 35 passed.
Sibling-provider regression check:
scripts/run_tests.sh tests/tools/test_web_providers_brave_free.py \ tests/tools/test_web_providers_ddgs.py \ tests/tools/test_web_providers_searxng.py \ tests/tools/test_web_providers.py -qExpected: 79 passed, no regressions.
Live smoke test (requires xAI credentials):
Expected: agent calls
web_search, results come back as{title, url, description, position}rows, no credential prompts, no errors.Verify
is_available()is cheap (regression-guard test asserts the resolver is never called):Checklist
Code
feat(web):,fix(web/xai):)scripts/run_tests.shand all tests pass (35 new + 79 adjacent green)Documentation & Housekeeping
docs/, docstrings) — provider has extensive docstrings; config keys documented inprovider.pymodule docstringcli-config.yaml.exampleif I added/changed config keys — N/A (web.xai.*keys are documented in the provider module docstring; no top-level schema change)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A (uses existing plugin + web-provider patterns)httpx(already a dep); no POSIX-only primitivesweb_searchtool)For New Skills
N/A — this PR adds a web search provider plugin, not a skill.
Screenshots / Logs
Test run output:
Sibling-suite regression check:
Trust-model note (also captured in the provider class docstring): unlike index-backed providers (Brave / Tavily / Exa) which return verbatim search-engine results, this backend is an LLM that decides which URLs to surface, generates titles and descriptions itself, and is influenced by the content of the query. A maliciously crafted query (e.g. injected via untrusted upstream input the agent picked up) can in principle steer Grok into emitting attacker-chosen URLs. Callers that pipe untrusted text directly into
web_searchshould treat returned URLs the same way they'd treat any model-generated link, validate before fetching.