@absolutejs/voice 0.0.22-beta.481 → 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 +2 -0
- package/dist/index.js +130 -0
- package/dist/llmJudge.d.ts +45 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ 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";
|
|
74
76
|
export { DEFAULT_VOICE_REDACTION_PATTERNS, createVoiceTranscriptRedactor, redactVoiceTranscript, } from "./redaction";
|
|
75
77
|
export type { CreateVoiceTranscriptRedactorOptions, VoiceRedactionPattern, VoiceTranscriptRedactor, } from "./redaction";
|
|
76
78
|
export { DEFAULT_VOICE_PRICE_BOOK, createVoiceCostAccountant, } from "./costAccounting";
|
package/dist/index.js
CHANGED
|
@@ -34981,6 +34981,134 @@ var createAIVoiceModel = (options) => ({
|
|
|
34981
34981
|
return output;
|
|
34982
34982
|
}
|
|
34983
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
|
+
};
|
|
34984
35112
|
// src/redaction.ts
|
|
34985
35113
|
var DEFAULT_VOICE_REDACTION_PATTERNS = [
|
|
34986
35114
|
{
|
|
@@ -46252,6 +46380,7 @@ export {
|
|
|
46252
46380
|
createVoiceLinearIssueUpdateSink,
|
|
46253
46381
|
createVoiceLinearIssueSyncSinks,
|
|
46254
46382
|
createVoiceLinearIssueSink,
|
|
46383
|
+
createVoiceLLMJudge,
|
|
46255
46384
|
createVoiceIntegrationSinkWorkerLoop,
|
|
46256
46385
|
createVoiceIntegrationSinkWorker,
|
|
46257
46386
|
createVoiceIntegrationHTTPSink,
|
|
@@ -46347,6 +46476,7 @@ export {
|
|
|
46347
46476
|
createVoiceAgentTool,
|
|
46348
46477
|
createVoiceAgentSquad,
|
|
46349
46478
|
createVoiceAgent,
|
|
46479
|
+
createVoiceAIJudgeCompletion,
|
|
46350
46480
|
createTwilioVoiceRoutes,
|
|
46351
46481
|
createTwilioVoiceResponse,
|
|
46352
46482
|
createTwilioMediaStreamBridge,
|
|
@@ -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;
|