@llmrtc/llmrtc-core 1.0.0

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.
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Metrics adapter interface for pluggable metrics reporting
3
+ *
4
+ * This module provides a simple interface for emitting metrics that can be
5
+ * implemented by various backends (Prometheus, StatsD, CloudWatch, etc.)
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Using console metrics for debugging
10
+ * const server = new LLMRTCServer({
11
+ * providers: { llm, stt, tts },
12
+ * metrics: new ConsoleMetrics()
13
+ * });
14
+ *
15
+ * // Using a custom Prometheus adapter
16
+ * class PrometheusMetrics implements MetricsAdapter {
17
+ * private histogram = new promClient.Histogram({
18
+ * name: 'llmrtc_duration_ms',
19
+ * help: 'Operation duration in milliseconds',
20
+ * labelNames: ['operation']
21
+ * });
22
+ *
23
+ * timing(name: string, durationMs: number, tags?: Record<string, string>) {
24
+ * this.histogram.observe({ operation: name, ...tags }, durationMs);
25
+ * }
26
+ * // ... other methods
27
+ * }
28
+ * ```
29
+ */
30
+ // =============================================================================
31
+ // Metric Names
32
+ // =============================================================================
33
+ /**
34
+ * Standard metric names used by the SDK
35
+ *
36
+ * Integrators can use these constants when building dashboards or alerts:
37
+ *
38
+ * ```typescript
39
+ * // Example Prometheus query
40
+ * // histogram_quantile(0.95, rate(llmrtc_stt_duration_ms_bucket[5m]))
41
+ * ```
42
+ */
43
+ export const MetricNames = {
44
+ // STT metrics
45
+ /** Duration of STT transcription in milliseconds */
46
+ STT_DURATION: 'llmrtc.stt.duration_ms',
47
+ // LLM metrics
48
+ /** Time to first LLM token in milliseconds */
49
+ LLM_TTFT: 'llmrtc.llm.ttft_ms',
50
+ /** Total LLM inference duration in milliseconds */
51
+ LLM_DURATION: 'llmrtc.llm.duration_ms',
52
+ /** Number of tokens generated (when available from provider) */
53
+ LLM_TOKENS: 'llmrtc.llm.tokens',
54
+ // TTS metrics
55
+ /** Duration of TTS synthesis in milliseconds */
56
+ TTS_DURATION: 'llmrtc.tts.duration_ms',
57
+ // Turn metrics
58
+ /** Total turn duration (STT + LLM + TTS) in milliseconds */
59
+ TURN_DURATION: 'llmrtc.turn.duration_ms',
60
+ // Session metrics
61
+ /** Session duration in milliseconds */
62
+ SESSION_DURATION: 'llmrtc.session.duration_ms',
63
+ /** Number of active sessions (gauge) */
64
+ ACTIVE_SESSIONS: 'llmrtc.sessions.active',
65
+ /** Total turns per session */
66
+ SESSION_TURNS: 'llmrtc.session.turns',
67
+ // Error metrics
68
+ /** Error counter by type/component */
69
+ ERRORS: 'llmrtc.errors',
70
+ // Connection metrics
71
+ /** Number of active WebSocket connections */
72
+ CONNECTIONS: 'llmrtc.connections.active',
73
+ /** Reconnection attempts */
74
+ RECONNECTIONS: 'llmrtc.reconnections',
75
+ // Tool metrics
76
+ /** Tool execution duration in milliseconds */
77
+ TOOL_DURATION: 'llmrtc.tool.duration_ms',
78
+ /** Number of tool calls */
79
+ TOOL_CALLS: 'llmrtc.tool.calls',
80
+ /** Tool call errors */
81
+ TOOL_ERRORS: 'llmrtc.tool.errors',
82
+ // Playbook metrics
83
+ /** Stage duration in milliseconds */
84
+ STAGE_DURATION: 'llmrtc.playbook.stage.duration_ms',
85
+ /** Number of stage transitions */
86
+ STAGE_TRANSITIONS: 'llmrtc.playbook.transitions',
87
+ /** Turns per stage */
88
+ STAGE_TURNS: 'llmrtc.playbook.stage.turns',
89
+ /** Current playbook stage (gauge) */
90
+ CURRENT_STAGE: 'llmrtc.playbook.stage.current',
91
+ /** Playbook completion counter */
92
+ PLAYBOOK_COMPLETIONS: 'llmrtc.playbook.completions'
93
+ };
94
+ // =============================================================================
95
+ // Built-in Implementations
96
+ // =============================================================================
97
+ /**
98
+ * No-op metrics adapter (default)
99
+ *
100
+ * Use this when metrics collection is disabled or not needed.
101
+ * All methods are no-ops with minimal overhead.
102
+ */
103
+ export class NoopMetrics {
104
+ increment() {
105
+ // No-op
106
+ }
107
+ timing() {
108
+ // No-op
109
+ }
110
+ gauge() {
111
+ // No-op
112
+ }
113
+ }
114
+ /**
115
+ * Console metrics adapter for debugging
116
+ *
117
+ * Logs all metrics to the console with timestamps.
118
+ * Useful during development to verify metrics are being emitted correctly.
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const server = new LLMRTCServer({
123
+ * providers: { llm, stt, tts },
124
+ * metrics: new ConsoleMetrics({ prefix: 'myapp' })
125
+ * });
126
+ *
127
+ * // Output:
128
+ * // [metric] myapp.llmrtc.stt.duration_ms 150ms { provider: 'whisper' }
129
+ * // [metric] myapp.llmrtc.llm.duration_ms 320ms { model: 'gpt-4' }
130
+ * ```
131
+ */
132
+ export class ConsoleMetrics {
133
+ constructor(options) {
134
+ Object.defineProperty(this, "prefix", {
135
+ enumerable: true,
136
+ configurable: true,
137
+ writable: true,
138
+ value: void 0
139
+ });
140
+ this.prefix = options?.prefix ? `${options.prefix}.` : '';
141
+ }
142
+ increment(name, value = 1, tags) {
143
+ const tagStr = tags ? ` ${JSON.stringify(tags)}` : '';
144
+ console.log(`[metric] ${this.prefix}${name} +${value}${tagStr}`);
145
+ }
146
+ timing(name, durationMs, tags) {
147
+ const tagStr = tags ? ` ${JSON.stringify(tags)}` : '';
148
+ console.log(`[metric] ${this.prefix}${name} ${durationMs}ms${tagStr}`);
149
+ }
150
+ gauge(name, value, tags) {
151
+ const tagStr = tags ? ` ${JSON.stringify(tags)}` : '';
152
+ console.log(`[metric] ${this.prefix}${name} = ${value}${tagStr}`);
153
+ }
154
+ }
155
+ /**
156
+ * In-memory metrics collector for testing
157
+ *
158
+ * Stores all metrics in arrays for later inspection.
159
+ * Useful in unit tests to verify metrics are emitted correctly.
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * const metrics = new InMemoryMetrics();
164
+ * const orchestrator = new ConversationOrchestrator({
165
+ * providers: { llm, stt, tts },
166
+ * metrics
167
+ * });
168
+ *
169
+ * // Run orchestrator...
170
+ *
171
+ * // Verify metrics
172
+ * expect(metrics.timings.find(t => t.name === MetricNames.STT_DURATION)).toBeDefined();
173
+ * expect(metrics.getLatestTiming(MetricNames.STT_DURATION)?.durationMs).toBeLessThan(200);
174
+ * ```
175
+ */
176
+ export class InMemoryMetrics {
177
+ constructor() {
178
+ Object.defineProperty(this, "counters", {
179
+ enumerable: true,
180
+ configurable: true,
181
+ writable: true,
182
+ value: []
183
+ });
184
+ Object.defineProperty(this, "timings", {
185
+ enumerable: true,
186
+ configurable: true,
187
+ writable: true,
188
+ value: []
189
+ });
190
+ Object.defineProperty(this, "gauges", {
191
+ enumerable: true,
192
+ configurable: true,
193
+ writable: true,
194
+ value: []
195
+ });
196
+ }
197
+ increment(name, value = 1, tags) {
198
+ this.counters.push({ name, value, tags, timestamp: Date.now() });
199
+ }
200
+ timing(name, durationMs, tags) {
201
+ this.timings.push({ name, durationMs, tags, timestamp: Date.now() });
202
+ }
203
+ gauge(name, value, tags) {
204
+ this.gauges.push({ name, value, tags, timestamp: Date.now() });
205
+ }
206
+ /**
207
+ * Get the latest counter entry for a metric name
208
+ */
209
+ getLatestCounter(name) {
210
+ return this.counters.filter(c => c.name === name).pop();
211
+ }
212
+ /**
213
+ * Get the latest timing entry for a metric name
214
+ */
215
+ getLatestTiming(name) {
216
+ return this.timings.filter(t => t.name === name).pop();
217
+ }
218
+ /**
219
+ * Get the latest gauge entry for a metric name
220
+ */
221
+ getLatestGauge(name) {
222
+ return this.gauges.filter(g => g.name === name).pop();
223
+ }
224
+ /**
225
+ * Get sum of all counter increments for a metric name
226
+ */
227
+ getCounterSum(name) {
228
+ return this.counters
229
+ .filter(c => c.name === name)
230
+ .reduce((sum, c) => sum + c.value, 0);
231
+ }
232
+ /**
233
+ * Clear all stored metrics
234
+ */
235
+ clear() {
236
+ this.counters.length = 0;
237
+ this.timings.length = 0;
238
+ this.gauges.length = 0;
239
+ }
240
+ }
241
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { ConversationOrchestratorConfig, LLMResult, OrchestratorYield, STTResult, TTSResult, VisionAttachment } from './types.js';
2
+ import { OrchestratorHooks } from './hooks.js';
3
+ import { MetricsAdapter } from './metrics.js';
4
+ /**
5
+ * Extended orchestrator configuration with hooks and metrics
6
+ */
7
+ export interface OrchestratorConfigWithHooks extends ConversationOrchestratorConfig {
8
+ /** Hooks for observability and extensibility */
9
+ hooks?: OrchestratorHooks;
10
+ /** Metrics adapter for emitting timing and counter metrics */
11
+ metrics?: MetricsAdapter;
12
+ /** Custom sentence boundary splitter for streaming TTS */
13
+ sentenceChunker?: (text: string) => string[];
14
+ }
15
+ export declare class ConversationOrchestrator {
16
+ private readonly config;
17
+ private readonly providers;
18
+ private readonly history;
19
+ private readonly systemPrompt?;
20
+ private readonly historyLimit;
21
+ private readonly logger;
22
+ private readonly streamingTTS;
23
+ private readonly sessionId?;
24
+ private readonly hooks;
25
+ private readonly metrics;
26
+ private readonly sentenceChunker?;
27
+ constructor(config: OrchestratorConfigWithHooks);
28
+ init(): Promise<void>;
29
+ /**
30
+ * Run a single voice+vision turn: audio -> STT -> LLM (+optional vision) -> TTS.
31
+ */
32
+ runTurn(audio: Buffer, attachments?: VisionAttachment[]): Promise<{
33
+ transcript: STTResult;
34
+ llm: LLMResult;
35
+ tts: TTSResult;
36
+ }>;
37
+ /**
38
+ * Streaming version: yields LLM chunks as they arrive.
39
+ *
40
+ * With streaming TTS enabled (when provider supports speakStream):
41
+ * - Detects sentence boundaries during LLM streaming
42
+ * - Starts TTS generation as soon as each sentence is complete
43
+ * - Yields TTSChunk events with PCM audio data
44
+ * - User hears audio while LLM is still generating
45
+ *
46
+ * Hooks and metrics are called throughout the turn lifecycle.
47
+ */
48
+ runTurnStream(audio: Buffer, attachments?: VisionAttachment[]): AsyncGenerator<OrchestratorYield, void, unknown>;
49
+ /**
50
+ * Split text into sentences using custom chunker or default regex
51
+ */
52
+ private splitIntoSentences;
53
+ /**
54
+ * Stream TTS chunks for a sentence using PCM format with hooks.
55
+ * Yields TTSChunk events as audio data arrives.
56
+ */
57
+ private streamTTSChunksWithHooks;
58
+ /**
59
+ * Non-streaming TTS with hooks: generates complete audio and yields as single TTSResult.
60
+ */
61
+ private generateTTSWithHooks;
62
+ /**
63
+ * @deprecated Use streamTTSChunksWithHooks instead
64
+ * Stream TTS chunks for a sentence using PCM format.
65
+ */
66
+ private streamTTSChunks;
67
+ /**
68
+ * @deprecated Use generateTTSWithHooks instead
69
+ * Non-streaming TTS: generates complete audio and yields as single TTSResult.
70
+ */
71
+ private generateTTS;
72
+ private pushHistory;
73
+ }