Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agent/agent_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ def init_agent(
reasoning_config: Dict[str, Any] = None,
service_tier: str = None,
request_overrides: Dict[str, Any] = None,
text_verbosity: str = "",
prefill_messages: List[Dict[str, Any]] = None,
platform: str = None,
user_id: str = None,
Expand Down Expand Up @@ -500,6 +501,7 @@ def init_agent(
agent.reasoning_config = reasoning_config # None = use default (medium for OpenRouter)
agent.service_tier = service_tier
agent.request_overrides = dict(request_overrides or {})
agent.text_verbosity = text_verbosity or ""
agent.prefill_messages = prefill_messages or [] # Prefilled conversation turns
agent._force_ascii_payload = False

Expand Down
1 change: 1 addition & 0 deletions agent/chat_completion_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ def build_api_kwargs(agent, api_messages: list) -> dict:
max_tokens=agent.max_tokens,
timeout=agent._resolved_api_call_timeout(),
request_overrides=agent.request_overrides,
text_verbosity=getattr(agent, "text_verbosity", ""),
is_github_responses=is_github_responses,
is_codex_backend=is_codex_backend,
is_xai_responses=is_xai_responses,
Expand Down
20 changes: 20 additions & 0 deletions agent/transports/codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,26 @@ def build_kwargs(
if request_overrides:
kwargs.update(request_overrides)

# Text verbosity - GPT-5+ only (OpenAI Responses API parameter).
# Injected AFTER request_overrides so it merges into any existing
# text dict (e.g. text.format from overrides is preserved).
text_verbosity = params.get("text_verbosity", "")
if text_verbosity and isinstance(text_verbosity, str):
model_stem = model.lower().rsplit("/", 1)[-1]
if model_stem.startswith("gpt-"):
try:
major = int(model_stem.split("-")[1].split(".")[0])
except (IndexError, ValueError):
major = 0
if major >= 5:
text_obj = kwargs.get("text", {})
if not isinstance(text_obj, dict):
text_obj = {}
else:
text_obj = dict(text_obj)
text_obj["verbosity"] = text_verbosity
kwargs["text"] = text_obj

# xAI Responses API rejects ``service_tier`` (HTTP 400 "Argument not
# supported: service_tier") — hit when ``/fast`` priority-processing
# mode lingers from a prior model in the same session, or when a
Expand Down
3 changes: 3 additions & 0 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3907,6 +3907,9 @@ def __init__(
self.service_tier = _parse_service_tier_config(
CLI_CONFIG["agent"].get("service_tier", "")
)
self.text_verbosity = str(
CLI_CONFIG["agent"].get("text_verbosity", "") or ""
).strip()

# OpenRouter provider routing preferences
pr = CLI_CONFIG.get("provider_routing", {}) or {}
Expand Down
1 change: 1 addition & 0 deletions gateway/platforms/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,7 @@ def _create_agent(
session_db=self._ensure_session_db(),
fallback_model=fallback_model,
reasoning_config=reasoning_config,
text_verbosity=GatewayRunner._load_text_verbosity(),
gateway_session_key=gateway_session_key,
)
return agent
Expand Down
12 changes: 12 additions & 0 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -2792,6 +2792,7 @@ def __init__(self, config: Optional[GatewayConfig] = None):
self._ephemeral_system_prompt = self._load_ephemeral_system_prompt()
self._reasoning_config = self._load_reasoning_config()
self._service_tier = self._load_service_tier()
self._text_verbosity = self._load_text_verbosity()
self._show_reasoning = self._load_show_reasoning()
self._busy_input_mode = self._load_busy_input_mode()
self._busy_text_mode = self._load_busy_text_mode()
Expand Down Expand Up @@ -4818,6 +4819,12 @@ def _load_service_tier() -> str | None:
logger.warning("Unknown service_tier '%s', ignoring", raw)
return None

@staticmethod
def _load_text_verbosity() -> str:
"""Load text.verbosity for GPT-5+ from config.yaml agent section."""
cfg = _load_gateway_runtime_config()
return str(cfg_get(cfg, "agent", "text_verbosity", default="") or "").strip()

@staticmethod
def _load_show_reasoning() -> bool:
"""Load show_reasoning toggle from config.yaml display section."""
Expand Down Expand Up @@ -12985,6 +12992,7 @@ async def _run_background_task(
reasoning_config = self._resolve_session_reasoning_config(source=source)
self._reasoning_config = reasoning_config
self._service_tier = self._load_service_tier()
self._text_verbosity = self._load_text_verbosity()
turn_route = self._resolve_turn_agent_config(prompt, model, runtime_kwargs)

# Enrich the prompt with image descriptions so the background
Expand Down Expand Up @@ -13015,6 +13023,7 @@ def run_sync():
disabled_toolsets=disabled_toolsets,
reasoning_config=reasoning_config,
service_tier=self._service_tier,
text_verbosity=self._text_verbosity,
request_overrides=turn_route.get("request_overrides"),
providers_allowed=pr.get("only"),
providers_ignored=pr.get("ignore"),
Expand Down Expand Up @@ -17535,6 +17544,7 @@ def run_sync():
)
self._reasoning_config = reasoning_config
self._service_tier = self._load_service_tier()
self._text_verbosity = self._load_text_verbosity()
# Set up stream consumer for token streaming or interim commentary.
_stream_consumer = None
_stream_delta_cb = None
Expand Down Expand Up @@ -17788,6 +17798,7 @@ def _interim_assistant_cb(text: str, *, already_streamed: bool = False) -> None:
prefill_messages=self._prefill_messages or None,
reasoning_config=reasoning_config,
service_tier=self._service_tier,
text_verbosity=self._text_verbosity,
request_overrides=turn_route.get("request_overrides"),
providers_allowed=pr.get("only"),
providers_ignored=pr.get("ignore"),
Expand Down Expand Up @@ -17877,6 +17888,7 @@ def _notice_callback_sync(notice) -> None:
agent.event_callback = _event_callback_sync
agent.reasoning_config = reasoning_config
agent.service_tier = self._service_tier
agent.text_verbosity = self._text_verbosity
agent.request_overrides = turn_route.get("request_overrides") or {}

_bg_review_release = threading.Event()
Expand Down
1 change: 1 addition & 0 deletions hermes_cli/cli_agent_setup_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ def _init_agent(self, *, model_override: str = None, runtime_override: dict = No
prefill_messages=self.prefill_messages or None,
reasoning_config=self.reasoning_config,
service_tier=self.service_tier,
text_verbosity=getattr(self, "text_verbosity", ""),
request_overrides=request_overrides,
providers_allowed=self._providers_only,
providers_ignored=self._providers_ignore,
Expand Down
1 change: 1 addition & 0 deletions hermes_cli/cli_commands_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,7 @@ def run_background():
session_db=self._session_db,
reasoning_config=self.reasoning_config,
service_tier=self.service_tier,
text_verbosity=getattr(self, "text_verbosity", ""),
request_overrides=turn_route.get("request_overrides"),
providers_allowed=self._providers_only,
providers_ignored=self._providers_ignore,
Expand Down
1 change: 1 addition & 0 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,7 @@ def _ensure_hermes_home_managed(home: Path):
# provider hiccups on a single provider.
"api_max_retries": 3,
"service_tier": "",
"text_verbosity": "", # GPT-5+ text.verbosity: "low", "medium", "high", or "" to omit
# Tool-use enforcement: injects system prompt guidance that tells the
# model to actually call tools instead of describing intended actions.
# Values: "auto" (default — applies to gpt/codex models), true/false
Expand Down
2 changes: 2 additions & 0 deletions run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ def __init__(
reasoning_config: Dict[str, Any] = None,
service_tier: str = None,
request_overrides: Dict[str, Any] = None,
text_verbosity: str = "",
prefill_messages: List[Dict[str, Any]] = None,
platform: str = None,
user_id: str = None,
Expand Down Expand Up @@ -537,6 +538,7 @@ def __init__(
reasoning_config=reasoning_config,
service_tier=service_tier,
request_overrides=request_overrides,
text_verbosity=text_verbosity,
prefill_messages=prefill_messages,
platform=platform,
user_id=user_id,
Expand Down
85 changes: 85 additions & 0 deletions tests/agent/transports/test_codex_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -836,3 +836,88 @@ def test_non_grok_model_preserves_slash_enum_values(self):
assert params["properties"]["model_id"].get("enum") == [
"Qwen/Qwen3.5-0.8B", "plain-id"
]


class TestTextVerbosity:
"""Tests for text.verbosity injection on GPT-5+ models."""

@pytest.fixture
def transport(self):
import agent.transports.codex # noqa: F401
return get_transport("codex_responses")

def _build(self, transport, model, text_verbosity="", **extra):
messages = [{"role": "user", "content": "Hi"}]
return transport.build_kwargs(
model=model, messages=messages, tools=[],
text_verbosity=text_verbosity, **extra,
)

# -- Model guard: should inject --

@pytest.mark.parametrize("model", [
"gpt-5.5",
"gpt-5.4-mini",
"gpt-5.6-sol",
"gpt-5",
"gpt-7",
"gpt-12.3",
])
def test_injects_on_gpt5_plus(self, transport, model):
kw = self._build(transport, model, text_verbosity="low")
assert kw.get("text") == {"verbosity": "low"}

# -- Model guard: should skip --

@pytest.mark.parametrize("model", [
"gpt-4o",
"gpt-4.1-mini",
"claude-opus-4.8",
"grok-4",
"gemini-2.5-pro",
])
def test_skips_non_gpt5(self, transport, model):
kw = self._build(transport, model, text_verbosity="low")
assert "text" not in kw

def test_skips_bare_gpt_prefix(self, transport):
"""gpt- with no version number should not inject."""
kw = self._build(transport, "gpt-", text_verbosity="low")
assert "text" not in kw

# -- Empty verbosity: should not inject --

def test_empty_verbosity_no_injection(self, transport):
kw = self._build(transport, "gpt-5.5", text_verbosity="")
assert "text" not in kw

def test_no_verbosity_param_no_injection(self, transport):
"""No text_verbosity kwarg at all."""
messages = [{"role": "user", "content": "Hi"}]
kw = transport.build_kwargs(model="gpt-5.5", messages=messages, tools=[])
assert "text" not in kw

# -- Precedence: text_verbosity merges with request_overrides --

def test_verbosity_merges_with_request_overrides(self, transport):
kw = self._build(
transport, "gpt-5.5", text_verbosity="low",
request_overrides={"text": {"format": {"type": "json_schema"}}},
)
assert kw["text"]["verbosity"] == "low"
assert kw["text"]["format"] == {"type": "json_schema"}

# -- Vendor-prefixed model names --

@pytest.mark.parametrize("model", [
"openai/gpt-5.5",
"azure/gpt-5.4-mini",
"litellm/gpt-7",
])
def test_injects_on_vendor_prefixed_gpt5(self, transport, model):
kw = self._build(transport, model, text_verbosity="low")
assert kw.get("text") == {"verbosity": "low"}

def test_skips_vendor_prefixed_gpt4(self, transport):
kw = self._build(transport, "openai/gpt-4o", text_verbosity="low")
assert "text" not in kw
9 changes: 9 additions & 0 deletions tui_gateway/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2396,6 +2396,13 @@ def _load_service_tier() -> str | None:
return None


def _load_text_verbosity() -> str:
"""Load text.verbosity for GPT-5+ from config.yaml agent section."""
return str(
(_load_cfg().get("agent") or {}).get("text_verbosity", "") or ""
).strip()


def _load_provider_routing() -> dict:
"""OpenRouter provider-routing prefs from config.yaml (``provider_routing``).

Expand Down Expand Up @@ -3983,6 +3990,7 @@ def _background_agent_kwargs(agent, task_id: str) -> dict:
"reasoning_config": getattr(agent, "reasoning_config", None)
or _load_reasoning_config(),
"service_tier": getattr(agent, "service_tier", None) or _load_service_tier(),
"text_verbosity": getattr(agent, "text_verbosity", "") or _load_text_verbosity(),
"request_overrides": dict(getattr(agent, "request_overrides", {}) or {}),
"platform": "tui",
"session_db": _get_db(),
Expand Down Expand Up @@ -4415,6 +4423,7 @@ def _make_agent(
if service_tier_override is not None
else _load_service_tier()
),
text_verbosity=_load_text_verbosity(),
enabled_toolsets=_load_enabled_toolsets(),
# OpenRouter provider-routing prefs (config.yaml `provider_routing`).
# Mirrors the messaging gateway + CLI so the desktop/TUI honors the same
Expand Down
13 changes: 13 additions & 0 deletions website/docs/user-guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,19 @@ You can also change the reasoning effort at runtime with the `/reasoning` comman
/reasoning hide # Hide model thinking
```

## Text Verbosity (GPT-5+)

Control the verbosity of text output from GPT-5 and later models. This injects the `text.verbosity` parameter into the OpenAI Responses API, which instructs the model to adjust its output length.

```yaml
agent:
text_verbosity: "" # empty = no injection (default). Options: low, medium, high
```

When unset (default), no `text` parameter is sent and the model uses its own default verbosity. Setting `"low"` produces noticeably shorter responses - useful for agent workflows where concise output reduces token overhead.

Only applies to GPT-5+ models on the `codex_responses` transport. Other models (Claude, Grok, Gemini) ignore this setting silently - no errors, no injection.

## Tool-Use Enforcement

Some models occasionally describe intended actions as text instead of making tool calls ("I would run the tests..." instead of actually calling the terminal). Tool-use enforcement injects system prompt guidance that steers the model back to actually calling tools.
Expand Down
Loading