Compare commits

..
1 Commits
Author SHA1 Message Date
infosave2007 8e369f99af feat(playground): top-p and a thoughts switch (auto/off/on)
Docker / publish (push) Canceled after 0s
Release / crates-io (push) Canceled after 0s
Release / release (macos-14, aarch64-apple-darwin) (push) Canceled after 0s
Release / release (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Canceled after 0s
Release / release (windows-latest, x86_64-pc-windows-msvc) (push) Canceled after 0s
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.
2026-07-14 16:13:19 +03:00
6 changed files with 52 additions and 12 deletions
Generated
+1 -1
View File
@@ -285,7 +285,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "cortiq-gateway"
version = "0.2.22"
version = "0.2.23"
dependencies = [
"anyhow",
"arc-swap",
+1 -1
View File
@@ -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"
+27 -10
View File
@@ -1447,24 +1447,44 @@ struct TestBody {
temperature: Option<f32>,
#[serde(default)]
max_tokens: Option<u32>,
#[serde(default)]
top_p: Option<f32>,
/// 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<bool>,
}
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<SharedState>, Json(b): Json<TestBody>) -> 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<SharedState>, Json(b): Json<TestBod
if b.messages.is_empty() {
return ApiError::bad("messages must not be empty").into_response();
}
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: true,
meta: RequestMeta {
account: "playground".into(),
+6
View File
@@ -223,6 +223,12 @@ export default {
"pg.model": "Model",
"pg.temp": "Temperature",
"pg.maxTokens": "Max tokens",
"pg.think": "Thoughts",
"pg.think.auto": "auto (model default)",
"pg.think.off": "off — answer directly",
"pg.think.on": "on — force reasoning",
"pg.thinkHelp": "Reasoning phase of thinking models. Auto = model/row default; off/on override for this request (needs cortiq >= 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…",
+6
View File
@@ -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": "Отправка…",
+11
View File
@@ -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" },