Add admin LLM model setting; rename Pi to LLM in the UI; drop table Review column
Test and deploy / test-and-deploy (push) Successful in 49s
Test and deploy / test-and-deploy (push) Successful in 49s
- New server/llm.js consolidates ModelRuntime + model selection: admin setting (app_settings.llm_model) > LLM_MODEL/PREFILL_MODEL env > first available; used by both prefill and roast evaluation - GET/PUT /api/admin/llm lists configured models and stores the choice (validated against the list; empty = auto; audited); admin page gains an LLM section with a model picker - All user-facing 'Pi agent' wording is now 'LLM'; no_model error message no longer references the pi CLI - /roasts table: Review column removed (review lives in the detail view) Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
eb82263ead
commit
5efaeb63c9
+72
-3
@@ -7,6 +7,7 @@ import { runPrefill } from "./prefill.js";
|
||||
import { parseAlog } from "./alog.js";
|
||||
import { listAlogLibrary, readAlogFromLibrary } from "./alog-library.js";
|
||||
import { evaluateRoast as defaultEvaluateRoast } from "./evaluate-roast.js";
|
||||
import { listAvailableModels as defaultListModels } from "./llm.js";
|
||||
import { sendMail } from "./mailer.js";
|
||||
import { coerceSession, computeTotalScore, blankSession } from "../shared/cupping.js";
|
||||
import { computeMachineProfile } from "../shared/learn.js";
|
||||
@@ -47,8 +48,9 @@ export function createApp({
|
||||
db,
|
||||
root,
|
||||
env = process.env,
|
||||
// Injectable so tests can stub the Pi-agent call; production always uses the real one.
|
||||
// Injectable so tests can stub the LLM calls; production always uses the real ones.
|
||||
evaluateRoast = defaultEvaluateRoast,
|
||||
listModels = defaultListModels,
|
||||
} = {}) {
|
||||
const app = express();
|
||||
const production = env.NODE_ENV === "production";
|
||||
@@ -1119,6 +1121,13 @@ export function createApp({
|
||||
},
|
||||
);
|
||||
|
||||
// Admin-chosen LLM model ("provider:id"), empty = auto. Read per call so a change takes
|
||||
// effect immediately without restarting the app.
|
||||
const llmModelSetting = async () =>
|
||||
(
|
||||
await db.query("SELECT value FROM app_settings WHERE key='llm_model'")
|
||||
).rows[0]?.value || "";
|
||||
|
||||
// ─── Actual roasts (finished .alog uploads) ────────────────────────────
|
||||
const toRoastRow = (row, { full = false } = {}) => {
|
||||
const parsed = row.parsed || {};
|
||||
@@ -1154,7 +1163,11 @@ export function createApp({
|
||||
).rows[0];
|
||||
if (!row) return;
|
||||
try {
|
||||
const evaluation = await evaluateRoast(row.parsed, row.plan ?? null);
|
||||
const evaluation = await evaluateRoast(
|
||||
row.parsed,
|
||||
row.plan ?? null,
|
||||
await llmModelSetting(),
|
||||
);
|
||||
await db.query(
|
||||
"UPDATE actual_roasts SET evaluation=$1, evaluation_status='done', evaluation_error=NULL, updated_at=now() WHERE id=$2",
|
||||
[evaluation, roastId],
|
||||
@@ -1727,6 +1740,59 @@ export function createApp({
|
||||
res.json({ ok: true, links });
|
||||
},
|
||||
);
|
||||
// The LLM model picker: which configured model runs prefill and roast reviews. Listing can
|
||||
// legitimately fail (no models configured yet) — the GET still succeeds so the admin page can
|
||||
// say so instead of erroring, and only a non-empty PUT requires the list for validation.
|
||||
app.get("/api/admin/llm", requireAuth, admin, async (req, res, next) => {
|
||||
try {
|
||||
const current = await llmModelSetting();
|
||||
let models = [];
|
||||
let modelsError = null;
|
||||
try {
|
||||
models = await listModels();
|
||||
} catch (e) {
|
||||
modelsError = "llm_unavailable";
|
||||
}
|
||||
res.json({ ok: true, current, models, modelsError });
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
});
|
||||
app.put(
|
||||
"/api/admin/llm",
|
||||
requireAuth,
|
||||
csrf,
|
||||
admin,
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const model = req.body.model;
|
||||
if (typeof model !== "string" || model.length > 200)
|
||||
return res.status(400).json({ ok: false, code: "bad_request" });
|
||||
if (model !== "") {
|
||||
let models;
|
||||
try {
|
||||
models = await listModels();
|
||||
} catch (e) {
|
||||
return res
|
||||
.status(503)
|
||||
.json({ ok: false, code: "llm_unavailable" });
|
||||
}
|
||||
if (!models.some((m) => m.key === model))
|
||||
return res
|
||||
.status(400)
|
||||
.json({ ok: false, code: "unknown_model" });
|
||||
}
|
||||
await db.query(
|
||||
"UPDATE app_settings SET value=$1 WHERE key='llm_model'",
|
||||
[model],
|
||||
);
|
||||
await audit(req.user.id, "llm_model_changed", model || "auto");
|
||||
res.json({ ok: true, current: model });
|
||||
} catch (e) {
|
||||
next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
app.put(
|
||||
"/api/admin/signup-enabled",
|
||||
requireAuth,
|
||||
@@ -1837,7 +1903,10 @@ export function createApp({
|
||||
.status(400)
|
||||
.json({ ok: false, code: "bad_url", error: "Missing url." });
|
||||
try {
|
||||
const result = await runPrefill(await fetchPageText(url));
|
||||
const result = await runPrefill(
|
||||
await fetchPageText(url),
|
||||
await llmModelSetting(),
|
||||
);
|
||||
res.json({ ok: true, ...result });
|
||||
} catch (err) {
|
||||
const code = err.code ?? "prefill_failed";
|
||||
|
||||
Reference in New Issue
Block a user