vLLM 모델 해석 및 max_tokens 상한 보정
Some checks failed
Release Gate / gate (push) Has been cancelled

vLLM 연결 시 등록 모델 alias와 실제 모델 ID가 섞여 payload로 전달되던 경로를 보정해 RegisteredModel에서 실제 모델명을 우선 찾아 요청에 사용하도록 수정했다.

OpenAI-compatible 일반 대화와 도구 호출 모두 vLLM 서버 허용 범위를 넘지 않도록 max_tokens를 자동 보정하도록 통일했다.

검증: 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-05 21:40:43 +09:00
parent 5765888229
commit 53afdb3472
4 changed files with 32 additions and 4 deletions

View File

@@ -1078,3 +1078,6 @@ MIT License
- AX Agent 채팅창의 기본 시작 높이를 소폭 늘려, 처음 열었을 때 상하 여백과 프리셋 영역이 더 여유 있게 보이도록 조정했다. - AX Agent 채팅창의 기본 시작 높이를 소폭 늘려, 처음 열었을 때 상하 여백과 프리셋 영역이 더 여유 있게 보이도록 조정했다.
- 업데이트: 2026-04-06 00:31 (KST) - 업데이트: 2026-04-06 00:31 (KST)
- AX Agent 상단 중앙 탭 그룹의 버튼 padding, 최소 폭/높이와 바깥 pill 래퍼 높이를 한 단계 더 줄였다. 이제 탭 바깥 테두리 안쪽 여백이 더 살아 있어, 레퍼런스처럼 답답하지 않은 세그먼트 탭 비율로 보인다. - AX Agent 상단 중앙 탭 그룹의 버튼 padding, 최소 폭/높이와 바깥 pill 래퍼 높이를 한 단계 더 줄였다. 이제 탭 바깥 테두리 안쪽 여백이 더 살아 있어, 레퍼런스처럼 답답하지 않은 세그먼트 탭 비율로 보인다.
- 업데이트: 2026-04-06 00:38 (KST)
- vLLM 연결 시 등록 모델 alias/실제 모델 ID가 섞여 전달되던 경로를 보정했다. 내부 서비스(Ollama/vLLM)는 현재 선택값이 alias여도 등록 모델의 실제 모델명을 다시 찾아 요청 payload에 넣도록 정리했다.
- vLLM OpenAI-compatible 요청의 `max_tokens`는 서버 허용 범위를 넘지 않도록 자동 보정했다. 일반 대화와 도구 호출 모두 같은 상한 계산을 써 `invalid max_tokens` 오류가 덜 나도록 맞췄다.

View File

