summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Peter Christen <mc@yacy.net>2026-03-29 00:20:49 +0100
committerMichael Peter Christen <mc@yacy.net>2026-03-29 00:20:49 +0100
commit5d5e8e889a0f53ac437caa2cfd4729b9a18be2ea (patch)
tree6787419b497a1c8ca5b9cf3c5840db01091892ae
parentc79b7a19c8d6e9679219bc5f64bd34daa2ae2574 (diff)
Restore tool-enabled chat requests from persisted model capabilities
-rw-r--r--htroot/LLMSelection_p.html10
-rw-r--r--htroot/yacychat.html14
-rw-r--r--source/net/yacy/ai/LLM.java14
-rw-r--r--source/net/yacy/ai/ToolCallProtocol.java5
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) {