diff options
| author | Michael Peter Christen <mc@yacy.net> | 2026-03-29 00:20:49 +0100 |
|---|---|---|
| committer | Michael Peter Christen <mc@yacy.net> | 2026-03-29 00:20:49 +0100 |
| commit | 5d5e8e889a0f53ac437caa2cfd4729b9a18be2ea (patch) | |
| tree | 6787419b497a1c8ca5b9cf3c5840db01091892ae | |
| parent | c79b7a19c8d6e9679219bc5f64bd34daa2ae2574 (diff) | |
Restore tool-enabled chat requests from persisted model capabilities
| -rw-r--r-- | htroot/LLMSelection_p.html | 10 | ||||
| -rw-r--r-- | htroot/yacychat.html | 14 | ||||
| -rw-r--r-- | source/net/yacy/ai/LLM.java | 14 | ||||
| -rw-r--r-- | source/net/yacy/ai/ToolCallProtocol.java | 5 |
4 files changed, 35 insertions, 8 deletions
diff --git a/htroot/LLMSelection_p.html b/htroot/LLMSelection_p.html index de0c9deea..4c77e4d21 100644 --- a/htroot/LLMSelection_p.html +++ b/htroot/LLMSelection_p.html @@ -177,6 +177,10 @@ } } + function capabilityLabelToBoolean(label) { + return (label || "").trim().toLowerCase() === "yes"; + } + function isCapabilityUnsupportedError(error) { const status = error && typeof error.status === "number" ? error.status : null; return status === 400 || status === 404 || status === 415 || status === 422; @@ -931,9 +935,11 @@ } const rowData = {}; PRODUCTION_MODEL_COLUMN_NAMES.forEach((columnName, index) => { - if (index >= PRODUCTION_MODEL_USAGE_COLUMN_START && index <= PRODUCTION_MODEL_FEATURE_COLUMN_END) { + if (index >= PRODUCTION_MODEL_FEATURE_COLUMN_START && index <= PRODUCTION_MODEL_FEATURE_COLUMN_END) { + rowData[columnName] = capabilityLabelToBoolean(row.cells[index] ? row.cells[index].textContent : ""); + } else if (index >= PRODUCTION_MODEL_USAGE_COLUMN_START && index <= PRODUCTION_MODEL_USAGE_COLUMN_END) { const checkbox = row.cells[index] ? row.cells[index].querySelector('input[type="checkbox"]') : null; - rowData[columnName] = checkbox && (index >= PRODUCTION_MODEL_FEATURE_COLUMN_START || isSelectableUsageColumn(index)) ? checkbox.checked : false; + rowData[columnName] = checkbox && isSelectableUsageColumn(index) ? checkbox.checked : false; } else { rowData[columnName] = row.cells[index] ? row.cells[index].textContent.trim() : ""; } diff --git a/htroot/yacychat.html b/htroot/yacychat.html index 3ac314d04..d94fc2a8b 100644 --- a/htroot/yacychat.html +++ b/htroot/yacychat.html @@ -1716,14 +1716,12 @@ messages: sanitizedUserMessage ? [...sanitizedMessages, sanitizedUserMessage] : sanitizedMessages, stream: true }; - console.debug('[yacychat] payload search modes', payload.messages.map(m => ({ role: m.role, search: m.search }))); const headers = { 'Content-Type': 'application/json' }; const response = await fetch(state.config.apiHost.replace(/\/$/, '') + '/v1/chat/completions', { method: 'POST', headers, body: JSON.stringify(payload) }); - console.debug('[yacychat] request ms', Math.round(performance.now() - requestStart)); if (!response.ok) { if (response.status === 429) { throw new Error('Rate limit reached: please wait and try again. The server is protecting itself from overload.'); @@ -1761,7 +1759,6 @@ const searchName = parsed['search-filename']; const searchBase64 = parsed['search-text-base64']; if (!searchName || !searchBase64) return; - console.debug('[yacychat] received search attachment', { searchName, size: searchBase64.length, ms: Math.round(performance.now() - requestStart) }); const dataUrl = `data:text/markdown;base64,${searchBase64}`; const textContent = base64ToUtf8(searchBase64); for (let i = state.messages.length - 1; i >= 0; i--) { @@ -1801,8 +1798,16 @@ try { const parsed = JSON.parse(clean); applySearchAttachment(parsed); + if (Array.isArray(parsed['tool-calls']) && parsed['tool-calls'].length > 0) { + const injectedToolCalls = normalizeToolCalls(parsed['tool-calls']); + if (injectedToolCalls.length > 0) { + collectedToolCalls = injectedToolCalls; + roundToolCalls = []; + } + } if (Array.isArray(parsed['tool-results']) && parsed['tool-results'].length > 0) { - collectedToolResults = collectedToolResults.concat(normalizeToolResults(parsed['tool-results'])); + const normalizedResults = normalizeToolResults(parsed['tool-results']); + collectedToolResults = collectedToolResults.concat(normalizedResults); } const choice = parsed?.choices?.[0]; const delta = choice?.delta; @@ -1926,7 +1931,6 @@ : `Tool call: ${normalizedToolCalls.map(call => call?.function?.name || 'tool').join(', ')}`; } state.messages.push(assistantMessage); - console.debug('[yacychat] response chars', assistantText.length, 'total ms', Math.round(performance.now() - requestStart)); state.assistantSeen = true; persistConversation(); updateClearChatVisibility(); diff --git a/source/net/yacy/ai/LLM.java b/source/net/yacy/ai/LLM.java index 6314d5cbd..5a32b6592 100644 --- a/source/net/yacy/ai/LLM.java +++ b/source/net/yacy/ai/LLM.java @@ -98,6 +98,13 @@ public class LLM { public static LLMModel llmFromUsage(LLMUsage llmUsage) { Switchboard sb = Switchboard.getSwitchboard(); String pms = sb.getConfig("ai.production_models", "[]"); + String mcs = sb.getConfig("ai.model_capabilities", "{}"); + JSONObject model_capabilities = new JSONObject(true); + try { + model_capabilities = new JSONObject(new JSONTokener(mcs)); + } catch (JSONException e) { + model_capabilities = new JSONObject(true); + } try { JSONArray production_models = new JSONArray(new JSONTokener(pms)); // got through all the selected models to find which one has the wanted usage flag switched on @@ -110,7 +117,12 @@ public class LLM { final String api_key = row.optString("api_key", ""); final int max_tokens = Integer.parseInt(row.optString("max_tokens", "4096")); final String model = row.optString("model", ""); - final boolean tooling = row.optBoolean("tooling", false); + boolean tooling = row.optBoolean("tooling", false); + if (!tooling) { + final String capabilityKey = row.optString("service", "OLLAMA") + "|" + hoststub.replaceAll("/+$", "") + "|" + model; + final JSONObject capabilityEntry = model_capabilities.optJSONObject(capabilityKey); + tooling = capabilityEntry != null && "supported".equals(capabilityEntry.optString("tooling", "")); + } final LLMType type = LLMType.valueOf(row.optString("service", "OLLAMA")); LLM llm = new LLM(hoststub, api_key, max_tokens, type); LLMModel llmmodel = new LLMModel(llm, model, tooling); diff --git a/source/net/yacy/ai/ToolCallProtocol.java b/source/net/yacy/ai/ToolCallProtocol.java index 97ed6d950..b545c85b7 100644 --- a/source/net/yacy/ai/ToolCallProtocol.java +++ b/source/net/yacy/ai/ToolCallProtocol.java @@ -88,6 +88,11 @@ public final class ToolCallProtocol { try { final JSONObject prepared = body == null ? new JSONObject(true) : new JSONObject(body.toString()); if (forceStream) prepared.put("stream", true); + final String model = prepared.optString("model", ""); + if (model.toLowerCase().contains("qwen3.5")) { + prepared.put("reasoning_effort", "none"); + prepared.put("enable_thinking", false); + } if (toolingEnabled) net.yacy.ai.ToolProvider.ensureTools(prepared); return prepared; } catch (JSONException e) { |