@@ -4835,3 +4835,6 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
- 업데이트: 2026-04-06 00:31 (KST) - 업데이트: 2026-04-06 00:31 (KST)
- [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 상단 `채팅 / Cowork / 코드` 탭 그룹에서 각 버튼의 margin, padding, 최소 폭/높이와 바깥 래퍼의 padding, 최소 높이를 한 단계 더 줄였다. - [ChatWindow.xaml](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.xaml) 의 상단 `채팅 / Cowork / 코드` 탭 그룹에서 각 버튼의 margin, padding, 최소 폭/높이와 바깥 래퍼의 padding, 최소 높이를 한 단계 더 줄였다.
- 결과적으로 탭 그룹이 바깥 테두리를 거의 꽉 채우지 않고, pill 바깥선 안쪽에 숨 쉴 여백이 남는 레퍼런스형 비율로 정리됐다. - 결과적으로 탭 그룹이 바깥 테두리를 거의 꽉 채우지 않고, pill 바깥선 안쪽에 숨 쉴 여백이 남는 레퍼런스형 비율로 정리됐다.
- 업데이트: 2026-04-06 00:38 (KST)
- [LlmService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/LlmService.cs) 에서 내부 서비스(Ollama/vLLM) 모델 해석 경로를 보강했다. 현재 선택값이 alias 또는 등록 모델 키여도 `RegisteredModel`에서 실제 모델명을 다시 찾아 payload의 `model` 값으로 보내도록 정리했다.
- 같은 파일에 vLLM용 `max_tokens` 상한 보정 helper를 추가하고, [LlmService.ToolUse.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/LlmService.ToolUse.cs) 의 일반 도구 호출 / OpenAI-compatible tool body 생성에도 같은 값을 쓰게 맞췄다. 이로써 `Model Not Exist`, `invalid max_tokens` 계열 오류를 줄이는 방향으로 정리했다.

View File

@@ -225,7 +225,7 @@ public partial class LlmService
return new return new
{ {
model = activeModel, model = activeModel,
max_tokens = Math.Max(llm.MaxContextTokens, 4096), max_tokens = ResolveOpenAiCompatibleMaxTokens(),
temperature = llm.Temperature, temperature = llm.Temperature,
system = systemPrompt, system = systemPrompt,
messages = msgs, messages = msgs,
@@ -237,7 +237,7 @@ public partial class LlmService
return new return new
{ {
model = activeModel, model = activeModel,
max_tokens = Math.Max(llm.MaxContextTokens, 4096), max_tokens = ResolveOpenAiCompatibleMaxTokens(),
temperature = llm.Temperature, temperature = llm.Temperature,
messages = msgs, messages = msgs,
tools = toolDefs, tools = toolDefs,
@@ -661,7 +661,7 @@ public partial class LlmService
["tools"] = toolDefs, ["tools"] = toolDefs,
["stream"] = false, ["stream"] = false,
["temperature"] = ResolveTemperature(), ["temperature"] = ResolveTemperature(),
["max_tokens"] = llm.MaxContextTokens, ["max_tokens"] = ResolveOpenAiCompatibleMaxTokens(),
}; };
var effort = ResolveReasoningEffort(); var effort = ResolveReasoningEffort();
if (!string.IsNullOrWhiteSpace(effort)) if (!string.IsNullOrWhiteSpace(effort))

View File

@@ -249,10 +249,32 @@ public partial class LlmService : IDisposable
var llm = _settings.Settings.Llm; var llm = _settings.Settings.Llm;
var service = NormalizeServiceName(llm.Service); var service = NormalizeServiceName(llm.Service);
if (service is "ollama" or "vllm" && !string.IsNullOrEmpty(llm.Model)) if (service is "ollama" or "vllm" && !string.IsNullOrEmpty(llm.Model))
{
var registered = FindRegisteredModel(llm, service, llm.Model);
if (registered != null)
{
var registeredModelName = CryptoService.DecryptIfEnabled(registered.EncryptedModelName, llm.EncryptionEnabled);
if (!string.IsNullOrWhiteSpace(registeredModelName))
return registeredModelName;
}
return CryptoService.DecryptIfEnabled(llm.Model, llm.EncryptionEnabled); return CryptoService.DecryptIfEnabled(llm.Model, llm.EncryptionEnabled);
}
return llm.Model; return llm.Model;
} }
private int ResolveOpenAiCompatibleMaxTokens()
{
var llm = _settings.Settings.Llm;
var requested = Math.Clamp(llm.MaxContextTokens, 1, 1_000_000);
var service = NormalizeServiceName(llm.Service);
if (service == "vllm")
return Math.Min(requested, 8192);
return requested;
}
/// <summary> /// <summary>
/// 현재 활성 모델에 매칭되는 RegisteredModel을 찾아 엔드포인트/API키를 반환합니다. /// 현재 활성 모델에 매칭되는 RegisteredModel을 찾아 엔드포인트/API키를 반환합니다.
/// RegisteredModel에 전용 서버 정보가 있으면 그것을 사용하고, 없으면 기본 설정을 사용합니다. /// RegisteredModel에 전용 서버 정보가 있으면 그것을 사용하고, 없으면 기본 설정을 사용합니다.
@@ -648,7 +670,7 @@ public partial class LlmService : IDisposable
["messages"] = msgs, ["messages"] = msgs,
["stream"] = stream, ["stream"] = stream,
["temperature"] = ResolveTemperature(), ["temperature"] = ResolveTemperature(),
["max_tokens"] = llm.MaxContextTokens ["max_tokens"] = ResolveOpenAiCompatibleMaxTokens()
}; };
var effort = ResolveReasoningEffort(); var effort = ResolveReasoningEffort();
if (!string.IsNullOrWhiteSpace(effort)) if (!string.IsNullOrWhiteSpace(effort))