@lzhzzzzwill/cofos 1.3.3 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/bin/cofos.js +63 -221
- package/package.json +1 -4
- package/runtime/src/inference/student_adapter_inference.py +22 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ COFOS CLI is a standalone Node.js command line wrapper around the COFOS Python r
|
|
|
6
6
|
npx @lzhzzzzwill/cofos
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
COFOS uses a plain terminal interface, so normal terminal scrollback, mouse selection, copy, and `Ctrl+C` work as expected.
|
|
10
10
|
|
|
11
11
|
## Runtime Model
|
|
12
12
|
|
|
@@ -71,18 +71,19 @@ Disable RAG for a session:
|
|
|
71
71
|
npx @lzhzzzzwill/cofos --no-rag
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Show model reasoning scaffold when emitted:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
npx @lzhzzzzwill/cofos -
|
|
77
|
+
npx @lzhzzzzwill/cofos --show-reasoning
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Ask once and exit, useful for scripts and smoke tests:
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
npx @lzhzzzzwill/cofos
|
|
83
|
+
npx @lzhzzzzwill/cofos -p "What ROS does TT-T-COF generate under visible-light photocatalysis?"
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
|
|
86
87
|
## Startup Behavior
|
|
87
88
|
|
|
88
89
|
On startup, the CLI does the following:
|
|
@@ -133,7 +134,7 @@ cofos \
|
|
|
133
134
|
| `--rebuild-pdf-index` | Force PDF parsing and BM25 rebuild |
|
|
134
135
|
| `--top-k` | Retrieval top-k |
|
|
135
136
|
| `-p`, `--prompt` | Ask one question and exit |
|
|
136
|
-
| `--
|
|
137
|
+
| `--show-reasoning` | Show emitted reasoning scaffold in dim text before the final answer |
|
|
137
138
|
| `--max-new-tokens` | Override generation length |
|
|
138
139
|
| `--no-rag` | Run without KG/BM25 retrieval |
|
|
139
140
|
|
package/bin/cofos.js
CHANGED
|
@@ -158,208 +158,7 @@ function ensureDeps(py, log = console.log) {
|
|
|
158
158
|
if (r.status !== 0) { log("[cofos] Failed to install Python dependencies."); process.exit(1); }
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
async function createTui() {
|
|
164
|
-
const { default: blessed } = await import("blessed");
|
|
165
|
-
const screen = blessed.screen({
|
|
166
|
-
smartCSR: true,
|
|
167
|
-
fullUnicode: true,
|
|
168
|
-
title: "COFOS",
|
|
169
|
-
mouse: false,
|
|
170
|
-
});
|
|
171
|
-
screen.program.disableMouse();
|
|
172
|
-
|
|
173
|
-
const history = blessed.box({
|
|
174
|
-
top: 0,
|
|
175
|
-
left: 0,
|
|
176
|
-
width: "100%",
|
|
177
|
-
height: "100%-3",
|
|
178
|
-
scrollable: true,
|
|
179
|
-
alwaysScroll: true,
|
|
180
|
-
keys: true,
|
|
181
|
-
vi: true,
|
|
182
|
-
mouse: false,
|
|
183
|
-
tags: true,
|
|
184
|
-
scrollbar: { ch: " ", track: { bg: "black" }, style: { bg: "cyan" } },
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
const suggestions = blessed.box({
|
|
188
|
-
bottom: 3,
|
|
189
|
-
left: 2,
|
|
190
|
-
width: "70%",
|
|
191
|
-
height: 6,
|
|
192
|
-
hidden: true,
|
|
193
|
-
border: { type: "line" },
|
|
194
|
-
tags: true,
|
|
195
|
-
style: { border: { fg: "yellow" }, fg: "white", bg: "black" },
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const input = blessed.textbox({
|
|
199
|
-
bottom: 0,
|
|
200
|
-
left: 0,
|
|
201
|
-
width: "100%",
|
|
202
|
-
height: 3,
|
|
203
|
-
inputOnFocus: true,
|
|
204
|
-
keys: true,
|
|
205
|
-
mouse: false,
|
|
206
|
-
border: { type: "line" },
|
|
207
|
-
label: " cofos ",
|
|
208
|
-
style: {
|
|
209
|
-
border: { fg: "cyan" },
|
|
210
|
-
focus: { border: { fg: "green" } },
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
screen.append(history);
|
|
215
|
-
screen.append(suggestions);
|
|
216
|
-
screen.append(input);
|
|
217
|
-
|
|
218
|
-
let buffer = "";
|
|
219
|
-
let onSubmit = () => {};
|
|
220
|
-
let onExit = () => {};
|
|
221
|
-
let submitting = false;
|
|
222
|
-
let suggestionMatches = [];
|
|
223
|
-
let selectedSuggestion = 0;
|
|
224
|
-
|
|
225
|
-
function renderBuffer(wasAtBottom = true) {
|
|
226
|
-
history.setContent(buffer);
|
|
227
|
-
if (wasAtBottom) history.setScrollPerc(100);
|
|
228
|
-
screen.render();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function append(text = "") {
|
|
232
|
-
const wasAtBottom = history.getScrollPerc() >= 98;
|
|
233
|
-
const data = String(text).replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
|
234
|
-
for (const part of data.split("\r")) {
|
|
235
|
-
if (part === "") continue;
|
|
236
|
-
if (data.includes("\r")) {
|
|
237
|
-
const lastNewline = buffer.lastIndexOf("\n");
|
|
238
|
-
buffer = (lastNewline === -1 ? "" : buffer.slice(0, lastNewline + 1)) + part;
|
|
239
|
-
} else {
|
|
240
|
-
buffer += part;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (buffer.length > 250000) buffer = buffer.slice(-220000);
|
|
244
|
-
renderBuffer(wasAtBottom);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function hideSuggestions() {
|
|
248
|
-
suggestionMatches = [];
|
|
249
|
-
selectedSuggestion = 0;
|
|
250
|
-
if (!suggestions.hidden) {
|
|
251
|
-
suggestions.hide();
|
|
252
|
-
screen.render();
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function renderSuggestions() {
|
|
257
|
-
if (!suggestionMatches.length) {
|
|
258
|
-
hideSuggestions();
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
selectedSuggestion = Math.max(0, Math.min(selectedSuggestion, suggestionMatches.length - 1));
|
|
262
|
-
suggestions.setContent(suggestionMatches.map(({ cmd, desc }, i) => {
|
|
263
|
-
const marker = i === selectedSuggestion ? "{black-fg}{yellow-bg}›" : " ";
|
|
264
|
-
const close = i === selectedSuggestion ? "{/yellow-bg}{/black-fg}" : "";
|
|
265
|
-
return `${marker} {cyan-fg}${cmd}{/cyan-fg} ${desc}${close}`;
|
|
266
|
-
}).join("\n"));
|
|
267
|
-
suggestions.height = Math.min(8, suggestionMatches.length + 2);
|
|
268
|
-
suggestions.show();
|
|
269
|
-
screen.render();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function updateSuggestions() {
|
|
273
|
-
const value = input.getValue() || "";
|
|
274
|
-
if (!value.startsWith("/")) {
|
|
275
|
-
hideSuggestions();
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
suggestionMatches = slashMatches(value);
|
|
279
|
-
selectedSuggestion = 0;
|
|
280
|
-
renderSuggestions();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function selectedCommand() {
|
|
284
|
-
return suggestionMatches[selectedSuggestion]?.cmd || "";
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function submitInput() {
|
|
288
|
-
if (submitting) return;
|
|
289
|
-
submitting = true;
|
|
290
|
-
let value = input.getValue();
|
|
291
|
-
if (!suggestions.hidden && selectedCommand()) value = selectedCommand();
|
|
292
|
-
hideSuggestions();
|
|
293
|
-
input.clearValue();
|
|
294
|
-
input.focus();
|
|
295
|
-
screen.render();
|
|
296
|
-
onSubmit(String(value || ""));
|
|
297
|
-
setTimeout(() => { submitting = false; }, 0);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
input.key(["enter"], submitInput);
|
|
301
|
-
screen.key(["enter"], submitInput);
|
|
302
|
-
|
|
303
|
-
input.on("keypress", (_ch, key = {}) => {
|
|
304
|
-
if (["up", "down", "tab", "enter", "escape"].includes(key.name)) return;
|
|
305
|
-
setTimeout(updateSuggestions, 0);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
screen.key(["tab"], () => {
|
|
309
|
-
const value = input.getValue() || "";
|
|
310
|
-
if (!value.startsWith("/")) return;
|
|
311
|
-
if (!suggestionMatches.length) suggestionMatches = slashMatches(value);
|
|
312
|
-
if (!suggestionMatches.length) return;
|
|
313
|
-
input.setValue(selectedCommand());
|
|
314
|
-
hideSuggestions();
|
|
315
|
-
input.focus();
|
|
316
|
-
screen.render();
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
screen.key(["up"], () => {
|
|
320
|
-
if (suggestions.hidden || !suggestionMatches.length) return;
|
|
321
|
-
selectedSuggestion = (selectedSuggestion - 1 + suggestionMatches.length) % suggestionMatches.length;
|
|
322
|
-
renderSuggestions();
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
screen.key(["down"], () => {
|
|
326
|
-
if (suggestions.hidden || !suggestionMatches.length) return;
|
|
327
|
-
selectedSuggestion = (selectedSuggestion + 1) % suggestionMatches.length;
|
|
328
|
-
renderSuggestions();
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
screen.key(["escape"], hideSuggestions);
|
|
332
|
-
screen.key(["pageup"], () => { history.scroll(-Math.max(3, Math.floor(screen.height * 0.8))); screen.render(); });
|
|
333
|
-
screen.key(["pagedown"], () => { history.scroll(Math.max(3, Math.floor(screen.height * 0.8))); screen.render(); });
|
|
334
|
-
screen.key(["home"], () => { history.setScrollPerc(0); screen.render(); });
|
|
335
|
-
screen.key(["end"], () => { history.setScrollPerc(100); screen.render(); });
|
|
336
|
-
screen.key(["C-c"], () => onExit());
|
|
337
|
-
|
|
338
|
-
input.focus();
|
|
339
|
-
screen.render();
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
supportsReplace: true,
|
|
343
|
-
echoUser: true,
|
|
344
|
-
screen,
|
|
345
|
-
input,
|
|
346
|
-
append,
|
|
347
|
-
print: (text = "") => append(String(text) + "\n"),
|
|
348
|
-
clear: () => { buffer = ""; renderBuffer(true); },
|
|
349
|
-
length: () => buffer.length,
|
|
350
|
-
replaceFrom: (index, text = "") => { buffer = buffer.slice(0, Math.max(0, index)) + String(text); renderBuffer(true); },
|
|
351
|
-
setSubmitHandler: (fn) => { onSubmit = fn; },
|
|
352
|
-
setExitHandler: (fn) => { onExit = fn; },
|
|
353
|
-
focus: () => { input.focus(); screen.render(); },
|
|
354
|
-
setPrompt: (label) => { input.setLabel(` ${label} `); screen.render(); },
|
|
355
|
-
destroy: () => {
|
|
356
|
-
try { screen.program.disableMouse(); } catch (_) {}
|
|
357
|
-
screen.destroy();
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function blessedToAnsi(text) {
|
|
161
|
+
function markupToAnsi(text) {
|
|
363
162
|
return String(text)
|
|
364
163
|
.replace(/\{bold\}/g, B)
|
|
365
164
|
.replace(/\{\/bold\}/g, R)
|
|
@@ -399,11 +198,11 @@ function createPlainUi() {
|
|
|
399
198
|
return {
|
|
400
199
|
supportsReplace: false,
|
|
401
200
|
echoUser: false,
|
|
402
|
-
append: (text = "") => process.stdout.write(
|
|
403
|
-
print: (text = "") => console.log(
|
|
201
|
+
append: (text = "") => process.stdout.write(markupToAnsi(text)),
|
|
202
|
+
print: (text = "") => console.log(markupToAnsi(text)),
|
|
404
203
|
clear: () => console.clear(),
|
|
405
204
|
length: () => 0,
|
|
406
|
-
replaceFrom: (_index, text = "") => process.stdout.write(
|
|
205
|
+
replaceFrom: (_index, text = "") => process.stdout.write(markupToAnsi(text)),
|
|
407
206
|
setSubmitHandler: (fn) => { onSubmit = fn; },
|
|
408
207
|
setExitHandler: (fn) => { onExit = fn; },
|
|
409
208
|
focus: () => rl.prompt(),
|
|
@@ -417,7 +216,7 @@ function createPlainUi() {
|
|
|
417
216
|
async function main() {
|
|
418
217
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
419
218
|
console.log("\n COFOS CLI — 9B + RAG chat (Willlzh/COFOS)\n");
|
|
420
|
-
console.log(" Usage: cofos [
|
|
219
|
+
console.log(" Usage: cofos [-p QUESTION] [--show-reasoning] [--model PATH] [--config PATH] [--pdf-dir DIR] [--cache-dir DIR] [--top-k N] [--no-rag]\n");
|
|
421
220
|
console.log(" Commands in chat:");
|
|
422
221
|
console.log(" /help Show this help");
|
|
423
222
|
console.log(" /exit Quit");
|
|
@@ -430,7 +229,7 @@ async function main() {
|
|
|
430
229
|
console.log(" --pdf-dir DIR Add PDFs from DIR as extra BM25 evidence");
|
|
431
230
|
console.log(" --cache-dir DIR Store model/data cache here (default: ./.cofos)");
|
|
432
231
|
console.log(" --max-new-tokens N Override generation length");
|
|
433
|
-
console.log(" --
|
|
232
|
+
console.log(" --show-reasoning Show model reasoning scaffold in dim text when emitted");
|
|
434
233
|
console.log(" -p, --prompt TEXT Ask once and exit");
|
|
435
234
|
console.log(" --rebuild-pdf-index Force reparsing PDFs and rebuilding BM25\n");
|
|
436
235
|
console.log(" Multi-line: end a line with \\ to continue on the next line.\n");
|
|
@@ -447,7 +246,7 @@ async function main() {
|
|
|
447
246
|
let maxNewTokens = null;
|
|
448
247
|
let cacheDir = process.env.COFOS_CACHE_DIR ? resolve(process.env.COFOS_CACHE_DIR) : resolve(process.cwd(), ".cofos");
|
|
449
248
|
let rebuildPdfIndex = false;
|
|
450
|
-
let
|
|
249
|
+
let showReasoning = false;
|
|
451
250
|
let singlePrompt = null;
|
|
452
251
|
const backendArgs = [chatScript];
|
|
453
252
|
|
|
@@ -479,8 +278,8 @@ async function main() {
|
|
|
479
278
|
cacheDir = resolve(value(++i, arg));
|
|
480
279
|
} else if (arg === "--rebuild-pdf-index") {
|
|
481
280
|
rebuildPdfIndex = true;
|
|
482
|
-
} else if (arg === "--
|
|
483
|
-
|
|
281
|
+
} else if (arg === "--show-reasoning") {
|
|
282
|
+
showReasoning = true;
|
|
484
283
|
} else if (arg === "-p" || arg === "--prompt") {
|
|
485
284
|
singlePrompt = value(++i, arg);
|
|
486
285
|
} else if (arg === "--no-rag") {
|
|
@@ -498,8 +297,8 @@ async function main() {
|
|
|
498
297
|
if (!py) die("Python not found. Install Python 3.9+ and re-run.");
|
|
499
298
|
if (!existsSync(chatScript)) die(`Missing script: ${chatScript}`);
|
|
500
299
|
|
|
501
|
-
const ui =
|
|
502
|
-
ui.print(
|
|
300
|
+
const ui = createPlainUi();
|
|
301
|
+
ui.print(render({ modelPath, cacheDir, pdfDir }));
|
|
503
302
|
ensureDeps(py, (msg) => ui.print(msg));
|
|
504
303
|
ui.print(" Loading COFOS 9B + RAG backend from Hugging Face … first run may take a while");
|
|
505
304
|
if (pdfDir) ui.print(" Local PDF RAG: enabled for this session");
|
|
@@ -516,6 +315,9 @@ async function main() {
|
|
|
516
315
|
HF_HUB_ETAG_TIMEOUT: process.env.HF_HUB_ETAG_TIMEOUT || "30",
|
|
517
316
|
HF_HUB_VERBOSITY: process.env.HF_HUB_VERBOSITY || "error",
|
|
518
317
|
TRANSFORMERS_VERBOSITY: process.env.TRANSFORMERS_VERBOSITY || "error",
|
|
318
|
+
PYTHONUNBUFFERED: process.env.PYTHONUNBUFFERED || "1",
|
|
319
|
+
PYTHONIOENCODING: process.env.PYTHONIOENCODING || "utf-8",
|
|
320
|
+
COFOS_SHOW_REASONING: showReasoning ? "1" : (process.env.COFOS_SHOW_REASONING || "0"),
|
|
519
321
|
};
|
|
520
322
|
const proc = spawn(py, backendArgs, {
|
|
521
323
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -551,6 +353,33 @@ async function main() {
|
|
|
551
353
|
}
|
|
552
354
|
}
|
|
553
355
|
|
|
356
|
+
function handleLocalOnly(rawInput) {
|
|
357
|
+
const input = String(rawInput || "").trim();
|
|
358
|
+
if (!input) return true;
|
|
359
|
+
if (input === "/") {
|
|
360
|
+
ui.print("Commands: " + SLASH_COMMANDS.map(({ cmd }) => cmd).join(" ") + "\nType a prefix such as /r or /to, then press Tab for completion.");
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
if (/^\/(exit|quit)$/.test(input)) { shutdown(); return true; }
|
|
364
|
+
if (/^\/(help)$/.test(input)) {
|
|
365
|
+
ui.print(slashHint("/") + "\n Add local papers to RAG:\n cofos --pdf-dir ./new_pdfs\n cofos --pdf-dir ./new_pdfs --rebuild-pdf-index\n\n Multi-line: end a line with \\ to continue.");
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
if (/^\/(cls)$/.test(input)) {
|
|
369
|
+
ui.clear();
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
if (/^\/(info)$/.test(input)) {
|
|
373
|
+
ui.print(`\n COFOS CLI — v${pkg.version}\n Model: ${modelPath} (Hugging Face)\n RAG: Willlzh/COFOS_data/runtime (KG + BM25) + optional --pdf-dir BM25 evidence\n Cache: ${cacheDir}\n HF: ${backendEnv.HF_HOME}\n Engine: StudentAdapterInference + transformers + PyTorch\n Status: ${ready ? "ready" : "loading"}\n Hist: ~/.cofos_history.jsonl\n`);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
if (input.startsWith("/") && !/^\/(clear|save|topk)(?:\s|$)/.test(input) && !/^\/rag\s+(?:on|off)$/.test(input)) {
|
|
377
|
+
ui.print(`{red-fg}•{/red-fg} Unknown command: ${input}.\n${slashPrefixHint(input)}`);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
554
383
|
function handle(rawInput) {
|
|
555
384
|
let input = String(rawInput || "");
|
|
556
385
|
if (input.endsWith("\\")) {
|
|
@@ -569,7 +398,7 @@ async function main() {
|
|
|
569
398
|
if (!input) return false;
|
|
570
399
|
|
|
571
400
|
if (input === "/") {
|
|
572
|
-
ui.print("
|
|
401
|
+
ui.print("Commands: " + SLASH_COMMANDS.map(({ cmd }) => cmd).join(" ") + "\nType a prefix such as /r or /to, then press Tab for completion.");
|
|
573
402
|
return false;
|
|
574
403
|
}
|
|
575
404
|
if (/^\/(exit|quit)$/.test(input)) { shutdown(); return true; }
|
|
@@ -632,8 +461,14 @@ async function main() {
|
|
|
632
461
|
}
|
|
633
462
|
|
|
634
463
|
ui.setSubmitHandler((line) => {
|
|
464
|
+
if (handleLocalOnly(line)) {
|
|
465
|
+
if (!closed) ui.focus();
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
635
468
|
if (!ready || generating) {
|
|
636
469
|
inputQ.push(line);
|
|
470
|
+
ui.print(ready ? "• Queued until the current answer finishes." : "• Queued until the model is ready. Use /exit to quit while loading.");
|
|
471
|
+
if (!closed) ui.focus();
|
|
637
472
|
return;
|
|
638
473
|
}
|
|
639
474
|
const started = handle(line);
|
|
@@ -681,6 +516,16 @@ async function main() {
|
|
|
681
516
|
|
|
682
517
|
function streamPlainAssistant(text, final = false) {
|
|
683
518
|
if (ui.supportsReplace) return;
|
|
519
|
+
if (!showReasoning) {
|
|
520
|
+
if (plainAssistantMode === "undecided") {
|
|
521
|
+
if (!text && !final) return;
|
|
522
|
+
plainAssistantMode = "answer";
|
|
523
|
+
ui.append(`${GR}${B}◆${R} `);
|
|
524
|
+
}
|
|
525
|
+
if (text) ui.append(text);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
684
529
|
plainAssistantPending += text;
|
|
685
530
|
if (plainAssistantMode === "undecided") {
|
|
686
531
|
const start = plainAssistantPending.trimStart();
|
|
@@ -691,12 +536,10 @@ async function main() {
|
|
|
691
536
|
plainReasonPrinted = 0;
|
|
692
537
|
plainAssistantPending = "";
|
|
693
538
|
ui.append(`${G}-${R} ${G}`);
|
|
694
|
-
} else
|
|
539
|
+
} else {
|
|
695
540
|
plainAssistantMode = "answer";
|
|
696
541
|
plainAssistantPending = start;
|
|
697
542
|
ui.append(`${GR}${B}◆${R} `);
|
|
698
|
-
} else {
|
|
699
|
-
return;
|
|
700
543
|
}
|
|
701
544
|
}
|
|
702
545
|
|
|
@@ -731,15 +574,14 @@ async function main() {
|
|
|
731
574
|
}
|
|
732
575
|
}
|
|
733
576
|
|
|
734
|
-
if (plainAssistantMode === "answer") {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
plainAssistantPending = "";
|
|
738
|
-
}
|
|
577
|
+
if (plainAssistantMode === "answer" && plainAssistantPending) {
|
|
578
|
+
ui.append(plainAssistantPending);
|
|
579
|
+
plainAssistantPending = "";
|
|
739
580
|
}
|
|
740
581
|
}
|
|
741
582
|
|
|
742
583
|
|
|
584
|
+
|
|
743
585
|
proc.stdout.on("data", (chunk) => {
|
|
744
586
|
buf += chunk;
|
|
745
587
|
if (!ready) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lzhzzzzwill/cofos",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "COFOS 9B + RAG chat CLI with optional local PDF ingestion.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,8 +38,5 @@
|
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=18"
|
|
41
|
-
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"blessed": "^0.1.81"
|
|
44
41
|
}
|
|
45
42
|
}
|
|
@@ -489,6 +489,11 @@ class StudentAdapterInference:
|
|
|
489
489
|
@staticmethod
|
|
490
490
|
def _sanitize_stream(chunks: Any) -> Generator[str, None, None]:
|
|
491
491
|
"""Drop ``<think>`` blocks and leading labels from a text stream."""
|
|
492
|
+
if os.environ.get("COFOS_SHOW_REASONING", "").lower() in {"1", "true", "yes", "on"}:
|
|
493
|
+
for chunk in chunks:
|
|
494
|
+
yield chunk
|
|
495
|
+
return
|
|
496
|
+
|
|
492
497
|
buffer = ""
|
|
493
498
|
started = False
|
|
494
499
|
in_think = False
|
|
@@ -538,7 +543,23 @@ class StudentAdapterInference:
|
|
|
538
543
|
)
|
|
539
544
|
if not stripped.strip():
|
|
540
545
|
continue
|
|
541
|
-
|
|
546
|
+
stripped = stripped.lstrip()
|
|
547
|
+
if awaiting_initial_answer and re.match(
|
|
548
|
+
r"(?is)^(?:the user|okay[,,]?\s+the user|用户问|用户用|i need|i should|i'll|let me|since the user|we need)\b",
|
|
549
|
+
stripped,
|
|
550
|
+
):
|
|
551
|
+
paragraph_end = re.search(r"\n\s*\n", stripped)
|
|
552
|
+
if paragraph_end is None:
|
|
553
|
+
# Keep waiting for the end of the leading reasoning paragraph.
|
|
554
|
+
buffer = stripped[-4000:]
|
|
555
|
+
continue
|
|
556
|
+
buffer = stripped[paragraph_end.end():].lstrip()
|
|
557
|
+
awaiting_initial_answer = False
|
|
558
|
+
if not buffer.strip():
|
|
559
|
+
continue
|
|
560
|
+
else:
|
|
561
|
+
buffer = stripped
|
|
562
|
+
awaiting_initial_answer = False
|
|
542
563
|
started = True
|
|
543
564
|
|
|
544
565
|
if len(buffer) > len("<think>"):
|