🐾 fix(mantle): survive Mantle's 20k output ceiling on tool-heavy turns
Two new Mantle shims, auto-installed alongside the existing pair: - Opt out of the fine-grained-tool-streaming beta. Under it, an output-token cutoff mid-tool_use ends the stream without content_block_stop; fast-agent raises "Streaming completed but tool call never finished" and burns its retry ladder against the same wall (the observed ~700s Alan revise_workspace_file failures on Taurus). - Clamp default maxTokens to Mantle's observed 20 000-token server ceiling, so the model stops gracefully (proper block close + stop_reason=max_tokens) instead of being cut off by the gateway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -114,34 +114,97 @@ def test_install_tool_use_caller_strip_is_idempotent() -> None:
|
||||
mantle_shims.install_tool_use_caller_strip() # must not raise or re-wrap
|
||||
|
||||
|
||||
# ── install_fine_grained_tool_streaming_opt_out ──────────────────────────────
|
||||
|
||||
def test_fine_grained_beta_opt_out() -> None:
|
||||
from fast_agent.llm.provider.anthropic.llm_anthropic import AnthropicLLM
|
||||
|
||||
mantle_shims.install_fine_grained_tool_streaming_opt_out()
|
||||
|
||||
# The patched method never touches self, so a bare object suffices.
|
||||
stub = object()
|
||||
assert AnthropicLLM.supports_direct_anthropic_beta(stub, "fine_grained_tool_streaming") is False
|
||||
# Every other beta keeps the base-class answer (True).
|
||||
assert AnthropicLLM.supports_direct_anthropic_beta(stub, "interleaved_thinking") is True
|
||||
assert AnthropicLLM.supports_direct_anthropic_beta(stub, "long_context") is True
|
||||
|
||||
|
||||
def test_fine_grained_beta_opt_out_is_idempotent() -> None:
|
||||
mantle_shims.install_fine_grained_tool_streaming_opt_out()
|
||||
mantle_shims.install_fine_grained_tool_streaming_opt_out() # must not re-wrap
|
||||
|
||||
from fast_agent.llm.provider.anthropic.llm_anthropic import AnthropicLLM
|
||||
|
||||
assert (
|
||||
AnthropicLLM.supports_direct_anthropic_beta(object(), "fine_grained_tool_streaming")
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
# ── install_max_tokens_clamp ─────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"initial,expected",
|
||||
[
|
||||
(128000, mantle_shims.MANTLE_MAX_OUTPUT_TOKENS), # over the ceiling → clamped
|
||||
(None, mantle_shims.MANTLE_MAX_OUTPUT_TOKENS), # unset → pinned to ceiling
|
||||
(4096, 4096), # under the ceiling → untouched
|
||||
],
|
||||
)
|
||||
def test_max_tokens_clamp(
|
||||
monkeypatch: pytest.MonkeyPatch, initial: int | None, expected: int
|
||||
) -> None:
|
||||
from fast_agent.llm.provider.anthropic.llm_anthropic import AnthropicLLM
|
||||
from fast_agent.types import RequestParams
|
||||
|
||||
# Stub the underlying initializer, then force a fresh wrap around it.
|
||||
monkeypatch.setattr(
|
||||
AnthropicLLM,
|
||||
"_initialize_default_params",
|
||||
lambda self, kwargs: RequestParams(maxTokens=initial),
|
||||
)
|
||||
monkeypatch.setattr(mantle_shims, "_max_tokens_clamp_installed", False)
|
||||
mantle_shims.install_max_tokens_clamp()
|
||||
|
||||
params = AnthropicLLM._initialize_default_params(object(), {})
|
||||
assert params.maxTokens == expected
|
||||
|
||||
|
||||
def test_max_tokens_clamp_is_idempotent() -> None:
|
||||
mantle_shims.install_max_tokens_clamp()
|
||||
mantle_shims.install_max_tokens_clamp() # must not raise or re-wrap
|
||||
|
||||
|
||||
# ── maybe_install ────────────────────────────────────────────────────────────
|
||||
|
||||
def test_maybe_install_installs_when_mantle(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_INSTALLER_NAMES = [
|
||||
("install_wire_name_prefix", "wire"),
|
||||
("install_tool_use_caller_strip", "tool_use"),
|
||||
("install_fine_grained_tool_streaming_opt_out", "beta_opt_out"),
|
||||
("install_max_tokens_clamp", "max_tokens"),
|
||||
]
|
||||
|
||||
|
||||
def _patch_installers(monkeypatch: pytest.MonkeyPatch) -> list[str]:
|
||||
calls: list[str] = []
|
||||
monkeypatch.setattr(
|
||||
mantle_shims, "install_wire_name_prefix",
|
||||
lambda: calls.append("wire"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
mantle_shims, "install_tool_use_caller_strip",
|
||||
lambda: calls.append("tool_use"),
|
||||
)
|
||||
for attr, label in _INSTALLER_NAMES:
|
||||
monkeypatch.setattr(
|
||||
mantle_shims, attr,
|
||||
lambda label=label: calls.append(label),
|
||||
)
|
||||
return calls
|
||||
|
||||
|
||||
def test_maybe_install_installs_when_mantle(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
calls = _patch_installers(monkeypatch)
|
||||
|
||||
installed = mantle_shims.maybe_install("https://bedrock-mantle.us-east-1.api.aws/anthropic")
|
||||
assert installed is True
|
||||
assert calls == ["wire", "tool_use"]
|
||||
assert calls == ["wire", "tool_use", "beta_opt_out", "max_tokens"]
|
||||
|
||||
|
||||
def test_maybe_install_noop_for_non_mantle(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
calls: list[str] = []
|
||||
monkeypatch.setattr(
|
||||
mantle_shims, "install_wire_name_prefix",
|
||||
lambda: calls.append("wire"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
mantle_shims, "install_tool_use_caller_strip",
|
||||
lambda: calls.append("tool_use"),
|
||||
)
|
||||
calls = _patch_installers(monkeypatch)
|
||||
|
||||
assert mantle_shims.maybe_install("https://api.anthropic.com") is False
|
||||
assert mantle_shims.maybe_install(None) is False
|
||||
|
||||
Reference in New Issue
Block a user