Files
AX-Copilot-Codex/src/AxCopilot/Services/Agent/AgentLoopTransitions.Documents.cs
T
lacvet 33c1db4dae
Release Gate / gate (push) Has been cancelled
에이전트 선택적 탐색 구조 개선과 경고 정리 반영
- claude-code 선택적 탐색 흐름을 참고해 Cowork/Code 시스템 프롬프트에서 folder_map 상시 선행 지시를 완화하고 glob/grep 기반 좁은 탐색을 우선하도록 조정함

- FolderMapTool 기본 depth를 2로, include_files 기본값을 false로 낮추고 MultiReadTool 최대 파일 수를 8개로 줄여 초기 과탐색 폭을 보수적으로 조정함

- AgentLoopExplorationPolicy partial을 추가해 탐색 범위 분류, broad-scan corrective hint, exploration_breadth 성능 로그를 연결함

- AgentLoopService에 탐색 범위 가이드 주입과 실행 중 탐색 폭 추적을 추가하고, 좁은 질문에서 반복적인 folder_map/대량 multi_read를 교정하도록 정리함

- DocxToHtmlConverter nullable 경고를 수정해 Release 빌드 경고 0 / 오류 0 기준을 다시 충족함

- README와 docs/DEVELOPMENT.md에 2026-04-09 10:36 (KST) 기준 개발 이력을 반영함
2026-04-09 14:27:59 +09:00

163 lines
6.4 KiB
C#

using AxCopilot.Models;
namespace AxCopilot.Services.Agent;
public partial class AgentLoopService
{
private void ApplyDocumentPlanSuccessTransitions(
LlmService.ContentBlock call,
ToolResult result,
List<ChatMessage> messages,
ref bool documentPlanCalled,
ref string? documentPlanPath,
ref string? documentPlanTitle,
ref string? documentPlanScaffold)
{
if (!string.Equals(call.ToolName, "document_plan", StringComparison.OrdinalIgnoreCase))
return;
documentPlanCalled = true;
var po = result.Output ?? string.Empty;
var pm = System.Text.RegularExpressions.Regex.Match(po, @"path:\s*""([^""]+)""");
if (pm.Success) documentPlanPath = pm.Groups[1].Value;
var tm = System.Text.RegularExpressions.Regex.Match(po, @"title:\s*""([^""]+)""");
if (tm.Success) documentPlanTitle = tm.Groups[1].Value;
documentPlanScaffold = ExtractDocumentPlanScaffold(po);
if (!ContainsDocumentPlanFollowUpInstruction(po))
return;
var toolHint = ResolveDocumentPlanFollowUpTool(po);
messages.Add(new ChatMessage
{
Role = "user",
Content =
"document_plan이 완료되었습니다. " +
"방금 생성된 골격의 [내용...] 자리와 각 섹션 내용을 실제 상세 본문으로 모두 채운 뒤 " +
$"{toolHint} 도구를 지금 즉시 호출하세요. " +
"설명만 하지 말고 실제 문서 생성 도구 호출로 바로 이어가세요."
});
EmitEvent(AgentEventType.Thinking, "", $"문서 개요 완료 · {toolHint} 실행 유도");
}
private static string? ExtractDocumentPlanScaffold(string output)
{
if (string.IsNullOrWhiteSpace(output))
return null;
var markers = new (string Start, string End)[]
{
("--- body 시작 ---", "--- body 끝 ---"),
("--- body start ---", "--- body end ---"),
("<!-- body start marker -->", "<!-- body end marker -->"),
};
foreach (var (startMarker, endMarker) in markers)
{
var start = output.IndexOf(startMarker, StringComparison.OrdinalIgnoreCase);
if (start < 0)
continue;
var contentStart = start + startMarker.Length;
var end = output.IndexOf(endMarker, contentStart, StringComparison.OrdinalIgnoreCase);
if (end <= contentStart)
continue;
var scaffold = output[contentStart..end].Trim();
if (!string.IsNullOrWhiteSpace(scaffold))
return scaffold;
}
return null;
}
private static bool ContainsDocumentPlanFollowUpInstruction(string output)
{
if (string.IsNullOrWhiteSpace(output))
return false;
return output.Contains("즉시 실행", StringComparison.OrdinalIgnoreCase)
|| output.Contains("immediate next step", StringComparison.OrdinalIgnoreCase)
|| output.Contains("call html_create", StringComparison.OrdinalIgnoreCase)
|| output.Contains("call document_assemble", StringComparison.OrdinalIgnoreCase);
}
private static string ResolveDocumentPlanFollowUpTool(string output)
{
if (output.Contains("document_assemble", StringComparison.OrdinalIgnoreCase))
return "document_assemble";
if (output.Contains("docx_create", StringComparison.OrdinalIgnoreCase))
return "docx_create";
if (output.Contains("markdown_create", StringComparison.OrdinalIgnoreCase))
return "markdown_create";
if (output.Contains("file_write", StringComparison.OrdinalIgnoreCase))
return "file_write";
return "html_create";
}
private async Task<(bool Completed, bool ConsumedExtraIteration)> TryHandleTerminalDocumentCompletionTransitionAsync(
LlmService.ContentBlock call,
ToolResult result,
List<LlmService.ContentBlock> toolCalls,
List<ChatMessage> messages,
Models.LlmSettings llm,
ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy,
AgentContext context,
CancellationToken ct,
bool documentPlanWasCalled = false)
{
if (!result.Success || !IsTerminalDocumentTool(call.ToolName) || toolCalls.Count != 1)
return (false, false);
// document_plan 없이 바로 문서 도구가 호출된 경우 — 아직 LLM이 추가 반복을 할 수 있음.
// 한 번에 생성된 문서는 내용이 부실할 수 있으므로 조기 종료하지 않고 LLM에 판단을 맡긴다.
if (!_docFallbackAttempted && !documentPlanWasCalled)
return (false, false);
var verificationEnabled = executionPolicy.EnablePostToolVerification
&& AgentTabSettingsResolver.IsPostToolVerificationEnabled(ActiveTab, llm);
var shouldVerify = ShouldRunPostToolVerification(
ActiveTab,
call.ToolName,
result.Success,
verificationEnabled,
verificationEnabled);
var consumedExtraIteration = false;
if (shouldVerify)
{
await RunPostToolVerificationAsync(messages, call.ToolName, result, context, ct);
consumedExtraIteration = true;
}
EmitEvent(AgentEventType.Complete, "", "에이전트 작업 완료");
return (true, consumedExtraIteration);
}
private async Task<bool> TryApplyPostToolVerificationTransitionAsync(
LlmService.ContentBlock call,
ToolResult result,
List<ChatMessage> messages,
Models.LlmSettings llm,
ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy,
AgentContext context,
CancellationToken ct)
{
if (!result.Success)
return false;
var verificationEnabled = executionPolicy.EnablePostToolVerification
&& AgentTabSettingsResolver.IsPostToolVerificationEnabled(ActiveTab, llm);
var shouldVerify = ShouldRunPostToolVerification(
ActiveTab,
call.ToolName,
result.Success,
verificationEnabled,
verificationEnabled);
if (!shouldVerify)
return false;
await RunPostToolVerificationAsync(messages, call.ToolName, result, context, ct);
return true;
}
}