using AxCopilot.Models; namespace AxCopilot.Services.Agent; public partial class AgentLoopService { private void ApplyDocumentPlanSuccessTransitions( LlmService.ContentBlock call, ToolResult result, List 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 ---"), ("", ""), }; 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 toolCalls, List 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 TryApplyPostToolVerificationTransitionAsync( LlmService.ContentBlock call, ToolResult result, List 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; } }