mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
feat(voice): voice core — honest layer reach, spoken summary with on-screen card, narration, point-and-ask
- Voice layer manifest (src/voice/layerManifest.js) generates the layer enums, aliases and analyst field hints; a test fails when a catalog layer has no entry. - analyst_query validates fields, operators, units, values and scopes, counts up to 250,000 loaded records per layer, reports complete/partial/unanswered, tells LAYER_OFF / NOT_READY / FEED_UNAVAILABLE apart from zero, refuses mismatched follow-ups and never lets a superseded query replace follow-up memory. Seven more layers expose analyst records. - Tool results carry a code-built say line and a display card; the voice card in the mic control shows captions, plan steps and the result (src/voice/resultDisplay.js is the one adapter). Slow tools narrate their current step out of band; holding Space while the assistant talks cuts it off. Turn spans go to the debug log; caption transcription is metered; the log omits what was said unless GEV_VOICE_LOG_CONTENT=1. - Point and ask: the Space-down pointer snapshot resolves "this", "that one" and "around here" through 'pointer' targets, and "the second one" through referent:n from the last numbered list. - Prompt states each policy once (instructions + tools 48.5k -> 46.7k chars). - Voice benchmark harness (scripts/qa-voice-bench.mjs, scripts/voice-bench/) shares its phrase table and grading with scripts/qa-voice-routing.mjs.
This commit is contained in:
1 parent
e7707d9a0f
commit
a19897ad15
97 files changed
+14343
-755
No files matched your search
@@ -2,37 +2,51 @@ import { readRealtimeSource } from '../testSupport/readRealtimeSource.mjs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
import { responseInstructionForToolResult } from './realtimeProtocol.js';
|
||||
import { attachVoiceResult } from './speech.js';
|
||||
|
||||
const voiceConfig = readFileSync(new URL('../../server/providers/openai/instructions.js', import.meta.url), 'utf8');
|
||||
const voiceConfig = readFileSync(
|
||||
new URL('../../server/providers/openai/instructions.js', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const realtime = readRealtimeSource();
|
||||
|
||||
test('aircraft identity narration acknowledges missing enrichment', () => {
|
||||
const start = voiceConfig.indexOf("'For \"what is this aircraft?\" answers");
|
||||
const start = voiceConfig.indexOf('\'For "what is this aircraft?"');
|
||||
assert.ok(start >= 0, 'aircraft identity honesty instruction is missing');
|
||||
const text = voiceConfig.slice(start, voiceConfig.indexOf('\n', start));
|
||||
assert.match(text, /get_entity_context selected\.properties/);
|
||||
assert.match(text, /callsign, operator, registration, type, and route/);
|
||||
assert.match(text, /route, routeOrigin, and routeDestination as the only authoritative route fields/);
|
||||
assert.match(text, /Every aircraft identity answer MUST explicitly cover operator, type, and route/);
|
||||
assert.match(text, /repeat its endpoint codes exactly/);
|
||||
assert.match(text, /do not expand airport codes into city names/);
|
||||
assert.match(text, /"Operator details are unavailable"/);
|
||||
assert.match(text, /"Aircraft type is unavailable"/);
|
||||
assert.match(text, /"Route details are unavailable"/);
|
||||
assert.match(text, /never silently omit missing enrichment/i);
|
||||
assert.match(text, /never .* infer it from the callsign/i);
|
||||
assert.match(text, /get_entity_context/);
|
||||
assert.match(
|
||||
text,
|
||||
/callsign, operator, type and route from returned fields only/,
|
||||
);
|
||||
assert.match(text, /airport codes verbatim/);
|
||||
assert.match(text, /Never infer them from the callsign/);
|
||||
assert.match(
|
||||
text,
|
||||
/Other questions about the selection answer from its properties/,
|
||||
);
|
||||
|
||||
const followupStart = realtime.indexOf("if (result?.action === 'get_entity_context')");
|
||||
const followupEnd = realtime.indexOf("if (result?.action === 'get_current_view_state')", followupStart);
|
||||
assert.ok(followupStart >= 0 && followupEnd > followupStart, 'entity-context follow-up instruction is missing');
|
||||
const followup = realtime.slice(followupStart, followupEnd);
|
||||
assert.match(followup, /selectedLayerId === 'flights' \|\| selectedLayerId === 'military'/);
|
||||
assert.match(followup, /Begin with the returned callsign and include the returned registration when available/);
|
||||
assert.match(followup, /explicitly cover operator, aircraft type, and route before finishing/);
|
||||
assert.match(followup, /selectedProperties\.operator/);
|
||||
assert.match(followup, /selectedProperties\.type/);
|
||||
assert.match(followup, /selectedProperties\.route \|\|\s*selectedProperties\.routeOrigin \|\|\s*selectedProperties\.routeDestination/);
|
||||
assert.match(followup, /Operator details are unavailable/);
|
||||
assert.match(followup, /Aircraft type is unavailable/);
|
||||
assert.match(followup, /Route details are unavailable/);
|
||||
// The follow-up now speaks the code-built say line, which always covers
|
||||
// operator, type and route (see speech.test.mjs for the field rules).
|
||||
const followup = responseInstructionForToolResult(
|
||||
attachVoiceResult('get_entity_context', {
|
||||
ok: true,
|
||||
action: 'get_entity_context',
|
||||
selected: {
|
||||
id: 'a',
|
||||
layerId: 'flights',
|
||||
properties: { callsign: 'UAL1' },
|
||||
},
|
||||
}),
|
||||
);
|
||||
assert.match(
|
||||
followup,
|
||||
/asked what the aircraft is, speak its identityLine and add nothing/,
|
||||
);
|
||||
assert.match(
|
||||
followup,
|
||||
/any other question \(altitude, speed, registration\), answer from selected\.properties/,
|
||||
);
|
||||
assert.doesNotMatch(realtime, /Operator details are unavailable/);
|
||||
});
|
||||
Reference in new issue
Block a user