코드탭 pre-LLM 단계 결정을 별도 서비스로 분리
- AgentLoopPreLlmStageService를 추가해 thinking summary, Gemini free-tier delay, user prompt submit hook, missing-tool guard, request assembly handoff를 한 단계로 정리함 - AgentLoopService는 pre-LLM stage 결과를 소비하는 형태로 단순화해 history/query assembly 다음 단계가 claw-code처럼 더 선명하게 보이도록 구조를 개선함 - AgentLoopPreLlmStageServiceTests를 추가하고 관련 구조 테스트 60개를 통과시켰으며 dotnet build 경고/오류 0으로 검증함
This commit is contained in:
167
src/AxCopilot.Tests/Services/AgentLoopPreLlmStageServiceTests.cs
Normal file
167
src/AxCopilot.Tests/Services/AgentLoopPreLlmStageServiceTests.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services.Agent;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace AxCopilot.Tests.Services;
|
||||
|
||||
public class AgentLoopPreLlmStageServiceTests
|
||||
{
|
||||
private sealed class FakeAgentTool : IAgentTool
|
||||
{
|
||||
public string Name { get; init; } = "file_read";
|
||||
public string Description { get; init; } = "Reads a file";
|
||||
public ToolParameterSchema Parameters { get; init; } = new();
|
||||
|
||||
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
|
||||
=> Task.FromResult(ToolResult.Ok("ok"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prepare_ShouldPlanFreeTierDelayForGeminiAfterFirstIteration()
|
||||
{
|
||||
var result = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||
QueryMessages: [new ChatMessage { Role = "user", Content = "inspect" }],
|
||||
ActiveTools: [new FakeAgentTool()],
|
||||
CodeWorkingSet: null,
|
||||
ActiveTab: "Code",
|
||||
AgentLogLevel: "info",
|
||||
Iteration: 2,
|
||||
MaxIterations: 40,
|
||||
FreeTierMode: true,
|
||||
ActiveService: "gemini",
|
||||
FreeTierDelaySeconds: 5,
|
||||
LatestUserPrompt: "inspect",
|
||||
LastUserPromptHookFingerprint: null,
|
||||
RunId: "run-1",
|
||||
TotalToolCalls: 1,
|
||||
ForceInitialToolCallEnabled: true,
|
||||
InjectPreCallToolReminder: true,
|
||||
NoToolCallLoopRetry: 0,
|
||||
HasRuntimeOverride: false,
|
||||
RuntimeAllowedToolCount: 0));
|
||||
|
||||
result.FreeTierDelayMilliseconds.Should().Be(5000);
|
||||
result.FreeTierDelaySummary.Should().Contain("5s");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prepare_ShouldEmitUserPromptHookOnlyWhenFingerprintChanges()
|
||||
{
|
||||
var unchanged = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||
QueryMessages: [new ChatMessage { Role = "user", Content = "fix build" }],
|
||||
ActiveTools: [new FakeAgentTool()],
|
||||
CodeWorkingSet: null,
|
||||
ActiveTab: "Code",
|
||||
AgentLogLevel: "debug",
|
||||
Iteration: 1,
|
||||
MaxIterations: 20,
|
||||
FreeTierMode: false,
|
||||
ActiveService: "vllm",
|
||||
FreeTierDelaySeconds: 0,
|
||||
LatestUserPrompt: "fix build",
|
||||
LastUserPromptHookFingerprint: $"{ "fix build".Length}:{ "fix build".GetHashCode()}",
|
||||
RunId: "run-2",
|
||||
TotalToolCalls: 0,
|
||||
ForceInitialToolCallEnabled: true,
|
||||
InjectPreCallToolReminder: true,
|
||||
NoToolCallLoopRetry: 0,
|
||||
HasRuntimeOverride: false,
|
||||
RuntimeAllowedToolCount: 0));
|
||||
|
||||
unchanged.ShouldRunUserPromptSubmitHook.Should().BeFalse();
|
||||
unchanged.UserPromptSubmitHookPayloadJson.Should().BeNull();
|
||||
|
||||
var changed = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||
QueryMessages: [new ChatMessage { Role = "user", Content = "fix build" }],
|
||||
ActiveTools: [new FakeAgentTool()],
|
||||
CodeWorkingSet: null,
|
||||
ActiveTab: "Code",
|
||||
AgentLogLevel: "debug",
|
||||
Iteration: 1,
|
||||
MaxIterations: 20,
|
||||
FreeTierMode: false,
|
||||
ActiveService: "vllm",
|
||||
FreeTierDelaySeconds: 0,
|
||||
LatestUserPrompt: "fix build",
|
||||
LastUserPromptHookFingerprint: null,
|
||||
RunId: "run-3",
|
||||
TotalToolCalls: 0,
|
||||
ForceInitialToolCallEnabled: true,
|
||||
InjectPreCallToolReminder: true,
|
||||
NoToolCallLoopRetry: 0,
|
||||
HasRuntimeOverride: false,
|
||||
RuntimeAllowedToolCount: 0));
|
||||
|
||||
changed.ShouldRunUserPromptSubmitHook.Should().BeTrue();
|
||||
changed.UserPromptSubmitHookPayloadJson.Should().Contain("\"runId\":\"run-3\"");
|
||||
changed.ThinkingSummary.Should().Contain("iteration 1/20");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prepare_ShouldReturnRuntimeSpecificMissingToolsFailure()
|
||||
{
|
||||
var result = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||
QueryMessages: [new ChatMessage { Role = "user", Content = "fix build" }],
|
||||
ActiveTools: [],
|
||||
CodeWorkingSet: null,
|
||||
ActiveTab: "Code",
|
||||
AgentLogLevel: "info",
|
||||
Iteration: 1,
|
||||
MaxIterations: 20,
|
||||
FreeTierMode: false,
|
||||
ActiveService: "vllm",
|
||||
FreeTierDelaySeconds: 0,
|
||||
LatestUserPrompt: "fix build",
|
||||
LastUserPromptHookFingerprint: null,
|
||||
RunId: "run-4",
|
||||
TotalToolCalls: 0,
|
||||
ForceInitialToolCallEnabled: true,
|
||||
InjectPreCallToolReminder: true,
|
||||
NoToolCallLoopRetry: 0,
|
||||
HasRuntimeOverride: true,
|
||||
RuntimeAllowedToolCount: 3));
|
||||
|
||||
result.LlmRequest.Should().BeNull();
|
||||
result.MissingToolsEventSummary.Should().Contain("skill runtime policy");
|
||||
result.MissingToolsReturnMessage.Should().Contain("allowed-tools");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Prepare_ShouldBuildLlmRequestWhenToolsExist()
|
||||
{
|
||||
var workingSet = new CodeTaskWorkingSetService(
|
||||
"create WPF shell",
|
||||
@"E:\code",
|
||||
"wpf-mvvm",
|
||||
startedFromEmptyWorkspace: true);
|
||||
using var mkdirDoc = JsonDocument.Parse("""{"action":"mkdir","paths":["Views","ViewModels"]}""");
|
||||
workingSet.RecordToolResult("file_manage", mkdirDoc.RootElement.Clone(), ToolResult.Ok("created directories"));
|
||||
|
||||
var result = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||
QueryMessages: [new ChatMessage { Role = "user", Content = "create WPF shell" }],
|
||||
ActiveTools: [new FakeAgentTool()],
|
||||
CodeWorkingSet: workingSet,
|
||||
ActiveTab: "Code",
|
||||
AgentLogLevel: "info",
|
||||
Iteration: 1,
|
||||
MaxIterations: 30,
|
||||
FreeTierMode: false,
|
||||
ActiveService: "vllm",
|
||||
FreeTierDelaySeconds: 0,
|
||||
LatestUserPrompt: "create WPF shell",
|
||||
LastUserPromptHookFingerprint: null,
|
||||
RunId: "run-5",
|
||||
TotalToolCalls: 0,
|
||||
ForceInitialToolCallEnabled: true,
|
||||
InjectPreCallToolReminder: true,
|
||||
NoToolCallLoopRetry: 0,
|
||||
HasRuntimeOverride: false,
|
||||
RuntimeAllowedToolCount: 0));
|
||||
|
||||
result.LlmRequest.Should().NotBeNull();
|
||||
result.LlmRequest!.SupplementalMessageCount.Should().Be(1);
|
||||
result.LlmRequest.SendMessages.Should().Contain(message => message.MetaKind == "code_working_set");
|
||||
}
|
||||
}
|
||||
174
src/AxCopilot/Services/Agent/AgentLoopPreLlmStageService.cs
Normal file
174
src/AxCopilot/Services/Agent/AgentLoopPreLlmStageService.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Text.Json;
|
||||
using AxCopilot.Models;
|
||||
|
||||
namespace AxCopilot.Services.Agent;
|
||||
|
||||
internal sealed record AgentLoopPreLlmStageInput(
|
||||
IReadOnlyList<ChatMessage> QueryMessages,
|
||||
IReadOnlyCollection<IAgentTool> ActiveTools,
|
||||
CodeTaskWorkingSetService? CodeWorkingSet,
|
||||
string? ActiveTab,
|
||||
string? AgentLogLevel,
|
||||
int Iteration,
|
||||
int MaxIterations,
|
||||
bool FreeTierMode,
|
||||
string? ActiveService,
|
||||
int FreeTierDelaySeconds,
|
||||
string? LatestUserPrompt,
|
||||
string? LastUserPromptHookFingerprint,
|
||||
string RunId,
|
||||
int TotalToolCalls,
|
||||
bool ForceInitialToolCallEnabled,
|
||||
bool InjectPreCallToolReminder,
|
||||
int NoToolCallLoopRetry,
|
||||
bool HasRuntimeOverride,
|
||||
int RuntimeAllowedToolCount);
|
||||
|
||||
internal sealed record AgentLoopPreLlmStageResult(
|
||||
string ThinkingSummary,
|
||||
string? FreeTierDelaySummary,
|
||||
int FreeTierDelayMilliseconds,
|
||||
bool ShouldRunUserPromptSubmitHook,
|
||||
string? UpdatedUserPromptFingerprint,
|
||||
string? UserPromptSubmitHookPayloadJson,
|
||||
AgentLoopLlmRequestPreparationResult? LlmRequest,
|
||||
string? MissingToolsEventSummary,
|
||||
string? MissingToolsReturnMessage);
|
||||
|
||||
/// <summary>
|
||||
/// Builds the pre-LLM stage decisions for an iteration:
|
||||
/// status text, free-tier wait, prompt-submit hook plan, missing-tool guard,
|
||||
/// and the final request payload assembly.
|
||||
/// </summary>
|
||||
internal static class AgentLoopPreLlmStageService
|
||||
{
|
||||
public static AgentLoopPreLlmStageResult Prepare(AgentLoopPreLlmStageInput input)
|
||||
{
|
||||
var thinkingSummary = BuildThinkingSummary(
|
||||
input.AgentLogLevel,
|
||||
input.Iteration,
|
||||
input.MaxIterations);
|
||||
|
||||
var freeTierDelay = BuildFreeTierDelayPlan(
|
||||
input.FreeTierMode,
|
||||
input.ActiveService,
|
||||
input.Iteration,
|
||||
input.FreeTierDelaySeconds);
|
||||
|
||||
var promptHookPlan = BuildUserPromptHookPlan(
|
||||
input.LatestUserPrompt,
|
||||
input.LastUserPromptHookFingerprint,
|
||||
input.RunId,
|
||||
input.ActiveTab);
|
||||
|
||||
if (input.ActiveTools.Count == 0)
|
||||
{
|
||||
var (eventSummary, returnMessage) = BuildMissingToolsFailure(
|
||||
input.ActiveTab,
|
||||
input.HasRuntimeOverride,
|
||||
input.RuntimeAllowedToolCount);
|
||||
return new AgentLoopPreLlmStageResult(
|
||||
thinkingSummary,
|
||||
freeTierDelay.Summary,
|
||||
freeTierDelay.DelayMilliseconds,
|
||||
promptHookPlan.ShouldRunHook,
|
||||
promptHookPlan.UpdatedFingerprint,
|
||||
promptHookPlan.PayloadJson,
|
||||
null,
|
||||
eventSummary,
|
||||
returnMessage);
|
||||
}
|
||||
|
||||
var llmRequest = AgentLoopQueryAssemblyService.PrepareRequest(
|
||||
input.QueryMessages,
|
||||
input.CodeWorkingSet,
|
||||
input.TotalToolCalls,
|
||||
input.ForceInitialToolCallEnabled,
|
||||
input.InjectPreCallToolReminder,
|
||||
input.NoToolCallLoopRetry);
|
||||
|
||||
return new AgentLoopPreLlmStageResult(
|
||||
thinkingSummary,
|
||||
freeTierDelay.Summary,
|
||||
freeTierDelay.DelayMilliseconds,
|
||||
promptHookPlan.ShouldRunHook,
|
||||
promptHookPlan.UpdatedFingerprint,
|
||||
promptHookPlan.PayloadJson,
|
||||
llmRequest,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
private static string BuildThinkingSummary(string? agentLogLevel, int iteration, int maxIterations)
|
||||
{
|
||||
var isDebugLog = string.Equals(agentLogLevel, "debug", StringComparison.OrdinalIgnoreCase);
|
||||
return isDebugLog
|
||||
? $"Requesting the LLM... (iteration {iteration}/{maxIterations})"
|
||||
: "Requesting the LLM...";
|
||||
}
|
||||
|
||||
private static (string? Summary, int DelayMilliseconds) BuildFreeTierDelayPlan(
|
||||
bool freeTierMode,
|
||||
string? activeService,
|
||||
int iteration,
|
||||
int configuredDelaySeconds)
|
||||
{
|
||||
var shouldDelay =
|
||||
freeTierMode
|
||||
&& iteration > 1
|
||||
&& string.Equals((activeService ?? "").Trim(), "gemini", StringComparison.OrdinalIgnoreCase);
|
||||
if (!shouldDelay)
|
||||
return (null, 0);
|
||||
|
||||
var delaySeconds = configuredDelaySeconds > 0 ? configuredDelaySeconds : 4;
|
||||
return ($"Gemini free-tier wait: continue after {delaySeconds}s", delaySeconds * 1000);
|
||||
}
|
||||
|
||||
private static (bool ShouldRunHook, string? UpdatedFingerprint, string? PayloadJson) BuildUserPromptHookPlan(
|
||||
string? latestUserPrompt,
|
||||
string? lastUserPromptHookFingerprint,
|
||||
string runId,
|
||||
string? activeTab)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(latestUserPrompt))
|
||||
return (false, lastUserPromptHookFingerprint, null);
|
||||
|
||||
var fingerprint = $"{latestUserPrompt.Length}:{latestUserPrompt.GetHashCode()}";
|
||||
if (string.Equals(fingerprint, lastUserPromptHookFingerprint, StringComparison.Ordinal))
|
||||
return (false, lastUserPromptHookFingerprint, null);
|
||||
|
||||
var payload = JsonSerializer.Serialize(new
|
||||
{
|
||||
prompt = Truncate(latestUserPrompt, 4000),
|
||||
runId,
|
||||
tab = activeTab
|
||||
});
|
||||
return (true, fingerprint, payload);
|
||||
}
|
||||
|
||||
private static (string EventSummary, string ReturnMessage) BuildMissingToolsFailure(
|
||||
string? activeTab,
|
||||
bool hasRuntimeOverride,
|
||||
int runtimeAllowedToolCount)
|
||||
{
|
||||
if (hasRuntimeOverride && runtimeAllowedToolCount > 0)
|
||||
{
|
||||
return (
|
||||
"No tools remain available under the active skill runtime policy.",
|
||||
"⚠ No tools are currently allowed by the active skill policy, so the task cannot continue. Check the allowed-tools configuration.");
|
||||
}
|
||||
|
||||
var tabLabel = string.IsNullOrWhiteSpace(activeTab) ? "current" : activeTab;
|
||||
return (
|
||||
$"No tools are available in the {tabLabel} tab.",
|
||||
$"⚠ No tools are available in the {tabLabel} tab, so the task cannot continue. Check the tab-specific tool exposure policy.");
|
||||
}
|
||||
|
||||
private static string Truncate(string text, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text) || text.Length <= maxLength)
|
||||
return text;
|
||||
|
||||
return text[..maxLength];
|
||||
}
|
||||
}
|
||||
@@ -495,44 +495,6 @@ public partial class AgentLoopService
|
||||
AgentLoopDiagnosticsFormatter.BuildQueryViewSummary(queryView));
|
||||
}
|
||||
|
||||
var isDebugLog = string.Equals(_settings.Settings.Llm.AgentLogLevel, "debug", StringComparison.OrdinalIgnoreCase);
|
||||
EmitEvent(AgentEventType.Thinking, "", isDebugLog
|
||||
? $"LLM에 요청 중... (반복 {iteration}/{maxIterations})"
|
||||
: "LLM에 요청 중...");
|
||||
|
||||
// Gemini 무료 티어 모드: LLM 호출 간 딜레이 (RPM 한도 초과 방지)
|
||||
var activeService = (_settings.Settings.Llm.Service ?? "").Trim();
|
||||
var shouldApplyFreeTierDelay =
|
||||
llm.FreeTierMode
|
||||
&& iteration > 1
|
||||
&& string.Equals(activeService, "gemini", StringComparison.OrdinalIgnoreCase);
|
||||
if (shouldApplyFreeTierDelay)
|
||||
{
|
||||
var delaySec = llm.FreeTierDelaySeconds > 0 ? llm.FreeTierDelaySeconds : 4;
|
||||
EmitEvent(AgentEventType.Thinking, "", $"Gemini 무료 티어 대기: {delaySec}초 후 다음 호출을 진행합니다...");
|
||||
await Task.Delay(delaySec * 1000, ct);
|
||||
}
|
||||
|
||||
var latestUserPrompt = messages.LastOrDefault(m => string.Equals(m.Role, "user", StringComparison.OrdinalIgnoreCase))?.Content;
|
||||
if (!string.IsNullOrWhiteSpace(latestUserPrompt))
|
||||
{
|
||||
var fingerprint = $"{latestUserPrompt.Length}:{latestUserPrompt.GetHashCode()}";
|
||||
if (!string.Equals(fingerprint, lastUserPromptHookFingerprint, StringComparison.Ordinal))
|
||||
{
|
||||
lastUserPromptHookFingerprint = fingerprint;
|
||||
EmitEvent(AgentEventType.UserPromptSubmit, "", "사용자 프롬프트 제출");
|
||||
await RunRuntimeHooksAsync(
|
||||
"__user_prompt_submit__",
|
||||
"pre",
|
||||
JsonSerializer.Serialize(new
|
||||
{
|
||||
prompt = TruncateOutput(latestUserPrompt, 4000),
|
||||
runId = _currentRunId,
|
||||
tab = ActiveTab
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// P1: 반복당 1회 캐시 — GetRuntimeActiveTools는 동일 파라미터로 반복 내 여러 번 호출됨
|
||||
var cachedActiveTools = FilterExplorationToolsForCurrentIteration(
|
||||
GetRuntimeActiveTools(llm.DisabledTools, runtimeOverrides),
|
||||
@@ -541,31 +503,56 @@ public partial class AgentLoopService
|
||||
ActiveTab,
|
||||
totalToolCalls);
|
||||
|
||||
var preLlmStage = AgentLoopPreLlmStageService.Prepare(new AgentLoopPreLlmStageInput(
|
||||
queryMessages,
|
||||
cachedActiveTools,
|
||||
codeWorkingSet,
|
||||
ActiveTab,
|
||||
_settings.Settings.Llm.AgentLogLevel,
|
||||
iteration,
|
||||
maxIterations,
|
||||
llm.FreeTierMode,
|
||||
_settings.Settings.Llm.Service,
|
||||
llm.FreeTierDelaySeconds,
|
||||
messages.LastOrDefault(m => string.Equals(m.Role, "user", StringComparison.OrdinalIgnoreCase))?.Content,
|
||||
lastUserPromptHookFingerprint,
|
||||
_currentRunId,
|
||||
totalToolCalls,
|
||||
executionPolicy.ForceInitialToolCall,
|
||||
executionPolicy.InjectPreCallToolReminder,
|
||||
runState.NoToolCallLoopRetry,
|
||||
runtimeOverrides != null,
|
||||
runtimeOverrides?.AllowedToolNames.Count ?? 0));
|
||||
|
||||
EmitEvent(AgentEventType.Thinking, "", preLlmStage.ThinkingSummary);
|
||||
if (!string.IsNullOrWhiteSpace(preLlmStage.FreeTierDelaySummary) && preLlmStage.FreeTierDelayMilliseconds > 0)
|
||||
{
|
||||
EmitEvent(AgentEventType.Thinking, "", preLlmStage.FreeTierDelaySummary);
|
||||
await Task.Delay(preLlmStage.FreeTierDelayMilliseconds, ct);
|
||||
}
|
||||
|
||||
if (preLlmStage.ShouldRunUserPromptSubmitHook && !string.IsNullOrWhiteSpace(preLlmStage.UserPromptSubmitHookPayloadJson))
|
||||
{
|
||||
lastUserPromptHookFingerprint = preLlmStage.UpdatedUserPromptFingerprint;
|
||||
EmitEvent(AgentEventType.UserPromptSubmit, "", "사용자 프롬프트 제출");
|
||||
await RunRuntimeHooksAsync(
|
||||
"__user_prompt_submit__",
|
||||
"pre",
|
||||
preLlmStage.UserPromptSubmitHookPayloadJson);
|
||||
}
|
||||
|
||||
// LLM에 도구 정의와 함께 요청
|
||||
List<ContentBlock> blocks;
|
||||
var llmCallSw = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
var activeTools = cachedActiveTools;
|
||||
if (activeTools.Count == 0)
|
||||
if (!string.IsNullOrWhiteSpace(preLlmStage.MissingToolsReturnMessage))
|
||||
{
|
||||
if (runtimeOverrides != null && runtimeOverrides.AllowedToolNames.Count > 0)
|
||||
{
|
||||
EmitEvent(AgentEventType.Error, "", "현재 스킬 런타임 정책으로 사용 가능한 도구가 없습니다.");
|
||||
return "⚠ 현재 스킬 정책에서 허용된 도구가 없어 작업을 진행할 수 없습니다. allowed-tools 설정을 확인하세요.";
|
||||
}
|
||||
|
||||
var tabLabel = string.IsNullOrWhiteSpace(ActiveTab) ? "현재" : ActiveTab;
|
||||
EmitEvent(AgentEventType.Error, "", $"{tabLabel} 탭에서 사용 가능한 도구가 없습니다.");
|
||||
return $"⚠ 현재 {tabLabel} 탭에서 사용 가능한 도구가 없어 작업을 진행할 수 없습니다. 탭별 도구 노출 정책을 확인하세요.";
|
||||
EmitEvent(AgentEventType.Error, "", preLlmStage.MissingToolsEventSummary ?? "사용 가능한 도구가 없습니다.");
|
||||
return preLlmStage.MissingToolsReturnMessage;
|
||||
}
|
||||
var llmRequest = AgentLoopQueryAssemblyService.PrepareRequest(
|
||||
queryMessages,
|
||||
codeWorkingSet,
|
||||
totalToolCalls,
|
||||
executionPolicy.ForceInitialToolCall,
|
||||
executionPolicy.InjectPreCallToolReminder,
|
||||
runState.NoToolCallLoopRetry);
|
||||
var activeTools = cachedActiveTools;
|
||||
var llmRequest = preLlmStage.LlmRequest!;
|
||||
var forceFirst = llmRequest.ForceInitialToolCall;
|
||||
var sendMessages = llmRequest.SendMessages;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user