summaryrefslogtreecommitdiff
path: root/htroot
diff options
context:
space:
mode:
authorMichael Peter Christen <mc@yacy.net>2026-03-29 17:33:49 +0200
committerMichael Peter Christen <mc@yacy.net>2026-03-29 17:33:49 +0200
commit7b303b6f82b1eee57c05e984ce7f4f42fc992405 (patch)
tree73ce9bf6bcb1b6527a4590d987e3c247b2b214a2 /htroot
parentf0464e7fbcfcb69127f0325910f92f113ce23677 (diff)
Add thinking/tooling/vision/format model capability tests and suppress thinking across LLM calls
Diffstat (limited to 'htroot')
-rw-r--r--htroot/LLMSelection_p.html500
1 files changed, 472 insertions, 28 deletions
diff --git a/htroot/LLMSelection_p.html b/htroot/LLMSelection_p.html
index 4c77e4d21..6e3e54f22 100644
--- a/htroot/LLMSelection_p.html
+++ b/htroot/LLMSelection_p.html
@@ -12,31 +12,58 @@
let availableModels = [];
const downloadActivities = new Map();
+ const testActivities = new Map();
let activeDownloadCount = 0;
const beforeUnloadHandler = event => {
+ const hasActiveTests = hasRunningCapabilityTests();
+ const hasActiveDownloads = activeDownloadCount > 0;
+ if (!hasActiveTests && !hasActiveDownloads) {
+ return;
+ }
event.preventDefault();
- event.returnValue = "Model downloads are still running. Please wait until they finish.";
+ event.returnValue = hasActiveTests
+ ? "Production model tests are still running. Please wait until they finish."
+ : "Model downloads are still running. Please wait until they finish.";
};
// Localization-friendly test strings and hints (translate/tune as needed)
const TEST_STRINGS = {
toolingEndpointPath: "/v1/chat/completions", // Endpoint used to probe tooling capability on OpenAI-compatible APIs
+ thinkingUserMessage: "Hello", // User prompt for thinking capability test
toolingSystemMessage: "You are a home assistant.", // System prompt for tooling capability test
toolingUserMessage: "Switch on the light", // User prompt for tooling capability test
visionSystemMessage: "you read out images", // System prompt for vision capability test
visionUserMessage: "what is in the image?", // User prompt for vision capability test
visionExpectedText: "42", // Expected mention in LLM response when reading the test image
- visionTestImagePath: "env/grafics/llmtest.png" // Image used for the vision capability test
+ visionTestImagePath: "env/grafics/llmtest.png", // Image used for the vision capability test
+ formatSystemMessage: "You are a mood classifier. Identify the mood of the request."
+ };
+
+ const FORMAT_TEST_CASES = [
+ { text: "I hate programming", expectedMood: "angry" },
+ { text: "I love programming", expectedMood: "happy" },
+ { text: "Wait, that worked perfectly?", expectedMood: "surprised" }
+ ];
+
+ const FORMAT_TEST_SCHEMA = {
+ title: "Classifier",
+ type: "object",
+ properties: {
+ mood: { type: "literal", enum: ["surprised", "angry", "happy"] }
+ },
+ required: ["mood"]
};
- const PRODUCTION_MODEL_TOTAL_COLUMNS = 15;
+ const PRODUCTION_MODEL_TOTAL_COLUMNS = 17;
const PRODUCTION_MODEL_MODEL_COLUMN_INDEX = 1;
const PRODUCTION_MODEL_USAGE_COLUMN_START = 5;
const PRODUCTION_MODEL_USAGE_COLUMN_END = 11; // including
const PRODUCTION_MODEL_FEATURE_COLUMN_START = 12;
- const PRODUCTION_MODEL_FEATURE_COLUMN_END = 13; // including
+ const PRODUCTION_MODEL_FEATURE_COLUMN_END = 15; // including
const PRODUCTION_MODEL_ACTION_COLUMN_INDEX = PRODUCTION_MODEL_TOTAL_COLUMNS - 1;
- const PRODUCTION_MODEL_TOOLING_COLUMN_INDEX = PRODUCTION_MODEL_FEATURE_COLUMN_START;
- const PRODUCTION_MODEL_VISION_COLUMN_INDEX = PRODUCTION_MODEL_FEATURE_COLUMN_START + 1;
+ const PRODUCTION_MODEL_THINKING_COLUMN_INDEX = PRODUCTION_MODEL_FEATURE_COLUMN_START;
+ const PRODUCTION_MODEL_TOOLING_COLUMN_INDEX = PRODUCTION_MODEL_FEATURE_COLUMN_START + 1;
+ const PRODUCTION_MODEL_VISION_COLUMN_INDEX = PRODUCTION_MODEL_FEATURE_COLUMN_START + 2;
+ const PRODUCTION_MODEL_FORMAT_COLUMN_INDEX = PRODUCTION_MODEL_FEATURE_COLUMN_START + 3;
const PRODUCTION_MODEL_ENABLED_USAGE_COLUMNS = new Set([
6, // chat
11 // tldr
@@ -56,8 +83,10 @@
"qapairs",
"tldr",
+ "thinking",
"tooling",
- "vision"
+ "vision",
+ "format"
];
const PRODUCTION_MODEL_SUBMIT_URL = "LLMSelection_p.html";
const TOOLING_EXPECTED_FUNCTION_NAME = "lightswitch";
@@ -86,7 +115,7 @@
];
const MODEL_TABLE_HEADERS = ["Model", "Ranking", "Size", "Description", "Provider", "License", "Actions"];
- const AVAILABLE_MODEL_TABLE_HEADERS = ["Model", "Ranking", "Size", "Description", "Provider", "License", "Tooling", "Vision", "Actions"];
+ const AVAILABLE_MODEL_TABLE_HEADERS = ["Model", "Ranking", "Size", "Description", "Provider", "License", "Thinking", "Tooling", "Vision", "Format", "Actions"];
const RECOMMENDED_MODEL_MAP = new Map(
RECOMMENDED_MODELS.map(([name, ranking, size, description, provider, license]) => [
name,
@@ -143,8 +172,10 @@
if (!persistedModelCapabilities[key] || typeof persistedModelCapabilities[key] !== "object") {
persistedModelCapabilities[key] = {};
}
+ persistedModelCapabilities[key].thinking = normalizeCapabilityStatus(persistedModelCapabilities[key].thinking);
persistedModelCapabilities[key].tooling = normalizeCapabilityStatus(persistedModelCapabilities[key].tooling);
persistedModelCapabilities[key].vision = normalizeCapabilityStatus(persistedModelCapabilities[key].vision);
+ persistedModelCapabilities[key].format = normalizeCapabilityStatus(persistedModelCapabilities[key].format);
return persistedModelCapabilities[key];
}
@@ -152,11 +183,13 @@
const key = modelCapabilityKey(service, hoststub, modelName);
const entry = key ? persistedModelCapabilities[key] : null;
if (!entry || typeof entry !== "object") {
- return { tooling: "unknown", vision: "unknown" };
+ return { thinking: "unknown", tooling: "unknown", vision: "unknown", format: "unknown" };
}
return {
+ thinking: normalizeCapabilityStatus(entry.thinking),
tooling: normalizeCapabilityStatus(entry.tooling),
- vision: normalizeCapabilityStatus(entry.vision)
+ vision: normalizeCapabilityStatus(entry.vision),
+ format: normalizeCapabilityStatus(entry.format)
};
}
@@ -271,13 +304,19 @@
***/
function updateBeforeUnloadGuard() {
- if (activeDownloadCount > 0) {
+ if (activeDownloadCount > 0 || hasRunningCapabilityTests()) {
window.addEventListener("beforeunload", beforeUnloadHandler);
} else {
window.removeEventListener("beforeunload", beforeUnloadHandler);
}
}
+ function hasRunningCapabilityTests() {
+ return !!document.querySelector(
+ 'tr[data-thinking-test-in-flight="true"], tr[data-tooling-test-in-flight="true"], tr[data-vision-test-in-flight="true"], tr[data-format-test-in-flight="true"]'
+ );
+ }
+
function getDownloadActivityElements() {
return {
container: document.getElementById("downloadActivityContainer"),
@@ -285,6 +324,96 @@
};
}
+ function getTestActivityElements() {
+ return {
+ container: document.getElementById("testActivityContainer"),
+ list: document.getElementById("testActivityList")
+ };
+ }
+
+ function addTestActivity(modelName, testName) {
+ const { container, list } = getTestActivityElements();
+ if (!container || !list || !modelName || !testName) return null;
+
+ const activityId = `test_${modelName}`;
+ const existing = testActivities.get(activityId);
+ if (existing) {
+ existing.activeTests.add(testName);
+ updateTestActivitySubtitle(existing);
+ return activityId;
+ }
+
+ const wrapper = document.createElement("div");
+ wrapper.className = "test-activity";
+ wrapper.dataset.activityId = activityId;
+ wrapper.style.marginBottom = "8px";
+ wrapper.style.padding = "8px";
+ wrapper.style.border = "1px solid #ddd";
+ wrapper.style.borderRadius = "4px";
+ wrapper.style.backgroundColor = "#f8f8f8";
+
+ const title = document.createElement("div");
+ title.className = "test-activity-title";
+ title.textContent = `Testing ${modelName}`;
+ title.style.fontWeight = "bold";
+ title.style.marginBottom = "4px";
+ wrapper.appendChild(title);
+
+ const progress = document.createElement("progress");
+ progress.max = 100;
+ progress.style.width = "100%";
+ progress.removeAttribute("value");
+ progress.setAttribute("aria-busy", "true");
+ wrapper.appendChild(progress);
+
+ const subtitle = document.createElement("div");
+ subtitle.className = "test-activity-subtitle";
+ subtitle.style.fontSize = "0.9em";
+ subtitle.style.marginTop = "4px";
+ wrapper.appendChild(subtitle);
+
+ list.appendChild(wrapper);
+ container.style.display = "block";
+ const activity = {
+ wrapper,
+ subtitle,
+ activeTests: new Set([testName])
+ };
+ updateTestActivitySubtitle(activity);
+ testActivities.set(activityId, activity);
+ return activityId;
+ }
+
+ function updateTestActivitySubtitle(activity) {
+ if (!activity || !activity.subtitle) return;
+ const names = Array.from(activity.activeTests);
+ activity.subtitle.textContent = names.length <= 1
+ ? `${names[0]} test in progress...`
+ : `${names.join(", ")} tests in progress...`;
+ }
+
+ function removeTestActivity(activityId, testName) {
+ const activity = testActivities.get(activityId);
+ if (!activity) return;
+ if (testName) {
+ activity.activeTests.delete(testName);
+ }
+ if (activity.activeTests.size > 0) {
+ updateTestActivitySubtitle(activity);
+ return;
+ }
+ if (activity.wrapper && activity.wrapper.parentNode) {
+ activity.wrapper.parentNode.removeChild(activity.wrapper);
+ }
+ if (testActivities.has(activityId)) {
+ testActivities.delete(activityId);
+ }
+ const { container, list } = getTestActivityElements();
+ if (container && list && !list.hasChildNodes()) {
+ container.style.display = "none";
+ }
+ }
+
function addDownloadActivity(modelName) {
const { container, list } = getDownloadActivityElements();
if (!container || !list) return null;
@@ -464,8 +593,10 @@
description: info.description || "",
provider: info.provider || "",
license: info.license || "",
+ thinking: capabilityStatusLabel(capabilities.thinking),
tooling: capabilityStatusLabel(capabilities.tooling),
vision: capabilityStatusLabel(capabilities.vision),
+ format: capabilityStatusLabel(capabilities.format),
renderActions: () => createAvailableModelActionButtons(service, id)
});
});
@@ -539,8 +670,10 @@
row.description || "",
row.provider || "",
row.license || "",
+ row.thinking || "?",
row.tooling || "?",
- row.vision || "?"
+ row.vision || "?",
+ row.format || "?"
]
: [
row.model || "",
@@ -720,8 +853,10 @@
});
ensureProductionRowUsageCells(targetRow, !hadRowsBeforeInsert);
+ setThinkingFlagForRow(targetRow, persistedCaps.thinking);
setToolingFlagForRow(targetRow, persistedCaps.tooling);
setVisionFlagForRow(targetRow, persistedCaps.vision);
+ setFormatFlagForRow(targetRow, persistedCaps.format);
ensureProductionRowActionButton(targetRow);
persistProductionModels();
scheduleCapabilityVerificationForRow(targetRow, persistedCaps);
@@ -757,12 +892,19 @@
const apikey = row.cells[3] ? row.cells[3].textContent.trim() : "";
if (!service || !modelName || !hoststub) return;
const statuses = capabilityStatuses || getPersistedCapabilitiesForModel(service, hoststub, modelName);
+ if (statuses.thinking === "unknown") {
+ triggerThinkingCapabilityVerification(row, { service, hoststub, modelName, apikey });
+ return;
+ }
if (statuses.tooling === "unknown") {
triggerToolingCapabilityVerification(row, { service, hoststub, modelName, apikey });
}
if (statuses.vision === "unknown") {
triggerVisionCapabilityVerification(row, { service, hoststub, modelName, apikey });
}
+ if (statuses.format === "unknown") {
+ triggerFormatCapabilityVerification(row, { service, hoststub, modelName, apikey });
+ }
}
function persistInferenceSystem() {
@@ -969,6 +1111,140 @@
updateAvailableModelButtons();
}
+ function maybeApplyNoThinkingParameters(service, hoststub, modelName, payload) {
+ if (!payload) return payload;
+ const capabilities = getPersistedCapabilitiesForModel(service, hoststub, modelName);
+ if (capabilities.thinking === "supported") {
+ payload.reasoning_effort = "none";
+ payload.enable_thinking = false;
+ }
+ return payload;
+ }
+
+ function buildThinkingTestPayload(modelName) {
+ return {
+ model: modelName,
+ temperature: 0.1,
+ max_tokens: 64,
+ messages: [
+ { role: "user", content: TEST_STRINGS.thinkingUserMessage }
+ ],
+ stream: true
+ };
+ }
+
+ function chunkContainsThinkingTokens(chunkText) {
+ if (!chunkText) return false;
+ return /<think\b/i.test(chunkText)
+ || /"reasoning_content"\s*:/i.test(chunkText)
+ || /"reasoning"\s*:/i.test(chunkText)
+ || /"thinking"\s*:/i.test(chunkText);
+ }
+
+ function setThinkingFlagForRow(row, status) {
+ if (!row || row.cells.length <= PRODUCTION_MODEL_THINKING_COLUMN_INDEX) {
+ return;
+ }
+ const cell = row.cells[PRODUCTION_MODEL_THINKING_COLUMN_INDEX];
+ if (!cell) return;
+ cell.textContent = capabilityStatusLabel(status);
+ }
+
+ function triggerThinkingCapabilityVerification(row, { service, hoststub, modelName, apikey }) {
+ if (!row) return;
+ const normalizedHoststub = (hoststub || "").trim();
+ if (!normalizedHoststub || !modelName) {
+ return;
+ }
+ if (row.dataset.thinkingTestInFlight === "true") {
+ return;
+ }
+ row.dataset.thinkingTestInFlight = "true";
+ const activityId = addTestActivity(modelName, "thinking");
+ updateBeforeUnloadGuard();
+ runThinkingCapabilityTest(service, normalizedHoststub, modelName, apikey)
+ .then(success => {
+ const rowStillMounted = !!(document && document.body && document.body.contains(row));
+ setPersistedCapability(service, normalizedHoststub, modelName, "thinking", success ? "supported" : "unsupported");
+ if (!rowStillMounted) {
+ persistProductionModels();
+ return;
+ }
+ setThinkingFlagForRow(row, success ? "supported" : "unsupported");
+ persistProductionModels();
+ updateAvailableModelCapabilityCells(service, normalizedHoststub, modelName);
+ scheduleCapabilityVerificationForRow(row, getPersistedCapabilitiesForModel(service, normalizedHoststub, modelName));
+ })
+ .catch(error => {
+ if (isCapabilityUnsupportedError(error)) {
+ setPersistedCapability(service, normalizedHoststub, modelName, "thinking", "unsupported");
+ setThinkingFlagForRow(row, "unsupported");
+ persistProductionModels();
+ updateAvailableModelCapabilityCells(service, normalizedHoststub, modelName);
+ scheduleCapabilityVerificationForRow(row, getPersistedCapabilitiesForModel(service, normalizedHoststub, modelName));
+ return;
+ }
+ console.warn(`Thinking capability check failed for model "${modelName}".`, error);
+ })
+ .finally(() => {
+ delete row.dataset.thinkingTestInFlight;
+ if (activityId) removeTestActivity(activityId, "thinking");
+ updateBeforeUnloadGuard();
+ });
+ }
+
+ async function runThinkingCapabilityTest(service, hoststub, modelName, apikey) {
+ const endpointBase = hoststub.replace(/\/+$/, "");
+ if (!endpointBase) {
+ return false;
+ }
+ const targetUrl = `${endpointBase}${TEST_STRINGS.toolingEndpointPath}`;
+ const headers = { "Content-Type": "application/json" };
+ if (apikey) {
+ headers.Authorization = `Bearer ${apikey}`;
+ }
+ const controller = new AbortController();
+ const response = await fetch(targetUrl, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(buildThinkingTestPayload(modelName)),
+ signal: controller.signal
+ });
+ if (!response.ok) {
+ const error = new Error(`HTTP status ${response.status}`);
+ error.status = response.status;
+ throw error;
+ }
+ if (!response.body) {
+ return false;
+ }
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let detectedThinking = false;
+ try {
+ while (true) {
+ const { value, done } = await reader.read();
+ if (done) break;
+ const chunkText = decoder.decode(value, { stream: true });
+ if (chunkContainsThinkingTokens(chunkText)) {
+ detectedThinking = true;
+ controller.abort();
+ break;
+ }
+ }
+ } catch (error) {
+ if (!(detectedThinking && error && error.name === "AbortError")) {
+ throw error;
+ }
+ } finally {
+ try {
+ reader.releaseLock();
+ } catch (error) {
+ }
+ }
+ return detectedThinking;
+ }
+
/**
* Tooling capability check
*/
@@ -982,7 +1258,9 @@
return;
}
row.dataset.toolingTestInFlight = "true";
- runToolingCapabilityTest(normalizedHoststub, modelName, apikey)
+ const activityId = addTestActivity(modelName, "tooling");
+ updateBeforeUnloadGuard();
+ runToolingCapabilityTest(service, normalizedHoststub, modelName, apikey)
.then(success => {
const rowStillMounted = !!(document && document.body && document.body.contains(row));
setPersistedCapability(service, normalizedHoststub, modelName, "tooling", success ? "supported" : "unsupported");
@@ -1006,10 +1284,12 @@
})
.finally(() => {
delete row.dataset.toolingTestInFlight;
+ if (activityId) removeTestActivity(activityId, "tooling");
+ updateBeforeUnloadGuard();
});
}
- async function runToolingCapabilityTest(hoststub, modelName, apikey) {
+ async function runToolingCapabilityTest(service, hoststub, modelName, apikey) {
const endpointBase = hoststub.replace(/\/+$/, "");
if (!endpointBase) {
return false;
@@ -1019,7 +1299,7 @@
if (apikey) {
headers.Authorization = `Bearer ${apikey}`;
}
- const payload = buildToolingTestPayload(modelName);
+ const payload = buildToolingTestPayload(service, hoststub, modelName);
const response = await fetch(targetUrl, {
method: "POST",
headers,
@@ -1034,8 +1314,8 @@
return toolingResponseIncludesExpectedToolCall(result);
}
- function buildToolingTestPayload(modelName) {
- return {
+ function buildToolingTestPayload(service, hoststub, modelName) {
+ return maybeApplyNoThinkingParameters(service, hoststub, modelName, {
model: modelName,
temperature: 0.1,
max_tokens: 1024,
@@ -1063,7 +1343,7 @@
}
}],
stream: false
- };
+ });
}
function toolingResponseIncludesExpectedToolCall(response) {
@@ -1118,7 +1398,9 @@
return;
}
row.dataset.visionTestInFlight = "true";
- runVisionCapabilityTest(normalizedHoststub, modelName, apikey)
+ const activityId = addTestActivity(modelName, "vision");
+ updateBeforeUnloadGuard();
+ runVisionCapabilityTest(service, normalizedHoststub, modelName, apikey)
.then(success => {
const rowStillMounted = !!(document && document.body && document.body.contains(row));
setPersistedCapability(service, normalizedHoststub, modelName, "vision", success ? "supported" : "unsupported");
@@ -1135,10 +1417,12 @@
})
.finally(() => {
delete row.dataset.visionTestInFlight;
+ if (activityId) removeTestActivity(activityId, "vision");
+ updateBeforeUnloadGuard();
});
}
- async function runVisionCapabilityTest(hoststub, modelName, apikey) {
+ async function runVisionCapabilityTest(service, hoststub, modelName, apikey) {
const endpointBase = hoststub.replace(/\/+$/, "");
if (!endpointBase) {
return false;
@@ -1149,7 +1433,7 @@
headers.Authorization = `Bearer ${apikey}`;
}
const base64Image = await loadVisionTestImageBase64();
- const payload = buildVisionTestPayload(modelName, base64Image);
+ const payload = buildVisionTestPayload(service, hoststub, modelName, base64Image);
const response = await fetch(targetUrl, {
method: "POST",
headers,
@@ -1164,8 +1448,8 @@
return visionResponseContainsExpectedAnswer(result);
}
- function buildVisionTestPayload(modelName, base64Image) {
- return {
+ function buildVisionTestPayload(service, hoststub, modelName, base64Image) {
+ return maybeApplyNoThinkingParameters(service, hoststub, modelName, {
model: modelName,
temperature: 0.1,
max_tokens: 512,
@@ -1184,7 +1468,7 @@
]
}
]
- };
+ });
}
function visionResponseContainsExpectedAnswer(response) {
@@ -1276,6 +1560,157 @@
cell.textContent = capabilityStatusLabel(status);
}
+ function triggerFormatCapabilityVerification(row, { service, hoststub, modelName, apikey }) {
+ if (!row) return;
+ const normalizedHoststub = (hoststub || "").trim();
+ if (!normalizedHoststub || !modelName) {
+ return;
+ }
+ if (row.dataset.formatTestInFlight === "true") {
+ return;
+ }
+ row.dataset.formatTestInFlight = "true";
+ const activityId = addTestActivity(modelName, "format");
+ updateBeforeUnloadGuard();
+ runFormatCapabilityTest(service, normalizedHoststub, modelName, apikey)
+ .then(success => {
+ const rowStillMounted = !!(document && document.body && document.body.contains(row));
+ setPersistedCapability(service, normalizedHoststub, modelName, "format", success ? "supported" : "unsupported");
+ if (!rowStillMounted) {
+ persistProductionModels();
+ return;
+ }
+ setFormatFlagForRow(row, success ? "supported" : "unsupported");
+ persistProductionModels();
+ updateAvailableModelCapabilityCells(service, normalizedHoststub, modelName);
+ })
+ .catch(error => {
+ if (isCapabilityUnsupportedError(error)) {
+ setPersistedCapability(service, normalizedHoststub, modelName, "format", "unsupported");
+ setFormatFlagForRow(row, "unsupported");
+ persistProductionModels();
+ updateAvailableModelCapabilityCells(service, normalizedHoststub, modelName);
+ return;
+ }
+ console.warn(`Format capability check failed for model "${modelName}".`, error);
+ })
+ .finally(() => {
+ delete row.dataset.formatTestInFlight;
+ if (activityId) removeTestActivity(activityId, "format");
+ updateBeforeUnloadGuard();
+ });
+ }
+
+ async function runFormatCapabilityTest(service, hoststub, modelName, apikey) {
+ const endpointBase = hoststub.replace(/\/+$/, "");
+ if (!endpointBase) {
+ return false;
+ }
+ for (const testCase of FORMAT_TEST_CASES) {
+ const mood = await runSingleFormatCapabilityTest(service, endpointBase, modelName, apikey, testCase.text);
+ if (mood !== testCase.expectedMood) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ async function runSingleFormatCapabilityTest(service, endpointBase, modelName, apikey, inputText) {
+ const targetUrl = `${endpointBase}${TEST_STRINGS.toolingEndpointPath}`;
+ const headers = { "Content-Type": "application/json" };
+ if (apikey) {
+ headers.Authorization = `Bearer ${apikey}`;
+ }
+ const payload = buildFormatTestPayload(service, endpointBase, modelName, inputText);
+ const response = await fetch(targetUrl, {
+ method: "POST",
+ headers,
+ body: JSON.stringify(payload)
+ });
+ if (!response.ok) {
+ const error = new Error(`HTTP status ${response.status}`);
+ error.status = response.status;
+ throw error;
+ }
+ const result = await response.json();
+ return extractMoodFromFormatResponse(result);
+ }
+
+ function buildFormatTestPayload(service, hoststub, modelName, inputText) {
+ return maybeApplyNoThinkingParameters(service, hoststub, modelName, {
+ model: modelName,
+ temperature: 0.1,
+ max_tokens: 128,
+ messages: [
+ { role: "system", content: TEST_STRINGS.formatSystemMessage },
+ { role: "user", content: inputText }
+ ],
+ stream: false,
+ response_format: {
+ type: "json_schema",
+ json_schema: {
+ strict: true,
+ schema: FORMAT_TEST_SCHEMA
+ }
+ }
+ });
+ }
+
+ function extractMoodFromFormatResponse(response) {
+ const candidates = [];
+ if (response && Array.isArray(response.choices)) {
+ response.choices.forEach(choice => {
+ if (choice && choice.message) {
+ candidates.push(choice.message);
+ }
+ });
+ }
+ if (response && response.message) {
+ candidates.push(response.message);
+ }
+ for (const message of candidates) {
+ const parsedMood = extractMoodFromMessage(message);
+ if (parsedMood) {
+ return parsedMood;
+ }
+ }
+ return "";
+ }
+
+ function extractMoodFromMessage(message) {
+ if (!message) return "";
+ if (message.parsed && typeof message.parsed === "object") {
+ const mood = normalizeMoodValue(message.parsed.mood);
+ if (mood) return mood;
+ }
+ if (message.content && typeof message.content === "object" && !Array.isArray(message.content)) {
+ const mood = normalizeMoodValue(message.content.mood);
+ if (mood) return mood;
+ }
+ const normalizedText = normalizeMessageText(message);
+ if (!normalizedText) return "";
+ try {
+ const parsed = JSON.parse(normalizedText);
+ return normalizeMoodValue(parsed && parsed.mood);
+ } catch (error) {
+ return "";
+ }
+ }
+
+ function normalizeMoodValue(value) {
+ const mood = typeof value === "string" ? value.trim().toLowerCase() : "";
+ return mood === "angry" || mood === "happy" || mood === "surprised" ? mood : "";
+ }
+
+ function setFormatFlagForRow(row, status) {
+ if (!row || row.cells.length <= PRODUCTION_MODEL_FORMAT_COLUMN_INDEX) {
+ return;
+ }
+ const cell = row.cells[PRODUCTION_MODEL_FORMAT_COLUMN_INDEX];
+ if (!cell) return;
+ cell.textContent = capabilityStatusLabel(status);
+ }
+
function updateAvailableModelCapabilityCells(service, hoststub, modelName) {
const container = document.getElementById("availableModelsContainer");
if (!container) return;
@@ -1283,8 +1718,10 @@
container.querySelectorAll("tbody tr").forEach(row => {
const modelCell = row.cells && row.cells[0];
if (!modelCell || modelCell.textContent.trim() !== modelName) return;
- if (row.cells[6]) row.cells[6].textContent = capabilityStatusLabel(capabilities.tooling);
- if (row.cells[7]) row.cells[7].textContent = capabilityStatusLabel(capabilities.vision);
+ if (row.cells[6]) row.cells[6].textContent = capabilityStatusLabel(capabilities.thinking);
+ if (row.cells[7]) row.cells[7].textContent = capabilityStatusLabel(capabilities.tooling);
+ if (row.cells[8]) row.cells[8].textContent = capabilityStatusLabel(capabilities.vision);
+ if (row.cells[9]) row.cells[9].textContent = capabilityStatusLabel(capabilities.format);
});
}
@@ -1415,6 +1852,9 @@
<fieldset id="availableModelsContainer" style="display:none"><a name="availableModels"></a></fieldset>
<fieldset id="productionModelsContainer" style="display: block;"><a name="productionModels"></a><legend>Production Models Matrix</legend>
+ <div id="testActivityContainer" style="display:none; margin-bottom:12px;">
+ <div id="testActivityList"></div>
+ </div>
<table class="table table-striped" id="productionModelsTable">
<thead class="thead-dark">
<tr>
@@ -1432,8 +1872,10 @@
<td class="narrow">qa-pairs<br/><span class="info"><img src="env/grafics/i16.gif" width="16" height="16" alt="info"/><span>This model can be used to produce query-answer pairs which enhance search from chat prompts</span></span></td>
<td class="narrow">tldr-shortener<br/><span class="info"><img src="env/grafics/i16.gif" width="16" height="16" alt="info"/><span>This model is used to make summaries from web content</span></span></td>
- <td>tooling</td>
- <td>vision</td>
+ <td class="narrow">thinking<br/><span class="info"><img src="env/grafics/i16.gif" width="16" height="16" alt="info"/><span>we detect thinking only to be able to suppress thinking. thinking is not used in YaCy</span></span></td>
+ <td class="narrow">tooling<br/><span class="info"><img src="env/grafics/i16.gif" width="16" height="16" alt="info"/><span>tooling is required for agentic abilities.</span></span></td>
+ <td class="narrow">vision<br/><span class="info"><img src="env/grafics/i16.gif" width="16" height="16" alt="info"/><span>this enables image recognition in the chat</span></span></td>
+ <td class="narrow">format<br/><span class="info"><img src="env/grafics/i16.gif" width="16" height="16" alt="info"/><span>this is required for classification</span></span></td>
<td>Actions</td>
</tr>
</thead>
@@ -1454,8 +1896,10 @@
<td><input type="checkbox" #(qapairs)#::checked=true#(/qapairs)# disabled="disabled"></td>
<td><input type="checkbox" #(tldr)#::checked=true#(/tldr)#></td>
+ <td>#[thinking]#</td>
<td>#[tooling]#</td>
<td>#[vision]#</td>
+ <td>#[format]#</td>
<td></td>
</tr>
#{/productionmodels}#