@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 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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.481",
3
+ "version": "0.0.22-beta.482",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",