AX Agent 루프 마감 복구 규칙을 서비스화하고 회귀 검증을 고정합니다

- AgentLoopService 안에 섞여 있던 도구 미호출 루프/계획 미실행 복구 문구와 재시도 규칙을 AgentLoopNoToolResponseRecoveryService로 분리했습니다.

- probe-only 즉시 복구, 최종 경고 전환, 계획 미실행 재시도 규칙을 별도 테스트로 고정해 루프 마감 품질을 높였습니다.

- README.md, docs/DEVELOPMENT.md, docs/NEXT_ROADMAP.md에 2026-04-15 10:57 (KST) 기준 변경 이력과 검증 결과를 반영했습니다.

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_closeout\\ -p:IntermediateOutputPath=obj\\verify_closeout\\ (경고 0 / 오류 0)

- 검증: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentLoopNoToolResponseRecoveryServiceTests|AgentLoopIterationPreparationServiceTests|AgentLoopLlmRequestPreparationServiceTests|AgentQueuedCommandProjectorTests|AgentMessageInvariantHelperTests|AgentToolResultBudgetTests|AgentQueryContextBuilderTests|ChatStorageServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests -p:OutputPath=bin\\verify_closeout_tests\\ -p:IntermediateOutputPath=obj\\verify_closeout_tests\\ (통과 27)
This commit is contained in:
2026-04-15 10:58:47 +09:00
parent 48e8c57cf3
commit f283662d30
6 changed files with 234 additions and 64 deletions

View File

@@ -0,0 +1,84 @@
using AxCopilot.Services.Agent;
using FluentAssertions;
using Xunit;
namespace AxCopilot.Tests.Services;
public class AgentLoopNoToolResponseRecoveryServiceTests
{
[Fact]
public void BuildNoToolLoopRecovery_ShouldTriggerImmediatelyForProbeOnlyRuns()
{
var result = AgentLoopNoToolResponseRecoveryService.BuildNoToolLoopRecovery(
requiresConcreteArtifactOrEdit: true,
onlyProbeToolsUsed: true,
totalToolCalls: 1,
consecutiveNoToolResponses: 1,
noToolResponseThreshold: 2,
currentRetryCount: 0,
maxRetryCount: 2,
preferredInitialToolSequence: "file_read -> file_edit",
activeToolNames: ["dev_env_detect", "file_read", "file_edit"]);
result.Should().NotBeNull();
result!.NextRetryCount.Should().Be(1);
result.RecoveryContent.Should().Contain("도구를 호출하지 않았습니다");
result.RecoveryContent.Should().Contain("file_read -> file_edit");
result.RecoveryContent.Should().Contain("dev_env_detect, file_read, file_edit");
}
[Fact]
public void BuildNoToolLoopRecovery_ShouldReturnFinalWarningOnLastRetry()
{
var result = AgentLoopNoToolResponseRecoveryService.BuildNoToolLoopRecovery(
requiresConcreteArtifactOrEdit: true,
onlyProbeToolsUsed: false,
totalToolCalls: 0,
consecutiveNoToolResponses: 2,
noToolResponseThreshold: 2,
currentRetryCount: 1,
maxRetryCount: 2,
preferredInitialToolSequence: "file_read -> file_edit",
activeToolNames: ["file_read", "file_edit", "file_read"]);
result.Should().NotBeNull();
result!.NextRetryCount.Should().Be(2);
result.RecoveryContent.Should().Contain("최종 경고");
result.RecoveryContent.Should().Contain("file_read, file_edit");
}
[Fact]
public void BuildPlanExecutionRecovery_ShouldGeneratePlanSpecificRetryPrompt()
{
var result = AgentLoopNoToolResponseRecoveryService.BuildPlanExecutionRecovery(
requiresConcreteArtifactOrEdit: true,
forceToolCallAfterPlan: true,
planStepCount: 3,
totalToolCalls: 0,
currentRetryCount: 0,
maxRetryCount: 2,
preferredInitialToolSequence: "file_read -> file_edit",
activeToolNames: ["file_read", "file_edit", "git_tool"]);
result.Should().NotBeNull();
result!.NextRetryCount.Should().Be(1);
result.RecoveryContent.Should().Contain("계획을 세웠지만 도구를 호출하지 않았습니다");
result.EventSummary.Should().Contain("실행 재시도 1/2");
}
[Fact]
public void BuildPlanExecutionRecovery_ShouldReturnNullWhenRetryIsExhausted()
{
var result = AgentLoopNoToolResponseRecoveryService.BuildPlanExecutionRecovery(
requiresConcreteArtifactOrEdit: true,
forceToolCallAfterPlan: true,
planStepCount: 2,
totalToolCalls: 0,
currentRetryCount: 2,
maxRetryCount: 2,
preferredInitialToolSequence: "file_read -> file_edit",
activeToolNames: ["file_read", "file_edit"]);
result.Should().BeNull();
}
}