code wiki / (root) / nx_llm_route.nx

nx_llm_route.nx source

↩ module page · 156 lines · 7393 B

1// nx_llm_route.nx -- MIXED-LLM ROUTING (companionchat CC6, 2026-08-30): a DATA ROW can send a chat turn to a stronger 2// backend FIRST; the local encoder-LLM stays the fallback; and the reply records which one answered (model_route), so a 3// route is never silent and an absent backend is never a mystery. Operator ask: "the companion intelligent, use your mixed llm". 4// CONF llm_routes.conf, rows `tag|trigger|a.b.c.d|port`: the FIRST row whose trigger appears (case-insensitive) in the user 5// turn selects the backend; trigger `*` matches every turn (a default route). Comments `#` and blank lines ignored. The conf 6// is read PER TURN (data, no rebuild) from the daemon cwd, then from the nishihost knowledge tree. No conf, no match, or a 7// malformed row = NO ROUTE, never a guess. The backend must speak the same OpenAI-shape /v1/chat/completions the local 8// worker speaks; nothing here re-implements dispatch -- the orchestrator prepends the routed candidate to its own list. 9// Contract symbol: go_llm_route. Gate: nx_llm_route_gate (fixture confs under /tmp, neg-controls for no-match and no-conf). 10// license_tier: ORIGINAL 11import "nx_syscalls.nx" 12 13const LLR_CONF: *u8 = "llm_routes.conf" as *u8 14const LLR_CONF_ABS: *u8 = "/volume1/homes/elderwesto/nishihost/knowledge/llm_routes.conf" as *u8 15// the orchestrator's candidate arrays hold eight workers; prepending never pushes past that 16const LLR_MAXW: i64 = 8 17const LLR_TAG_CAP: i64 = 48 18const LLR_CH_PIPE: i64 = 124 19const LLR_CH_NL: i64 = 10 20const LLR_CH_CR: i64 = 13 21const LLR_CH_HASH: i64 = 35 22const LLR_CH_STAR: i64 = 42 23const LLR_CH_DOT: i64 = 46 24const LLR_CH_D0: i64 = 48 25const LLR_CH_D9: i64 = 57 26const LLR_PORT_MAX: i64 = 65535 27 28func llr_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 29func llr_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64 = off; var i: i64 = 0; while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 } return o } 30func llr_lower(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 31 32// case-insensitive substring: needle is a bounded slice of the conf buffer, hay is the user turn 33func llr_has_ci(hay: *u8, hn: i64, nd: *u8, nn: i64) -> i64 { 34 if nn <= 0 { return 0 } 35 var i: i64 = 0 36 while i + nn <= hn { 37 var j: i64 = 0 38 var same: i64 = 1 39 var scan: i64 = 1 40 while scan == 1 { 41 if j >= nn { scan = 0 } else { 42 if llr_lower(hay[i + j] as i64) != llr_lower(nd[j] as i64) { same = 0; scan = 0 } else { j = j + 1 } 43 } 44 } 45 if same == 1 { return 1 } 46 i = i + 1 47 } 48 return 0 49} 50 51// parse `a.b.c.d` then `|port` starting at s[i0..n): fills w[0..4], returns 1 when every field is a sane number 52func llr_parse_target(s: *u8, i0: i64, n: i64, w: *i64) -> i64 { 53 var i: i64 = i0 54 var f: i64 = 0 55 while f < 5 { 56 var v: i64 = 0 57 var digits: i64 = 0 58 var scan: i64 = 1 59 while scan == 1 { 60 if i >= n { scan = 0 } else { 61 let c: i64 = s[i] as i64 62 if c < LLR_CH_D0 { scan = 0 } else { if c > LLR_CH_D9 { scan = 0 } else { v = v * 10 + (c - LLR_CH_D0); digits = digits + 1; i = i + 1 } } 63 } 64 } 65 if digits == 0 { return 0 } 66 if f < 4 { if v > 255 { return 0 } } else { if v <= 0 { return 0 } if v > LLR_PORT_MAX { return 0 } } 67 w[f] = v 68 f = f + 1 69 if f < 5 { 70 var want: i64 = LLR_CH_DOT 71 if f == 4 { want = LLR_CH_PIPE } 72 if i >= n { return 0 } 73 if (s[i] as i64) != want { return 0 } 74 i = i + 1 75 } 76 } 77 return 1 78} 79 80// THE CORE, conf path explicit so a gate can drive it with fixtures. Returns 1 and fills out_tag/out_w on the first match. 81func go_llm_route_from(path: *u8, umsg: *u8, umlen: i64, out_tag: *u8, out_w: *i64) -> i64 { 82 out_tag[0] = 0 as u8 83 if umlen <= 0 { return 0 } 84 let lp: *i64 = sys_mmap(16) as *i64 85 lp[0] = 0 86 let b: *u8 = sys_read_file(path, lp) 87 if (b as i64) == 0 { return 0 } 88 let n: i64 = lp[0] 89 var i: i64 = 0 90 while i < n { 91 let ls: i64 = i 92 var eol: i64 = i 93 var scan: i64 = 1 94 while scan == 1 { if eol >= n { scan = 0 } else { if b[eol] == (LLR_CH_NL as u8) { scan = 0 } else { eol = eol + 1 } } } 95 var e2: i64 = eol 96 if e2 > ls { if b[e2 - 1] == (LLR_CH_CR as u8) { e2 = e2 - 1 } } 97 var usable: i64 = 1 98 if e2 == ls { usable = 0 } else { if b[ls] == (LLR_CH_HASH as u8) { usable = 0 } } 99 if usable == 1 { 100 // tag | trigger | a.b.c.d | port 101 var p1: i64 = ls 102 var s1: i64 = 1 103 while s1 == 1 { if p1 >= e2 { s1 = 0 } else { if b[p1] == (LLR_CH_PIPE as u8) { s1 = 0 } else { p1 = p1 + 1 } } } 104 if p1 < e2 { 105 var p2: i64 = p1 + 1 106 var s2: i64 = 1 107 while s2 == 1 { if p2 >= e2 { s2 = 0 } else { if b[p2] == (LLR_CH_PIPE as u8) { s2 = 0 } else { p2 = p2 + 1 } } } 108 if p2 < e2 { 109 let tlen: i64 = p2 - (p1 + 1) 110 var hit: i64 = 0 111 if tlen == 1 { if b[p1 + 1] == (LLR_CH_STAR as u8) { hit = 1 } } 112 if hit == 0 { hit = llr_has_ci(umsg, umlen, ((b as i64) + p1 + 1) as *u8, tlen) } 113 if hit == 1 { 114 let w: *i64 = sys_mmap(8 * 8) as *i64 115 if llr_parse_target(b, p2 + 1, e2, w) == 1 { 116 var k: i64 = 0 117 while k < 5 { out_w[k] = w[k]; k = k + 1 } 118 var t: i64 = 0 119 while t < (p1 - ls) { if t < LLR_TAG_CAP - 1 { out_tag[t] = b[ls + t] } t = t + 1 } 120 if t > LLR_TAG_CAP - 1 { t = LLR_TAG_CAP - 1 } 121 out_tag[t] = 0 as u8 122 return 1 123 } 124 } 125 } 126 } 127 } 128 i = eol + 1 129 } 130 return 0 131} 132 133// the daemon-facing wrapper: cwd conf first (the daemon's own data dir), then the nishihost knowledge tree 134func go_llm_route(umsg: *u8, umlen: i64, out_tag: *u8, out_w: *i64) -> i64 { 135 if go_llm_route_from(LLR_CONF, umsg, umlen, out_tag, out_w) == 1 { return 1 } 136 return go_llm_route_from(LLR_CONF_ABS, umsg, umlen, out_tag, out_w) 137} 138 139// put the routed backend FIRST in the orchestrator's candidate arrays; the existing candidates keep their order behind it 140func go_llm_route_prepend(rw: *i64, wa: *i64, wb: *i64, wc: *i64, wd: *i64, wpa: *i64, wcount: i64) -> i64 { 141 var n: i64 = wcount 142 if n > LLR_MAXW - 1 { n = LLR_MAXW - 1 } 143 var k: i64 = n 144 while k > 0 { wa[k] = wa[k - 1]; wb[k] = wb[k - 1]; wc[k] = wc[k - 1]; wd[k] = wd[k - 1]; wpa[k] = wpa[k - 1]; k = k - 1 } 145 wa[0] = rw[0]; wb[0] = rw[1]; wc[0] = rw[2]; wd[0] = rw[3]; wpa[0] = rw[4] 146 return n + 1 147} 148 149// PROVENANCE for the reply: which backend answered. rwin = index of the winning candidate, -1 = none answered. 150func go_llm_route_label(routed: i64, rwin: i64, tag: *u8, dst: *u8, off: i64) -> i64 { 151 if routed == 0 { return llr_cat(dst, off, "local" as *u8) } 152 var o: i64 = llr_cat(dst, off, tag) 153 if rwin == 0 { return o } 154 if rwin > 0 { return llr_cat(dst, o, "-fallback-local" as *u8) } 155 return llr_cat(dst, o, "-unanswered" as *u8) 156}