@absolutejs/voice 0.0.22-beta.480 → 0.0.22-beta.482
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/index.d.ts +4 -0
- package/dist/index.js +181 -4
- package/dist/llmJudge.d.ts +45 -0
- package/dist/redaction.d.ts +13 -0
- package/dist/testing/index.js +8 -4
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,10 @@ export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, crea
|
|
|
71
71
|
export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "./agent";
|
|
72
72
|
export { createAIVoiceModel } from "./aiVoiceModel";
|
|
73
73
|
export type { CreateAIVoiceModelOptions } from "./aiVoiceModel";
|
|
74
|
+
export { createVoiceAIJudgeCompletion, createVoiceLLMJudge, } from "./llmJudge";
|
|
75
|
+
export type { CreateVoiceAIJudgeCompletionOptions, CreateVoiceLLMJudgeOptions, VoiceLLMJudge, VoiceLLMJudgeCompletion, VoiceLLMJudgeCriterionVerdict, VoiceLLMJudgeInput, VoiceLLMJudgeRubric, VoiceLLMJudgeRubricCriterion, VoiceLLMJudgeVerdict, } from "./llmJudge";
|
|
76
|
+
export { DEFAULT_VOICE_REDACTION_PATTERNS, createVoiceTranscriptRedactor, redactVoiceTranscript, } from "./redaction";
|
|
77
|
+
export type { CreateVoiceTranscriptRedactorOptions, VoiceRedactionPattern, VoiceTranscriptRedactor, } from "./redaction";
|
|
74
78
|
export { DEFAULT_VOICE_PRICE_BOOK, createVoiceCostAccountant, } from "./costAccounting";
|
|
75
79
|
export type { CreateVoiceCostAccountantOptions, VoiceCostAccountant, VoiceCostBreakdown, VoiceCostLLMRecord, VoiceCostSTTRecord, VoiceCostTTSRecord, VoiceCostTelephonyRecord, VoicePriceBook, VoiceProviderRates, } from "./costAccounting";
|
|
76
80
|
export { createMonologueAMDDetector } from "./amdDetector";
|
package/dist/index.js
CHANGED
|
@@ -4505,11 +4505,13 @@ var createVoiceSession = (options) => {
|
|
|
4505
4505
|
fallbackSession.on("final", ({ transcript }) => {
|
|
4506
4506
|
fallbackFinalReceived = true;
|
|
4507
4507
|
lastFallbackTranscriptAt = Date.now();
|
|
4508
|
-
|
|
4508
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
4509
|
+
fallbackTranscripts.push(cloneTranscript(next));
|
|
4509
4510
|
}),
|
|
4510
4511
|
fallbackSession.on("partial", ({ transcript }) => {
|
|
4511
4512
|
lastFallbackTranscriptAt = Date.now();
|
|
4512
|
-
|
|
4513
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
4514
|
+
fallbackTranscripts.push(cloneTranscript(next));
|
|
4513
4515
|
}),
|
|
4514
4516
|
fallbackSession.on("endOfTurn", () => {
|
|
4515
4517
|
fallbackEndOfTurnReceived = true;
|
|
@@ -4827,10 +4829,12 @@ var createVoiceSession = (options) => {
|
|
|
4827
4829
|
});
|
|
4828
4830
|
};
|
|
4829
4831
|
openedSession.on("partial", ({ transcript }) => {
|
|
4830
|
-
|
|
4832
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
4833
|
+
runAdapterEvent("adapter.partial", () => handlePartial(next));
|
|
4831
4834
|
});
|
|
4832
4835
|
openedSession.on("final", ({ transcript }) => {
|
|
4833
|
-
|
|
4836
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
4837
|
+
runAdapterEvent("adapter.final", () => handleFinal(next));
|
|
4834
4838
|
});
|
|
4835
4839
|
openedSession.on("endOfTurn", ({ reason }) => {
|
|
4836
4840
|
runAdapterEvent("adapter.endOfTurn", async () => {
|
|
@@ -34977,6 +34981,174 @@ var createAIVoiceModel = (options) => ({
|
|
|
34977
34981
|
return output;
|
|
34978
34982
|
}
|
|
34979
34983
|
});
|
|
34984
|
+
// src/llmJudge.ts
|
|
34985
|
+
var DEFAULT_SYSTEM_PROMPT = "You are an impartial evaluator scoring a voice-agent transcript against a rubric. " + "For each criterion, decide pass/fail and give a one-sentence rationale grounded in the transcript. " + 'Respond with strict JSON: {"criteria":[{"criterionId":"\u2026","passed":true,"rationale":"\u2026"}],"summary":"\u2026"}.';
|
|
34986
|
+
var buildPrompt = (rubric, input) => {
|
|
34987
|
+
const criteriaBlock = rubric.criteria.map((criterion) => `- ${criterion.id}${criterion.required ? " (required)" : ""}: ${criterion.description}`).join(`
|
|
34988
|
+
`);
|
|
34989
|
+
const metadataBlock = input.metadata ? `
|
|
34990
|
+
Metadata:
|
|
34991
|
+
${JSON.stringify(input.metadata, null, 2)}
|
|
34992
|
+
` : "";
|
|
34993
|
+
return `Rubric criteria:
|
|
34994
|
+
${criteriaBlock}
|
|
34995
|
+
${metadataBlock}
|
|
34996
|
+
Transcript:
|
|
34997
|
+
${input.transcript}
|
|
34998
|
+
|
|
34999
|
+
Return JSON only.`;
|
|
35000
|
+
};
|
|
35001
|
+
var extractJson = (raw) => {
|
|
35002
|
+
const trimmed = raw.trim();
|
|
35003
|
+
if (!trimmed) {
|
|
35004
|
+
throw new Error("LLM judge returned an empty response");
|
|
35005
|
+
}
|
|
35006
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(trimmed);
|
|
35007
|
+
const candidate = fenced ? fenced[1].trim() : trimmed;
|
|
35008
|
+
try {
|
|
35009
|
+
return JSON.parse(candidate);
|
|
35010
|
+
} catch {
|
|
35011
|
+
const start = candidate.indexOf("{");
|
|
35012
|
+
const end = candidate.lastIndexOf("}");
|
|
35013
|
+
if (start >= 0 && end > start) {
|
|
35014
|
+
return JSON.parse(candidate.slice(start, end + 1));
|
|
35015
|
+
}
|
|
35016
|
+
throw new Error(`LLM judge response was not valid JSON: ${raw.slice(0, 200)}`);
|
|
35017
|
+
}
|
|
35018
|
+
};
|
|
35019
|
+
var parseCriteria = (payload, rubric) => {
|
|
35020
|
+
if (!payload || typeof payload !== "object") {
|
|
35021
|
+
throw new Error("LLM judge response is not a JSON object");
|
|
35022
|
+
}
|
|
35023
|
+
const root = payload;
|
|
35024
|
+
const criteriaRaw = root.criteria;
|
|
35025
|
+
if (!Array.isArray(criteriaRaw)) {
|
|
35026
|
+
throw new Error("LLM judge response is missing the 'criteria' array");
|
|
35027
|
+
}
|
|
35028
|
+
const verdictById = new Map;
|
|
35029
|
+
for (const entry of criteriaRaw) {
|
|
35030
|
+
if (!entry || typeof entry !== "object") {
|
|
35031
|
+
continue;
|
|
35032
|
+
}
|
|
35033
|
+
const record = entry;
|
|
35034
|
+
const criterionId = typeof record.criterionId === "string" ? record.criterionId : typeof record.id === "string" ? record.id : undefined;
|
|
35035
|
+
if (!criterionId) {
|
|
35036
|
+
continue;
|
|
35037
|
+
}
|
|
35038
|
+
verdictById.set(criterionId, {
|
|
35039
|
+
criterionId,
|
|
35040
|
+
passed: record.passed === true,
|
|
35041
|
+
rationale: typeof record.rationale === "string" ? record.rationale : ""
|
|
35042
|
+
});
|
|
35043
|
+
}
|
|
35044
|
+
const criteria = rubric.criteria.map((criterion) => verdictById.get(criterion.id) ?? {
|
|
35045
|
+
criterionId: criterion.id,
|
|
35046
|
+
passed: false,
|
|
35047
|
+
rationale: "Judge did not return a verdict for this criterion."
|
|
35048
|
+
});
|
|
35049
|
+
return {
|
|
35050
|
+
criteria,
|
|
35051
|
+
summary: typeof root.summary === "string" ? root.summary : undefined
|
|
35052
|
+
};
|
|
35053
|
+
};
|
|
35054
|
+
var scoreVerdict = (rubric, criteria) => {
|
|
35055
|
+
const totalWeight = rubric.criteria.reduce((sum, criterion) => sum + (criterion.weight ?? 1), 0);
|
|
35056
|
+
if (totalWeight === 0) {
|
|
35057
|
+
return { passed: false, score: 0 };
|
|
35058
|
+
}
|
|
35059
|
+
const weightById = new Map(rubric.criteria.map((criterion) => [criterion.id, criterion.weight ?? 1]));
|
|
35060
|
+
const requiredIds = new Set(rubric.criteria.filter((criterion) => criterion.required).map((criterion) => criterion.id));
|
|
35061
|
+
let earned = 0;
|
|
35062
|
+
let allRequiredPassed = true;
|
|
35063
|
+
for (const verdict of criteria) {
|
|
35064
|
+
if (verdict.passed) {
|
|
35065
|
+
earned += weightById.get(verdict.criterionId) ?? 1;
|
|
35066
|
+
} else if (requiredIds.has(verdict.criterionId)) {
|
|
35067
|
+
allRequiredPassed = false;
|
|
35068
|
+
}
|
|
35069
|
+
}
|
|
35070
|
+
const score = earned / totalWeight;
|
|
35071
|
+
const minPassScore = rubric.minPassScore ?? 1;
|
|
35072
|
+
return {
|
|
35073
|
+
passed: allRequiredPassed && score >= minPassScore,
|
|
35074
|
+
score
|
|
35075
|
+
};
|
|
35076
|
+
};
|
|
35077
|
+
var createVoiceLLMJudge = (options) => ({
|
|
35078
|
+
evaluate: async (input) => {
|
|
35079
|
+
const prompt = buildPrompt(options.rubric, input);
|
|
35080
|
+
const raw = await options.completion({
|
|
35081
|
+
prompt,
|
|
35082
|
+
systemPrompt: options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
|
|
35083
|
+
});
|
|
35084
|
+
const parsed = parseCriteria(extractJson(raw), options.rubric);
|
|
35085
|
+
const { passed, score } = scoreVerdict(options.rubric, parsed.criteria);
|
|
35086
|
+
return {
|
|
35087
|
+
criteria: parsed.criteria,
|
|
35088
|
+
passed,
|
|
35089
|
+
score,
|
|
35090
|
+
summary: parsed.summary
|
|
35091
|
+
};
|
|
35092
|
+
},
|
|
35093
|
+
rubric: options.rubric
|
|
35094
|
+
});
|
|
35095
|
+
var createVoiceAIJudgeCompletion = (options) => async ({ prompt, systemPrompt }) => {
|
|
35096
|
+
const messages = [
|
|
35097
|
+
{ content: prompt, role: "user" }
|
|
35098
|
+
];
|
|
35099
|
+
const stream = options.provider.stream({
|
|
35100
|
+
messages,
|
|
35101
|
+
model: options.model,
|
|
35102
|
+
systemPrompt
|
|
35103
|
+
});
|
|
35104
|
+
let buffered = "";
|
|
35105
|
+
for await (const chunk of stream) {
|
|
35106
|
+
if (chunk.type === "text") {
|
|
35107
|
+
buffered += chunk.content;
|
|
35108
|
+
}
|
|
35109
|
+
}
|
|
35110
|
+
return buffered;
|
|
35111
|
+
};
|
|
35112
|
+
// src/redaction.ts
|
|
35113
|
+
var DEFAULT_VOICE_REDACTION_PATTERNS = [
|
|
35114
|
+
{
|
|
35115
|
+
label: "credit-card",
|
|
35116
|
+
regex: /\b(?:\d[ -]?){13,19}\b/g,
|
|
35117
|
+
replacement: "[REDACTED:CC]"
|
|
35118
|
+
},
|
|
35119
|
+
{
|
|
35120
|
+
label: "ssn",
|
|
35121
|
+
regex: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
35122
|
+
replacement: "[REDACTED:SSN]"
|
|
35123
|
+
},
|
|
35124
|
+
{
|
|
35125
|
+
label: "email",
|
|
35126
|
+
regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
35127
|
+
replacement: "[REDACTED:EMAIL]"
|
|
35128
|
+
},
|
|
35129
|
+
{
|
|
35130
|
+
label: "phone",
|
|
35131
|
+
regex: /\b(?:\+?1[ .-]?)?\(?\d{3}\)?[ .-]?\d{3}[ .-]?\d{4}\b/g,
|
|
35132
|
+
replacement: "[REDACTED:PHONE]"
|
|
35133
|
+
}
|
|
35134
|
+
];
|
|
35135
|
+
var createVoiceTranscriptRedactor = (options = {}) => {
|
|
35136
|
+
const patterns = options.patterns ?? DEFAULT_VOICE_REDACTION_PATTERNS;
|
|
35137
|
+
return (transcript) => {
|
|
35138
|
+
if (!transcript.text) {
|
|
35139
|
+
return transcript;
|
|
35140
|
+
}
|
|
35141
|
+
let redacted = transcript.text;
|
|
35142
|
+
for (const pattern of patterns) {
|
|
35143
|
+
redacted = redacted.replace(pattern.regex, pattern.replacement ?? `[REDACTED:${pattern.label.toUpperCase()}]`);
|
|
35144
|
+
}
|
|
35145
|
+
if (redacted === transcript.text) {
|
|
35146
|
+
return transcript;
|
|
35147
|
+
}
|
|
35148
|
+
return { ...transcript, text: redacted };
|
|
35149
|
+
};
|
|
35150
|
+
};
|
|
35151
|
+
var redactVoiceTranscript = (transcript, patterns = DEFAULT_VOICE_REDACTION_PATTERNS) => createVoiceTranscriptRedactor({ patterns })(transcript);
|
|
34980
35152
|
// src/costAccounting.ts
|
|
34981
35153
|
var DEFAULT_VOICE_PRICE_BOOK = {
|
|
34982
35154
|
"anthropic:claude-opus-4-5": {
|
|
@@ -45884,6 +46056,7 @@ export {
|
|
|
45884
46056
|
renderVoiceAuditHTML,
|
|
45885
46057
|
renderVoiceAuditDeliveryHTML,
|
|
45886
46058
|
renderVoiceAssistantHealthHTML,
|
|
46059
|
+
redactVoiceTranscript,
|
|
45887
46060
|
redactVoiceTraceText,
|
|
45888
46061
|
redactVoiceTraceEvents,
|
|
45889
46062
|
redactVoiceTraceEvent,
|
|
@@ -46005,6 +46178,7 @@ export {
|
|
|
46005
46178
|
createVoiceTurnLatencyJSONHandler,
|
|
46006
46179
|
createVoiceTurnLatencyHTMLHandler,
|
|
46007
46180
|
createVoiceTransferCallTool,
|
|
46181
|
+
createVoiceTranscriptRedactor,
|
|
46008
46182
|
createVoiceTraceTimelineRoutes,
|
|
46009
46183
|
createVoiceTraceSinkStore,
|
|
46010
46184
|
createVoiceTraceSinkDeliveryWorkerLoop,
|
|
@@ -46206,6 +46380,7 @@ export {
|
|
|
46206
46380
|
createVoiceLinearIssueUpdateSink,
|
|
46207
46381
|
createVoiceLinearIssueSyncSinks,
|
|
46208
46382
|
createVoiceLinearIssueSink,
|
|
46383
|
+
createVoiceLLMJudge,
|
|
46209
46384
|
createVoiceIntegrationSinkWorkerLoop,
|
|
46210
46385
|
createVoiceIntegrationSinkWorker,
|
|
46211
46386
|
createVoiceIntegrationHTTPSink,
|
|
@@ -46301,6 +46476,7 @@ export {
|
|
|
46301
46476
|
createVoiceAgentTool,
|
|
46302
46477
|
createVoiceAgentSquad,
|
|
46303
46478
|
createVoiceAgent,
|
|
46479
|
+
createVoiceAIJudgeCompletion,
|
|
46304
46480
|
createTwilioVoiceRoutes,
|
|
46305
46481
|
createTwilioVoiceResponse,
|
|
46306
46482
|
createTwilioMediaStreamBridge,
|
|
@@ -46475,6 +46651,7 @@ export {
|
|
|
46475
46651
|
acknowledgeVoiceMonitorIssue,
|
|
46476
46652
|
VOICE_LIVE_OPS_ACTIONS,
|
|
46477
46653
|
TURN_PROFILE_DEFAULTS,
|
|
46654
|
+
DEFAULT_VOICE_REDACTION_PATTERNS,
|
|
46478
46655
|
DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS,
|
|
46479
46656
|
DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS,
|
|
46480
46657
|
DEFAULT_VOICE_PRICE_BOOK
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AIProviderConfig } from "@absolutejs/ai";
|
|
2
|
+
export type VoiceLLMJudgeRubricCriterion = {
|
|
3
|
+
description: string;
|
|
4
|
+
id: string;
|
|
5
|
+
required?: boolean;
|
|
6
|
+
weight?: number;
|
|
7
|
+
};
|
|
8
|
+
export type VoiceLLMJudgeRubric = {
|
|
9
|
+
criteria: VoiceLLMJudgeRubricCriterion[];
|
|
10
|
+
minPassScore?: number;
|
|
11
|
+
};
|
|
12
|
+
export type VoiceLLMJudgeCriterionVerdict = {
|
|
13
|
+
criterionId: string;
|
|
14
|
+
passed: boolean;
|
|
15
|
+
rationale: string;
|
|
16
|
+
};
|
|
17
|
+
export type VoiceLLMJudgeVerdict = {
|
|
18
|
+
criteria: VoiceLLMJudgeCriterionVerdict[];
|
|
19
|
+
passed: boolean;
|
|
20
|
+
score: number;
|
|
21
|
+
summary?: string;
|
|
22
|
+
};
|
|
23
|
+
export type VoiceLLMJudgeInput = {
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
transcript: string;
|
|
26
|
+
};
|
|
27
|
+
export type VoiceLLMJudgeCompletion = (input: {
|
|
28
|
+
prompt: string;
|
|
29
|
+
systemPrompt?: string;
|
|
30
|
+
}) => Promise<string>;
|
|
31
|
+
export type CreateVoiceLLMJudgeOptions = {
|
|
32
|
+
completion: VoiceLLMJudgeCompletion;
|
|
33
|
+
rubric: VoiceLLMJudgeRubric;
|
|
34
|
+
systemPrompt?: string;
|
|
35
|
+
};
|
|
36
|
+
export type VoiceLLMJudge = {
|
|
37
|
+
evaluate: (input: VoiceLLMJudgeInput) => Promise<VoiceLLMJudgeVerdict>;
|
|
38
|
+
rubric: VoiceLLMJudgeRubric;
|
|
39
|
+
};
|
|
40
|
+
export declare const createVoiceLLMJudge: (options: CreateVoiceLLMJudgeOptions) => VoiceLLMJudge;
|
|
41
|
+
export type CreateVoiceAIJudgeCompletionOptions = {
|
|
42
|
+
model: string;
|
|
43
|
+
provider: AIProviderConfig;
|
|
44
|
+
};
|
|
45
|
+
export declare const createVoiceAIJudgeCompletion: (options: CreateVoiceAIJudgeCompletionOptions) => VoiceLLMJudgeCompletion;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Transcript } from "./types";
|
|
2
|
+
export type VoiceRedactionPattern = {
|
|
3
|
+
label: string;
|
|
4
|
+
replacement?: string;
|
|
5
|
+
regex: RegExp;
|
|
6
|
+
};
|
|
7
|
+
export declare const DEFAULT_VOICE_REDACTION_PATTERNS: VoiceRedactionPattern[];
|
|
8
|
+
export type VoiceTranscriptRedactor = (transcript: Transcript) => Transcript;
|
|
9
|
+
export type CreateVoiceTranscriptRedactorOptions = {
|
|
10
|
+
patterns?: VoiceRedactionPattern[];
|
|
11
|
+
};
|
|
12
|
+
export declare const createVoiceTranscriptRedactor: (options?: CreateVoiceTranscriptRedactorOptions) => VoiceTranscriptRedactor;
|
|
13
|
+
export declare const redactVoiceTranscript: (transcript: Transcript, patterns?: VoiceRedactionPattern[]) => Transcript;
|
package/dist/testing/index.js
CHANGED
|
@@ -6473,11 +6473,13 @@ var createVoiceSession = (options) => {
|
|
|
6473
6473
|
fallbackSession.on("final", ({ transcript }) => {
|
|
6474
6474
|
fallbackFinalReceived = true;
|
|
6475
6475
|
lastFallbackTranscriptAt = Date.now();
|
|
6476
|
-
|
|
6476
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
6477
|
+
fallbackTranscripts.push(cloneTranscript(next));
|
|
6477
6478
|
}),
|
|
6478
6479
|
fallbackSession.on("partial", ({ transcript }) => {
|
|
6479
6480
|
lastFallbackTranscriptAt = Date.now();
|
|
6480
|
-
|
|
6481
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
6482
|
+
fallbackTranscripts.push(cloneTranscript(next));
|
|
6481
6483
|
}),
|
|
6482
6484
|
fallbackSession.on("endOfTurn", () => {
|
|
6483
6485
|
fallbackEndOfTurnReceived = true;
|
|
@@ -6795,10 +6797,12 @@ var createVoiceSession = (options) => {
|
|
|
6795
6797
|
});
|
|
6796
6798
|
};
|
|
6797
6799
|
openedSession.on("partial", ({ transcript }) => {
|
|
6798
|
-
|
|
6800
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
6801
|
+
runAdapterEvent("adapter.partial", () => handlePartial(next));
|
|
6799
6802
|
});
|
|
6800
6803
|
openedSession.on("final", ({ transcript }) => {
|
|
6801
|
-
|
|
6804
|
+
const next = options.redact ? options.redact(transcript) : transcript;
|
|
6805
|
+
runAdapterEvent("adapter.final", () => handleFinal(next));
|
|
6802
6806
|
});
|
|
6803
6807
|
openedSession.on("endOfTurn", ({ reason }) => {
|
|
6804
6808
|
runAdapterEvent("adapter.endOfTurn", async () => {
|
package/dist/types.d.ts
CHANGED
|
@@ -730,6 +730,7 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
|
|
|
730
730
|
costTelephony?: {
|
|
731
731
|
provider?: string;
|
|
732
732
|
};
|
|
733
|
+
redact?: import("./redaction").VoiceTranscriptRedactor;
|
|
733
734
|
reconnect: Required<VoiceReconnectConfig>;
|
|
734
735
|
phraseHints?: VoicePhraseHint[];
|
|
735
736
|
sessionMetadata?: Record<string, unknown>;
|