feat(xai): xai_web_search tool — live web search via xAI Responses API web_search - #23345
Closed
Julientalbot wants to merge 2 commits into
Closed
feat(xai): xai_web_search tool — live web search via xAI Responses API web_search#23345Julientalbot wants to merge 2 commits into
Julientalbot wants to merge 2 commits into
Conversation
…s API web_search
xAI's Responses API supports a web_search built-in tool: pass tools:
[{type: "web_search", ...}] and the model searches the live web,
scrapes pages it finds, and grounds its answer with citations.
Per the xAI API reference: "only functions and web search are
supported as tools" — so this is the canonical way to wire xAI-native
web search into Hermes without going through a third-party search
provider.
This is the web counterpart of x_search (NousResearch#14541) which targets
X / Twitter; xai_web_search targets the open web.
Changes:
- tools/xai_web_search_tool.py: self-contained tool implementation
- web_search_tool(query, allowed_websites, excluded_websites,
from_date, to_date, country) → JSON-encoded result with
{success, provider, tool, model, query, answer, citations,
inline_citations}
- Uses Responses API tool_def shape symmetric with x_search:
type=web_search, optional allowed_websites/excluded_websites
(max 10, mutually exclusive), from_date/to_date (ISO YYYY-MM-DD),
country (uppercased ISO alpha-2)
- Citation extraction: top-level data.citations + inline
url_citation annotations on output[*].content[*].annotations
- Retry on 5xx and read-timeout/connection errors with exponential
backoff capped at 5s; no retry on 4xx (auth)
- Configurable via config.yaml under web_search:
{model, timeout_seconds, retries}
- Reuses tools.xai_http.hermes_xai_user_agent
- Self-registers via tools.registry.registry.register
- Tool name xai_web_search (the bare web_search name is already
taken by tools.web_tools)
- tests/tools/test_xai_web_search_tool.py: 33 unit tests
- check_web_search_requirements (with/without/blank API key)
- schema (required query, optional params advertised)
- _normalize_websites (strips protocol/www/path, drops empty,
rejects >10, handles None)
- argument validation (empty query, missing API key, allowed +
excluded mutually exclusive)
- body construction (default model, /responses endpoint, allowed
websites, excluded websites, date range, country uppercase,
minimal tool_def, headers)
- response parsing (answer, top-level citations, inline citations,
legacy output_text fallback, multi-piece concat, non-url types
skipped)
- HTTP errors (401 surface, 500 retry then succeed, 500 exhaust
retries, 4xx no retry)
- toolsets.py: add xai_web_search to _HERMES_CORE_TOOLS and new
xai_web_search toolset
- hermes_cli/tools_config.py: add xai_web_search to
CONFIGURABLE_TOOLSETS
- tests/tools/test_registry.py: add tools.xai_web_search_tool to
manual builtin tool set snapshot
Requires XAI_API_KEY in ~/.hermes/.env.
Contributor
Author
|
Closing this older xAI web search PR in favor of #27023, which supersedes it with a fresh main base, the current xAI Responses API web_search shape (filters.allowed_domains / filters.excluded_domains, max 5), refreshed tests, and active CI. |
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.
Summary
Adds
xai_web_search— a tool that runs a live web search via xAI's Responses APIweb_searchbuilt-in tool, returning an answer grounded in citations.This is the web counterpart of
x_search(#14541):x_searchtargets X / Twitter,xai_web_searchtargets the open web. Together they cover both surfaces of xAI's two-tool search ecosystem (per the xAI API reference: "only functions and web search are supported as tools").Why
Hermes already has a generic
web_searchtool (intools/web_tools.py) backed by various third-party providers (Exa, Firecrawl, Parallel-Web). That's the right default for portability.But when an xAI API key is available, the xAI-native
web_searchis the more direct path:start_index/end_indexoffsets into the answer text.This tool gives Hermes profiles that have
XAI_API_KEYset the option of routing search through xAI without changing the existingweb_searchdefault.Changes (5 files, +743/-0)
tools/xai_web_search_tool.py(new, ~395 LoC)Implementation symmetric with the rebased
tools/x_search_tool.pyfrom #14541 — the two share the same Responses-API-as-search-backend pattern.Key design choices:
https://www.nytimes.com/section/foo→nytimes.com(strips protocol,www., path) so the user can paste any URL form.data.citations(xAI's flat list of sources used) + inlineurl_citationannotations onoutput[*].content[*].annotationswithstart_index/end_indexoffsets so callers can hyperlink the answer text.x_search: retry on 5xx and read-timeout / connection errors with exponential backoff capped at 5s; no retry on 4xx auth errors.config.yaml:tools.xai_http.hermes_xai_user_agentforUser-Agent.tools.registry.registry.register, gated onXAI_API_KEY.xai_web_searchbecause the bareweb_searchname is already owned bytools/web_tools.py(the generic, provider-neutral search). Profiles can enable both — the agent picks per-call.tests/tools/test_xai_web_search_tool.py(new, ~290 LoC)33 unit tests with a
requests.postmonkeypatch fake (no real network):check_web_search_requirements(with / without / blank API key); requiredquery; optional params advertised._normalize_websites: stripshttps:///http:///www./ paths; drops empty entries; rejects lists > 10; handles None.allowed_websites+excluded_websitesmutually exclusive — all surface assuccess: falseJSON./responsesendpoint (not/chat/completions), allowed-/excluded-websites threading, date range, country uppercased, minimaltool_defwhen no options, complete headers (Bearer, JSON, User-Agent).url_citationannotation extraction; legacyoutput_textfallback; multi-piece text concatenation; non-url_citationannotation types skipped.Wiring
toolsets.py:xai_web_searchadded to_HERMES_CORE_TOOLS; newxai_web_searchtoolset.hermes_cli/tools_config.py: newxai_web_searchentry inCONFIGURABLE_TOOLSETS.tests/tools/test_registry.py:tools.xai_web_search_tooladded to the manual snapshot list (same convention as feat(xai): add x_search tool — search X via xAI Responses API #14541, feat(xai): add video generation tool — generate/edit/extend via xAI grok-imagine-video #14543, feat(xai): xai_deferred_chat tool — long-running completions via submit + poll #23329, feat(xai): xai_batch_chat tool — submit many completions to xAI Batch API #23333, feat(xai): xai_responses_chat tool — stateful Responses API surface (store, previous_response_id, max_turns) #23343).Validation
pytest tests/tools/test_xai_web_search_tool.py tests/tools/test_registry.py→ 64/64 passing locally on top of currentmain.Backward compatibility
web_searchtool intools/web_tools.pyis untouched.check_fn-gated onXAI_API_KEY— invisible to users without an xAI key.xai_web_searchis not in any default toolset roster; users opt in viatools_configor by enabling the toolset in their profile.web_searchandxai_web_searchsimultaneously; the agent picks per-call based on which is more appropriate for the query.Scope deliberately not in this PR
xai_web_searchoverweb_searchwhen xAI is available. That's a per-profile policy decision, not a tool-level concern.stream=trueon the underlying Responses call). Web search is typically a single-shot answer; streaming would mostly mask the citation block until the end.max_search_resultsas a tool-level argument. Currently controlled via thesearch_parameters.max_search_resultsbody field if needed; happy to expose if the maintainer wants it.