SQL 리뷰 계층과 AgentLoop 응답 분해 helper를 추가해 코드 탭 마감 품질을 높임

- SqlReviewService를 추가해 SQL fallback 결과에 review severity, key findings, review checklist를 붙이고 schema migration, seed/reference data, reporting query마다 다른 검토 포인트를 안내하도록 확장했습니다.
- SqlAnalysisService와 CodeLanguageCatalog를 업데이트해 SQL fallback summary와 workflow summary가 rollback notes, dependency order, row-count guard 같은 리뷰 힌트를 직접 포함하도록 보강했습니다.
- AgentLoopResponseClassificationService를 추가해 LLM 응답에서 text/tool_use 분리, no-tool 연속 카운트 계산, thinking summary 생성을 helper로 분리했고 AgentLoopService 본체는 해당 helper를 사용하도록 정리했습니다.
- README, docs/DEVELOPMENT.md, docs/NEXT_ROADMAP.md에 2026-04-15 11:50 (KST) 기준 이력을 반영했습니다.

검증 결과
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_loop_sql_finalize\\ -p:IntermediateOutputPath=obj\\verify_loop_sql_finalize\\ : 경고 0 / 오류 0
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "AgentLoopResponseClassificationServiceTests|AgentLoopLlmRequestPreparationServiceTests|AgentLoopIterationPreparationServiceTests|SqlAnalysisServiceTests|SqlReviewServiceTests|CodeLanguageCatalogTests|WorkspaceContextGeneratorTests" -p:OutputPath=bin\\verify_loop_sql_finalize_tests\\ -p:IntermediateOutputPath=obj\\verify_loop_sql_finalize_tests\\ : 통과 48
This commit is contained in:
2026-04-15 11:51:42 +09:00
parent 2a49b1da24
commit 717d0f2143
12 changed files with 354 additions and 36 deletions

View File

@@ -0,0 +1,52 @@
using AxCopilot.Services;
namespace AxCopilot.Services.Agent;
internal sealed record AgentLoopResponseClassificationResult(
string TextResponse,
IReadOnlyList<string> TextParts,
List<ContentBlock> ToolCalls,
int NextConsecutiveNoToolResponses)
{
public string BuildThinkingSummary(int maxLength = 150)
{
if (string.IsNullOrEmpty(TextResponse))
return string.Empty;
return TextResponse.Length > maxLength
? TextResponse[..maxLength] + "…"
: TextResponse;
}
}
/// <summary>
/// LLM 응답 블록을 텍스트와 tool_use로 분리하고, 무도구 응답 연속 횟수를 계산한다.
/// </summary>
internal static class AgentLoopResponseClassificationService
{
public static AgentLoopResponseClassificationResult Classify(
IReadOnlyList<ContentBlock> blocks,
int consecutiveNoToolResponses)
{
var textParts = new List<string>();
var toolCalls = new List<ContentBlock>();
foreach (var block in blocks)
{
if (block.Type == "text" && !string.IsNullOrWhiteSpace(block.Text))
textParts.Add(block.Text);
else if (block.Type == "tool_use")
toolCalls.Add(block);
}
var nextConsecutiveNoToolResponses = toolCalls.Count == 0
? consecutiveNoToolResponses + 1
: 0;
return new AgentLoopResponseClassificationResult(
string.Join("\n", textParts),
textParts,
toolCalls,
nextConsecutiveNoToolResponses);
}
}

View File

@@ -726,21 +726,12 @@ public partial class AgentLoopService
return $"⚠ LLM 오류: {ex.Message}";
}
// 응답에서 텍스트와 도구 호출 분리
var textParts = new List<string>();
var toolCalls = new List<ContentBlock>();
foreach (var block in blocks)
{
if (block.Type == "text" && !string.IsNullOrWhiteSpace(block.Text))
textParts.Add(block.Text);
else if (block.Type == "tool_use")
toolCalls.Add(block);
}
// 텍스트 부분
var textResponse = string.Join("\n", textParts);
consecutiveNoToolResponses = toolCalls.Count == 0 ? consecutiveNoToolResponses + 1 : 0;
var responseClassification = AgentLoopResponseClassificationService.Classify(
blocks,
consecutiveNoToolResponses);
var textResponse = responseClassification.TextResponse;
var toolCalls = responseClassification.ToolCalls;
consecutiveNoToolResponses = responseClassification.NextConsecutiveNoToolResponses;
// 워크플로우 상세 로그: LLM 응답
WorkflowLogService.LogLlmResponse(_conversationId, _currentRunId, iteration,
@@ -804,9 +795,7 @@ public partial class AgentLoopService
// Thinking UI: 텍스트 응답 중 도구 호출이 있으면 "사고 과정"으로 표시
if (!string.IsNullOrEmpty(textResponse) && toolCalls.Count > 0)
{
var thinkingSummary = textResponse.Length > 150
? textResponse[..150] + "…"
: textResponse;
var thinkingSummary = responseClassification.BuildThinkingSummary();
EmitEvent(AgentEventType.Thinking, "", thinkingSummary);
}