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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user