Retry incomplete agent repairs safely
This commit is contained in:
@@ -86,5 +86,5 @@ class AiderAgent:
|
|||||||
"--edit-format",
|
"--edit-format",
|
||||||
"diff",
|
"diff",
|
||||||
"--timeout",
|
"--timeout",
|
||||||
"60",
|
"180",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ def build_task(report: FailureReport) -> str:
|
|||||||
response = redact(report.response)
|
response = redact(report.response)
|
||||||
error = redact(report.error_message)
|
error = redact(report.error_message)
|
||||||
sql = redact(report.sql_query)
|
sql = redact(report.sql_query)
|
||||||
|
tenant_scope = ''
|
||||||
|
if report.context.get('database_type') == 'customer':
|
||||||
|
tenant_scope = """
|
||||||
|
Tenant routing fact:
|
||||||
|
The customer has already selected one tenant database. SQL inside that database must not
|
||||||
|
invent an additional customer discriminator column. Use only columns present in tracked
|
||||||
|
schema/semantics, and fix SQL-generation guidance rather than rewriting one unknown column
|
||||||
|
to another guessed column.
|
||||||
|
"""
|
||||||
|
|
||||||
return f"""You are fixing failure report #{report.id} in the Laravel AI Chat repository.
|
return f"""You are fixing failure report #{report.id} in the Laravel AI Chat repository.
|
||||||
|
|
||||||
@@ -68,6 +77,7 @@ Observed error:
|
|||||||
|
|
||||||
Generated SQL, if relevant:
|
Generated SQL, if relevant:
|
||||||
{sql or '[not available]'}
|
{sql or '[not available]'}
|
||||||
|
{tenant_scope}
|
||||||
|
|
||||||
Run focused tests while working. Leave the repository with the fix and regression test applied.
|
Run focused tests while working. Leave the repository with the fix and regression test applied.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -50,6 +50,18 @@ class AutomationWorker:
|
|||||||
metadata["changes"] = asdict(summary)
|
metadata["changes"] = asdict(summary)
|
||||||
policy = evaluate_changes(summary, self.settings, workspace.path)
|
policy = evaluate_changes(summary, self.settings, workspace.path)
|
||||||
if not policy.allowed:
|
if not policy.allowed:
|
||||||
|
if not summary.files and attempt < self.settings.max_agent_attempts:
|
||||||
|
LOG.warning(
|
||||||
|
"Agent produced no changes for report %s on attempt %s; requesting correction",
|
||||||
|
report.id,
|
||||||
|
attempt,
|
||||||
|
)
|
||||||
|
task = build_retry_task(
|
||||||
|
RuntimeError(
|
||||||
|
"No file changes were applied. Make the smallest concrete code and regression-test edits now."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Policy rejected changes: " + "; ".join(policy.errors)
|
"Policy rejected changes: " + "; ".join(policy.errors)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ def test_aider_command_cannot_mutate_git_configuration_or_send_analytics() -> No
|
|||||||
assert "--no-analytics" in command
|
assert "--no-analytics" in command
|
||||||
assert "--no-auto-commits" in command
|
assert "--no-auto-commits" in command
|
||||||
assert command.count("/dev/null") == 2
|
assert command.count("/dev/null") == 2
|
||||||
assert command[-4:] == ["--edit-format", "diff", "--timeout", "60"]
|
assert command[-4:] == ["--edit-format", "diff", "--timeout", "180"]
|
||||||
|
|
||||||
|
|
||||||
def test_aider_environment_does_not_include_database_credentials(tmp_path, monkeypatch) -> None:
|
def test_aider_environment_does_not_include_database_credentials(tmp_path, monkeypatch) -> None:
|
||||||
|
|||||||
@@ -59,3 +59,26 @@ def test_retry_task_contains_failure_without_secrets() -> None:
|
|||||||
assert "Missing import" in task
|
assert "Missing import" in task
|
||||||
assert "secret-value" not in task
|
assert "secret-value" not in task
|
||||||
assert "Do not remove or weaken tests" in task
|
assert "Do not remove or weaken tests" in task
|
||||||
|
|
||||||
|
|
||||||
|
def test_customer_database_task_explains_tenant_scope() -> None:
|
||||||
|
report = FailureReport(
|
||||||
|
id=13,
|
||||||
|
chat_message_id=1,
|
||||||
|
source="automatic",
|
||||||
|
category="sql_execution",
|
||||||
|
user_feedback=None,
|
||||||
|
feedback_type=None,
|
||||||
|
expected_behavior=None,
|
||||||
|
prompt="What did this customer buy most?",
|
||||||
|
response=None,
|
||||||
|
sql_query="SELECT * FROM invoice_row WHERE InvoiceCreator = 'tenant'",
|
||||||
|
error_message="Unknown column InvoiceCreator",
|
||||||
|
context={"database_type": "customer"},
|
||||||
|
)
|
||||||
|
|
||||||
|
task = build_task(report)
|
||||||
|
|
||||||
|
assert "already selected one tenant database" in task
|
||||||
|
assert "must not" in task
|
||||||
|
assert "guessed column" in task
|
||||||
|
|||||||
@@ -97,3 +97,45 @@ def test_failed_validation_gets_one_correction_before_pr() -> None:
|
|||||||
assert worker._run_tests.call_count == 2
|
assert worker._run_tests.call_count == 2
|
||||||
workspace.push_branch.assert_called_once()
|
workspace.push_branch.assert_called_once()
|
||||||
worker.reports.complete.assert_called_once()
|
worker.reports.complete.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_change_response_gets_one_correction() -> None:
|
||||||
|
report = FailureReport(
|
||||||
|
id=14,
|
||||||
|
chat_message_id=1,
|
||||||
|
source="automatic",
|
||||||
|
category="sql_execution",
|
||||||
|
user_feedback=None,
|
||||||
|
feedback_type=None,
|
||||||
|
expected_behavior=None,
|
||||||
|
prompt="List products",
|
||||||
|
response=None,
|
||||||
|
sql_query=None,
|
||||||
|
error_message="Unknown column",
|
||||||
|
context={},
|
||||||
|
)
|
||||||
|
config = settings(mode="pr", dry_run=False, gitea_token="token")
|
||||||
|
worker = AutomationWorker(config)
|
||||||
|
worker.reports = Mock()
|
||||||
|
worker.reports.claim_next.return_value = report
|
||||||
|
worker.agent = Mock()
|
||||||
|
worker.agent.run.side_effect = ["no edit", "applied edit"]
|
||||||
|
worker.gitea = Mock()
|
||||||
|
worker._run_tests = Mock(return_value=[])
|
||||||
|
workspace = Mock()
|
||||||
|
workspace.path = Path("/tmp/nonexistent-workspace")
|
||||||
|
workspace.branch = "ai/failure-14"
|
||||||
|
workspace.summary.side_effect = [
|
||||||
|
ChangeSummary((), 0, 0),
|
||||||
|
ChangeSummary(("tests/Feature/FixTest.php",), 5, 0),
|
||||||
|
ChangeSummary(("tests/Feature/FixTest.php",), 5, 0),
|
||||||
|
]
|
||||||
|
workspace.commit.return_value = "commit-id"
|
||||||
|
|
||||||
|
with patch("lai_automation.worker.GitWorkspace", return_value=workspace):
|
||||||
|
assert worker.run_once()
|
||||||
|
|
||||||
|
assert worker.agent.run.call_count == 2
|
||||||
|
assert "No file changes" in worker.agent.run.call_args_list[1].args[1]
|
||||||
|
worker._run_tests.assert_called_once()
|
||||||
|
workspace.push_branch.assert_called_once()
|
||||||
|
|||||||
Reference in New Issue
Block a user