IBM vLLM 도구 호출 스트리밍과 모델 프로파일 기반 실행 정책 강화
Some checks failed
Release Gate / gate (push) Has been cancelled

- IBM 배포형 도구 호출 바디에 프로파일 기반 tool temperature를 적용하고 tool_call_strict 프로파일에서 더 직접적인 tool-only 지시를 추가함
- IBM 경로가 tool_choice를 거부할 때 tool_choice만 제거한 대체 강제 재시도 경로를 추가함
- OpenAI/vLLM tool-use 응답을 SSE로 수신하고 delta.tool_calls를 부분 조립해 도구 호출을 더 빨리 감지하도록 변경함
- read-only 도구 조기 실행과 결과 재사용 경로를 도입해 Cowork/Code 도구 착수 속도를 개선함
- README와 DEVELOPMENT 문서를 2026-04-08 11:14(KST) 기준으로 갱신함

검증
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\
- 경고 0 / 오류 0
This commit is contained in:
2026-04-08 16:48:11 +09:00
parent a2c952879d
commit 90ef3400f6
20 changed files with 1231 additions and 241 deletions

View File

@@ -143,10 +143,15 @@ public partial class AgentLoopService
using var gate = new SemaphoreSlim(maxConcurrency, maxConcurrency);
var tasks = executableCalls.Select(async call =>
{
await gate.WaitAsync(ct).ConfigureAwait(false);
var tool = _tools.Get(call.ToolName);
// gate.WaitAsync를 try 안에서 호출: ct 취소 시 WaitAsync가 OperationCanceledException을
// 던져도 Release()가 잘못 호출되지 않도록 보호 (SemaphoreFullException 방지)
var acquired = false;
try
{
await gate.WaitAsync(ct).ConfigureAwait(false);
acquired = true;
var tool = _tools.Get(call.ToolName);
if (tool == null)
return (call, ToolResult.Fail($"알 수 없는 도구: {call.ToolName}"), 0L);
@@ -154,19 +159,32 @@ public partial class AgentLoopService
try
{
var input = call.ToolInput ?? JsonDocument.Parse("{}").RootElement;
var result = await ExecuteToolWithTimeoutAsync(tool, call.ToolName, input, context, messages, ct);
// 병렬 실행 중에는 messages를 null로 전달:
// 훅이 messages.Add()를 동시 호출하면 List<T> race condition 발생.
// 읽기 전용 도구이므로 hook 추가 컨텍스트는 이 배치 후 순차로 처리됨.
var result = await ExecuteToolWithTimeoutAsync(tool, call.ToolName, input, context, null, ct);
sw.Stop();
return (call, result, sw.ElapsedMilliseconds);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
sw.Stop();
return (call, ToolResult.Fail($"도구 실행이 취소되었습니다: {call.ToolName}"), sw.ElapsedMilliseconds);
}
catch (Exception ex)
{
sw.Stop();
return (call, ToolResult.Fail($"도구 실행 오류: {ex.Message}"), sw.ElapsedMilliseconds);
}
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
// WaitAsync 도중 취소됨 — 세마포어 미취득 상태이므로 Release 하지 않음
return (call, ToolResult.Fail($"도구 실행 대기 중 취소됨: {call.ToolName}"), 0L);
}
finally
{
gate.Release();
if (acquired) gate.Release();
}
}).ToList();
@@ -241,6 +259,11 @@ public partial class AgentLoopService
TruncateOutput(result.Output, 500),
result.FilePath, result.Success);
}
// 워크플로우 상세 로그: 병렬 도구 실행 결과
WorkflowLogService.LogToolResult(_conversationId, _currentRunId, 0,
call.ToolName, TruncateOutput(result.Output, 2000),
result.Success, 0);
}
}
}