???? ?? ?? ?? ??? ?? fallback ???? ?? ??
- CodeLanguageCatalog? UTF-8 ???? ????? ?? fallback ???? ??? ??? manifest/build/test/lint ?? ?? ??? ???? - WorkspaceContextGenerator? ?? ??? ????? ?? Language Workflow ??? ?????? ??? no-LSP ?????? ?? ??? ?? ??? ?? ??? - AgentLoopLlmRequestPreparationService? ??? ?? tool-call ??? pre-call reminder ?? ??? AgentLoopService?? ??? - CodeLanguageCatalogTests, WorkspaceContextGeneratorTests, AgentLoopLlmRequestPreparationServiceTests? ??? ?? fallback/????/LLM ?? ?? ??? ??? - ??: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_final_batch\\ -p:IntermediateOutputPath=obj\\verify_final_batch\\ (?? 0 / ?? 0) - ??: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "CodeLanguageCatalogTests|WorkspaceContextGeneratorTests|AgentLoopLlmRequestPreparationServiceTests|AgentLoopIterationPreparationServiceTests|AgentMessageInvariantHelperTests|AgentToolResultBudgetTests|ChatStorageServiceTests|HtmlSkillGoldenReportTests|PptxSkillGoldenDeckTests|DocxSkillGoldenDocumentTests|ExcelSkillGoldenWorkbookTests" -p:OutputPath=bin\\verify_final_batch_tests\\ -p:IntermediateOutputPath=obj\\verify_final_batch_tests\\ (?? 54)
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
using AxCopilot.Models;
|
||||
|
||||
namespace AxCopilot.Services.Agent;
|
||||
|
||||
internal sealed record AgentLoopLlmRequestPreparationResult(
|
||||
List<ChatMessage> SendMessages,
|
||||
bool ForceInitialToolCall,
|
||||
bool InjectedToolReminder);
|
||||
|
||||
/// <summary>
|
||||
/// query view가 만들어진 뒤 실제 LLM 요청 배열을 조립합니다.
|
||||
/// 초기 tool call 강제 여부와 사전 reminder 주입을 한곳에서 결정해
|
||||
/// AgentLoopService 본체가 orchestration에 더 집중하도록 분리합니다.
|
||||
/// </summary>
|
||||
internal static class AgentLoopLlmRequestPreparationService
|
||||
{
|
||||
public static AgentLoopLlmRequestPreparationResult Prepare(
|
||||
IReadOnlyList<ChatMessage> queryMessages,
|
||||
int totalToolCalls,
|
||||
bool forceInitialToolCallEnabled,
|
||||
bool injectPreCallToolReminder,
|
||||
int noToolCallLoopRetry)
|
||||
{
|
||||
var forceInitialToolCall = totalToolCalls == 0 && forceInitialToolCallEnabled;
|
||||
if (!forceInitialToolCall
|
||||
|| !injectPreCallToolReminder
|
||||
|| noToolCallLoopRetry > 0)
|
||||
{
|
||||
return new AgentLoopLlmRequestPreparationResult(
|
||||
queryMessages.ToList(),
|
||||
forceInitialToolCall,
|
||||
false);
|
||||
}
|
||||
|
||||
var sendMessages = queryMessages.ToList();
|
||||
sendMessages.Add(BuildToolReminderMessage());
|
||||
return new AgentLoopLlmRequestPreparationResult(
|
||||
sendMessages,
|
||||
forceInitialToolCall,
|
||||
true);
|
||||
}
|
||||
|
||||
internal static ChatMessage BuildToolReminderMessage()
|
||||
{
|
||||
return new ChatMessage
|
||||
{
|
||||
Role = "user",
|
||||
Content = "[TOOL_REQUIRED] 지금 즉시 <tool_call> 형식으로 도구를 호출하세요. 텍스트만 반환하면 거부됩니다.\n" +
|
||||
"Output format:\n<tool_call>\n{\"name\": \"TOOL_NAME\", \"arguments\": {\"param\": \"value\"}}\n</tool_call>"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -87,22 +87,6 @@ public partial class AgentLoopService
|
||||
requestInterrupt && IsRunning);
|
||||
}
|
||||
|
||||
private void DrainPendingCommands(List<ChatMessage> messages)
|
||||
{
|
||||
var queuedSnapshot = _pendingCommands.Snapshot();
|
||||
if (queuedSnapshot.Count == 0)
|
||||
return;
|
||||
|
||||
var drained = _pendingCommands.DequeuePriorityBatch();
|
||||
if (drained.Count == 0)
|
||||
return;
|
||||
|
||||
var projection = AgentQueuedCommandProjector.Project(drained, queuedSnapshot.Count - drained.Count);
|
||||
messages.AddRange(projection.Messages);
|
||||
foreach (var evt in projection.Events)
|
||||
EmitEvent(evt.Type, evt.ToolName, evt.Summary);
|
||||
}
|
||||
|
||||
/// <summary>에이전트 이벤트 스트림 (UI 바인딩용).</summary>
|
||||
public ObservableCollection<AgentEvent> Events { get; } = new();
|
||||
|
||||
@@ -557,25 +541,14 @@ public partial class AgentLoopService
|
||||
EmitEvent(AgentEventType.Error, "", "현재 스킬 런타임 정책으로 사용 가능한 도구가 없습니다.");
|
||||
return "⚠ 현재 스킬 정책에서 허용된 도구가 없어 작업을 진행할 수 없습니다. allowed-tools 설정을 확인하세요.";
|
||||
}
|
||||
// totalToolCalls == 0: 아직 한 번도 도구를 안 불렀으면 tool_choice:"required" 강제
|
||||
// → chatty 모델(Qwen 등)이 텍스트 설명만 하고 도구를 안 부르는 현상 방지
|
||||
var forceFirst = totalToolCalls == 0 && executionPolicy.ForceInitialToolCall;
|
||||
|
||||
// IBM/Qwen 등 chatty 모델 대응: 첫 번째 호출 직전 마지막 user 메시지로 도구 호출 강제 reminder 주입.
|
||||
// recovery 메시지가 이미 추가된 경우(NoToolCallLoopRetry > 0)에는 중복 주입하지 않음.
|
||||
// 임시 메시지이므로 실제 messages 목록은 수정하지 않고, 별도 sendMessages로 전달.
|
||||
List<ChatMessage> sendMessages = queryMessages;
|
||||
if (forceFirst
|
||||
&& executionPolicy.InjectPreCallToolReminder
|
||||
&& runState.NoToolCallLoopRetry == 0)
|
||||
{
|
||||
sendMessages = [.. queryMessages, new ChatMessage
|
||||
{
|
||||
Role = "user",
|
||||
Content = "[TOOL_REQUIRED] 지금 즉시 <tool_call> 형식으로 도구를 호출하세요. 텍스트만 반환하면 거부됩니다.\n" +
|
||||
"Output format:\n<tool_call>\n{\"name\": \"TOOL_NAME\", \"arguments\": {\"param\": \"value\"}}\n</tool_call>"
|
||||
}];
|
||||
}
|
||||
var llmRequest = AgentLoopLlmRequestPreparationService.Prepare(
|
||||
queryMessages,
|
||||
totalToolCalls,
|
||||
executionPolicy.ForceInitialToolCall,
|
||||
executionPolicy.InjectPreCallToolReminder,
|
||||
runState.NoToolCallLoopRetry);
|
||||
var forceFirst = llmRequest.ForceInitialToolCall;
|
||||
var sendMessages = llmRequest.SendMessages;
|
||||
|
||||
// 워크플로우 상세 로그: LLM 요청
|
||||
llmCallSw.Restart();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -174,6 +175,21 @@ internal static class WorkspaceContextGenerator
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
internal static IReadOnlyList<string> DetectLanguageWorkflowHints(
|
||||
string? workFolder,
|
||||
string? preferredLanguage = null,
|
||||
int maxLanguages = 3)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(workFolder) || !Directory.Exists(workFolder))
|
||||
return [];
|
||||
|
||||
var extDist = GetExtensionDistribution(workFolder, CancellationToken.None);
|
||||
return CodeLanguageCatalog.BuildWorkspaceWorkflowSummaries(
|
||||
extDist.Select(x => x.Key),
|
||||
preferredLanguage,
|
||||
maxLanguages);
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 분석 로직
|
||||
// ════════════════════════════════════════════════════════════
|
||||
@@ -450,26 +466,7 @@ internal static class WorkspaceContextGenerator
|
||||
.ToList();
|
||||
|
||||
private static List<string> BuildLanguageWorkflow(List<KeyValuePair<string, int>> extDist)
|
||||
{
|
||||
var workflow = new List<string>();
|
||||
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var (extension, _) in extDist)
|
||||
{
|
||||
var capability = Services.CodeLanguageCatalog.FindByExtension(extension);
|
||||
if (capability == null || !seen.Add(capability.Key))
|
||||
continue;
|
||||
|
||||
var summary = Services.CodeLanguageCatalog.BuildWorkflowSummary(capability.Key);
|
||||
if (!string.IsNullOrWhiteSpace(summary))
|
||||
workflow.Add(summary);
|
||||
|
||||
if (workflow.Count >= 3)
|
||||
break;
|
||||
}
|
||||
|
||||
return workflow;
|
||||
}
|
||||
=> CodeLanguageCatalog.BuildWorkspaceWorkflowSummaries(extDist.Select(x => x.Key)).ToList();
|
||||
|
||||
private static async Task<(string? Branch, string? Remote)> GetGitInfoAsync(
|
||||
string folder, CancellationToken ct)
|
||||
|
||||
Reference in New Issue
Block a user