@absolutejs/voice 0.0.22-beta.244 → 0.0.22-beta.246
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/README.md +20 -0
- package/dist/guardrails.d.ts +128 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +344 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ These are the primitive-first paths a Vapi-style buyer usually needs. Each path
|
|
|
37
37
|
| Phone voice assistant | `createVoicePhoneAgent(...)` | carrier matrix, setup instructions, phone smoke contract, production readiness, operations record | phone setup HTML/JSON, smoke HTML/JSON, framework status UI |
|
|
38
38
|
| Multi-specialist support flow | `createVoiceAgentSquad(...)` | squad contract, handoff traces, context traces, operations record | Agent Squad status hooks/composables/services/widgets |
|
|
39
39
|
| Business actions and tools | `createVoiceAgentTool(...)` plus agent tool runtime | tool contracts, audit events, integration events, outcome contracts | operations record, action center, contract routes |
|
|
40
|
+
| Guardrails and policy checks | `createVoiceGuardrailPolicy(...)` plus `createVoiceGuardrailRoutes(...)` | blocking/warning decisions, redacted content, `assistant.guardrail` trace events | guardrail JSON/Markdown routes and operations record traces |
|
|
40
41
|
| Provider routing and fallback | provider routers, health checks, simulation controls | provider contract matrix, provider-stage traces, latency SLO reports | provider contract hooks/composables/services/widgets |
|
|
41
42
|
| Production operations | ops status, ops recovery, production readiness, delivery runtime | readiness gates, recovery report, incident Markdown, delivery queues | ops action center, delivery runtime UI, operations record |
|
|
42
43
|
| Outbound campaigns | `createVoiceCampaignRoutes(...)` | recipient validation, consent/dedupe, carrier dry-run, campaign readiness | campaign routes and operations-record-linked attempt proof |
|
|
@@ -113,6 +114,25 @@ app.use(
|
|
|
113
114
|
);
|
|
114
115
|
```
|
|
115
116
|
|
|
117
|
+
## Guardrails
|
|
118
|
+
|
|
119
|
+
Use `createVoiceGuardrailRoutes(...)` when you need code-owned policy checks for what an agent may say, what tool payloads may contain, or which transcript content should warn/redact before downstream workflow. The primitive does not force a moderation vendor or hosted dashboard; it returns JSON/Markdown proof and can emit `assistant.guardrail` trace events.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import {
|
|
123
|
+
createVoiceGuardrailRoutes,
|
|
124
|
+
voiceGuardrailPolicyPresets
|
|
125
|
+
} from '@absolutejs/voice';
|
|
126
|
+
|
|
127
|
+
app.use(
|
|
128
|
+
createVoiceGuardrailRoutes({
|
|
129
|
+
path: '/api/voice/guardrails',
|
|
130
|
+
policies: [voiceGuardrailPolicyPresets.supportSafeDefaults],
|
|
131
|
+
trace: runtime.traces
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
```
|
|
135
|
+
|
|
116
136
|
## Use-Case Recipe: Support Triage
|
|
117
137
|
|
|
118
138
|
Use this path when you want a Vapi-style support assistant that can answer web or phone calls, look up customer context, route billing issues to a specialist, create follow-up work, and leave one debuggable call record. It is a recipe over primitives, not a support app kit.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import type { VoiceAgentTool } from './agent';
|
|
3
|
+
import type { VoiceTraceEventStore } from './trace';
|
|
4
|
+
import type { VoiceOnTurnObjectHandler, VoiceRouteResult, VoiceSessionRecord } from './types';
|
|
5
|
+
import type { VoiceAssistantGuardrails } from './assistant';
|
|
6
|
+
export type VoiceGuardrailStage = 'assistant-output' | 'handoff' | 'model-input' | 'tool-input' | 'tool-output' | 'transcript';
|
|
7
|
+
export type VoiceGuardrailSeverity = 'block' | 'warn';
|
|
8
|
+
export type VoiceGuardrailStatus = 'blocked' | 'pass' | 'warn';
|
|
9
|
+
export type VoiceGuardrailRule = {
|
|
10
|
+
action?: VoiceGuardrailSeverity;
|
|
11
|
+
description?: string;
|
|
12
|
+
id: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
match: RegExp | string | ((input: VoiceGuardrailEvaluationInput) => boolean | Promise<boolean>);
|
|
15
|
+
redactWith?: string;
|
|
16
|
+
stages?: VoiceGuardrailStage[];
|
|
17
|
+
};
|
|
18
|
+
export type VoiceGuardrailEvaluationInput = {
|
|
19
|
+
content?: unknown;
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
stage: VoiceGuardrailStage;
|
|
23
|
+
turnId?: string;
|
|
24
|
+
};
|
|
25
|
+
export type VoiceGuardrailFinding = {
|
|
26
|
+
action: VoiceGuardrailSeverity;
|
|
27
|
+
description?: string;
|
|
28
|
+
label: string;
|
|
29
|
+
ruleId: string;
|
|
30
|
+
stage: VoiceGuardrailStage;
|
|
31
|
+
};
|
|
32
|
+
export type VoiceGuardrailDecision = {
|
|
33
|
+
allowed: boolean;
|
|
34
|
+
checkedAt: number;
|
|
35
|
+
content?: unknown;
|
|
36
|
+
findings: VoiceGuardrailFinding[];
|
|
37
|
+
redactedContent?: unknown;
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
stage: VoiceGuardrailStage;
|
|
40
|
+
status: VoiceGuardrailStatus;
|
|
41
|
+
turnId?: string;
|
|
42
|
+
};
|
|
43
|
+
export type VoiceGuardrailPolicy = {
|
|
44
|
+
defaultAction?: VoiceGuardrailSeverity;
|
|
45
|
+
id: string;
|
|
46
|
+
label?: string;
|
|
47
|
+
rules: VoiceGuardrailRule[];
|
|
48
|
+
};
|
|
49
|
+
export type VoiceGuardrailReport = {
|
|
50
|
+
checkedAt: number;
|
|
51
|
+
decisions: VoiceGuardrailDecision[];
|
|
52
|
+
failed: number;
|
|
53
|
+
policies: Array<{
|
|
54
|
+
id: string;
|
|
55
|
+
label?: string;
|
|
56
|
+
rules: number;
|
|
57
|
+
}>;
|
|
58
|
+
status: 'fail' | 'pass' | 'warn';
|
|
59
|
+
summary: {
|
|
60
|
+
blocked: number;
|
|
61
|
+
passed: number;
|
|
62
|
+
warned: number;
|
|
63
|
+
};
|
|
64
|
+
total: number;
|
|
65
|
+
};
|
|
66
|
+
export type VoiceGuardrailRoutesOptions = {
|
|
67
|
+
headers?: HeadersInit;
|
|
68
|
+
name?: string;
|
|
69
|
+
path?: string;
|
|
70
|
+
policies?: VoiceGuardrailPolicy[];
|
|
71
|
+
source?: ((input: VoiceGuardrailEvaluationInput) => Promise<VoiceGuardrailDecision | VoiceGuardrailReport> | VoiceGuardrailDecision | VoiceGuardrailReport) | VoiceGuardrailDecision | VoiceGuardrailReport;
|
|
72
|
+
trace?: VoiceTraceEventStore;
|
|
73
|
+
};
|
|
74
|
+
export type VoiceGuardrailRuntimeBlockInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = Parameters<VoiceOnTurnObjectHandler<TContext, TSession, TResult>>[0] & {
|
|
75
|
+
decision: VoiceGuardrailDecision;
|
|
76
|
+
stage: VoiceGuardrailStage;
|
|
77
|
+
};
|
|
78
|
+
export type VoiceGuardrailRuntimeOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
79
|
+
blockResult?: (input: VoiceGuardrailRuntimeBlockInput<TContext, TSession, TResult>) => Promise<VoiceRouteResult<TResult>> | VoiceRouteResult<TResult>;
|
|
80
|
+
name?: string;
|
|
81
|
+
policies: VoiceGuardrailPolicy[];
|
|
82
|
+
trace?: VoiceTraceEventStore;
|
|
83
|
+
};
|
|
84
|
+
export type VoiceGuardrailRuntime<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
85
|
+
assistantGuardrails: VoiceAssistantGuardrails<TContext, TSession, TResult>;
|
|
86
|
+
evaluate: (input: VoiceGuardrailEvaluationInput) => Promise<VoiceGuardrailDecision>;
|
|
87
|
+
wrapTool: <TArgs extends Record<string, unknown>, TToolResult>(tool: VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TResult>) => VoiceAgentTool<TContext, TSession, TArgs, TToolResult, TResult>;
|
|
88
|
+
wrapTools: (tools: Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>) => Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>;
|
|
89
|
+
};
|
|
90
|
+
export declare const evaluateVoiceGuardrailPolicy: (policy: VoiceGuardrailPolicy, input: VoiceGuardrailEvaluationInput) => Promise<VoiceGuardrailDecision>;
|
|
91
|
+
export declare const buildVoiceGuardrailReport: (input?: {
|
|
92
|
+
decisions: VoiceGuardrailDecision[];
|
|
93
|
+
policies?: VoiceGuardrailPolicy[];
|
|
94
|
+
}) => VoiceGuardrailReport;
|
|
95
|
+
export declare const createVoiceGuardrailPolicy: (policy: VoiceGuardrailPolicy) => VoiceGuardrailPolicy;
|
|
96
|
+
export declare const createVoiceGuardrailRuntime: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceGuardrailRuntimeOptions<TContext, TSession, TResult>) => VoiceGuardrailRuntime<TContext, TSession, TResult>;
|
|
97
|
+
export declare const voiceGuardrailPolicyPresets: {
|
|
98
|
+
supportSafeDefaults: VoiceGuardrailPolicy;
|
|
99
|
+
};
|
|
100
|
+
export declare const renderVoiceGuardrailMarkdown: (report: VoiceGuardrailReport) => string;
|
|
101
|
+
export declare const createVoiceGuardrailRoutes: (options?: VoiceGuardrailRoutesOptions) => Elysia<"", {
|
|
102
|
+
decorator: {};
|
|
103
|
+
store: {};
|
|
104
|
+
derive: {};
|
|
105
|
+
resolve: {};
|
|
106
|
+
}, {
|
|
107
|
+
typebox: {};
|
|
108
|
+
error: {};
|
|
109
|
+
}, {
|
|
110
|
+
schema: {};
|
|
111
|
+
standaloneSchema: {};
|
|
112
|
+
macro: {};
|
|
113
|
+
macroFn: {};
|
|
114
|
+
parser: {};
|
|
115
|
+
response: {};
|
|
116
|
+
}, {}, {
|
|
117
|
+
derive: {};
|
|
118
|
+
resolve: {};
|
|
119
|
+
schema: {};
|
|
120
|
+
standaloneSchema: {};
|
|
121
|
+
response: {};
|
|
122
|
+
}, {
|
|
123
|
+
derive: {};
|
|
124
|
+
resolve: {};
|
|
125
|
+
schema: {};
|
|
126
|
+
standaloneSchema: {};
|
|
127
|
+
response: {};
|
|
128
|
+
}>;
|
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export { createVoiceOpsRuntime } from './opsRuntime';
|
|
|
84
84
|
export { resolveVoiceOpsPreset } from './opsPresets';
|
|
85
85
|
export { resolveVoiceOutcomeRecipe } from './outcomeRecipes';
|
|
86
86
|
export { buildVoicePostCallAnalysisReport, createVoicePostCallAnalysisRoutes, renderVoicePostCallAnalysisMarkdown } from './postCallAnalysis';
|
|
87
|
+
export { buildVoiceGuardrailReport, createVoiceGuardrailPolicy, createVoiceGuardrailRuntime, createVoiceGuardrailRoutes, evaluateVoiceGuardrailPolicy, renderVoiceGuardrailMarkdown, voiceGuardrailPolicyPresets } from './guardrails';
|
|
87
88
|
export { createId, createVoiceSessionRecord } from './store';
|
|
88
89
|
export { createVoiceSTTRoutingCorrectionHandler, resolveVoiceSTTRoutingStrategy } from './routing';
|
|
89
90
|
export { applyRiskTieredPhraseHintCorrections, applyPhraseHintCorrections, createDomainLexicon, createDomainPhraseHints, createPhraseHintCorrectionHandler, createRiskyTurnCorrectionHandler } from './correction';
|
|
@@ -137,6 +138,7 @@ export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, Vo
|
|
|
137
138
|
export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
|
|
138
139
|
export type { VoiceOutcomeRecipe, VoiceOutcomeRecipeName, VoiceOutcomeRecipeOptions } from './outcomeRecipes';
|
|
139
140
|
export type { VoicePostCallAnalysisFieldRequirement, VoicePostCallAnalysisFieldResult, VoicePostCallAnalysisIssue, VoicePostCallAnalysisIssueCode, VoicePostCallAnalysisOptions, VoicePostCallAnalysisReport, VoicePostCallAnalysisRoutesOptions, VoicePostCallAnalysisStatus } from './postCallAnalysis';
|
|
141
|
+
export type { VoiceGuardrailDecision, VoiceGuardrailEvaluationInput, VoiceGuardrailFinding, VoiceGuardrailPolicy, VoiceGuardrailReport, VoiceGuardrailRuntime, VoiceGuardrailRuntimeBlockInput, VoiceGuardrailRuntimeOptions, VoiceGuardrailRoutesOptions, VoiceGuardrailRule, VoiceGuardrailSeverity, VoiceGuardrailStage, VoiceGuardrailStatus } from './guardrails';
|
|
140
142
|
export type { VoiceCRMActivitySinkOptions, VoiceHubSpotTaskSinkOptions, VoiceHubSpotTaskUpdateSinkOptions, VoiceHelpdeskTicketSinkOptions, VoiceIntegrationHTTPSinkOptions, VoiceIntegrationSink, VoiceIntegrationSinkDeliveryResult, VoiceLinearIssueSinkOptions, VoiceLinearIssueUpdateSinkOptions, VoiceZendeskTicketSinkOptions, VoiceZendeskTicketUpdateSinkOptions } from './opsSinks';
|
|
141
143
|
export type { VoiceOpsWebhookEnvelope, VoiceOpsWebhookEntity, VoiceOpsWebhookLinkResolver, VoiceOpsWebhookReceiverRoutesOptions, VoiceOpsWebhookSinkOptions, VoiceOpsWebhookVerificationResult } from './opsWebhook';
|
|
142
144
|
export type { VoiceHandoffDelivery, VoiceHandoffDeliveryRecord, VoiceHandoffDeliveryRecordInput, VoiceHandoffFanoutResult, VoiceQueuedHandoffDeliveryOptions, VoiceTwilioRedirectHandoffAdapterOptions, VoiceWebhookHandoffAdapterOptions } from './handoff';
|
package/dist/index.js
CHANGED
|
@@ -28931,6 +28931,343 @@ var createVoicePostCallAnalysisRoutes = (options = {}) => {
|
|
|
28931
28931
|
});
|
|
28932
28932
|
return routes;
|
|
28933
28933
|
};
|
|
28934
|
+
// src/guardrails.ts
|
|
28935
|
+
import { Elysia as Elysia50 } from "elysia";
|
|
28936
|
+
var stringifyContent = (value) => typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
28937
|
+
var appliesToStage = (rule, stage) => !rule.stages || rule.stages.length === 0 || rule.stages.includes(stage);
|
|
28938
|
+
var matchesRule = async (rule, input) => {
|
|
28939
|
+
if (!appliesToStage(rule, input.stage)) {
|
|
28940
|
+
return false;
|
|
28941
|
+
}
|
|
28942
|
+
if (typeof rule.match === "function") {
|
|
28943
|
+
return rule.match(input);
|
|
28944
|
+
}
|
|
28945
|
+
const content = stringifyContent(input.content);
|
|
28946
|
+
return typeof rule.match === "string" ? content.toLowerCase().includes(rule.match.toLowerCase()) : rule.match.test(content);
|
|
28947
|
+
};
|
|
28948
|
+
var applyRedactions = (content, rules, findings) => {
|
|
28949
|
+
if (typeof content !== "string") {
|
|
28950
|
+
return content;
|
|
28951
|
+
}
|
|
28952
|
+
return findings.reduce((value, finding) => {
|
|
28953
|
+
const rule = rules.find((candidate) => candidate.id === finding.ruleId);
|
|
28954
|
+
if (!rule || !rule.redactWith) {
|
|
28955
|
+
return value;
|
|
28956
|
+
}
|
|
28957
|
+
if (typeof rule.match === "string") {
|
|
28958
|
+
return value.replaceAll(rule.match, rule.redactWith);
|
|
28959
|
+
}
|
|
28960
|
+
if (rule.match instanceof RegExp) {
|
|
28961
|
+
return value.replace(rule.match, rule.redactWith);
|
|
28962
|
+
}
|
|
28963
|
+
return value;
|
|
28964
|
+
}, content);
|
|
28965
|
+
};
|
|
28966
|
+
var evaluateVoiceGuardrailPolicy = async (policy, input) => {
|
|
28967
|
+
const findings = [];
|
|
28968
|
+
for (const rule of policy.rules) {
|
|
28969
|
+
if (!await matchesRule(rule, input)) {
|
|
28970
|
+
continue;
|
|
28971
|
+
}
|
|
28972
|
+
findings.push({
|
|
28973
|
+
action: rule.action ?? policy.defaultAction ?? "block",
|
|
28974
|
+
description: rule.description,
|
|
28975
|
+
label: rule.label ?? rule.id,
|
|
28976
|
+
ruleId: rule.id,
|
|
28977
|
+
stage: input.stage
|
|
28978
|
+
});
|
|
28979
|
+
}
|
|
28980
|
+
const blocked = findings.some((finding) => finding.action === "block");
|
|
28981
|
+
const status = blocked ? "blocked" : findings.length > 0 ? "warn" : "pass";
|
|
28982
|
+
return {
|
|
28983
|
+
allowed: !blocked,
|
|
28984
|
+
checkedAt: Date.now(),
|
|
28985
|
+
content: input.content,
|
|
28986
|
+
findings,
|
|
28987
|
+
redactedContent: applyRedactions(input.content, policy.rules, findings),
|
|
28988
|
+
sessionId: input.sessionId,
|
|
28989
|
+
stage: input.stage,
|
|
28990
|
+
status,
|
|
28991
|
+
turnId: input.turnId
|
|
28992
|
+
};
|
|
28993
|
+
};
|
|
28994
|
+
var buildVoiceGuardrailReport = (input = { decisions: [] }) => {
|
|
28995
|
+
const blocked = input.decisions.filter((decision) => decision.status === "blocked").length;
|
|
28996
|
+
const warned = input.decisions.filter((decision) => decision.status === "warn").length;
|
|
28997
|
+
const passed = input.decisions.filter((decision) => decision.status === "pass").length;
|
|
28998
|
+
const status = blocked > 0 ? "fail" : warned > 0 ? "warn" : "pass";
|
|
28999
|
+
return {
|
|
29000
|
+
checkedAt: Date.now(),
|
|
29001
|
+
decisions: input.decisions,
|
|
29002
|
+
failed: blocked,
|
|
29003
|
+
policies: (input.policies ?? []).map((policy) => ({
|
|
29004
|
+
id: policy.id,
|
|
29005
|
+
label: policy.label,
|
|
29006
|
+
rules: policy.rules.length
|
|
29007
|
+
})),
|
|
29008
|
+
status,
|
|
29009
|
+
summary: {
|
|
29010
|
+
blocked,
|
|
29011
|
+
passed,
|
|
29012
|
+
warned
|
|
29013
|
+
},
|
|
29014
|
+
total: input.decisions.length
|
|
29015
|
+
};
|
|
29016
|
+
};
|
|
29017
|
+
var createVoiceGuardrailPolicy = (policy) => policy;
|
|
29018
|
+
var appendGuardrailTrace = async (trace, decision, metadata) => {
|
|
29019
|
+
await trace?.append({
|
|
29020
|
+
at: decision.checkedAt,
|
|
29021
|
+
payload: {
|
|
29022
|
+
allowed: decision.allowed,
|
|
29023
|
+
findingCount: decision.findings.length,
|
|
29024
|
+
findings: decision.findings,
|
|
29025
|
+
metadata,
|
|
29026
|
+
redactedContent: decision.redactedContent,
|
|
29027
|
+
stage: decision.stage,
|
|
29028
|
+
status: decision.status
|
|
29029
|
+
},
|
|
29030
|
+
sessionId: decision.sessionId ?? "guardrail-check",
|
|
29031
|
+
turnId: decision.turnId,
|
|
29032
|
+
type: "assistant.guardrail"
|
|
29033
|
+
});
|
|
29034
|
+
};
|
|
29035
|
+
var defaultGuardrailBlockResult = (reason) => ({
|
|
29036
|
+
assistantText: "I cannot safely complete that in the automated flow. I am routing this to a human specialist.",
|
|
29037
|
+
escalate: {
|
|
29038
|
+
metadata: {
|
|
29039
|
+
guardrail: true
|
|
29040
|
+
},
|
|
29041
|
+
reason
|
|
29042
|
+
}
|
|
29043
|
+
});
|
|
29044
|
+
var createVoiceGuardrailRuntime = (options) => {
|
|
29045
|
+
const evaluate = async (input) => {
|
|
29046
|
+
const decisions = await Promise.all(options.policies.map((policy) => evaluateVoiceGuardrailPolicy(policy, input)));
|
|
29047
|
+
await Promise.all(decisions.map((decision) => appendGuardrailTrace(options.trace, decision, input.metadata)));
|
|
29048
|
+
return decisions.find((decision) => !decision.allowed) ?? decisions[0];
|
|
29049
|
+
};
|
|
29050
|
+
const blockResult = async (input) => options.blockResult?.(input) ?? defaultGuardrailBlockResult(`guardrail-blocked-${input.stage}`);
|
|
29051
|
+
const assistantGuardrails = {
|
|
29052
|
+
beforeTurn: async (input) => {
|
|
29053
|
+
const transcriptDecision = await evaluate({
|
|
29054
|
+
content: input.turn.text,
|
|
29055
|
+
metadata: {
|
|
29056
|
+
runtime: options.name,
|
|
29057
|
+
surface: "live-transcript"
|
|
29058
|
+
},
|
|
29059
|
+
sessionId: input.session.id,
|
|
29060
|
+
stage: "transcript",
|
|
29061
|
+
turnId: input.turn.id
|
|
29062
|
+
});
|
|
29063
|
+
if (!transcriptDecision.allowed) {
|
|
29064
|
+
return blockResult({
|
|
29065
|
+
...input,
|
|
29066
|
+
decision: transcriptDecision,
|
|
29067
|
+
stage: "transcript"
|
|
29068
|
+
});
|
|
29069
|
+
}
|
|
29070
|
+
const modelInputDecision = await evaluate({
|
|
29071
|
+
content: JSON.stringify({
|
|
29072
|
+
scenarioId: input.session.scenarioId,
|
|
29073
|
+
turn: input.turn.text
|
|
29074
|
+
}),
|
|
29075
|
+
metadata: {
|
|
29076
|
+
runtime: options.name,
|
|
29077
|
+
surface: "live-model-input"
|
|
29078
|
+
},
|
|
29079
|
+
sessionId: input.session.id,
|
|
29080
|
+
stage: "model-input",
|
|
29081
|
+
turnId: input.turn.id
|
|
29082
|
+
});
|
|
29083
|
+
if (!modelInputDecision.allowed) {
|
|
29084
|
+
return blockResult({
|
|
29085
|
+
...input,
|
|
29086
|
+
decision: modelInputDecision,
|
|
29087
|
+
stage: "model-input"
|
|
29088
|
+
});
|
|
29089
|
+
}
|
|
29090
|
+
return;
|
|
29091
|
+
},
|
|
29092
|
+
afterTurn: async (input) => {
|
|
29093
|
+
if (!input.result.assistantText) {
|
|
29094
|
+
return;
|
|
29095
|
+
}
|
|
29096
|
+
const decision = await evaluate({
|
|
29097
|
+
content: input.result.assistantText,
|
|
29098
|
+
metadata: {
|
|
29099
|
+
runtime: options.name,
|
|
29100
|
+
surface: "live-assistant-output"
|
|
29101
|
+
},
|
|
29102
|
+
sessionId: input.session.id,
|
|
29103
|
+
stage: "assistant-output",
|
|
29104
|
+
turnId: input.turn.id
|
|
29105
|
+
});
|
|
29106
|
+
if (!decision.allowed) {
|
|
29107
|
+
return blockResult({
|
|
29108
|
+
...input,
|
|
29109
|
+
decision,
|
|
29110
|
+
stage: "assistant-output"
|
|
29111
|
+
});
|
|
29112
|
+
}
|
|
29113
|
+
if (typeof decision.redactedContent === "string" && decision.redactedContent !== input.result.assistantText) {
|
|
29114
|
+
return {
|
|
29115
|
+
...input.result,
|
|
29116
|
+
assistantText: decision.redactedContent
|
|
29117
|
+
};
|
|
29118
|
+
}
|
|
29119
|
+
return;
|
|
29120
|
+
}
|
|
29121
|
+
};
|
|
29122
|
+
const wrapTool = (tool) => ({
|
|
29123
|
+
...tool,
|
|
29124
|
+
execute: async (input) => {
|
|
29125
|
+
const inputDecision = await evaluate({
|
|
29126
|
+
content: JSON.stringify({
|
|
29127
|
+
args: input.args,
|
|
29128
|
+
toolName: tool.name
|
|
29129
|
+
}),
|
|
29130
|
+
metadata: {
|
|
29131
|
+
runtime: options.name,
|
|
29132
|
+
surface: "live-tool-input",
|
|
29133
|
+
toolName: tool.name
|
|
29134
|
+
},
|
|
29135
|
+
sessionId: input.session.id,
|
|
29136
|
+
stage: "tool-input",
|
|
29137
|
+
turnId: input.turn.id
|
|
29138
|
+
});
|
|
29139
|
+
if (!inputDecision.allowed) {
|
|
29140
|
+
throw new Error(`Guardrail blocked tool input for ${tool.name}.`);
|
|
29141
|
+
}
|
|
29142
|
+
const result = await tool.execute(input);
|
|
29143
|
+
const outputDecision = await evaluate({
|
|
29144
|
+
content: JSON.stringify({
|
|
29145
|
+
result,
|
|
29146
|
+
toolName: tool.name
|
|
29147
|
+
}),
|
|
29148
|
+
metadata: {
|
|
29149
|
+
runtime: options.name,
|
|
29150
|
+
surface: "live-tool-output",
|
|
29151
|
+
toolName: tool.name
|
|
29152
|
+
},
|
|
29153
|
+
sessionId: input.session.id,
|
|
29154
|
+
stage: "tool-output",
|
|
29155
|
+
turnId: input.turn.id
|
|
29156
|
+
});
|
|
29157
|
+
if (!outputDecision.allowed) {
|
|
29158
|
+
throw new Error(`Guardrail blocked tool output for ${tool.name}.`);
|
|
29159
|
+
}
|
|
29160
|
+
return result;
|
|
29161
|
+
}
|
|
29162
|
+
});
|
|
29163
|
+
return {
|
|
29164
|
+
assistantGuardrails,
|
|
29165
|
+
evaluate,
|
|
29166
|
+
wrapTool,
|
|
29167
|
+
wrapTools: (tools) => tools.map((tool) => wrapTool(tool))
|
|
29168
|
+
};
|
|
29169
|
+
};
|
|
29170
|
+
var voiceGuardrailPolicyPresets = {
|
|
29171
|
+
supportSafeDefaults: createVoiceGuardrailPolicy({
|
|
29172
|
+
id: "support-safe-defaults",
|
|
29173
|
+
label: "Support safe defaults",
|
|
29174
|
+
rules: [
|
|
29175
|
+
{
|
|
29176
|
+
description: "Blocks final legal, medical, or financial advice claims that should route to a human or qualified professional.",
|
|
29177
|
+
id: "regulated-advice",
|
|
29178
|
+
label: "Regulated advice",
|
|
29179
|
+
match: /\b(legal advice|medical advice|financial advice|diagnose|prescribe|guaranteed refund|guaranteed approval)\b/i,
|
|
29180
|
+
stages: ["assistant-output"]
|
|
29181
|
+
},
|
|
29182
|
+
{
|
|
29183
|
+
description: "Warns when payment-card-like data appears in transcripts or tool payloads.",
|
|
29184
|
+
action: "warn",
|
|
29185
|
+
id: "payment-card-like-data",
|
|
29186
|
+
label: "Payment card-like data",
|
|
29187
|
+
match: /\b(?:\d[ -]*?){13,19}\b/,
|
|
29188
|
+
redactWith: "[redacted-card]",
|
|
29189
|
+
stages: ["transcript", "tool-input", "tool-output"]
|
|
29190
|
+
}
|
|
29191
|
+
]
|
|
29192
|
+
})
|
|
29193
|
+
};
|
|
29194
|
+
var renderVoiceGuardrailMarkdown = (report) => {
|
|
29195
|
+
const lines = [
|
|
29196
|
+
"# Voice Guardrail Report",
|
|
29197
|
+
"",
|
|
29198
|
+
`Status: ${report.status}`,
|
|
29199
|
+
`Checked: ${new Date(report.checkedAt).toISOString()}`,
|
|
29200
|
+
`Decisions: ${report.total}`,
|
|
29201
|
+
`Blocked: ${report.summary.blocked}`,
|
|
29202
|
+
`Warned: ${report.summary.warned}`,
|
|
29203
|
+
"",
|
|
29204
|
+
"## Decisions",
|
|
29205
|
+
...report.decisions.length > 0 ? report.decisions.map((decision) => `- ${decision.status}: ${decision.stage}${decision.sessionId ? ` session=${decision.sessionId}` : ""} findings=${decision.findings.length}`) : ["- none"]
|
|
29206
|
+
];
|
|
29207
|
+
return `${lines.join(`
|
|
29208
|
+
`)}
|
|
29209
|
+
`;
|
|
29210
|
+
};
|
|
29211
|
+
var isGuardrailReport = (value) => ("decisions" in value) && ("summary" in value);
|
|
29212
|
+
var normalizeGuardrailRouteInput = async (request) => {
|
|
29213
|
+
if (request.method === "POST") {
|
|
29214
|
+
return await request.json().catch(() => ({}));
|
|
29215
|
+
}
|
|
29216
|
+
const url = new URL(request.url);
|
|
29217
|
+
return {
|
|
29218
|
+
content: url.searchParams.get("content") ?? "",
|
|
29219
|
+
sessionId: url.searchParams.get("sessionId") ?? undefined,
|
|
29220
|
+
stage: url.searchParams.get("stage") ?? "assistant-output",
|
|
29221
|
+
turnId: url.searchParams.get("turnId") ?? undefined
|
|
29222
|
+
};
|
|
29223
|
+
};
|
|
29224
|
+
var resolveGuardrailReport = async (options, input) => {
|
|
29225
|
+
if (options.source !== undefined) {
|
|
29226
|
+
const value = typeof options.source === "function" ? await options.source(input) : options.source;
|
|
29227
|
+
return isGuardrailReport(value) ? value : buildVoiceGuardrailReport({ decisions: [value] });
|
|
29228
|
+
}
|
|
29229
|
+
const decisions = await Promise.all((options.policies ?? []).map((policy) => evaluateVoiceGuardrailPolicy(policy, input)));
|
|
29230
|
+
return buildVoiceGuardrailReport({
|
|
29231
|
+
decisions,
|
|
29232
|
+
policies: options.policies
|
|
29233
|
+
});
|
|
29234
|
+
};
|
|
29235
|
+
var createVoiceGuardrailRoutes = (options = {}) => {
|
|
29236
|
+
const path = options.path ?? "/api/voice/guardrails";
|
|
29237
|
+
const routes = new Elysia50({
|
|
29238
|
+
name: options.name ?? "absolutejs-voice-guardrails"
|
|
29239
|
+
});
|
|
29240
|
+
routes.all(path, async ({ request }) => {
|
|
29241
|
+
const input = await normalizeGuardrailRouteInput(request);
|
|
29242
|
+
const report = await resolveGuardrailReport(options, input);
|
|
29243
|
+
if (options.trace) {
|
|
29244
|
+
await Promise.all(report.decisions.map((decision) => options.trace.append({
|
|
29245
|
+
at: decision.checkedAt,
|
|
29246
|
+
payload: {
|
|
29247
|
+
allowed: decision.allowed,
|
|
29248
|
+
findings: decision.findings,
|
|
29249
|
+
stage: decision.stage,
|
|
29250
|
+
status: decision.status
|
|
29251
|
+
},
|
|
29252
|
+
sessionId: decision.sessionId ?? "guardrail-check",
|
|
29253
|
+
turnId: decision.turnId,
|
|
29254
|
+
type: "assistant.guardrail"
|
|
29255
|
+
})));
|
|
29256
|
+
}
|
|
29257
|
+
return Response.json(report, { headers: options.headers });
|
|
29258
|
+
});
|
|
29259
|
+
routes.all(`${path}.md`, async ({ request }) => {
|
|
29260
|
+
const input = await normalizeGuardrailRouteInput(request);
|
|
29261
|
+
const report = await resolveGuardrailReport(options, input);
|
|
29262
|
+
return new Response(renderVoiceGuardrailMarkdown(report), {
|
|
29263
|
+
headers: {
|
|
29264
|
+
"content-type": "text/markdown; charset=utf-8",
|
|
29265
|
+
...options.headers
|
|
29266
|
+
}
|
|
29267
|
+
});
|
|
29268
|
+
});
|
|
29269
|
+
return routes;
|
|
29270
|
+
};
|
|
28934
29271
|
// src/correction.ts
|
|
28935
29272
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28936
29273
|
var buildAliasMatcher = (alias) => new RegExp(`(?<![\\p{L}\\p{N}'])${escapeRegExp(alias)}(?![\\p{L}\\p{N}'])`, "giu");
|
|
@@ -29323,6 +29660,7 @@ export {
|
|
|
29323
29660
|
voiceTelephonyOutcomeToRouteResult,
|
|
29324
29661
|
voiceObservabilityExportSchemaVersion,
|
|
29325
29662
|
voiceObservabilityExportSchemaId,
|
|
29663
|
+
voiceGuardrailPolicyPresets,
|
|
29326
29664
|
voiceComplianceRedactionDefaults,
|
|
29327
29665
|
voice,
|
|
29328
29666
|
verifyVoiceTwilioWebhookSignature,
|
|
@@ -29438,6 +29776,7 @@ export {
|
|
|
29438
29776
|
renderVoiceLiveLatencyHTML,
|
|
29439
29777
|
renderVoiceLatencySLOMarkdown,
|
|
29440
29778
|
renderVoiceHandoffHealthHTML,
|
|
29779
|
+
renderVoiceGuardrailMarkdown,
|
|
29441
29780
|
renderVoiceEvalHTML,
|
|
29442
29781
|
renderVoiceEvalBaselineHTML,
|
|
29443
29782
|
renderVoiceDemoReadyHTML,
|
|
@@ -29497,6 +29836,7 @@ export {
|
|
|
29497
29836
|
evaluateVoiceTelephonyContract,
|
|
29498
29837
|
evaluateVoiceQuality,
|
|
29499
29838
|
evaluateVoiceProviderStackGaps,
|
|
29839
|
+
evaluateVoiceGuardrailPolicy,
|
|
29500
29840
|
encodeTwilioMulawBase64,
|
|
29501
29841
|
deliverVoiceTraceEventsToSinks,
|
|
29502
29842
|
deliverVoiceObservabilityExport,
|
|
@@ -29676,6 +30016,9 @@ export {
|
|
|
29676
30016
|
createVoiceHandoffDeliveryWorkerLoop,
|
|
29677
30017
|
createVoiceHandoffDeliveryWorker,
|
|
29678
30018
|
createVoiceHandoffDeliveryRecord,
|
|
30019
|
+
createVoiceGuardrailRuntime,
|
|
30020
|
+
createVoiceGuardrailRoutes,
|
|
30021
|
+
createVoiceGuardrailPolicy,
|
|
29679
30022
|
createVoiceFileTraceSinkDeliveryStore,
|
|
29680
30023
|
createVoiceFileTraceEventStore,
|
|
29681
30024
|
createVoiceFileTaskStore,
|
|
@@ -29792,6 +30135,7 @@ export {
|
|
|
29792
30135
|
buildVoiceLiveOpsControlState,
|
|
29793
30136
|
buildVoiceLatencySLOGate,
|
|
29794
30137
|
buildVoiceIncidentBundle,
|
|
30138
|
+
buildVoiceGuardrailReport,
|
|
29795
30139
|
buildVoiceDiagnosticsMarkdown,
|
|
29796
30140
|
buildVoiceDemoReadyReport,
|
|
29797
30141
|
buildVoiceDeliverySinkReport,
|