refactor: enhance fallback review to include detailed failure reasons from OpenAI API
This commit is contained in:
@@ -166,8 +166,42 @@ def _call_openai_review(settings: Settings, prompt: str) -> dict[str, Any]:
|
|||||||
raise ReviewError("OpenAI response did not contain JSON output text.")
|
raise ReviewError("OpenAI response did not contain JSON output text.")
|
||||||
|
|
||||||
|
|
||||||
def _fallback_review(diff_context: dict[str, Any]) -> dict[str, Any]:
|
def _summarize_openai_failure(exc: Exception) -> str:
|
||||||
findings = []
|
if isinstance(exc, httpx.HTTPStatusError):
|
||||||
|
status = exc.response.status_code
|
||||||
|
response_text = exc.response.text.strip()
|
||||||
|
if response_text:
|
||||||
|
compact = " ".join(response_text.split())
|
||||||
|
if len(compact) > 400:
|
||||||
|
compact = f"{compact[:400]}..."
|
||||||
|
return f"OpenAI API HTTP {status}: {compact}"
|
||||||
|
return f"OpenAI API HTTP {status}."
|
||||||
|
if isinstance(exc, httpx.TimeoutException):
|
||||||
|
return "OpenAI API request timed out."
|
||||||
|
message = str(exc).strip()
|
||||||
|
if message:
|
||||||
|
return message
|
||||||
|
return f"{exc.__class__.__name__} (no details)"
|
||||||
|
|
||||||
|
|
||||||
|
def _fallback_review(diff_context: dict[str, Any], *, failure_reason: str | None = None) -> dict[str, Any]:
|
||||||
|
findings: list[dict[str, Any]] = []
|
||||||
|
summary = "Fallback analysis was used because OpenAI review was unavailable."
|
||||||
|
|
||||||
|
if failure_reason:
|
||||||
|
summary = f"OpenAI review failed. Error: {failure_reason}"
|
||||||
|
findings.append(
|
||||||
|
{
|
||||||
|
"severity": "high",
|
||||||
|
"file": "unknown",
|
||||||
|
"line_start": 1,
|
||||||
|
"line_end": 1,
|
||||||
|
"title": "OpenAI review request failed",
|
||||||
|
"body": failure_reason,
|
||||||
|
"suggestion": "Fix API/auth/network issues and rerun @codex review.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if "TODO" in diff_context["diff"]:
|
if "TODO" in diff_context["diff"]:
|
||||||
findings.append(
|
findings.append(
|
||||||
{
|
{
|
||||||
@@ -183,7 +217,7 @@ def _fallback_review(diff_context: dict[str, Any]) -> dict[str, Any]:
|
|||||||
return {
|
return {
|
||||||
"verdict": "correct" if not findings else "has_issues",
|
"verdict": "correct" if not findings else "has_issues",
|
||||||
"confidence": 0.4 if not findings else 0.6,
|
"confidence": 0.4 if not findings else 0.6,
|
||||||
"summary": "Fallback analysis was used because OpenAI review was unavailable.",
|
"summary": summary,
|
||||||
"findings": findings,
|
"findings": findings,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,8 +232,8 @@ def run_review_for_pr(
|
|||||||
prompt, diff_context, repo_cfg = prepare_review_prompt(settings, gitea, repo, pr_number, command)
|
prompt, diff_context, repo_cfg = prepare_review_prompt(settings, gitea, repo, pr_number, command)
|
||||||
try:
|
try:
|
||||||
result = _call_openai_review(settings, prompt)
|
result = _call_openai_review(settings, prompt)
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
result = _fallback_review(diff_context)
|
result = _fallback_review(diff_context, failure_reason=_summarize_openai_failure(exc))
|
||||||
return normalize_review_result(result), repo_cfg
|
return normalize_review_result(result), repo_cfg
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
39
tests/test_reviewer_fallback.py
Normal file
39
tests/test_reviewer_fallback.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from gitea_codex_bot.config import get_settings
|
||||||
|
from gitea_codex_bot.services.repo_config import RepoReviewConfig
|
||||||
|
from gitea_codex_bot.services.reviewer import _fallback_review, run_review_for_pr
|
||||||
|
from gitea_codex_bot.types import ParsedCommand
|
||||||
|
|
||||||
|
|
||||||
|
def test_fallback_review_surfaces_failure_reason() -> None:
|
||||||
|
result = _fallback_review({"diff": ""}, failure_reason="OpenAI API HTTP 401: invalid_api_key")
|
||||||
|
|
||||||
|
assert result["verdict"] == "has_issues"
|
||||||
|
assert result["summary"] == "OpenAI review failed. Error: OpenAI API HTTP 401: invalid_api_key"
|
||||||
|
assert result["findings"][0]["title"] == "OpenAI review request failed"
|
||||||
|
assert result["findings"][0]["body"] == "OpenAI API HTTP 401: invalid_api_key"
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_review_for_pr_uses_openai_http_error_in_fallback(monkeypatch) -> None:
|
||||||
|
def _fake_prepare(*_args, **_kwargs):
|
||||||
|
return "prompt", {"diff": "TODO: tighten validation"}, RepoReviewConfig()
|
||||||
|
|
||||||
|
def _raise_http_error(*_args, **_kwargs):
|
||||||
|
request = httpx.Request("POST", "https://api.openai.com/v1/responses")
|
||||||
|
response = httpx.Response(429, request=request, text='{"error":{"message":"rate_limited"}}')
|
||||||
|
raise httpx.HTTPStatusError("rate limited", request=request, response=response)
|
||||||
|
|
||||||
|
monkeypatch.setattr("gitea_codex_bot.services.reviewer.prepare_review_prompt", _fake_prepare)
|
||||||
|
monkeypatch.setattr("gitea_codex_bot.services.reviewer._call_openai_review", _raise_http_error)
|
||||||
|
|
||||||
|
settings = get_settings()
|
||||||
|
command = ParsedCommand(name="review", raw="@codex review")
|
||||||
|
result, _repo_cfg = run_review_for_pr(settings, object(), "acme/repo", 9, command)
|
||||||
|
|
||||||
|
assert result["summary"].startswith("OpenAI review failed. Error: OpenAI API HTTP 429:")
|
||||||
|
assert result["findings"][0]["title"] == "OpenAI review request failed"
|
||||||
|
assert "rate_limited" in result["findings"][0]["body"]
|
||||||
|
assert any(finding["title"] == "TODO marker in diff" for finding in result["findings"])
|
||||||
Reference in New Issue
Block a user