Add three chat profiles: Varaverk Assistant, General Chat, Code Sketcher
Explicit buttons rather than an automatic router. Misclassifying a Varaverk question as chat produces a confident invention about the user's system, which is exactly what retrieval exists to prevent — with buttons there is no hidden heuristic to be wrong and the strict profile is the default you land on. Only Varaverk Assistant retrieves; the other two would be carrying passages that cannot help write a folder-copy script. Memory goes to all three, since that is what lets chat know the setup without claiming authority over it. History depth is per profile and set server-side: retrieval costs ~2500 of 16384, so the profiles that skip it can hold a real conversation. Code Sketcher is told to flag flags it is unsure of, after it invented rsync --no-overwrite.
This commit is contained in:
@@ -82,11 +82,14 @@ if (PHP_SAPI !== 'cli') {
|
||||
|
||||
require_once dirname(__DIR__) . '/include/ai.php';
|
||||
|
||||
[$jobFile, $question, $historyJson, $kind, $think] = array_slice($argv, 1, 5) + array_fill(0, 5, '');
|
||||
[$jobFile, $question, $historyJson, $kind, $think, $profile] =
|
||||
array_slice($argv, 1, 6) + array_fill(0, 6, '');
|
||||
|
||||
if ($jobFile === '' || $question === '') exit(1);
|
||||
if (!preg_match('#/[0-9a-f]{32}\.json$#', $jobFile)) exit(1);
|
||||
|
||||
$profile = in_array($profile, ['varaverk', 'chat', 'code'], true) ? $profile : 'varaverk';
|
||||
|
||||
function jw(string $f, array $d): void {
|
||||
file_put_contents($f, json_encode($d));
|
||||
}
|
||||
@@ -94,40 +97,47 @@ function jw(string $f, array $d): void {
|
||||
$cfg = vv_ai_config();
|
||||
$t0 = microtime(true);
|
||||
|
||||
jw($jobFile, ['status' => 'retrieving']);
|
||||
// Only the Varaverk profile retrieves. The other two answer from memory and the model's own
|
||||
// knowledge, so passages would be noise competing for context — documentation cannot help write
|
||||
// a folder-copy script, and it has nothing to say about how someone's day is going.
|
||||
$sources = [];
|
||||
$context = '';
|
||||
$tRetrieve = 0.0;
|
||||
|
||||
$r = vv_ai_retrieve($question, $kind);
|
||||
if (!$r['ok']) {
|
||||
jw($jobFile, ['status' => 'error', 'error' => $r['error'] ?? 'retrieval failed']);
|
||||
exit(1);
|
||||
}
|
||||
if (!$r['results']) {
|
||||
jw($jobFile, ['status' => 'error',
|
||||
'error' => 'No relevant documentation found. Try rephrasing, or use the readme filter '
|
||||
. 'for questions about what something is.']);
|
||||
exit(0);
|
||||
}
|
||||
if ($profile === 'varaverk') {
|
||||
jw($jobFile, ['status' => 'retrieving']);
|
||||
|
||||
$tRetrieve = microtime(true) - $t0;
|
||||
$r = vv_ai_retrieve($question, $kind);
|
||||
if (!$r['ok']) {
|
||||
jw($jobFile, ['status' => 'error', 'error' => $r['error'] ?? 'retrieval failed']);
|
||||
exit(1);
|
||||
}
|
||||
if (!$r['results']) {
|
||||
jw($jobFile, ['status' => 'error',
|
||||
'error' => 'No relevant documentation found. Try rephrasing, use the readme filter '
|
||||
. 'for questions about what something is, or switch to General Chat if this '
|
||||
. 'is not a Varaverk question.']);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$sources = array_map(fn($x) => [
|
||||
'path' => $x['path'] ?? '', 'section' => $x['section'] ?? '',
|
||||
'heading' => $x['heading'] ?? '', 'score' => $x['score'] ?? 0,
|
||||
], $r['results']);
|
||||
$tRetrieve = microtime(true) - $t0;
|
||||
|
||||
jw($jobFile, ['status' => 'generating', 'sources' => $sources]);
|
||||
$sources = array_map(fn($x) => [
|
||||
'path' => $x['path'] ?? '', 'section' => $x['section'] ?? '',
|
||||
'heading' => $x['heading'] ?? '', 'score' => $x['score'] ?? 0,
|
||||
], $r['results']);
|
||||
|
||||
$context = '';
|
||||
foreach ($r['results'] as $i => $x) {
|
||||
$label = implode(' › ', array_filter([$x['path'] ?? '', $x['section'] ?? '', $x['heading'] ?? '']));
|
||||
$context .= '[' . ($i + 1) . '] ' . $label . "\n" . trim($x['content'] ?? '') . "\n\n";
|
||||
foreach ($r['results'] as $i => $x) {
|
||||
$label = implode(' › ', array_filter([$x['path'] ?? '', $x['section'] ?? '', $x['heading'] ?? '']));
|
||||
$context .= '[' . ($i + 1) . '] ' . $label . "\n" . trim($x['content'] ?? '') . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostic questions get live state as well as documentation. The docs say what a script is
|
||||
// supposed to do; only the logs and the current config say what it actually did. Attached only
|
||||
// when the question is asking why something failed — otherwise it is a few thousand tokens of
|
||||
// noise competing with the retrieved passages for a context budget that is already tight.
|
||||
$diagnostic = (bool)preg_match(
|
||||
$diagnostic = $profile === 'varaverk' && (bool)preg_match(
|
||||
'/\b(why|fail(ed|ing|ure)?|error|broken?|not work|isn.t work|wrong|stuck|hang|'
|
||||
. 'never runs?|didn.t|won.t|debug|troubleshoot|diagnos)/i',
|
||||
$question
|
||||
@@ -155,12 +165,46 @@ if ($diagnostic) {
|
||||
}
|
||||
}
|
||||
|
||||
$system = "You are Varaverk's documentation assistant. Varaverk is this user's private "
|
||||
. "two-server Unraid media ecosystem; it is not in your training data, so the material "
|
||||
. "below is the only thing you know about it.\n\n"
|
||||
. "Answer only from this material and cite the passages inline as [1], [2]. If it does "
|
||||
. "not contain the answer, say so plainly and name what is missing — do not fill the "
|
||||
. "gap from general knowledge. Prefer the user's own terminology.\n\n";
|
||||
// One prompt per profile. Explicit profiles rather than an automatic router: misclassifying a
|
||||
// Varaverk question as chat produces a confident invention about the user's system, which is
|
||||
// precisely what retrieval exists to prevent. The user always knows which contract is in force,
|
||||
// and the strict profile is the default.
|
||||
if ($profile === 'chat') {
|
||||
$system = "You are the Varaverk assistant, talking with the operator of a private two-server "
|
||||
. "Unraid media ecosystem called Varaverk. This is ordinary conversation — no "
|
||||
. "documentation has been retrieved for it.\n\n"
|
||||
. "Talk like a knowledgeable colleague. Be warm and direct, follow a tangent if one "
|
||||
. "is interesting, and use your general knowledge freely.\n\n"
|
||||
. "The one hard rule: you have NOT been given Varaverk's documentation in this mode. "
|
||||
. "Anything you know about their setup comes from the memory section below and from "
|
||||
. "what they tell you. If they ask something specific about how Varaverk works — a "
|
||||
. "script, a config variable, a safeguard — say you would need the Varaverk Assistant "
|
||||
. "profile for that, and do not guess. Being wrong about their own system is worse "
|
||||
. "than sending them one click away.\n\n";
|
||||
|
||||
} elseif ($profile === 'code') {
|
||||
$system = "You are drafting a short shell script for the operator of an Unraid server, to be "
|
||||
. "saved as a Varaverk Custom Script. Assume bash on Unraid: GNU coreutils, paths "
|
||||
. "under /mnt/user, no systemd, and no packages beyond what Unraid ships.\n\n"
|
||||
. "Write the smallest script that does the job. Include a shebang, quote variables, "
|
||||
. "check that inputs exist, and exit non-zero on failure. Prefer plain coreutils and "
|
||||
. "rsync over anything exotic.\n\n"
|
||||
. "This is the important part: you sometimes invent command-line flags that sound "
|
||||
. "plausible but do not exist. After the script, list every flag or command you are "
|
||||
. "not completely certain about, and say how to check it — 'verify with rsync --help' "
|
||||
. "or similar. A stated assumption the operator can check beats a confident mistake "
|
||||
. "they run at 3am. If you are unsure a flag exists, say so rather than picking the "
|
||||
. "one that sounds right.\n\n"
|
||||
. "Treat this as a first draft to be tested, and say so.\n\n";
|
||||
|
||||
} else {
|
||||
$system = "You are Varaverk's documentation assistant. Varaverk is this user's private "
|
||||
. "two-server Unraid media ecosystem; it is not in your training data, so the material "
|
||||
. "below is the only thing you know about it.\n\n"
|
||||
. "Answer only from this material and cite the passages inline as [1], [2]. If it does "
|
||||
. "not contain the answer, say so plainly and name what is missing — do not fill the "
|
||||
. "gap from general knowledge. Prefer the user's own terminology.\n\n";
|
||||
}
|
||||
|
||||
// Memory first, before the passages. It is the standing context — who the operator is and what
|
||||
// has already been decided — so it should frame everything that follows rather than read as one
|
||||
@@ -190,7 +234,7 @@ if ($diagBlock !== '') {
|
||||
. $diagBlock;
|
||||
}
|
||||
|
||||
$system .= "PASSAGES\n" . $context;
|
||||
if ($context !== '') $system .= "PASSAGES\n" . $context;
|
||||
|
||||
$messages = [['role' => 'system', 'content' => $system]];
|
||||
$hist = json_decode($historyJson ?: '[]', true);
|
||||
|
||||
Reference in New Issue
Block a user