summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/net/yacy/ai/LLM.java75
-rw-r--r--source/net/yacy/ai/ToolCallProtocol.java11
-rw-r--r--source/net/yacy/htroot/LLMSelection_p.java18
3 files changed, 80 insertions, 24 deletions
diff --git a/source/net/yacy/ai/LLM.java b/source/net/yacy/ai/LLM.java
index 5a32b6592..7f74943d0 100644
--- a/source/net/yacy/ai/LLM.java
+++ b/source/net/yacy/ai/LLM.java
@@ -43,6 +43,7 @@ import net.yacy.search.Switchboard;
public class LLM {
+ private static final String MODEL_CAPABILITIES_CONFIG = "ai.model_capabilities";
private static String[] STOPTOKENS = new String[]{"[/INST]", "<|im_end|>", "<|end_of_turn|>", "<|eot_id|>", "<|end_header_id|>", "<EOS_TOKEN>", "</s>", "<|end|>"};
public static enum LLMType {
@@ -71,10 +72,12 @@ public class LLM {
public LLM llm;
public String model;
public boolean tooling;
- public LLMModel(LLM llm, String model, boolean tooling) {
+ public boolean thinking;
+ public LLMModel(LLM llm, String model, boolean tooling, boolean thinking) {
this.llm = llm;
this.model = model;
this.tooling = tooling;
+ this.thinking = thinking;
}
}
@@ -98,13 +101,7 @@ 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);
- }
+ JSONObject model_capabilities = readModelCapabilities();
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
@@ -117,15 +114,18 @@ 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 LLMType type = LLMType.valueOf(row.optString("service", "OLLAMA"));
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", ""));
+ boolean thinking = row.optBoolean("thinking", false);
+ if (!tooling || !thinking) {
+ final JSONObject capabilityEntry = model_capabilities.optJSONObject(capabilityKey(type, hoststub, model));
+ if (capabilityEntry != null) {
+ if (!tooling) tooling = "supported".equals(capabilityEntry.optString("tooling", ""));
+ if (!thinking) thinking = "supported".equals(capabilityEntry.optString("thinking", ""));
+ }
}
- 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);
+ LLMModel llmmodel = new LLMModel(llm, model, tooling, thinking);
return llmmodel;
}
}
@@ -140,6 +140,47 @@ public class LLM {
return this.hoststub;
}
+ public static String capabilityKey(final LLMType type, final String hoststub, final String model) {
+ final String normalizedType = type == null ? "" : type.name();
+ final String normalizedHoststub = hoststub == null ? "" : hoststub.replaceAll("/+$", "");
+ final String normalizedModel = model == null ? "" : model.trim();
+ return normalizedType + "|" + normalizedHoststub + "|" + normalizedModel;
+ }
+
+ private static JSONObject readModelCapabilities() {
+ final Switchboard sb = Switchboard.getSwitchboard();
+ if (sb == null) return new JSONObject(true);
+ final String capabilitiesJson = sb.getConfig(MODEL_CAPABILITIES_CONFIG, "{}");
+ try {
+ return new JSONObject(new JSONTokener(capabilitiesJson));
+ } catch (JSONException e) {
+ return new JSONObject(true);
+ }
+ }
+
+ public static boolean isCapabilitySupported(final LLMType type, final String hoststub, final String model, final String capabilityName) {
+ if (capabilityName == null || capabilityName.isEmpty()) return false;
+ final JSONObject modelCapabilities = readModelCapabilities();
+ final JSONObject capabilityEntry = modelCapabilities.optJSONObject(capabilityKey(type, hoststub, model));
+ return capabilityEntry != null && "supported".equalsIgnoreCase(capabilityEntry.optString(capabilityName, ""));
+ }
+
+ public static void applyNoThinkingParameters(final JSONObject data) {
+ if (data == null) return;
+ try {
+ data.put("reasoning_effort", "none");
+ data.put("enable_thinking", false);
+ } catch (JSONException e) {
+ }
+ }
+
+ public void applyNoThinkingParametersIfNeeded(final String model, final JSONObject data) {
+ final String normalizedModel = model == null ? "" : model.toLowerCase();
+ if (normalizedModel.contains("qwen3.5") || isCapabilitySupported(this.type, this.hoststub, model, "thinking")) {
+ applyNoThinkingParameters(data);
+ }
+ }
+
// API Helper Methods
@@ -287,11 +328,7 @@ public class LLM {
data.put("messages", context);
data.put("stop", new JSONArray(STOPTOKENS));
data.put("stream", false);
-
- if (model.toLowerCase().contains("qwen3.5")) { // we don't think
- data.put("reasoning_effort", "none");
- data.put("enable_thinking", false);
- }
+ applyNoThinkingParametersIfNeeded(model, data);
if (schema != null) {
System.out.println(schema.toString());
diff --git a/source/net/yacy/ai/ToolCallProtocol.java b/source/net/yacy/ai/ToolCallProtocol.java
index b545c85b7..2eab71687 100644
--- a/source/net/yacy/ai/ToolCallProtocol.java
+++ b/source/net/yacy/ai/ToolCallProtocol.java
@@ -88,11 +88,6 @@ 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) {
@@ -123,6 +118,9 @@ public final class ToolCallProtocol {
*/
public static int proxyToolLifecycle(ServletOutputStream out, LLM.LLMModel llm4Chat, JSONObject originalBody, JSONArray messages, JSONObject initialMetadata) throws IOException {
final JSONObject preparedBody = prepareToolRequestBody(originalBody, false, llm4Chat != null && llm4Chat.tooling);
+ if (llm4Chat != null && llm4Chat.thinking) {
+ LLM.applyNoThinkingParameters(preparedBody);
+ }
final HttpURLConnection conn = openChatCompletionConnection(llm4Chat, preparedBody);
final int status = conn.getResponseCode();
//final String message = conn.getResponseMessage();
@@ -284,6 +282,9 @@ public final class ToolCallProtocol {
// Build follow-up completion request from original body template.
final JSONObject followup = prepareToolRequestBody(originalBody, true, llm4Chat != null && llm4Chat.tooling);
+ if (llm4Chat != null && llm4Chat.thinking) {
+ LLM.applyNoThinkingParameters(followup);
+ }
followup.put("messages", newMessages);
final HttpURLConnection followConn = openChatCompletionConnection(llm4Chat, followup);
if (followConn.getResponseCode() != 200) {
diff --git a/source/net/yacy/htroot/LLMSelection_p.java b/source/net/yacy/htroot/LLMSelection_p.java
index 4a5955b32..03229a9ce 100644
--- a/source/net/yacy/htroot/LLMSelection_p.java
+++ b/source/net/yacy/htroot/LLMSelection_p.java
@@ -52,11 +52,15 @@ public class LLMSelection_p {
final JSONObject entry = source.optJSONObject(key);
final JSONObject normalizedEntry = new JSONObject(true);
if (entry != null) {
+ normalizedEntry.put("thinking", normalizeCapabilityStatus(entry.opt("thinking")));
normalizedEntry.put("tooling", normalizeCapabilityStatus(entry.opt("tooling")));
normalizedEntry.put("vision", normalizeCapabilityStatus(entry.opt("vision")));
+ normalizedEntry.put("format", normalizeCapabilityStatus(entry.opt("format")));
} else {
+ normalizedEntry.put("thinking", "unknown");
normalizedEntry.put("tooling", "unknown");
normalizedEntry.put("vision", "unknown");
+ normalizedEntry.put("format", "unknown");
}
normalized.put(key, normalizedEntry);
}
@@ -88,8 +92,10 @@ public class LLMSelection_p {
normalized.put("qapairs", false);
normalized.put("tldr", row.optBoolean("tldr", false));
+ normalized.put("thinking", row.optBoolean("thinking", false));
normalized.put("tooling", row.optBoolean("tooling", false));
normalized.put("vision", row.optBoolean("vision", false));
+ normalized.put("format", row.optBoolean("format", false));
return normalized;
}
@@ -183,14 +189,22 @@ public class LLMSelection_p {
final String key = capabilityKey(row);
JSONObject capabilityEntry = key.isEmpty() ? null : capabilities.optJSONObject(key);
+ String thinkingStatus = capabilityEntry == null ? "unknown" : normalizeCapabilityStatus(capabilityEntry.opt("thinking"));
String toolingStatus = capabilityEntry == null ? "unknown" : normalizeCapabilityStatus(capabilityEntry.opt("tooling"));
String visionStatus = capabilityEntry == null ? "unknown" : normalizeCapabilityStatus(capabilityEntry.opt("vision"));
+ String formatStatus = capabilityEntry == null ? "unknown" : normalizeCapabilityStatus(capabilityEntry.opt("format"));
+ if (row.optBoolean("thinking", false)) thinkingStatus = "supported";
if (row.optBoolean("tooling", false)) toolingStatus = "supported";
if (row.optBoolean("vision", false)) visionStatus = "supported";
+ if (row.optBoolean("format", false)) formatStatus = "supported";
+ prop.put("productionmodels_" + i + "_thinking",
+ "supported".equals(thinkingStatus) ? "yes" : "unsupported".equals(thinkingStatus) ? "no" : "?");
prop.put("productionmodels_" + i + "_tooling",
"supported".equals(toolingStatus) ? "yes" : "unsupported".equals(toolingStatus) ? "no" : "?");
prop.put("productionmodels_" + i + "_vision",
"supported".equals(visionStatus) ? "yes" : "unsupported".equals(visionStatus) ? "no" : "?");
+ prop.put("productionmodels_" + i + "_format",
+ "supported".equals(formatStatus) ? "yes" : "unsupported".equals(formatStatus) ? "no" : "?");
}
prop.put("productionmodels", production_models.length());
} catch (JSONException e) {
@@ -206,12 +220,16 @@ public class LLMSelection_p {
JSONObject entry = capabilities.optJSONObject(key);
if (entry == null) {
entry = new JSONObject(true);
+ entry.put("thinking", "unknown");
entry.put("tooling", "unknown");
entry.put("vision", "unknown");
+ entry.put("format", "unknown");
capabilities.put(key, entry);
}
+ if (row.optBoolean("thinking", false)) entry.put("thinking", "supported");
if (row.optBoolean("tooling", false)) entry.put("tooling", "supported");
if (row.optBoolean("vision", false)) entry.put("vision", "supported");
+ if (row.optBoolean("format", false)) entry.put("format", "supported");
}
}
prop.putHTML("model_capabilities", capabilities.toString());