feat(diarization-ui): per-prompt LLM settings with default/custom toggle
Each prompt can override model, thinking, num_ctx, num_predict, repeat_penalty and repeat_last_n. Default switch uses global settings; custom values are preserved when switching back to default. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
187
app.py
187
app.py
@@ -130,6 +130,19 @@ def init_db():
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
for col in [
|
||||
"llm_use_default INTEGER NOT NULL DEFAULT 1",
|
||||
"llm_model TEXT",
|
||||
"llm_think TEXT",
|
||||
"llm_num_ctx TEXT",
|
||||
"llm_num_predict INTEGER",
|
||||
"llm_repeat_penalty REAL",
|
||||
"llm_repeat_last_n INTEGER",
|
||||
]:
|
||||
try:
|
||||
c.execute(f"ALTER TABLE prompts ADD COLUMN {col}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# defaults
|
||||
c.execute("INSERT OR IGNORE INTO projects(name, created_at) VALUES (?,?)", ("Default", now_iso()))
|
||||
@@ -401,17 +414,25 @@ def _process_analysis_job(job_id: int):
|
||||
+ f"\\nTEXT:\\n{doc['content_md']}\\n"
|
||||
)
|
||||
|
||||
num_ctx_setting = get_setting("ollama_num_ctx", "auto")
|
||||
use_default = int(prm["llm_use_default"] if prm["llm_use_default"] is not None else 1)
|
||||
if use_default:
|
||||
model = get_setting("ollama_model", OLLAMA_MODEL)
|
||||
think = get_setting("ollama_think", "true" if OLLAMA_THINK else "false") == "true"
|
||||
num_ctx_setting = get_setting("ollama_num_ctx", "auto")
|
||||
num_predict = int(get_setting("ollama_num_predict", str(OLLAMA_NUM_PREDICT)))
|
||||
repeat_penalty = float(get_setting("ollama_repeat_penalty", "1.15"))
|
||||
repeat_last_n = int(get_setting("ollama_repeat_last_n", "128"))
|
||||
else:
|
||||
model = prm["llm_model"] or get_setting("ollama_model", OLLAMA_MODEL)
|
||||
think = (prm["llm_think"] or get_setting("ollama_think", "true" if OLLAMA_THINK else "false")) == "true"
|
||||
num_ctx_setting = prm["llm_num_ctx"] or get_setting("ollama_num_ctx", "auto")
|
||||
num_predict = int(prm["llm_num_predict"] or get_setting("ollama_num_predict", str(OLLAMA_NUM_PREDICT)))
|
||||
repeat_penalty = float(prm["llm_repeat_penalty"] or get_setting("ollama_repeat_penalty", "1.15"))
|
||||
repeat_last_n = int(prm["llm_repeat_last_n"] or get_setting("ollama_repeat_last_n", "128"))
|
||||
num_ctx = _estimate_num_ctx(llm_prompt) if num_ctx_setting == "auto" else int(num_ctx_setting)
|
||||
_job_set(job_id, llm_prompt=f"[num_ctx={num_ctx}]\n\n{llm_prompt}")
|
||||
with _JOB_STREAM_LOCK:
|
||||
_JOB_STREAMS[job_id] = {"thinking": "", "response": ""}
|
||||
|
||||
model = get_setting("ollama_model", OLLAMA_MODEL)
|
||||
think = get_setting("ollama_think", "true" if OLLAMA_THINK else "false") == "true"
|
||||
num_predict = int(get_setting("ollama_num_predict", str(OLLAMA_NUM_PREDICT)))
|
||||
repeat_penalty = float(get_setting("ollama_repeat_penalty", "1.15"))
|
||||
repeat_last_n = int(get_setting("ollama_repeat_last_n", "128"))
|
||||
r = requests.post(
|
||||
f"{OLLAMA_BASE_URL}/api/generate",
|
||||
json={"model": model, "prompt": llm_prompt, "stream": True, "think": think, "options": {
|
||||
@@ -1224,11 +1245,79 @@ def bulk_delete_documents(ids: List[int] = Form(...)):
|
||||
return HTMLResponse("<meta http-equiv='refresh' content='0; url=/library'>")
|
||||
|
||||
|
||||
def _prompt_llm_block(p, def_model, def_think, def_num_ctx, def_num_predict, def_repeat_penalty, def_repeat_last_n):
|
||||
pid = p["id"]
|
||||
use_default = int(p["llm_use_default"]) if p["llm_use_default"] is not None else 1
|
||||
m = p["llm_model"] or def_model
|
||||
th = p["llm_think"] or def_think
|
||||
nc = p["llm_num_ctx"] or def_num_ctx
|
||||
np_ = p["llm_num_predict"] or def_num_predict
|
||||
rp = p["llm_repeat_penalty"] or def_repeat_penalty
|
||||
rn = p["llm_repeat_last_n"] or def_repeat_last_n
|
||||
disabled = "disabled" if use_default else ""
|
||||
opacity = "opacity:.45;pointer-events:none" if use_default else ""
|
||||
ctx_opts = [("auto","Automatisch"), ("4096","4 096"), ("8192","8 192"), ("16384","16 384"),
|
||||
("32768","32 768"), ("65536","65 536"), ("131072","131 072")]
|
||||
ctx_html = "".join(f"<option value='{v}'{' selected' if v==str(nc) else ''}>{l}</option>" for v,l in ctx_opts)
|
||||
think_checked = "checked" if str(th) == "true" else ""
|
||||
return (
|
||||
f"<hr class='my-3'>"
|
||||
f"<div class='d-flex align-items-center gap-3 mb-2'>"
|
||||
f" <span class='text-secondary small fw-semibold'><i class='bi bi-robot me-1'></i>KI-Einstellungen</span>"
|
||||
f" <div class='form-check form-switch mb-0'>"
|
||||
f" <input class='form-check-input' type='checkbox' role='switch' id='llmdef_{pid}'"
|
||||
f" onchange='toggleLlmDef({pid},this.checked)' {'checked' if use_default else ''}>"
|
||||
f" <label class='form-check-label small' for='llmdef_{pid}'>Standard</label>"
|
||||
f" </div>"
|
||||
f"</div>"
|
||||
f"<input type='hidden' id='llm_use_default_{pid}' name='llm_use_default' value='{use_default}'>"
|
||||
f"<div id='llmfields_{pid}' class='row g-2' style='{opacity}'>"
|
||||
f" <div class='col-12'>"
|
||||
f" <label class='form-label small text-secondary mb-1'>Modell</label>"
|
||||
f" <div class='input-group input-group-sm'>"
|
||||
f" <input id='llmmodel_{pid}' class='form-control form-control-sm font-monospace' name='llm_model' value='{m}' {disabled}>"
|
||||
f" <button type='button' class='btn btn-outline-secondary btn-sm' onclick='loadModelsInto(\"llmmodel_{pid}\")' {'disabled' if use_default else ''}>"
|
||||
f" <i class='bi bi-arrow-clockwise'></i>"
|
||||
f" </button>"
|
||||
f" </div>"
|
||||
f" </div>"
|
||||
f" <div class='col-6 col-md-3'>"
|
||||
f" <label class='form-label small text-secondary mb-1'>Kontext</label>"
|
||||
f" <select class='form-select form-select-sm' name='llm_num_ctx' {disabled}>{ctx_html}</select>"
|
||||
f" </div>"
|
||||
f" <div class='col-6 col-md-3'>"
|
||||
f" <label class='form-label small text-secondary mb-1'>Max. Tokens</label>"
|
||||
f" <input class='form-control form-control-sm' type='number' name='llm_num_predict' min='256' max='131072' step='256' value='{np_}' {disabled}>"
|
||||
f" </div>"
|
||||
f" <div class='col-6 col-md-3'>"
|
||||
f" <label class='form-label small text-secondary mb-1'>Repeat Penalty</label>"
|
||||
f" <input class='form-control form-control-sm' type='number' name='llm_repeat_penalty' min='1.0' max='2.0' step='0.01' value='{rp}' {disabled}>"
|
||||
f" </div>"
|
||||
f" <div class='col-6 col-md-3'>"
|
||||
f" <label class='form-label small text-secondary mb-1'>Repeat Last N</label>"
|
||||
f" <input class='form-control form-control-sm' type='number' name='llm_repeat_last_n' min='0' max='2048' step='1' value='{rn}' {disabled}>"
|
||||
f" </div>"
|
||||
f" <div class='col-12'>"
|
||||
f" <div class='form-check form-switch'>"
|
||||
f" <input class='form-check-input' type='checkbox' role='switch' id='llmthink_{pid}' name='llm_think' value='true' {think_checked} {disabled}>"
|
||||
f" <label class='form-check-label small' for='llmthink_{pid}'>Thinking aktivieren</label>"
|
||||
f" </div>"
|
||||
f" </div>"
|
||||
f"</div>"
|
||||
)
|
||||
|
||||
|
||||
@app.get("/prompts", response_class=HTMLResponse)
|
||||
def prompts_page():
|
||||
with db() as c:
|
||||
prompts = c.execute("SELECT * FROM prompts ORDER BY name").fetchall()
|
||||
projects = c.execute("SELECT id,name FROM projects ORDER BY name").fetchall()
|
||||
def_model = get_setting("ollama_model", OLLAMA_MODEL)
|
||||
def_think = get_setting("ollama_think", "true" if OLLAMA_THINK else "false")
|
||||
def_num_ctx = get_setting("ollama_num_ctx", "auto")
|
||||
def_num_predict = get_setting("ollama_num_predict", str(OLLAMA_NUM_PREDICT))
|
||||
def_repeat_penalty = get_setting("ollama_repeat_penalty", "1.15")
|
||||
def_repeat_last_n = get_setting("ollama_repeat_last_n", "128")
|
||||
|
||||
project_opts = "".join([f"<option value='{p['name']}'>{p['name']}</option>" for p in projects])
|
||||
|
||||
@@ -1264,6 +1353,7 @@ def prompts_page():
|
||||
f"<textarea id='prompt_text_{p['id']}' class='form-control' name='prompt' style='min-height:110px; resize:vertical'>{p['prompt']}</textarea>"
|
||||
f"</div>"
|
||||
f"</div>"
|
||||
+ _prompt_llm_block(p, def_model, def_think, def_num_ctx, def_num_predict, def_repeat_penalty, def_repeat_last_n) +
|
||||
f"<div class='mt-3 d-flex flex-wrap gap-2'>"
|
||||
f"<button class='btn btn-primary btn-sm' type='submit'><i class='bi bi-check2-circle'></i> Speichern</button>"
|
||||
f"<button class='btn btn-outline-secondary btn-sm' type='button' onclick='previewPrompt({p['id']})'><i class='bi bi-eye'></i> Anzeigen</button>"
|
||||
@@ -1387,6 +1477,58 @@ document.addEventListener('DOMContentLoaded', () => {{
|
||||
const el = document.getElementById('promptEditorModal');
|
||||
if(el) el.addEventListener('hide.bs.modal', syncPromptEditorBack);
|
||||
}});
|
||||
|
||||
const _llmDefaults = {{
|
||||
model: {json.dumps(def_model)},
|
||||
think: {json.dumps(def_think)},
|
||||
num_ctx: {json.dumps(def_num_ctx)},
|
||||
num_predict: {json.dumps(def_num_predict)},
|
||||
repeat_penalty: {json.dumps(def_repeat_penalty)},
|
||||
repeat_last_n: {json.dumps(def_repeat_last_n)},
|
||||
}};
|
||||
|
||||
function toggleLlmDef(pid, isDefault) {{
|
||||
document.getElementById('llm_use_default_' + pid).value = isDefault ? '1' : '0';
|
||||
const wrap = document.getElementById('llmfields_' + pid);
|
||||
const inputs = wrap.querySelectorAll('input,select,button');
|
||||
if (isDefault) {{
|
||||
wrap.style.opacity = '.45';
|
||||
wrap.style.pointerEvents = 'none';
|
||||
inputs.forEach(el => el.disabled = true);
|
||||
}} else {{
|
||||
wrap.style.opacity = '';
|
||||
wrap.style.pointerEvents = '';
|
||||
inputs.forEach(el => el.disabled = false);
|
||||
// pre-fill from defaults if field is empty
|
||||
const modelInp = document.getElementById('llmmodel_' + pid);
|
||||
if (modelInp && !modelInp.value) modelInp.value = _llmDefaults.model;
|
||||
}}
|
||||
}}
|
||||
|
||||
async function loadModelsInto(inputId) {{
|
||||
const inp = document.getElementById(inputId);
|
||||
if (!inp) return;
|
||||
const btn = inp.nextElementSibling;
|
||||
if (btn) btn.disabled = true;
|
||||
try {{
|
||||
const res = await fetch('/settings/models');
|
||||
const data = await res.json();
|
||||
if (!data.ok || !data.models.length) {{
|
||||
alert('Keine Modelle gefunden: ' + (data.error || ''));
|
||||
return;
|
||||
}}
|
||||
const current = inp.value.trim();
|
||||
const v = await window.uiSelect('Modell wählen',
|
||||
data.models.map(m => ({{value: m, label: m}})),
|
||||
'– Modell auswählen –'
|
||||
);
|
||||
if (v) inp.value = v;
|
||||
}} catch(e) {{
|
||||
alert('Fehler: ' + e);
|
||||
}} finally {{
|
||||
if (btn) btn.disabled = false;
|
||||
}}
|
||||
}}
|
||||
</script>
|
||||
"""
|
||||
return layout("Prompts & Projekte", body)
|
||||
@@ -1412,9 +1554,36 @@ def prompt_add(name: str = Form(...), prompt: str = Form(...)):
|
||||
|
||||
|
||||
@app.post("/prompts/update", response_class=HTMLResponse)
|
||||
def prompt_update(id: int = Form(...), name: str = Form(...), prompt: str = Form(...)):
|
||||
def prompt_update(
|
||||
id: int = Form(...),
|
||||
name: str = Form(...),
|
||||
prompt: str = Form(...),
|
||||
llm_use_default: int = Form(1),
|
||||
llm_model: Optional[str] = Form(None),
|
||||
llm_think: Optional[str] = Form(None),
|
||||
llm_num_ctx: Optional[str] = Form(None),
|
||||
llm_num_predict: Optional[str] = Form(None),
|
||||
llm_repeat_penalty: Optional[str] = Form(None),
|
||||
llm_repeat_last_n: Optional[str] = Form(None),
|
||||
):
|
||||
with db() as c:
|
||||
c.execute("UPDATE prompts SET name=?, prompt=?, updated_at=? WHERE id=?", (name.strip(), prompt.strip(), now_iso(), id))
|
||||
c.execute(
|
||||
"""UPDATE prompts SET name=?, prompt=?, updated_at=?,
|
||||
llm_use_default=?, llm_model=?, llm_think=?,
|
||||
llm_num_ctx=?, llm_num_predict=?, llm_repeat_penalty=?, llm_repeat_last_n=?
|
||||
WHERE id=?""",
|
||||
(
|
||||
name.strip(), prompt.strip(), now_iso(),
|
||||
llm_use_default,
|
||||
(llm_model or "").strip() or None,
|
||||
"true" if llm_think == "true" else "false",
|
||||
(llm_num_ctx or "auto").strip(),
|
||||
int(float(llm_num_predict)) if llm_num_predict else None,
|
||||
float(llm_repeat_penalty) if llm_repeat_penalty else None,
|
||||
int(float(llm_repeat_last_n)) if llm_repeat_last_n else None,
|
||||
id,
|
||||
),
|
||||
)
|
||||
return HTMLResponse("<meta http-equiv='refresh' content='0; url=/prompts'>")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user