From 8e369f99afceb3aff97cbe7b1feb861629b3b0c1 Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Tue, 14 Jul 2026 16:13:19 +0300 Subject: [PATCH] feat(playground): top-p and a thoughts switch (auto/off/on) Playground gains top-p and a reasoning selector matching the Local-models row: auto follows the model/row default; off/on override per request via enable_thinking (cortiq >= 0.2.1 for local models). /admin/api/test(+stream) accept top_p and enable_thinking, proxied through GenParams passthrough. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/admin/mod.rs | 37 +++++++++++++++++++++++++++---------- web/js/i18n/en.js | 6 ++++++ web/js/i18n/ru.js | 6 ++++++ web/js/views/playground.js | 11 +++++++++++ 6 files changed, 52 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b21dd25..7e0a25c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "cortiq-gateway" -version = "0.2.22" +version = "0.2.23" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 331637a..3446463 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cortiq-gateway" -version = "0.2.22" +version = "0.2.23" edition = "2021" rust-version = "1.75" license = "Apache-2.0" diff --git a/src/admin/mod.rs b/src/admin/mod.rs index 03b2728..578b2ba 100644 --- a/src/admin/mod.rs +++ b/src/admin/mod.rs @@ -1447,24 +1447,44 @@ struct TestBody { temperature: Option, #[serde(default)] max_tokens: Option, + #[serde(default)] + top_p: Option, + /// Reasoning switch (cortiq ≥ 0.2.1): false = answer without the thinking + /// phase; true = force it on. Absent = model/row default. + #[serde(default)] + enable_thinking: Option, } fn default_test_model() -> String { "cortiq-auto".into() } +/// GenParams for a playground request: native sampling fields + the reasoning +/// switch proxied via passthrough (providers forward unknown fields as-is). +fn test_gen_params(b: &TestBody) -> GenParams { + let mut params = GenParams { + temperature: b.temperature, + max_tokens: b.max_tokens, + top_p: b.top_p, + ..Default::default() + }; + if let Some(v) = b.enable_thinking { + params + .passthrough + .insert("enable_thinking".into(), serde_json::Value::Bool(v)); + } + params +} + async fn run_test(State(state): State, Json(b): Json) -> ApiResult { if b.messages.is_empty() { return Err(ApiError::bad("messages must not be empty")); } + let params = test_gen_params(&b); let req = ChatRequest { routing: parse_routing(&b.model), messages: b.messages, tools: vec![], - params: GenParams { - temperature: b.temperature, - max_tokens: b.max_tokens, - ..Default::default() - }, + params, stream: false, meta: RequestMeta { account: "playground".into(), @@ -1513,15 +1533,12 @@ async fn run_test_stream(State(state): State, Json(b): Json= 0.2.1 for local models).", + "pg.topPHelp": "Nucleus sampling: only tokens within this cumulative probability are sampled. Empty = default.", "pg.send": "Send", "pg.stream": "Stream", "pg.sending": "Sending…", diff --git a/web/js/i18n/ru.js b/web/js/i18n/ru.js index aa98cfe..74ef7b9 100644 --- a/web/js/i18n/ru.js +++ b/web/js/i18n/ru.js @@ -222,6 +222,12 @@ export default { "pg.model": "Модель", "pg.temp": "Температура", "pg.maxTokens": "Макс. токены", + "pg.think": "Мысли", + "pg.think.auto": "авто (по настройке модели)", + "pg.think.off": "выкл — отвечать сразу", + "pg.think.on": "вкл — принудительно размышлять", + "pg.thinkHelp": "Фаза размышлений reasoning-моделей. Авто = настройка модели; выкл/вкл переопределяют для этого запроса (для локальных нужен cortiq ≥ 0.2.1).", + "pg.topPHelp": "Nucleus-сэмплинг: выбираются токены в пределах этой суммарной вероятности. Пусто = по умолчанию.", "pg.send": "Отправить", "pg.stream": "Стриминг", "pg.sending": "Отправка…", diff --git a/web/js/views/playground.js b/web/js/views/playground.js index 55aad35..b155777 100644 --- a/web/js/views/playground.js +++ b/web/js/views/playground.js @@ -16,6 +16,12 @@ export async function renderPlayground() { const promptIn = h("textarea", { placeholder: t("pg.promptPlaceholder"), rows: 5 }); const tempIn = h("input", { type: "number", step: "0.1", value: 0.7 }); const maxTokIn = h("input", { type: "number", value: 512 }); + const topPIn = h("input", { type: "number", step: "0.05", min: 0, max: 1, placeholder: "0.9", title: t("pg.topPHelp") }); + // reasoning switch: auto = model/row default; off/on override per request + const thinkSel = h("select", { title: t("pg.thinkHelp") }, + h("option", { value: "" }, t("pg.think.auto")), + h("option", { value: "off" }, t("pg.think.off")), + h("option", { value: "on" }, t("pg.think.on"))); const streamCb = h("input", { type: "checkbox", checked: true }); const profileSel = h("select", {}, ...(meta.profiles || []).map((p) => h("option", { value: p }, p))); const modelSel = h("select", {}, ...ids.map((id) => h("option", { value: id }, id))); @@ -69,6 +75,8 @@ export async function renderPlayground() { temperature: parseFloat(tempIn.value), max_tokens: parseInt(maxTokIn.value) || null, }; + if (topPIn.value !== "") body.top_p = parseFloat(topPIn.value); + if (thinkSel.value) body.enable_thinking = thinkSel.value === "on"; try { if (streamCb.checked) { const started = performance.now(); @@ -151,6 +159,9 @@ export async function renderPlayground() { h("label", { class: "field" }, h("span", {}, t("pg.mode")), modeSeg), modeExtra, h("div", { class: "row" }, h("label", { class: "field" }, h("span", {}, t("pg.temp")), tempIn), h("label", { class: "field" }, h("span", {}, t("pg.maxTokens")), maxTokIn)), + h("div", { class: "row" }, + h("label", { class: "field" }, h("span", {}, "top-p"), topPIn), + h("label", { class: "field" }, h("span", {}, t("pg.think")), thinkSel)), h( "div", { class: "flex", style: "justify-content:space-between" },