aboutsummaryrefslogtreecommitdiff
path: root/modules/core/model-profiles.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-11-02 20:46:18 +0100
committerAgando <roduccomm@gmail.com>2026-11-02 20:46:18 +0100
commit8d5c4b563da5225c4dbd2a44c7635c49a16d5ab1 (patch)
treede05cf1dcda3b45f131e4b1fca85421b49facdd6 /modules/core/model-profiles.js
parent89787327ec5aba15d11ca7bff2b4fb7f0df24a79 (diff)
feat(newos4): configurable tool budgets, per-model defaults, real tool telemetry
Closes STEER-20261102-201234-7200 (read_file capped at 6000 while the schema promised 32000). Budgets (module-aiassist.js + new modules/core/model-profiles.js): - one resolver, precedence: call arg -> loop profile -> model profile -> ai-config -> code default. Keys: read-page-chars, read-result-chars, tool-context-budget, fetch-chars. - model classes tiny/small/mid/large keyed by id then by size token; `mid` reproduces the old numbers so nothing moves for a mid model. - _clipToolResult now clips the `content` field, never the serialised JSON envelope: clipping the envelope deleted truncated/next_offset, so a 16k page lost its continuation pointer (the reported symptom). - read_file's schema no longer promises a number the model class may not give. Telemetry: - _recordToolCall keeps sizes, duration, ok and a 120-char arg preview instead of the whole payload; ai-stats prints calls / ok% / avg ms / KB / truncation from localStorage webx11_tool_stats. Server (api/proxy-api.py): - /api/extract honours max_chars (default 12000, cap 100000) and returns content_chars + truncated; the bs4 branch is no longer uncapped while the regex fallback was silently cut at 10 000. BEHAVIOUR CHANGE: num_ctx for a model with no explicit setting now follows the class default (16384 -> 65536 for cloud/large, 8192 for tiny). Cloud models bill on tokens used, so cost is unchanged; local models get a bigger KV cache. Two build traps, both now documented in AGENTS.md: - model-profiles.js must be the FIRST entry in CORE_FILES and must export the MODEL_PROFILES global (a TDZ typeof throws; without the global every budget silently falls back to the code default). - a budget key needs the setter branch AND the constructor loader at module-aiassist.js:49 - persisting without restoring looks fine until reload. Verified: test-tool-context-bound 23/23 (RED before: E1-E4 + TypeError on _toolLimit); live bundle read_page 32000 / result 12000 / budget 48000 / fetch 12000 and _ctxTokens 65536 for gemma4:31b-cloud; /api/extract 28970 -> 500 chars with truncated true; ai-config write+restore probe 6/6.
Diffstat (limited to 'modules/core/model-profiles.js')
-rw-r--r--modules/core/model-profiles.js43
1 files changed, 43 insertions, 0 deletions
diff --git a/modules/core/model-profiles.js b/modules/core/model-profiles.js
new file mode 100644
index 0000000..593378d
--- /dev/null
+++ b/modules/core/model-profiles.js
@@ -0,0 +1,43 @@
+// modules/core/model-profiles.js — per-model-class tool budgets.
+// class is picked by an ordered rule list; the FIRST match wins, so keep specific ids above size rules.
+const MODEL_CLASSES = {
+ tiny: { ctx: 8192, readPageChars: 4000, toolResultChars: 3000, toolContextBudgetChars: 12000, fetchChars: 3000 },
+ small: { ctx: 16384, readPageChars: 8000, toolResultChars: 4000, toolContextBudgetChars: 16000, fetchChars: 4000 },
+ mid: { ctx: 32768, readPageChars: 16000, toolResultChars: 6000, toolContextBudgetChars: 24000, fetchChars: 6000 },
+ large: { ctx: 65536, readPageChars: 32000, toolResultChars: 12000, toolContextBudgetChars: 48000, fetchChars: 12000 },
+};
+
+// Ordered: first match wins. Only ids we know; everything else falls through to the size parse.
+const MODEL_IDS = {
+ 'gemma4:31b-cloud': 'large',
+ 'qwen3:0.6b': 'tiny',
+ 'gemma2:2b': 'tiny',
+ 'llama3.1:8b': 'small',
+ 'gemma4:31b': 'mid',
+};
+
+// "235b" -> large, "31b" -> mid, "8b" -> small, "0.6b"/"2b" -> tiny
+function classFor(modelId) {
+ const id = String(modelId || '').toLowerCase();
+ if (!id) return 'mid';
+ if (MODEL_IDS[id]) return MODEL_IDS[id];
+ if (id.endsWith('-cloud') || id.includes(':cloud')) return 'large';
+ const m = id.match(/(\d+(?:\.\d+)?)\s*b\b/);
+ if (!m) return 'mid';
+ const p = parseFloat(m[1]);
+ if (p <= 3) return 'tiny';
+ if (p <= 9) return 'small';
+ if (p <= 32) return 'mid';
+ return 'large';
+}
+
+function profileFor(modelId) {
+ return MODEL_CLASSES[classFor(modelId)] || MODEL_CLASSES.mid;
+}
+
+// The bundle concatenates this file FIRST, so this binding exists before any module method runs.
+// module-aiassist/ollama-manager resolve budgets through it: a require() there resolved relative
+// to whoever called them and always returned null (found by the live ai-config probe 2026-11-02).
+const MODEL_PROFILES = { MODEL_CLASSES, classFor, profileFor };
+
+if (typeof module !== 'undefined' && module.exports) module.exports = { MODEL_CLASSES, classFor, profileFor, MODEL_PROFILES };