AX Agent 코워크·코드 후속 호환 과제 정리 및 IBM Qwen 도구호출 경로 보강
이번 커밋은 claude-code 기준 후속 과제를 이어서 반영해 Cowork/Code의 도구 선택 강도와 IBM 배포형 vLLM(Qwen) 호환 경로를 정리했다. 핵심 변경 사항: - IBM 전용 tool body에서 과거 assistant tool_calls 및 role=tool 이력을 OpenAI 형식으로 재전송하지 않고 평탄한 transcript로 직렬화하도록 변경 - Cowork 프롬프트에서 document_review 및 format_convert를 기본 단계처럼 강제하지 않고 file_read/document_read 중심의 가벼운 검증 흐름으로 완화 - unknown/disallowed tool recovery에서 tool_search를 항상 강제하지 않고 alias 후보나 활성 도구 예시로 바로 선택 가능하면 직접 사용하도록 조정 - Code 탐색에서 정의/참조/구현/호출관계 의도는 lsp_code_intel을 더 우선하도록 보강하고 LSP 결과 요약 품질 개선 - 문서 검증 근거는 file_read/document_read면 충분하도록 단순화 문서 반영: - README.md, docs/DEVELOPMENT.md에 2026-04-09 22:48 (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:
@@ -870,59 +870,28 @@ public partial class LlmService
|
||||
{
|
||||
if (m.Role == "system") continue;
|
||||
|
||||
// tool_result → OpenAI role:"tool" 형식 (watsonx /text/chat 지원)
|
||||
// IBM/Qwen 배포형은 과거 tool_calls/tool 메시지 이력을 엄격하게 검사하는 경우가 있어
|
||||
// 이전 tool-use 대화는 평탄한 transcript로 재구성한다.
|
||||
if (m.Role == "user" && m.Content.StartsWith("{\"type\":\"tool_result\""))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(m.Content);
|
||||
var root = doc.RootElement;
|
||||
msgs.Add(new
|
||||
{
|
||||
role = "tool",
|
||||
tool_call_id = root.GetProperty("tool_use_id").SafeGetString(),
|
||||
content = root.GetProperty("content").SafeGetString(),
|
||||
});
|
||||
msgs.Add(new { role = "user", content = BuildIbmToolResultTranscript(root) });
|
||||
continue;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// _tool_use_blocks → OpenAI tool_calls 형식
|
||||
// _tool_use_blocks도 assistant + tool_calls로 다시 보내지 않고 plain assistant transcript로 평탄화
|
||||
if (m.Role == "assistant" && m.Content.StartsWith("{\"_tool_use_blocks\""))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(m.Content);
|
||||
var blocksArr = doc.RootElement.GetProperty("_tool_use_blocks");
|
||||
var textContent = "";
|
||||
var toolCallsList = new List<object>();
|
||||
foreach (var b in blocksArr.EnumerateArray())
|
||||
{
|
||||
var bType = b.GetProperty("type").SafeGetString();
|
||||
if (bType == "text")
|
||||
textContent = b.GetProperty("text").SafeGetString() ?? "";
|
||||
else if (bType == "tool_use")
|
||||
{
|
||||
var argsJson = b.SafeTryGetProperty("input", out var inp) ? inp.GetRawText() : "{}";
|
||||
toolCallsList.Add(new
|
||||
{
|
||||
id = b.GetProperty("id").SafeGetString() ?? "",
|
||||
type = "function",
|
||||
function = new
|
||||
{
|
||||
name = b.GetProperty("name").SafeGetString() ?? "",
|
||||
arguments = argsJson,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
msgs.Add(new
|
||||
{
|
||||
role = "assistant",
|
||||
content = string.IsNullOrEmpty(textContent) ? (string?)null : textContent,
|
||||
tool_calls = toolCallsList,
|
||||
});
|
||||
msgs.Add(new { role = "assistant", content = BuildIbmAssistantTranscript(blocksArr) });
|
||||
continue;
|
||||
}
|
||||
catch { }
|
||||
@@ -999,6 +968,60 @@ public partial class LlmService
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildIbmAssistantTranscript(JsonElement blocksArr)
|
||||
{
|
||||
var textSegments = new List<string>();
|
||||
var toolSegments = new List<string>();
|
||||
|
||||
foreach (var block in blocksArr.EnumerateArray())
|
||||
{
|
||||
var blockType = block.GetProperty("type").SafeGetString();
|
||||
if (string.Equals(blockType, "text", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var text = block.SafeTryGetProperty("text", out var textEl) ? textEl.SafeGetString() ?? "" : "";
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
textSegments.Add(text.Trim());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.Equals(blockType, "tool_use", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var name = block.SafeTryGetProperty("name", out var nameEl) ? nameEl.SafeGetString() ?? "" : "";
|
||||
var args = block.SafeTryGetProperty("input", out var inputEl) ? inputEl.GetRawText() : "{}";
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
continue;
|
||||
|
||||
toolSegments.Add($"<tool_call>\n{{\"name\":\"{name}\",\"arguments\":{args}}}\n</tool_call>");
|
||||
}
|
||||
|
||||
var parts = new List<string>();
|
||||
if (textSegments.Count > 0)
|
||||
parts.Add(string.Join("\n\n", textSegments));
|
||||
if (toolSegments.Count > 0)
|
||||
parts.Add(string.Join("\n", toolSegments));
|
||||
|
||||
return parts.Count == 0
|
||||
? "<tool_call>\n{\"name\":\"unknown_tool\",\"arguments\":{}}\n</tool_call>"
|
||||
: string.Join("\n\n", parts);
|
||||
}
|
||||
|
||||
private static string BuildIbmToolResultTranscript(JsonElement root)
|
||||
{
|
||||
var toolCallId = root.SafeTryGetProperty("tool_use_id", out var idEl) ? idEl.SafeGetString() ?? "" : "";
|
||||
var toolName = root.SafeTryGetProperty("tool_name", out var nameEl) ? nameEl.SafeGetString() ?? "" : "";
|
||||
var content = root.SafeTryGetProperty("content", out var contentEl) ? contentEl.SafeGetString() ?? "" : "";
|
||||
var header = string.IsNullOrWhiteSpace(toolName)
|
||||
? "[Tool Result]"
|
||||
: $"[Tool Result: {toolName}]";
|
||||
if (!string.IsNullOrWhiteSpace(toolCallId))
|
||||
header += $" (id={toolCallId})";
|
||||
|
||||
return string.IsNullOrWhiteSpace(content)
|
||||
? $"{header}\n(no output)"
|
||||
: $"{header}\n{content}";
|
||||
}
|
||||
|
||||
private sealed class ToolCallAccumulator
|
||||
{
|
||||
public int Index { get; init; }
|
||||
|
||||
Reference in New Issue
Block a user