Skip to content

Commit 3c2e1c7

Browse files
Merge pull request #186 from TheWizardsCode/ge-hch.9/plan
WIP: Runtime: integrate embeddings into engine (bge-hch.5.22)
2 parents ed8161a + f90c55d commit 3c2e1c7

19 files changed

Lines changed: 1202 additions & 867 deletions

.beads/issues.jsonl

Lines changed: 105 additions & 101 deletions
Large diffs are not rendered by default.

.beads/sync_base.jsonl

Lines changed: 9 additions & 3 deletions
Large diffs are not rendered by default.

.gengine/config.example.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ directorConfig:
3838
# Default director risk threshold used by the demo UI and Director when not set per-call
3939
# Value between 0.0 (strict) and 1.0 (lenient). Default: 0.4
4040
riskThreshold: 0.4
41+
42+
# Enable use of embeddings in the local GEngine environment (true/false)
43+
enableEmbeddings: true
4144

4245
# Notes:
4346
# - The proxy prefers CLI args, then environment variables, then this file.
4447
# - To expand settings, update scripts/cors-proxy.js and package.json.
45-

docs/dev/embeddings.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Embedding Integration (runtime)
2+
3+
Overview
4+
--------
5+
The Director can optionally use local semantic embeddings to improve scoring for thematic consistency, lore adherence, and character voice. This feature is opt-in and disabled by default.
6+
7+
Enabling
8+
--------
9+
- Set `enableEmbeddings: true` in `.gengine/config.yaml` under `directorConfig`, or pass `{ enableEmbeddings: true }` via the `evaluate()` `config` argument.
10+
- For Node integration tests you may set environment flags (used by embedding service): `EMBED_NODE=1` to enable Node fallback.
11+
12+
Telemetry
13+
---------
14+
When embeddings are enabled, Director emits embedding telemetry inside the `director_decision` event under `metrics.embedding` with fields:
15+
- `used` (boolean) - whether embeddings were successfully computed
16+
- `latencyMs` (number) - inference time in milliseconds
17+
- `fallback` (boolean) - true when embeddings were not used and placeholders were applied
18+
- `metrics` (optional object) - similarity metrics (0..1) for `thematic`, `lore`, and `voice` when available
19+
20+
Implementation notes
21+
--------------------
22+
- `evaluate()` computes embeddings asynchronously (using `web/demo/js/embedding-service.js` when available) and derives similarity metrics when story-level embeddings are provided on `storyContext` as `themeEmbedding`, `loreEmbedding`, `voiceEmbedding` arrays.
23+
- `computeRiskScore()` remains synchronous and reads precomputed `config.embeddingMetrics` (if present) to convert similarities into placeholder risks. This keeps the core scoring deterministic and testable.
24+
25+
Follow-ups
26+
----------
27+
1) Precompute story-level embeddings at load-time and attach them to story context. (bead created)
28+
2) Optionally emit a dedicated `embedding_inference` telemetry event in addition to including embedding metadata in director telemetry. (bead created)

package-lock.json

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6+
"postinstall": "npx playwright install --with-deps chromium",
67
"serve-demo": "http-server web",
78
"build": "echo 'no-op build'",
89
"validate-story": "node scripts/validate-story.js --glob \"web/stories/**/*.ink\" --output json --max-steps 2000",

src/runtime/director-config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ const defaults = {
7474

7575
placeholderDefault: 0.3,
7676

77+
// Enable embedding-based scoring in the runtime. Disabled by default.
78+
// Can be toggled via local .gengine/config.yaml or environment overrides
79+
// (see loadLocalConfig/ENV parsing in this file). When enabled the Director
80+
// will attempt to compute semantic embeddings for proposals and use
81+
// similarity-derived metrics for thematic/lore/voice scoring.
82+
enableEmbeddings: false,
83+
7784
// Global default decision threshold used by the Director when not overridden per-call
7885
// Value is in 0.0..1.0 where lower is stricter (default 0.4)
7986
riskThreshold: 0.4

tests/demo.telemetry.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ test('Director threshold slider updates stored settings', async ({ page }) => {
151151
await expect(page.locator('.ai-config-section')).toBeVisible();
152152

153153
const slider = page.locator('#director-risk-threshold');
154-
await expect(slider).toHaveValue('0.4');
154+
// Wait for defaults to hydrate from ApiKeyManager. Expect UI default (0.4)
155+
// regardless of server-provided director-config.
156+
await expect(slider).toHaveValue('0.4', { timeout: 5000 });
155157

156158
await slider.evaluate((el) => {
157159
(el as HTMLInputElement).value = '0.65';
@@ -161,8 +163,10 @@ test('Director threshold slider updates stored settings', async ({ page }) => {
161163

162164
await expect(page.locator('#director-threshold-value')).toHaveText('0.65');
163165

166+
// Allow change handler to persist before reading (input -> change is async for storage)
167+
await page.waitForTimeout(50);
164168
const saved = await page.evaluate(() => window.ApiKeyManager.getSettings().directorRiskThreshold);
165-
expect(saved).toBeCloseTo(0.65, 2);
169+
await expect(saved).toBeCloseTo(0.65, 2);
166170
});
167171

168172
test('invalid threshold input clamps to range', async ({ page }) => {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Custom Jest environment that strips Node's Web Storage accessors, which throw
2+
// unless `--localstorage-file` is provided (Node 25+). We temporarily remove the
3+
// accessors before loading `jest-environment-node` so Jest won't copy them into
4+
// the test context, then restore them for the rest of the process.
5+
6+
const keys = ['localStorage', 'sessionStorage'];
7+
const saved = [];
8+
9+
for (const key of keys) {
10+
const desc = Object.getOwnPropertyDescriptor(globalThis, key);
11+
if (desc && desc.configurable) {
12+
saved.push([key, desc]);
13+
try {
14+
delete globalThis[key];
15+
} catch (err) {
16+
// If delete fails, fall back to defining a harmless value.
17+
try {
18+
Object.defineProperty(globalThis, key, {
19+
value: undefined,
20+
writable: true,
21+
configurable: true,
22+
enumerable: desc.enumerable,
23+
});
24+
} catch (err2) {
25+
// ignore
26+
}
27+
}
28+
}
29+
}
30+
31+
const NodeEnvironment = require('jest-environment-node').TestEnvironment || require('jest-environment-node');
32+
33+
for (const [key, desc] of saved) {
34+
try {
35+
Object.defineProperty(globalThis, key, desc);
36+
} catch (err) {
37+
// ignore
38+
}
39+
}
40+
41+
module.exports = class NodeNoWebStorageEnvironment extends NodeEnvironment {};

tests/integration/embedding.integration.test.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
/** @jest-environment node */
2-
3-
/** @jest-environment node */
1+
/** @jest-environment ./tests/helpers/node-no-webstorage-environment.js */
42

53
// Optional integration test. Requires network + model download. Skip in CI by default.
64
// Set EMBED_NODE=1 to force Node fallback (no Worker) or INTEGRATION_EMBEDDING=1 for browser path.

0 commit comments

Comments
 (0)