fix(tests): drive the lookaround strip-and-retry through recover_after_classification

The lookaround regression test only exercised classify_api_error, so the
production branch it exists to protect (turn_recovery._recover_format_errors
-> strip_pattern_and_format(agent.tools) for llama_cpp_grammar_pattern) could
be disabled without any test going red.

Replace it with a test that runs the real entry point,
recover_after_classification, against a fake agent whose tool schema carries
a lookaround ``pattern``: the pattern must be stripped from agent.tools and
the result must request an immediate retry. Still asserts the classifier
mapping and the generic-schema-400 negative control. A/B: sabotaging the
grammar branch (``if False:``) turns this test red; restoring it goes green.
This commit is contained in:
teknium1
2026-09-19 01:24:50 -07:00
committed by Teknium
parent 3306e105b3
commit 1d6b56c6e0

View File

@@ -968,16 +968,49 @@ class TestClassifyApiError:
assert result.retryable is True
assert result.should_compress is False
def test_openai_regex_lookaround_rejection_is_recoverable(self):
"""Strict OpenAI-compatible endpoints reject ``pattern`` lookaround with a 400 (#42631);
it must reuse the strip-pattern/format retry instead of failing the turn."""
def test_openai_regex_lookaround_rejection_strips_pattern_and_retries(self):
"""Strict OpenAI-compatible endpoints reject ``pattern`` lookaround with a 400 (#42631).
Driven through the production path (classifier → ``recover_after_classification``):
the lookaround ``pattern`` must be stripped from ``agent.tools`` and the turn retried."""
from agent.turn_recovery import recover_after_classification
from agent.turn_retry_state import TurnRetryState
class _Agent:
log_prefix = ""
api_mode = "chat_completions"
provider = "custom"
model = "gpt-5.5"
base_url = "http://relay.example/v1"
tools = [{
"type": "function",
"function": {
"name": "send",
"parameters": {
"type": "object",
"properties": {"email": {"type": "string", "pattern": r"^(?!no-reply).+@.+$"}},
},
},
}]
def _recover_with_credential_pool(self, **kwargs):
return False, False
def __getattr__(self, name):
return lambda *args, **kwargs: None
e = MockAPIError(
"Invalid JSON schema: regex lookaround is not supported. Found at $.properties.email.pattern.",
status_code=400,
)
result = classify_api_error(e, provider="custom", model="gpt-5.5")
assert result.reason == FailoverReason.llama_cpp_grammar_pattern
assert result.retryable is True
classified = classify_api_error(e, provider="custom", model="gpt-5.5")
assert classified.reason == FailoverReason.llama_cpp_grammar_pattern
agent = _Agent()
retry_now, _ = recover_after_classification(
agent, e, classified, TurnRetryState(),
status_code=400, error_context=None, messages=[], api_messages=[],
)
assert retry_now is True
assert "pattern" not in agent.tools[0]["function"]["parameters"]["properties"]["email"]
# A generic schema 400 without the lookaround sentence stays a plain client error.
other = classify_api_error(
MockAPIError("Invalid JSON schema: regex syntax error in pattern", status_code=400), provider="custom"