feat: configure X Search reasoning effort

This commit is contained in:
YAMAGUCHI Seiji 2026-07-10 08:02:49 +09:00 committed by Teknium
parent 0144743b21
commit 5befa15aba
5 changed files with 87 additions and 0 deletions

View file

@ -3303,6 +3303,9 @@ DEFAULT_CONFIG = {
# recommended default; any Grok model with x_search tool
# access works.
"model": "grok-4.5",
# Optional reasoning effort sent to xAI Responses API models that
# support it. Leave null to preserve the selected model's default.
"reasoning_effort": None,
# Request timeout in seconds (minimum 30). x_search can take
# 60-120s for complex queries — the default is generous.
"timeout_seconds": 180,

View file

@ -72,6 +72,7 @@ def test_x_search_posts_responses_request(monkeypatch):
assert captured["headers"]["User-Agent"] == f"Hermes-Agent/{__version__}"
assert captured["json"]["model"] == "grok-4.5"
assert captured["json"]["store"] is False
assert "reasoning" not in captured["json"]
assert tool_def["type"] == "x_search"
assert tool_def["allowed_x_handles"] == ["xai", "grok"]
assert tool_def["from_date"] == "2026-04-01"
@ -424,6 +425,49 @@ def test_x_search_honors_config_model_and_timeout(monkeypatch, tmp_path):
assert captured["timeout"] == 45
def test_x_search_honors_config_reasoning_effort(monkeypatch):
"""Configured reasoning effort reaches the xAI Responses request."""
from tools.x_search_tool import x_search_tool
monkeypatch.setenv("XAI_API_KEY", "xai-test-key")
monkeypatch.setattr(
"tools.x_search_tool._load_x_search_config",
lambda: {"reasoning_effort": "low", "retries": 0},
)
captured = {}
def _fake_post(url, headers=None, json=None, timeout=None):
assert json is not None
captured["reasoning"] = json.get("reasoning")
return _FakeResponse({"output_text": "Reasoning configured."})
monkeypatch.setattr("requests.post", _fake_post)
result = json.loads(x_search_tool(query="anything"))
assert result["success"] is True
assert captured["reasoning"] == {"effort": "low"}
def test_x_search_rejects_invalid_config_reasoning_effort(monkeypatch):
"""A typo must fail closed instead of silently using xAI's default effort."""
from tools.x_search_tool import x_search_tool
monkeypatch.setenv("XAI_API_KEY", "xai-test-key")
monkeypatch.setattr(
"tools.x_search_tool._load_x_search_config",
lambda: {"reasoning_effort": "minimal"},
)
_no_post_allowed(monkeypatch)
result = json.loads(x_search_tool(query="anything"))
assert result["error"] == (
"x_search.reasoning_effort must be one of: low, medium, high, xhigh "
"(got 'minimal')"
)
def test_x_search_registered_in_registry_with_check_fn():
"""The tool is registered under the x_search toolset with the gating check_fn."""
import tools.x_search_tool # noqa: F401 — ensures registration runs

View file

@ -59,6 +59,7 @@ DEFAULT_XAI_BASE_URL = "https://api.x.ai/v1"
DEFAULT_X_SEARCH_MODEL = "grok-4.5"
DEFAULT_X_SEARCH_TIMEOUT_SECONDS = 180
DEFAULT_X_SEARCH_RETRIES = 2
X_SEARCH_REASONING_EFFORTS = ("low", "medium", "high", "xhigh")
MAX_HANDLES = 10
@ -80,6 +81,22 @@ def _get_x_search_model() -> str:
return (str(cfg.get("model") or "").strip() or DEFAULT_X_SEARCH_MODEL)
def _get_x_search_reasoning_effort() -> Optional[str]:
cfg = _load_x_search_config()
raw_value = cfg.get("reasoning_effort")
if raw_value is None or not str(raw_value).strip():
return None
effort = str(raw_value).strip().lower()
if effort not in X_SEARCH_REASONING_EFFORTS:
allowed = ", ".join(X_SEARCH_REASONING_EFFORTS)
raise ValueError(
f"x_search.reasoning_effort must be one of: {allowed} "
f"(got {raw_value!r})"
)
return effort
def _get_x_search_timeout_seconds() -> int:
cfg = _load_x_search_config()
raw_value = cfg.get("timeout_seconds", DEFAULT_X_SEARCH_TIMEOUT_SECONDS)
@ -299,6 +316,11 @@ def x_search_tool(
except ValueError as exc:
return tool_error(str(exc))
try:
reasoning_effort = _get_x_search_reasoning_effort()
except ValueError as exc:
return tool_error(str(exc))
tool_def: Dict[str, Any] = {"type": "x_search"}
if allowed:
tool_def["allowed_x_handles"] = allowed
@ -324,6 +346,8 @@ def x_search_tool(
"tools": [tool_def],
"store": False,
}
if reasoning_effort:
payload["reasoning"] = {"effort": reasoning_effort}
timeout_seconds = _get_x_search_timeout_seconds()
max_retries = _get_x_search_retries()

View file

@ -54,6 +54,11 @@ x_search:
# with x_search tool access works.
model: grok-4.5
# Optional reasoning effort: low, medium, high, or xhigh. When omitted,
# the selected model's default applies. xhigh is supported only by
# models that document it, such as grok-4.20-multi-agent.
reasoning_effort: low
# Request timeout in seconds. x_search can take 60120s for
# complex queries — the default is generous. Minimum: 30.
timeout_seconds: 180
@ -63,6 +68,10 @@ x_search:
retries: 2
```
`reasoning_effort` is sent to the xAI Responses API as
`reasoning: {effort: ...}`. Leave it unset for models that do not support
configurable reasoning. Invalid values fail before an API request is made.
## Tool parameters
The agent calls `x_search` with these arguments:

View file

@ -50,6 +50,10 @@ x_search:
# x_search 工具访问权限的 Grok 模型均可使用。
model: grok-4.5
# 可选推理强度low、medium、high 或 xhigh。省略时使用所选模型的默认值。
# xhigh 仅适用于明确支持它的模型,例如 grok-4.20-multi-agent。
reasoning_effort: low
# 请求超时时间(秒)。复杂查询的 x_search 可能需要 60120 秒,
# 默认值较为宽松。最小值30。
timeout_seconds: 180
@ -59,6 +63,9 @@ x_search:
retries: 2
```
`reasoning_effort` 会以 `reasoning: {effort: ...}` 的形式发送到 xAI
Responses API。不支持可配置推理的模型应留空。无效值会在发起 API 请求前失败。
## 工具参数
agent 调用 `x_search` 时使用以下参数: