File size: 4,168 Bytes
29393b6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | /**
* WO-2 Pipeline 1, T4 — Amazon Bedrock Converse routing.
*
* Takes the final transcript from transcribeBridge.mjs (T3) and calls
* Amazon Bedrock's Converse API to generate EVE's response text. Model ID
* defaults to amazon.nova-pro-v1:0 per the CAT-5 protocol's Pipeline 1
* routing table — this is a data-plane model choice (which LLM answers
* EVE's conversation), unrelated to which model is driving this Kiro
* session.
*
* Credentials: retrieved via the AMANDA vault MCP (`get_key("aws")`) at
* call time, never hardcoded. Same pattern as transcribeBridge.mjs.
*/
export const DEFAULT_MODEL_ID = "amazon.nova-pro-v1:0";
export const SUPERVISOR_SYSTEM_PROMPT = `You are EVE, a real-time digital human companion.
Respond in 1-2 sentences. Be warm, present, and conversational.
Never describe your own actions. Never say "As an AI."`;
/**
* Calls Bedrock's Converse API with one transcript turn and returns the
* response text. `client` is injected (not constructed internally) so
* tests can supply a mock without real AWS credentials — the production
* caller in run.mjs constructs a real `BedrockRuntimeClient`.
*
* Kept as a plain function taking a pre-built client, rather than a class
* that also owns client construction, so T7's later Nemotron-Flash swap
* (a different provider entirely) doesn't need to inherit or fight this
* class's AWS-specific constructor — see the "swap points" design goal in
* pipeline-1-aws-native.md.
*/
export async function routeToConverse({ client, ConverseCommand, transcript, modelId }) {
const trimmed = String(transcript ?? "").trim();
if (!trimmed) {
return { text: "", modelId: modelId ?? DEFAULT_MODEL_ID };
}
const response = await client.send(
new ConverseCommand({
modelId: modelId ?? DEFAULT_MODEL_ID,
messages: [{ role: "user", content: [{ text: trimmed }] }],
system: [{ text: SUPERVISOR_SYSTEM_PROMPT }],
}),
);
const text = response.output?.message?.content?.[0]?.text ?? "";
return { text, modelId: modelId ?? DEFAULT_MODEL_ID };
}
/**
* WO-2 T4 — the full T2 -> T3 -> T4 orchestration for one utterance.
*
* Drains the given AudioSession, runs it through Transcribe (emitting
* partials via `onPartial`), and if a non-empty final transcript results,
* routes it to Bedrock Converse. Returns a `TurnComplete`-shaped result
* whether or not the AWS calls are real or mocked — the caller (run.mjs)
* is responsible for broadcasting it and for real timestamp logging.
*
* All AWS SDK classes are injected so this function has no hard
* dependency on live credentials to be tested. The real caller in run.mjs
* passes the real `TranscribeStreamingClient` / `StartStreamTranscriptionCommand`
* / `BedrockRuntimeClient` / `ConverseCommand` from the AWS SDK, constructed
* with a real vault-provided credential set.
*/
export async function runTurn({
session,
onPartial,
transcribeUtterance,
TranscribeStreamingClient,
StartStreamTranscriptionCommand,
transcribeClientConfig,
bedrockClient,
ConverseCommand,
modelId,
sampleRateHz = 16_000,
}) {
const t0 = Date.now();
const samples = session.drain();
const tRingWrite = Date.now() - t0; // "ring-write" timestamp per tasks.md's naming, though Pipeline 1 has no real ring — this marks when the buffered audio was handed off.
const { text: transcript } = await transcribeUtterance({
samples,
sampleRateHz,
onPartial,
TranscribeStreamingClient,
StartStreamTranscriptionCommand,
clientConfig: transcribeClientConfig,
});
const tTranscribeFinal = Date.now() - t0;
if (!transcript.trim()) {
return {
turnComplete: false,
transcript: "",
reply: "",
timestamps: { tRingWrite, tTranscribeFinal, tTurnComplete: null },
};
}
const { text: reply, modelId: usedModel } = await routeToConverse({
client: bedrockClient,
ConverseCommand,
transcript,
modelId,
});
const tTurnComplete = Date.now() - t0;
return {
turnComplete: true,
transcript,
reply,
modelId: usedModel,
timestamps: { tRingWrite, tTranscribeFinal, tTurnComplete },
};
}
|