@remnic/core 9.16.1 → 9.16.2
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/dist/access-cli.js +1 -1
- package/dist/{chunk-GRJ5UOFO.js → chunk-YHURQ3M5.js} +2 -2
- package/dist/chunk-YHURQ3M5.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/orchestrator.js +1 -1
- package/package.json +2 -2
- package/src/lcm-summarize-lane.test.ts +137 -0
- package/src/orchestrator.ts +1 -1
- package/dist/chunk-GRJ5UOFO.js.map +0 -1
package/dist/index.js
CHANGED
package/dist/orchestrator.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.16.
|
|
3
|
+
"version": "9.16.2",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3045,7 +3045,7 @@
|
|
|
3045
3045
|
"core"
|
|
3046
3046
|
],
|
|
3047
3047
|
"peerDependencies": {
|
|
3048
|
-
"@remnic/coding-graph": "^9.16.
|
|
3048
|
+
"@remnic/coding-graph": "^9.16.2"
|
|
3049
3049
|
},
|
|
3050
3050
|
"peerDependenciesMeta": {
|
|
3051
3051
|
"@remnic/coding-graph": {
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
import { parseConfig } from "./config.js";
|
|
9
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
10
|
+
|
|
11
|
+
type SummarizeFn = (text: string, targetTokens: number, aggressive: boolean) => Promise<string | null>;
|
|
12
|
+
|
|
13
|
+
interface LcmEngineTestSurface {
|
|
14
|
+
summarizeFn: SummarizeFn;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ChatStub {
|
|
18
|
+
url: string;
|
|
19
|
+
hits: () => number;
|
|
20
|
+
close: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Minimal OpenAI-compatible chat stub that records how often it served a
|
|
25
|
+
* completion. Both lanes get one so the test can observe which client the
|
|
26
|
+
* LCM summarize closure actually uses.
|
|
27
|
+
*/
|
|
28
|
+
async function startChatStub(label: string): Promise<ChatStub> {
|
|
29
|
+
let hits = 0;
|
|
30
|
+
const server = http.createServer((req, res) => {
|
|
31
|
+
let body = "";
|
|
32
|
+
req.on("data", (chunk) => {
|
|
33
|
+
body += chunk;
|
|
34
|
+
});
|
|
35
|
+
req.on("end", () => {
|
|
36
|
+
if (req.url?.endsWith("/chat/completions")) {
|
|
37
|
+
hits += 1;
|
|
38
|
+
res.setHeader("content-type", "application/json");
|
|
39
|
+
res.end(
|
|
40
|
+
JSON.stringify({
|
|
41
|
+
choices: [{ message: { role: "assistant", content: `summary from ${label}` }, finish_reason: "stop" }],
|
|
42
|
+
usage: { prompt_tokens: 10, completion_tokens: 4, total_tokens: 14 },
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Model detection / health probes: report a single model per lane.
|
|
48
|
+
res.setHeader("content-type", "application/json");
|
|
49
|
+
res.end(JSON.stringify({ data: [{ id: `${label}-model` }], object: "list" }));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
const listening = Promise.withResolvers<void>();
|
|
53
|
+
server.once("error", listening.reject);
|
|
54
|
+
server.listen(0, "127.0.0.1", listening.resolve);
|
|
55
|
+
try {
|
|
56
|
+
await listening.promise;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
server.close();
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
const address = server.address();
|
|
62
|
+
if (address === null || typeof address !== "object") {
|
|
63
|
+
server.close();
|
|
64
|
+
throw new Error("stub did not bind");
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
url: `http://127.0.0.1:${address.port}/v1`,
|
|
68
|
+
hits: () => hits,
|
|
69
|
+
close: () => {
|
|
70
|
+
const closed = Promise.withResolvers<void>();
|
|
71
|
+
server.close(() => closed.resolve());
|
|
72
|
+
return closed.promise;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test("LCM summarize uses the fast local-LLM lane when configured (latency contract)", async () => {
|
|
78
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-lcm-lane-"));
|
|
79
|
+
let mainStub: ChatStub | null = null;
|
|
80
|
+
let fastStub: ChatStub | null = null;
|
|
81
|
+
try {
|
|
82
|
+
mainStub = await startChatStub("main");
|
|
83
|
+
fastStub = await startChatStub("fast");
|
|
84
|
+
const config = parseConfig({
|
|
85
|
+
memoryDir,
|
|
86
|
+
qmdEnabled: false,
|
|
87
|
+
lcmEnabled: true,
|
|
88
|
+
localLlmEnabled: true,
|
|
89
|
+
localLlmUrl: mainStub.url,
|
|
90
|
+
localLlmModel: "main-model",
|
|
91
|
+
localLlmFastEnabled: true,
|
|
92
|
+
localLlmFastUrl: fastStub.url,
|
|
93
|
+
localLlmFastModel: "fast-model",
|
|
94
|
+
});
|
|
95
|
+
const orchestrator = new Orchestrator(config);
|
|
96
|
+
assert.ok(orchestrator.lcmEngine, "LCM engine must exist with lcmEnabled");
|
|
97
|
+
const summarizeFn = (orchestrator.lcmEngine as unknown as LcmEngineTestSurface).summarizeFn;
|
|
98
|
+
|
|
99
|
+
const result = await summarizeFn("A conversation segment worth compressing.", 64, false);
|
|
100
|
+
|
|
101
|
+
assert.equal(result, "summary from fast", "the summary must come from the fast lane");
|
|
102
|
+
assert.equal(fastStub.hits(), 1, "fast lane serves the LCM summarize call");
|
|
103
|
+
assert.equal(mainStub.hits(), 0, "the heavy main extraction lane must not serve lcm-summarize");
|
|
104
|
+
} finally {
|
|
105
|
+
await mainStub?.close();
|
|
106
|
+
await fastStub?.close();
|
|
107
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("LCM summarize falls back to the main client when the fast lane is disabled", async () => {
|
|
112
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-lcm-lane-fallback-"));
|
|
113
|
+
let mainStub: ChatStub | null = null;
|
|
114
|
+
try {
|
|
115
|
+
mainStub = await startChatStub("main");
|
|
116
|
+
const config = parseConfig({
|
|
117
|
+
memoryDir,
|
|
118
|
+
qmdEnabled: false,
|
|
119
|
+
lcmEnabled: true,
|
|
120
|
+
localLlmEnabled: true,
|
|
121
|
+
localLlmUrl: mainStub.url,
|
|
122
|
+
localLlmModel: "main-model",
|
|
123
|
+
localLlmFastEnabled: false,
|
|
124
|
+
});
|
|
125
|
+
const orchestrator = new Orchestrator(config);
|
|
126
|
+
assert.ok(orchestrator.lcmEngine, "LCM engine must exist with lcmEnabled");
|
|
127
|
+
const summarizeFn = (orchestrator.lcmEngine as unknown as LcmEngineTestSurface).summarizeFn;
|
|
128
|
+
|
|
129
|
+
const result = await summarizeFn("A conversation segment worth compressing.", 64, false);
|
|
130
|
+
|
|
131
|
+
assert.equal(result, "summary from main", "with no fast lane the main client serves the call");
|
|
132
|
+
assert.equal(mainStub.hits(), 1);
|
|
133
|
+
} finally {
|
|
134
|
+
await mainStub?.close();
|
|
135
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
});
|
package/src/orchestrator.ts
CHANGED
|
@@ -1724,7 +1724,7 @@ export class Orchestrator {
|
|
|
1724
1724
|
? { agentId: this.config.fastGatewayAgentId }
|
|
1725
1725
|
: gatewayTaskChainOptions(this.config)),
|
|
1726
1726
|
})
|
|
1727
|
-
: await this.
|
|
1727
|
+
: await this.fastLlm.chatCompletion(messages, {
|
|
1728
1728
|
maxTokens: targetTokens * 2,
|
|
1729
1729
|
operation: "lcm-summarize",
|
|
1730
1730
|
priority: "background",
|