@absolutejs/voice 0.0.22-beta.468 → 0.0.22-beta.469
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 +55 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +131 -0
- package/dist/ragTool.d.ts +47 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,61 @@ It gives your app the primitives hosted voice platforms usually keep behind thei
|
|
|
6
6
|
|
|
7
7
|
Use it when you want Vapi/Retell/Bland-style voice-agent capability, but you want the orchestration, data, traces, storage, and UI to live inside the AbsoluteJS server you already operate.
|
|
8
8
|
|
|
9
|
+
## What's new
|
|
10
|
+
|
|
11
|
+
### 0.0.22-beta.469 · Vapi parity — RAG-as-a-tool helper
|
|
12
|
+
|
|
13
|
+
`createVoiceRAGTool(collection, options?)` wraps any `@absolutejs/rag` `RAGCollection` (or any object that satisfies `VoiceRAGCollectionLike`) into a `VoiceAgentTool` shaped like Vapi's built-in `query` / `knowledgeBaseId` flow.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createInMemoryRAGStore, createRAGCollection } from "@absolutejs/rag";
|
|
17
|
+
import { createVoiceAssistant, createVoiceRAGTool } from "@absolutejs/voice";
|
|
18
|
+
|
|
19
|
+
const collection = createRAGCollection(
|
|
20
|
+
createInMemoryRAGStore(),
|
|
21
|
+
{ embedding: openaiEmbeddings("text-embedding-3-small") },
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const knowledgeBase = createVoiceRAGTool(collection, {
|
|
25
|
+
allowedFilterKeys: ["productLine"],
|
|
26
|
+
fixedFilter: ({ context }) => ({ tenantId: context.tenantId }),
|
|
27
|
+
maxChunkChars: 320,
|
|
28
|
+
scoreThreshold: 0.65,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const assistant = createVoiceAssistant({
|
|
32
|
+
id: "support",
|
|
33
|
+
model: openaiVoiceAssistantModel,
|
|
34
|
+
tools: [knowledgeBase],
|
|
35
|
+
system: "You are a support agent. Always cite the knowledge base.",
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Highlights:
|
|
40
|
+
- Default tool name `searchKnowledgeBase` matches the conventional Vapi knowledge-base tool name.
|
|
41
|
+
- Generated JSON-schema parameters expose `query` (required), `topK` (clamped to `maxTopK`), and an optional `filter` object whose keys are gated by `allowedFilterKeys`.
|
|
42
|
+
- `fixedFilter` can be a static record or a `(context) => filter` function (use for tenant scoping, locale, etc.). Fixed values always win over LLM-supplied filters.
|
|
43
|
+
- Default formatter returns numbered citations (`1. <title> (score 0.910): <chunk>`); override via `formatResult` or `resultToMessage`.
|
|
44
|
+
- No hard dependency on `@absolutejs/rag` — `VoiceRAGCollectionLike` is structurally compatible with `RAGCollection`, so install rag (or any duck-typed retrieval backend) as a peer.
|
|
45
|
+
|
|
46
|
+
### 0.0.22-beta.464 → 468 · Media pipeline issue codes across product surfaces
|
|
47
|
+
|
|
48
|
+
`@absolutejs/voice` now projects compact `@absolutejs/media` reports into every buyer-facing voice surface so media health is gateable, linkable, and visible without buying a hosted dashboard.
|
|
49
|
+
|
|
50
|
+
**Proof pack (beta.464–465)** — `summarizeVoiceMediaPipelineReport(report, { artifacts })` returns a compact `VoiceMediaPipelineProofSummary` with aggregated `issueCodes` and per-section status. `writeVoiceMediaPipelineArtifacts({ dir, report, hrefBase })` persists `media-quality.{json,md}`, `media-transport.{json,md}`, and `media-processor-graph.{json,md}` into a proof-pack run directory and returns href links to wire into the summary. Together these shrink the `mediaPipelineCalibrationAssertion` summary in voice proof packs from ~35 KB to ~1.7 KB (95% reduction) and trim the total proof JSON from ~170 KB to ~114 KB.
|
|
51
|
+
|
|
52
|
+
**Production readiness (beta.464)** — `buildVoiceMediaPipelineReadinessChecks(report, { baseHref, label })` returns drop-in `VoiceProductionReadinessCheck[]` entries (overall, media quality, transport, processor graph, interruption). Spread them into `additionalChecks` for granular gating alongside the existing aggregate "Media pipeline quality" check.
|
|
53
|
+
|
|
54
|
+
**Operations record (beta.466–467)** — `VoiceOperationsRecord.mediaPipeline` is a new optional `VoiceOperationsRecordMediaPipelineSummary` (status, qualityStatus, transportStatus, processorGraphStatus, issueCodes, jitterMs, frames, surface). Populate it by passing `mediaPipeline: VoiceMediaPipelineReport` to `buildVoiceOperationsRecord`, or pass `mediaPipeline` (a report or sessionId-keyed resolver) to `createVoiceOperationsRecordRoutes` so every `/api/voice-operations/:sessionId` response carries it automatically.
|
|
55
|
+
|
|
56
|
+
**Failure replay (beta.466)** — `buildVoiceFailureReplay` reads `record.mediaPipeline` and adds `media.pipelineIssueCodes` + `media.pipelineStatus` to the report, appends `"Media pipeline issue: <code>"` entries to `summary.issues`, and demotes failure-replay status to `failed`/`degraded` when the pipeline is `fail`/`warn`. The Markdown incident artifact gets a new "Media Pipeline" section.
|
|
57
|
+
|
|
58
|
+
**Incident timeline (beta.466)** — `VoiceIncidentTimelineOptions.extraEvents` accepts any custom event source. Feed `buildVoiceMediaPipelineIncidentEvents(report, { now, source, category })` into it to surface media-pipeline issues as `category: "monitor"` entries in `/api/voice/incident-timeline` alongside operational and failure-replay events.
|
|
59
|
+
|
|
60
|
+
**Ops record renderers (beta.468)** — `renderVoiceOperationsRecordIncidentMarkdown` adds a one-line `Media pipeline` summary and an issue-code section. `renderVoiceOperationsRecordHTML` adds a nav anchor, a summary-grid card, and a `#media-pipeline` section listing surface, per-section status, frame/jitter, and codes.
|
|
61
|
+
|
|
62
|
+
Requires `@absolutejs/media@0.0.1-beta.16+`. The voice bundle externalizes `@absolutejs/media` to keep browser bundles parseable; install media as a peer dependency.
|
|
63
|
+
|
|
9
64
|
## Why AbsoluteJS Voice
|
|
10
65
|
|
|
11
66
|
- Self-hosted by default: your app owns sessions, traces, reviews, tasks, handoffs, retention, and provider keys.
|
package/dist/index.d.ts
CHANGED
|
@@ -69,6 +69,8 @@ export { assertVoiceSimulationSuiteEvidence, createVoiceSimulationSuiteRoutes, e
|
|
|
69
69
|
export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult, } from "./workflowContract";
|
|
70
70
|
export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceProviderFallbackRecovery, summarizeVoiceSessions, summarizeVoiceSessionReplay, } from "./sessionReplay";
|
|
71
71
|
export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "./agent";
|
|
72
|
+
export { createVoiceRAGTool } from "./ragTool";
|
|
73
|
+
export type { VoiceRAGCollectionLike, VoiceRAGQueryResult, VoiceRAGSearchInput, VoiceRAGToolArgs, VoiceRAGToolOptions, VoiceRAGToolResult, } from "./ragTool";
|
|
72
74
|
export { assertVoiceAgentSquadContractEvidence, assertVoiceAgentSquadContract, evaluateVoiceAgentSquadContractEvidence, runVoiceAgentSquadContract, } from "./agentSquadContract";
|
|
73
75
|
export { createVoiceToolIdempotencyKey, createVoiceToolRuntime, } from "./toolRuntime";
|
|
74
76
|
export { assertVoiceToolContractEvidence, createVoiceToolContract, createVoiceToolContractHTMLHandler, createVoiceToolContractJSONHandler, createVoiceToolContractRoutes, createVoiceToolRuntimeContractDefaults, evaluateVoiceToolContractEvidence, renderVoiceToolContractHTML, runVoiceToolContractSuite, runVoiceToolContract, } from "./toolContract";
|
package/dist/index.js
CHANGED
|
@@ -34499,6 +34499,136 @@ var createVoiceWorkflowContractHandler = (input) => {
|
|
|
34499
34499
|
return result;
|
|
34500
34500
|
};
|
|
34501
34501
|
};
|
|
34502
|
+
// src/ragTool.ts
|
|
34503
|
+
var DEFAULT_TOOL_NAME = "searchKnowledgeBase";
|
|
34504
|
+
var DEFAULT_DESCRIPTION = "Search the knowledge base and return short grounded citations. Use this whenever the caller asks a question that may be answered by indexed reference material.";
|
|
34505
|
+
var DEFAULT_TOP_K = 6;
|
|
34506
|
+
var DEFAULT_MAX_TOP_K = 20;
|
|
34507
|
+
var DEFAULT_MAX_CHUNK_CHARS = 320;
|
|
34508
|
+
var truncate = (value, limit) => {
|
|
34509
|
+
if (limit <= 0 || value.length <= limit)
|
|
34510
|
+
return value;
|
|
34511
|
+
return `${value.slice(0, Math.max(0, limit - 1)).trimEnd()}\u2026`;
|
|
34512
|
+
};
|
|
34513
|
+
var formatScore = (score) => {
|
|
34514
|
+
if (!Number.isFinite(score))
|
|
34515
|
+
return "n/a";
|
|
34516
|
+
return score.toFixed(3);
|
|
34517
|
+
};
|
|
34518
|
+
var buildDefaultCitationMessage = (citations, args, maxChunkChars) => {
|
|
34519
|
+
if (citations.length === 0) {
|
|
34520
|
+
return `No knowledge base results for "${args.query}".`;
|
|
34521
|
+
}
|
|
34522
|
+
const lines = citations.map((citation, index) => {
|
|
34523
|
+
const label = citation.title ?? citation.source ?? citation.chunkId;
|
|
34524
|
+
const text = truncate(citation.chunkText, maxChunkChars);
|
|
34525
|
+
return `${String(index + 1)}. ${label} (score ${formatScore(citation.score)}): ${text}`;
|
|
34526
|
+
});
|
|
34527
|
+
return [
|
|
34528
|
+
`Knowledge base results for "${args.query}":`,
|
|
34529
|
+
...lines
|
|
34530
|
+
].join(`
|
|
34531
|
+
`);
|
|
34532
|
+
};
|
|
34533
|
+
var filterAllowedFilterKeys = (filter, allowedKeys) => {
|
|
34534
|
+
if (!filter)
|
|
34535
|
+
return;
|
|
34536
|
+
if (!allowedKeys)
|
|
34537
|
+
return filter;
|
|
34538
|
+
const allowed = new Set(allowedKeys);
|
|
34539
|
+
const entries = Object.entries(filter).filter(([key]) => allowed.has(key));
|
|
34540
|
+
if (entries.length === 0)
|
|
34541
|
+
return;
|
|
34542
|
+
return Object.fromEntries(entries);
|
|
34543
|
+
};
|
|
34544
|
+
var mergeFilters = (...filters) => {
|
|
34545
|
+
const present = filters.filter((entry) => entry !== undefined);
|
|
34546
|
+
if (present.length === 0)
|
|
34547
|
+
return;
|
|
34548
|
+
return Object.assign({}, ...present);
|
|
34549
|
+
};
|
|
34550
|
+
var buildVoiceRAGToolParameters = (options) => {
|
|
34551
|
+
if (options.parameters)
|
|
34552
|
+
return options.parameters;
|
|
34553
|
+
const defaultTopK = options.topK ?? DEFAULT_TOP_K;
|
|
34554
|
+
const maxTopK = options.maxTopK ?? DEFAULT_MAX_TOP_K;
|
|
34555
|
+
const properties = {
|
|
34556
|
+
query: {
|
|
34557
|
+
description: "Natural-language question to look up in the knowledge base.",
|
|
34558
|
+
type: "string"
|
|
34559
|
+
},
|
|
34560
|
+
topK: {
|
|
34561
|
+
default: defaultTopK,
|
|
34562
|
+
description: `How many citations to return (1-${String(maxTopK)}).`,
|
|
34563
|
+
maximum: maxTopK,
|
|
34564
|
+
minimum: 1,
|
|
34565
|
+
type: "integer"
|
|
34566
|
+
}
|
|
34567
|
+
};
|
|
34568
|
+
if (options.allowedFilterKeys && options.allowedFilterKeys.length > 0) {
|
|
34569
|
+
properties.filter = {
|
|
34570
|
+
additionalProperties: false,
|
|
34571
|
+
description: "Optional metadata filter. Only keys listed here are honored: " + options.allowedFilterKeys.join(", "),
|
|
34572
|
+
properties: Object.fromEntries(options.allowedFilterKeys.map((key) => [key, {}])),
|
|
34573
|
+
type: "object"
|
|
34574
|
+
};
|
|
34575
|
+
}
|
|
34576
|
+
return {
|
|
34577
|
+
additionalProperties: false,
|
|
34578
|
+
properties,
|
|
34579
|
+
required: ["query"],
|
|
34580
|
+
type: "object"
|
|
34581
|
+
};
|
|
34582
|
+
};
|
|
34583
|
+
var createVoiceRAGTool = (collection, options = {}) => {
|
|
34584
|
+
const name = options.name ?? DEFAULT_TOOL_NAME;
|
|
34585
|
+
const description = options.description ?? DEFAULT_DESCRIPTION;
|
|
34586
|
+
const defaultTopK = options.topK ?? DEFAULT_TOP_K;
|
|
34587
|
+
const maxTopK = options.maxTopK ?? DEFAULT_MAX_TOP_K;
|
|
34588
|
+
const maxChunkChars = options.maxChunkChars ?? DEFAULT_MAX_CHUNK_CHARS;
|
|
34589
|
+
const parameters = buildVoiceRAGToolParameters(options);
|
|
34590
|
+
return createVoiceAgentTool({
|
|
34591
|
+
description,
|
|
34592
|
+
execute: async ({ args, context }) => {
|
|
34593
|
+
const query = typeof args?.query === "string" ? args.query.trim() : "";
|
|
34594
|
+
if (query.length === 0) {
|
|
34595
|
+
const empty = {
|
|
34596
|
+
citations: [],
|
|
34597
|
+
message: "Knowledge base search requires a non-empty query.",
|
|
34598
|
+
query: "",
|
|
34599
|
+
topK: 0
|
|
34600
|
+
};
|
|
34601
|
+
return empty;
|
|
34602
|
+
}
|
|
34603
|
+
const requestedTopK = typeof args?.topK === "number" && Number.isFinite(args.topK) ? Math.min(maxTopK, Math.max(1, Math.floor(args.topK))) : defaultTopK;
|
|
34604
|
+
const llmFilter = filterAllowedFilterKeys(args?.filter, options.allowedFilterKeys);
|
|
34605
|
+
const fixedFilter = typeof options.fixedFilter === "function" ? options.fixedFilter({ context }) : options.fixedFilter;
|
|
34606
|
+
const filter = mergeFilters(fixedFilter, llmFilter);
|
|
34607
|
+
const rawResults = await collection.search({
|
|
34608
|
+
filter,
|
|
34609
|
+
query,
|
|
34610
|
+
scoreThreshold: options.scoreThreshold,
|
|
34611
|
+
topK: requestedTopK
|
|
34612
|
+
});
|
|
34613
|
+
const citations = Array.from(rawResults).slice(0, requestedTopK);
|
|
34614
|
+
const formatter = options.formatResult ? options.formatResult : (entries, innerArgs) => buildDefaultCitationMessage(entries, innerArgs, maxChunkChars);
|
|
34615
|
+
const message = formatter(citations, {
|
|
34616
|
+
filter,
|
|
34617
|
+
query,
|
|
34618
|
+
topK: requestedTopK
|
|
34619
|
+
});
|
|
34620
|
+
return {
|
|
34621
|
+
citations,
|
|
34622
|
+
message,
|
|
34623
|
+
query,
|
|
34624
|
+
topK: requestedTopK
|
|
34625
|
+
};
|
|
34626
|
+
},
|
|
34627
|
+
name,
|
|
34628
|
+
parameters,
|
|
34629
|
+
resultToMessage: options.resultToMessage ?? ((result) => result.message)
|
|
34630
|
+
});
|
|
34631
|
+
};
|
|
34502
34632
|
// src/agentSquadContract.ts
|
|
34503
34633
|
var normalizeIncludes = (value) => value.trim().toLowerCase();
|
|
34504
34634
|
var resolveOutcome4 = (result) => {
|
|
@@ -43156,6 +43286,7 @@ export {
|
|
|
43156
43286
|
createVoiceRealCallEvidenceRuntimeRoutes,
|
|
43157
43287
|
createVoiceRealCallEvidenceRuntime,
|
|
43158
43288
|
createVoiceReadinessProfile,
|
|
43289
|
+
createVoiceRAGTool,
|
|
43159
43290
|
createVoiceQualityRoutes,
|
|
43160
43291
|
createVoiceProviderSloRoutes,
|
|
43161
43292
|
createVoiceProviderRouter,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type VoiceAgentTool } from "./agent";
|
|
2
|
+
import type { VoiceSessionRecord } from "./types";
|
|
3
|
+
export type VoiceRAGQueryResult = {
|
|
4
|
+
chunkId: string;
|
|
5
|
+
chunkText: string;
|
|
6
|
+
score: number;
|
|
7
|
+
source?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
metadata?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
export type VoiceRAGSearchInput = {
|
|
12
|
+
filter?: Record<string, unknown>;
|
|
13
|
+
query: string;
|
|
14
|
+
scoreThreshold?: number;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
topK?: number;
|
|
17
|
+
};
|
|
18
|
+
export type VoiceRAGCollectionLike = {
|
|
19
|
+
search: (input: VoiceRAGSearchInput) => Promise<readonly VoiceRAGQueryResult[]> | readonly VoiceRAGQueryResult[];
|
|
20
|
+
};
|
|
21
|
+
export type VoiceRAGToolArgs = {
|
|
22
|
+
filter?: Record<string, unknown>;
|
|
23
|
+
query: string;
|
|
24
|
+
topK?: number;
|
|
25
|
+
};
|
|
26
|
+
export type VoiceRAGToolResult = {
|
|
27
|
+
citations: VoiceRAGQueryResult[];
|
|
28
|
+
message: string;
|
|
29
|
+
query: string;
|
|
30
|
+
topK: number;
|
|
31
|
+
};
|
|
32
|
+
export type VoiceRAGToolOptions<TContext = unknown> = {
|
|
33
|
+
allowedFilterKeys?: readonly string[];
|
|
34
|
+
description?: string;
|
|
35
|
+
fixedFilter?: Record<string, unknown> | ((input: {
|
|
36
|
+
context: TContext;
|
|
37
|
+
}) => Record<string, unknown> | undefined);
|
|
38
|
+
formatResult?: (citations: readonly VoiceRAGQueryResult[], args: VoiceRAGToolArgs) => string;
|
|
39
|
+
maxChunkChars?: number;
|
|
40
|
+
maxTopK?: number;
|
|
41
|
+
name?: string;
|
|
42
|
+
parameters?: Record<string, unknown>;
|
|
43
|
+
resultToMessage?: (result: VoiceRAGToolResult) => string;
|
|
44
|
+
scoreThreshold?: number;
|
|
45
|
+
topK?: number;
|
|
46
|
+
};
|
|
47
|
+
export declare const createVoiceRAGTool: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord>(collection: VoiceRAGCollectionLike, options?: VoiceRAGToolOptions<TContext>) => VoiceAgentTool<TContext, TSession, VoiceRAGToolArgs, VoiceRAGToolResult>;
|