dvgateway-sdk 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.
Files changed (78) hide show
  1. package/README.md +615 -0
  2. package/dist/audio/codec.d.ts +44 -0
  3. package/dist/audio/codec.d.ts.map +1 -0
  4. package/dist/audio/codec.js +136 -0
  5. package/dist/audio/codec.js.map +1 -0
  6. package/dist/audio/codec.test.d.ts +2 -0
  7. package/dist/audio/codec.test.d.ts.map +1 -0
  8. package/dist/audio/codec.test.js +155 -0
  9. package/dist/audio/codec.test.js.map +1 -0
  10. package/dist/auth/manager.d.ts +34 -0
  11. package/dist/auth/manager.d.ts.map +1 -0
  12. package/dist/auth/manager.js +122 -0
  13. package/dist/auth/manager.js.map +1 -0
  14. package/dist/auth/manager.test.d.ts +2 -0
  15. package/dist/auth/manager.test.d.ts.map +1 -0
  16. package/dist/auth/manager.test.js +147 -0
  17. package/dist/auth/manager.test.js.map +1 -0
  18. package/dist/client.d.ts +154 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +218 -0
  21. package/dist/client.js.map +1 -0
  22. package/dist/index.d.ts +36 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +40 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/minutes/manager.d.ts +43 -0
  27. package/dist/minutes/manager.d.ts.map +1 -0
  28. package/dist/minutes/manager.js +71 -0
  29. package/dist/minutes/manager.js.map +1 -0
  30. package/dist/observability/logger.d.ts +19 -0
  31. package/dist/observability/logger.d.ts.map +1 -0
  32. package/dist/observability/logger.js +70 -0
  33. package/dist/observability/logger.js.map +1 -0
  34. package/dist/observability/metrics.d.ts +53 -0
  35. package/dist/observability/metrics.d.ts.map +1 -0
  36. package/dist/observability/metrics.js +143 -0
  37. package/dist/observability/metrics.js.map +1 -0
  38. package/dist/observability/metrics.test.d.ts +2 -0
  39. package/dist/observability/metrics.test.d.ts.map +1 -0
  40. package/dist/observability/metrics.test.js +122 -0
  41. package/dist/observability/metrics.test.js.map +1 -0
  42. package/dist/pipeline/builder.d.ts +111 -0
  43. package/dist/pipeline/builder.d.ts.map +1 -0
  44. package/dist/pipeline/builder.js +323 -0
  45. package/dist/pipeline/builder.js.map +1 -0
  46. package/dist/session/manager.d.ts +21 -0
  47. package/dist/session/manager.d.ts.map +1 -0
  48. package/dist/session/manager.js +52 -0
  49. package/dist/session/manager.js.map +1 -0
  50. package/dist/streams/audio-stream.d.ts +29 -0
  51. package/dist/streams/audio-stream.d.ts.map +1 -0
  52. package/dist/streams/audio-stream.js +118 -0
  53. package/dist/streams/audio-stream.js.map +1 -0
  54. package/dist/streams/call-events.d.ts +32 -0
  55. package/dist/streams/call-events.d.ts.map +1 -0
  56. package/dist/streams/call-events.js +140 -0
  57. package/dist/streams/call-events.js.map +1 -0
  58. package/dist/streams/tts-stream.d.ts +46 -0
  59. package/dist/streams/tts-stream.d.ts.map +1 -0
  60. package/dist/streams/tts-stream.js +102 -0
  61. package/dist/streams/tts-stream.js.map +1 -0
  62. package/dist/transport/http-client.d.ts +36 -0
  63. package/dist/transport/http-client.d.ts.map +1 -0
  64. package/dist/transport/http-client.js +102 -0
  65. package/dist/transport/http-client.js.map +1 -0
  66. package/dist/transport/http-client.test.d.ts +2 -0
  67. package/dist/transport/http-client.test.d.ts.map +1 -0
  68. package/dist/transport/http-client.test.js +172 -0
  69. package/dist/transport/http-client.test.js.map +1 -0
  70. package/dist/transport/ws-pool.d.ts +34 -0
  71. package/dist/transport/ws-pool.d.ts.map +1 -0
  72. package/dist/transport/ws-pool.js +123 -0
  73. package/dist/transport/ws-pool.js.map +1 -0
  74. package/dist/types/index.d.ts +378 -0
  75. package/dist/types/index.d.ts.map +1 -0
  76. package/dist/types/index.js +25 -0
  77. package/dist/types/index.js.map +1 -0
  78. package/package.json +81 -0
package/README.md ADDED
@@ -0,0 +1,615 @@
1
+ # DVGateway SDK
2
+
3
+ DVGateway를 통해 AI 플랫폼과 실시간 음성 서비스를 **5줄의 코드**로 연동합니다.
4
+ **Node.js(TypeScript)** 와 **Python** 두 언어를 모두 지원합니다.
5
+
6
+ ---
7
+
8
+ ## Node.js SDK
9
+
10
+ ### 설치
11
+
12
+ ```bash
13
+ npm install dvgateway-sdk dvgateway-adapters
14
+ ```
15
+
16
+ ### 30초 시작 가이드
17
+
18
+ ```typescript
19
+ import { DVGatewayClient } from 'dvgateway-sdk';
20
+ import { DeepgramAdapter } from 'dvgateway-adapters/stt';
21
+ import { AnthropicAdapter } from 'dvgateway-adapters/llm';
22
+ import { ElevenLabsAdapter } from 'dvgateway-adapters/tts';
23
+
24
+ const gw = new DVGatewayClient({
25
+ baseUrl: 'http://localhost:8080', // DVGateway API 서버 (:8080)
26
+ auth: { type: 'apiKey', apiKey: 'your_key' },
27
+ });
28
+
29
+ await gw.pipeline()
30
+ .stt(new DeepgramAdapter({ apiKey: '...', language: 'ko' }))
31
+ .llm(new AnthropicAdapter({ apiKey: '...', model: 'claude-sonnet-4-6' }))
32
+ .tts(new ElevenLabsAdapter({ apiKey: '...' }))
33
+ .start();
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Python SDK
39
+
40
+ ### 설치
41
+
42
+ ```bash
43
+ pip install dvgateway dvgateway-adapters python-dotenv
44
+ ```
45
+
46
+ ### 30초 시작 가이드
47
+
48
+ ```python
49
+ import asyncio
50
+ import os
51
+ from dotenv import load_dotenv
52
+
53
+ from dvgateway import DVGatewayClient
54
+ from dvgateway.adapters.stt import DeepgramAdapter
55
+ from dvgateway.adapters.llm import AnthropicAdapter
56
+ from dvgateway.adapters.tts import ElevenLabsAdapter
57
+
58
+ load_dotenv()
59
+
60
+ async def main():
61
+ gw = DVGatewayClient(
62
+ base_url="http://localhost:8080",
63
+ auth={"type": "apiKey", "api_key": os.environ["GATEWAY_API_KEY"]},
64
+ )
65
+
66
+ await (
67
+ gw.pipeline()
68
+ .stt(DeepgramAdapter(api_key=os.environ["DEEPGRAM_API_KEY"], language="ko"))
69
+ .llm(AnthropicAdapter(api_key=os.environ["ANTHROPIC_API_KEY"], model="claude-sonnet-4-6"))
70
+ .tts(ElevenLabsAdapter(api_key=os.environ["ELEVENLABS_API_KEY"]))
71
+ .start()
72
+ )
73
+
74
+ asyncio.run(main())
75
+ ```
76
+
77
+ ### Python — 로컬 어댑터 (오프라인)
78
+
79
+ Python SDK는 로컬 STT/LLM 어댑터를 추가로 지원합니다. 인터넷 없이 완전 오프라인 운영이 가능합니다.
80
+
81
+ ```bash
82
+ # 로컬 어댑터 추가 패키지
83
+ pip install faster-whisper # Faster-Whisper STT
84
+ pip install openai-whisper # OpenAI Whisper STT (공식)
85
+ # Ollama는 별도 설치: https://ollama.com/install.sh
86
+ ```
87
+
88
+ ```python
89
+ # 완전 오프라인 파이프라인
90
+ from dvgateway.adapters.stt import FasterWhisperAdapter
91
+ from dvgateway.adapters.llm import OllamaAdapter
92
+
93
+ await (
94
+ gw.pipeline()
95
+ # Faster-Whisper: 로컬 고속 STT
96
+ .stt(FasterWhisperAdapter(
97
+ model="large-v3",
98
+ device="cpu", # GPU: "cuda"
99
+ compute_type="int8", # GPU: "float16"
100
+ language="ko",
101
+ vad_enabled=True,
102
+ ))
103
+ # Qwen via Ollama: 로컬 LLM
104
+ .llm(OllamaAdapter(
105
+ base_url="http://localhost:11434",
106
+ model="qwen3.5:9b",
107
+ system_prompt="친절한 한국어 AI 상담원입니다.",
108
+ options={"think": False},
109
+ ))
110
+ .tts(ElevenLabsAdapter(api_key="...")) # TTS는 ElevenLabs 권장
111
+ .start()
112
+ )
113
+ ```
114
+
115
+ ### Node.js — OpenAI Realtime (Speech-to-Speech)
116
+
117
+ For sub-300ms end-to-end latency, use the Realtime adapter to bypass the STT→LLM→TTS chain entirely:
118
+
119
+ ```typescript
120
+ import { DVGatewayClient } from 'dvgateway-sdk';
121
+ import { OpenAIRealtimeAdapter } from 'dvgateway-adapters/realtime';
122
+
123
+ const gw = new DVGatewayClient({
124
+ baseUrl: 'http://localhost:8080',
125
+ auth: { type: 'apiKey', apiKey: 'your_key' },
126
+ });
127
+
128
+ const realtime = new OpenAIRealtimeAdapter({
129
+ apiKey: process.env.OPENAI_API_KEY!,
130
+ model: 'gpt-4o-mini-realtime-preview', // cost-efficient; use gpt-4o-realtime-preview for best quality
131
+ voice: 'alloy', // alloy | echo | nova | shimmer | ash | coral | sage | verse
132
+ instructions: 'You are a helpful voice assistant. Keep answers short and conversational.',
133
+ turnDetection: { mode: 'server_vad', silenceDurationMs: 500 },
134
+ inputTranscription: true,
135
+ });
136
+
137
+ realtime.onAudioOutput((chunk, linkedId) => gw.injectAudio(linkedId, chunk));
138
+ realtime.onTranscript((result) => console.log(`[${result.speaker}] ${result.text}`));
139
+
140
+ gw.onCallEvent(async (event) => {
141
+ if (event.type === 'call:new') {
142
+ const audioStream = gw.streamAudio(event.session.linkedId, { dir: 'in' });
143
+ await realtime.startSession(event.session.linkedId, audioStream);
144
+ }
145
+ if (event.type === 'call:ended') {
146
+ await realtime.stop(event.linkedId);
147
+ }
148
+ });
149
+
150
+ await gw.connect();
151
+ ```
152
+
153
+ **Realtime vs cascaded pipeline:**
154
+
155
+ | | Cascaded (STT→LLM→TTS) | Realtime (Speech-to-Speech) |
156
+ |--|---|---|
157
+ | Latency | ~500ms | ~300ms |
158
+ | Cost | per-service billing | single API call |
159
+ | Control | full per-step control | unified session |
160
+ | Best for | complex agents, custom logic | low-latency voice bots |
161
+
162
+ ---
163
+
164
+ ---
165
+
166
+ ### 사람다운 음성 최적화 (Human Voice Options)
167
+
168
+ TTS 어댑터에 **사람다운 음성** 옵션이 내장되어 있습니다. 기본값은 **한국어 대화**에 최적화되어 있으며, 자연스러운 쉼, 숨소리, 필러 단어, 감탄사, 감정 표현을 제어할 수 있습니다.
169
+
170
+ ```typescript
171
+ import { ElevenLabsAdapter } from 'dvgateway-adapters/tts';
172
+ import { OpenAITtsAdapter } from 'dvgateway-adapters/tts';
173
+
174
+ // ✅ 기본값: 한국어 최적화 (humanVoice 자동 활성화)
175
+ const tts = new ElevenLabsAdapter({ apiKey: '...' });
176
+ // → stability: 0.3, style: 0.6, model: eleven_multilingual_v2
177
+
178
+ // ✅ OpenAI: gpt-4o-mini-tts 자동 선택 + 음성 지시 자동 생성
179
+ const openaiTts = new OpenAITtsAdapter({ apiKey: '...' });
180
+ // → model: gpt-4o-mini-tts, voiceInstructions 자동 생성
181
+
182
+ // ✅ 커스텀 설정
183
+ const customTts = new ElevenLabsAdapter({
184
+ apiKey: '...',
185
+ humanVoice: {
186
+ naturalPauses: true, // 문장 사이 자연스러운 쉼
187
+ breathingSounds: true, // 숨소리 포함
188
+ fillerWords: false, // 필러 단어 비활성화 (음, 어, 그...)
189
+ exclamations: true, // 감탄사 (아, 네, 맞아요...)
190
+ emotionalRange: 0.8, // 감정 표현 범위 (0.0–1.0)
191
+ speechVariation: 0.7, // 음성 변화도 (0.0–1.0)
192
+ },
193
+ });
194
+
195
+ // ❌ 비활성화: 기존 방식 사용
196
+ const flatTts = new ElevenLabsAdapter({
197
+ apiKey: '...',
198
+ humanVoice: false,
199
+ // → stability: 0.5, style: 0.0, model: eleven_flash_v2_5 (기존 기본값)
200
+ });
201
+ ```
202
+
203
+ #### HumanVoiceOptions 인터페이스
204
+
205
+ | 옵션 | 타입 | 기본값 (KO) | 설명 |
206
+ |------|------|-------------|------|
207
+ | `naturalPauses` | `boolean` | `true` | 문장·절 사이 자연스러운 쉼 |
208
+ | `breathingSounds` | `boolean` | `true` | 긴 문장 사이 숨소리 |
209
+ | `fillerWords` | `boolean` | `true` | 필러 단어 (음, 어, 그, 저) |
210
+ | `exclamations` | `boolean` | `true` | 감탄사 (아, 네, 맞아요) |
211
+ | `emotionalRange` | `number` | `0.6` | 감정 표현 범위 (0.0–1.0) |
212
+ | `speechVariation` | `number` | `0.7` | 음성 변화도 (0.0–1.0) |
213
+
214
+ #### 프로바이더별 매핑
215
+
216
+ | HumanVoiceOptions | ElevenLabs | OpenAI (gpt-4o-mini-tts) |
217
+ |---|---|---|
218
+ | `emotionalRange` | → `style` 파라미터 | → voiceInstructions 감정 지시 |
219
+ | `speechVariation` | → `stability` (역비례: 1.0 - variation) | → voiceInstructions 톤 변화 지시 |
220
+ | `naturalPauses` | 모델 내장 (multilingual v2) | → voiceInstructions 쉼 지시 |
221
+ | `breathingSounds` | 모델 내장 (multilingual v2) | → voiceInstructions 숨소리 지시 |
222
+ | `fillerWords` | 모델 내장 | → voiceInstructions 필러 단어 지시 |
223
+ | `exclamations` | 모델 내장 | → voiceInstructions 감탄사 지시 |
224
+
225
+ #### 프리셋
226
+
227
+ ```typescript
228
+ import { HUMAN_VOICE_DEFAULTS_KO, HUMAN_VOICE_DEFAULTS_EN } from 'dvgateway-sdk';
229
+
230
+ // 한국어 기본값 (기본 적용)
231
+ HUMAN_VOICE_DEFAULTS_KO // { naturalPauses: true, breathingSounds: true, fillerWords: true, ... }
232
+
233
+ // 영어 기본값
234
+ HUMAN_VOICE_DEFAULTS_EN // { naturalPauses: true, breathingSounds: true, fillerWords: false, ... }
235
+ ```
236
+
237
+ ---
238
+
239
+ ### ElevenLabs 한국어 네이티브 보이스
240
+
241
+ ElevenLabs Voice Library에서 선별한 **9개 한국어 네이티브 음성**이 내장되어 있습니다:
242
+
243
+ ```typescript
244
+ import { ELEVENLABS_KOREAN_VOICES } from 'dvgateway-adapters';
245
+
246
+ // 내장 한국어 음성 목록
247
+ for (const voice of ELEVENLABS_KOREAN_VOICES) {
248
+ console.log(`${voice.id} — ${voice.label}`);
249
+ }
250
+ // pjJMvFj0JGWi3mogOkHH — Hyun Bin (남성, 한국어)
251
+ // t0jbNlBVZ17f02VDIeMI — 지영 / JiYoung (여성, 한국어)
252
+ // zrHiDhphv9ZnVXBqCLjz — Jennie (여성, 한국어)
253
+ // ZJCNdOEhQGMOIbMuhBME — Han Aim (남성, 한국어)
254
+ // ova4yY2jqnnUdGOmTGbx — KKC HQ (남성, 한국어)
255
+ // Xb7hH8MSUJpSbSDYk0k2 — Anna Kim (여성, 한국어)
256
+ // XrExE9yKIg1WjnnlVkGX — Yuna (여성, 한국어)
257
+ // ThT5KcBeYPX3keUQqHPh — Jina (여성, 한국어)
258
+ // Sita5M0jWFxPiECPABjR — jjeong (여성, 한국어)
259
+
260
+ // 한국어 음성으로 TTS 생성
261
+ const tts = new ElevenLabsAdapter({
262
+ apiKey: '...',
263
+ voiceId: ELEVENLABS_KOREAN_VOICES[0].id, // Hyun Bin
264
+ });
265
+ ```
266
+
267
+ ```python
268
+ # Python
269
+ from dvgateway.adapters.tts import ElevenLabsAdapter, KOREAN_VOICES
270
+
271
+ for voice in KOREAN_VOICES:
272
+ print(f"{voice.id} — {voice.label}")
273
+
274
+ tts = ElevenLabsAdapter(api_key="...", voice_id=KOREAN_VOICES[0].id)
275
+ ```
276
+
277
+ ---
278
+
279
+ ### ElevenLabs Voice Fetch (동적 음성 목록 조회)
280
+
281
+ ElevenLabs 계정에 등록된 **모든 음성**(기본, 클론, 라이브러리)을 동적으로 조회합니다:
282
+
283
+ ```typescript
284
+ import { ElevenLabsAdapter } from 'dvgateway-adapters/tts';
285
+
286
+ // API에서 사용 가능한 모든 음성 조회
287
+ const voices = await ElevenLabsAdapter.fetchVoices('your-elevenlabs-api-key');
288
+ for (const v of voices) {
289
+ console.log(`${v.id} — ${v.label}`);
290
+ // 클론된 음성: "My Voice (클론) [ko]"
291
+ // 생성된 음성: "Custom Voice (생성됨)"
292
+ }
293
+ ```
294
+
295
+ ```python
296
+ # Python
297
+ voices = await ElevenLabsAdapter.fetch_voices("your-elevenlabs-api-key")
298
+ for v in voices:
299
+ print(f"{v.id} — {v.label}")
300
+ ```
301
+
302
+ **DVGateway REST API:**
303
+ ```
304
+ GET /api/v1/config/apikeys/voices/elevenlabs/fetch
305
+ ```
306
+
307
+ ---
308
+
309
+ ### ElevenLabs Voice Cloning (음성 복제)
310
+
311
+ 오디오 파일을 업로드하여 **커스텀 음성을 생성**합니다:
312
+
313
+ ```typescript
314
+ import { ElevenLabsAdapter } from 'dvgateway-adapters/tts';
315
+ import { readFileSync } from 'fs';
316
+
317
+ // 오디오 파일에서 음성 복제
318
+ const audioData = readFileSync('./my-voice-sample.wav');
319
+ const newVoice = await ElevenLabsAdapter.cloneVoice(
320
+ 'your-elevenlabs-api-key',
321
+ '내 목소리', // 음성 이름
322
+ audioData, // 오디오 데이터 (WAV, MP3, OGG)
323
+ 'my-voice-sample.wav', // 파일명
324
+ '한국어 남성 음성', // 설명 (선택)
325
+ );
326
+
327
+ console.log(`클론된 음성 ID: ${newVoice.id}`); // → 새로운 voice_id
328
+
329
+ // 클론된 음성으로 TTS 생성
330
+ const tts = new ElevenLabsAdapter({
331
+ apiKey: '...',
332
+ voiceId: newVoice.id,
333
+ });
334
+ ```
335
+
336
+ ```python
337
+ # Python
338
+ audio_data = open("./my-voice-sample.wav", "rb").read()
339
+ new_voice = await ElevenLabsAdapter.clone_voice(
340
+ api_key="your-elevenlabs-api-key",
341
+ name="내 목소리",
342
+ audio_data=audio_data,
343
+ file_name="my-voice-sample.wav",
344
+ description="한국어 남성 음성",
345
+ )
346
+ print(f"클론된 음성 ID: {new_voice.id}")
347
+ ```
348
+
349
+ **DVGateway REST API:**
350
+ ```
351
+ POST /api/v1/config/apikeys/voices/elevenlabs/clone
352
+ Content-Type: multipart/form-data
353
+ Fields: name, description (optional), file (audio)
354
+ ```
355
+
356
+ ---
357
+
358
+ ### TTS 캐싱 (CachedTtsAdapter)
359
+
360
+ 디스크 기반 TTS 오디오 캐싱으로 **비용 절감**과 **응답 속도 향상**을 동시에 달성합니다:
361
+
362
+ ```typescript
363
+ import { CachedTtsAdapter, ElevenLabsAdapter } from 'dvgateway-adapters';
364
+
365
+ const inner = new ElevenLabsAdapter({ apiKey: '...' });
366
+ const tts = new CachedTtsAdapter(inner, {
367
+ provider: 'elevenlabs',
368
+ cacheDir: './tts-cache',
369
+ ttlMs: 7 * 24 * 60 * 60 * 1000, // 7일
370
+ maxEntries: 1000, // LRU 제한
371
+ });
372
+
373
+ // 자주 사용하는 안내 멘트 사전 캐싱
374
+ await tts.warmup([
375
+ { text: '안녕하세요, 무엇을 도와드릴까요?' },
376
+ { text: '잠시만 기다려 주세요.' },
377
+ { text: '감사합니다. 좋은 하루 되세요.' },
378
+ ]);
379
+
380
+ // 캐시 히트 시 즉시 응답 (API 호출 없음)
381
+ const stats = tts.getStats();
382
+ console.log(`캐시 히트: ${stats.hits}, 미스: ${stats.misses}`);
383
+ ```
384
+
385
+ ```python
386
+ # Python
387
+ from dvgateway.adapters.tts import CachedTtsAdapter, ElevenLabsAdapter
388
+
389
+ inner = ElevenLabsAdapter(api_key="...")
390
+ tts = CachedTtsAdapter(inner, provider="elevenlabs", cache_dir="./tts-cache")
391
+
392
+ await tts.warmup([
393
+ {"text": "안녕하세요, 무엇을 도와드릴까요?"},
394
+ {"text": "잠시만 기다려 주세요."},
395
+ ])
396
+ ```
397
+
398
+ ---
399
+
400
+ ### STT 옵션 (SttOptions)
401
+
402
+ STT 어댑터에 전달 가능한 프로바이더 독립적 옵션입니다.
403
+
404
+ ```typescript
405
+ import type { SttOptions } from 'dvgateway-sdk';
406
+
407
+ const sttOptions: SttOptions = {
408
+ language: 'ko', // 언어 코드
409
+ diarize: true, // 화자 분리
410
+ vadSensitivity: 'medium', // VAD 감도 (low/medium/high)
411
+ endpointingMs: 300, // 발화 종료 감지 (ms)
412
+ utteranceEndMs: 800, // 발화 최종 확정 (ms, 한국어 최적화)
413
+ interimResults: true, // 중간 결과 수신
414
+ smartFormat: true, // 스마트 포맷팅 (숫자, 날짜, 구두점)
415
+ keywords: ['DVGateway', 'AI'], // 도메인 키워드 부스트
416
+ punctuate: true, // 구두점 자동 추가
417
+ profanityFilter: false, // 비속어 필터
418
+ sentiment: true, // 감정 분석 (Deepgram Nova-3)
419
+ };
420
+ ```
421
+
422
+ ---
423
+
424
+ ### 감정 분석 (Sentiment Analysis)
425
+
426
+ Deepgram Nova-3 모델에서 **실시간 감정 분석**을 지원합니다. 각 발화(transcript segment)를 `positive` / `neutral` / `negative`로 분류하고 신뢰도 점수를 반환합니다.
427
+
428
+ ```typescript
429
+ import { DeepgramAdapter } from 'dvgateway-adapters/stt';
430
+
431
+ // sentiment: true 로 감정 분석 활성화
432
+ const stt = new DeepgramAdapter({
433
+ apiKey: '...',
434
+ language: 'ko',
435
+ sentiment: true, // 감정 분석 활성화
436
+ });
437
+
438
+ gw.pipeline()
439
+ .stt(stt)
440
+ .onTranscript((result, session) => {
441
+ console.log(`[${result.speaker}] ${result.text}`);
442
+ if (result.sentiment) {
443
+ console.log(` 감정: ${result.sentiment.sentiment} (${result.sentiment.sentimentScore})`);
444
+ // → 감정: positive (0.87)
445
+ }
446
+ })
447
+ .start();
448
+ ```
449
+
450
+ ```python
451
+ # Python
452
+ from dvgateway.adapters.stt import DeepgramAdapter
453
+
454
+ stt = DeepgramAdapter(api_key="...", language="ko", sentiment=True)
455
+
456
+ # result.sentiment.sentiment → "positive" | "neutral" | "negative"
457
+ # result.sentiment.sentiment_score → 0.0–1.0
458
+ ```
459
+
460
+ #### SentimentResult 인터페이스
461
+
462
+ | 필드 | 타입 | 설명 |
463
+ |------|------|------|
464
+ | `sentiment` | `'positive' \| 'neutral' \| 'negative'` | 세그먼트 감정 분류 |
465
+ | `sentimentScore` | `number` | 감정 신뢰도 점수 (0.0–1.0) |
466
+
467
+ > 대시보드에서도 Deepgram STT 프로바이더 설정에서 "감정 분석" 체크박스로 활성화할 수 있습니다.
468
+
469
+ ---
470
+
471
+ ### Node.js vs Python SDK 비교
472
+
473
+ | 항목 | Node.js | Python |
474
+ |------|---------|--------|
475
+ | 패키지 | `dvgateway-sdk` | `dvgateway` |
476
+ | 어댑터 | `dvgateway-adapters` | `dvgateway-adapters` |
477
+ | 이벤트 핸들러 | `.onNewCall(cb)` | `.on_new_call(cb)` |
478
+ | 비동기 | `async/await` | `asyncio` |
479
+ | Realtime (S2S) | ✅ `OpenAIRealtimeAdapter` | ✅ `OpenAIRealtimeAdapter` |
480
+ | 로컬 STT 지원 | whisper.cpp (외부 서버) | ✅ whisper.cpp, Faster-Whisper, OpenAI Whisper (인프로세스) |
481
+ | 로컬 LLM 지원 | Ollama (외부 서버) | ✅ Ollama, vLLM (OpenAI 호환) |
482
+
483
+ ## DVGateway 포트 구조
484
+
485
+ | 포트 | 용도 |
486
+ |------|------|
487
+ | `:8080` | API 서버 — AI SDK가 연결하는 포트 |
488
+ | `:8081` | 대시보드 UI |
489
+ | `:8092` | 미디어 서버 — Asterisk ExternalMedia WebSocket (`GW_MEDIA_ADDR` 기본값) |
490
+ | `:8088` | Asterisk ARI HTTP — DVGateway가 Asterisk에 연결할 때 사용 |
491
+
492
+ > ⚠️ SDK는 항상 **:8080 API 서버**에 연결합니다. `:8092`는 Asterisk가 DVGateway에 연결하는 포트입니다.
493
+
494
+ ## 아키텍처
495
+
496
+ ```
497
+ Asterisk PBX
498
+ │ ExternalMedia WebSocket (slin16, 16kHz)
499
+
500
+ DVGateway (:8092 미디어 서버)
501
+
502
+
503
+ DVGateway API (:8080)
504
+ │ ├── /api/v1/ws/callinfo 콜 이벤트
505
+ │ ├── /api/v1/ws/stream 오디오 스트림 (AI → 구독)
506
+ │ └── /api/v1/ws/tts/{id} TTS 주입 (AI → Asterisk)
507
+
508
+
509
+ dvgateway-sdk (이 패키지)
510
+
511
+ ├── SttAdapter (Deepgram, whisper.cpp, Faster-Whisper, ...)
512
+ ├── LlmAdapter (Anthropic Claude, OpenAI GPT, Ollama/Qwen, ...)
513
+ ├── TtsAdapter (ElevenLabs, OpenAI TTS, ...)
514
+ └── RealtimeAdapter (OpenAI Realtime — speech-to-speech, bypasses STT/LLM/TTS)
515
+ ```
516
+
517
+ ## 오디오 포맷
518
+
519
+ DVGateway는 **slin16** 포맷으로 오디오를 전송합니다:
520
+ - 샘플레이트: 16,000 Hz
521
+ - 비트깊이: 16-bit signed integer
522
+ - 채널: Mono (1채널)
523
+ - 엔디안: Little-endian
524
+ - 프레임 크기: 640 bytes = 320 samples = **20ms**
525
+
526
+ SDK는 자동으로 `slin16 → Float32Array [-1.0, 1.0]`으로 변환합니다.
527
+
528
+ ## API 참조
529
+
530
+ ### 고수준 API (파이프라인 빌더)
531
+
532
+ ```typescript
533
+ gw.pipeline()
534
+ .stt(adapter) // STT 어댑터 설정
535
+ .llm(adapter) // LLM 어댑터 설정 (선택)
536
+ .tts(adapter) // TTS 어댑터 설정 (선택)
537
+ .audioFilter({ dir: 'in' }) // 오디오 방향 필터 (both/in/out)
538
+ .onNewCall(handler) // 새 콜 이벤트
539
+ .onCallEnded(handler) // 콜 종료 이벤트
540
+ .onTranscript(handler) // 전사 결과 이벤트
541
+ .onError(handler) // 오류 이벤트
542
+ .start() // 시작 (Promise — 중단할 때까지 실행)
543
+ ```
544
+
545
+ ### 중간수준 API
546
+
547
+ ```typescript
548
+ // 오디오 스트림 구독
549
+ const stream = gw.streamAudio(linkedId, { dir: 'both' });
550
+ for await (const chunk of stream) {
551
+ // chunk.samples: Float32Array
552
+ }
553
+
554
+ // TTS 주입
555
+ await gw.injectTts(linkedId, tts.synthesize('안녕하세요'));
556
+
557
+ // 컨퍼런스 TTS 브로드캐스트
558
+ await gw.broadcastTts(confId, tts.synthesize('안내말씀'));
559
+
560
+ // 콜 이벤트 구독
561
+ const unsub = gw.onCallEvent((event) => { /* ... */ });
562
+
563
+ // 세션 관리
564
+ const sessions = await gw.listSessions();
565
+ await gw.updateSessionMeta(linkedId, { customerId: 'C001' });
566
+
567
+ // 회의록
568
+ await gw.submitTranscript(confId, result);
569
+ const minutes = await gw.downloadMinutes(confId, 'txt');
570
+ ```
571
+
572
+ ## 보안
573
+
574
+ - **API Key → JWT 자동 교환**: SDK가 내부적으로 처리, 사용자 코드에서 토큰 관리 불필요
575
+ - **PII 자동 마스킹**: 로그에서 전화번호 등 개인정보 자동 제거 (기본 활성화)
576
+ - **TLS 강제**: `http://`를 `https://`로 자동 업그레이드 (프로덕션)
577
+ - **mTLS 지원**: 기업 환경의 클라이언트 인증서 지원
578
+
579
+ ## 서비스 지속성
580
+
581
+ - **자동 재연결**: WebSocket 끊김 시 지수 백오프(Exponential backoff)로 자동 재연결
582
+ - **활성 콜 보호**: 재연결 후 진행 중인 콜 자동 재구독
583
+ - **재연결 설정**:
584
+ ```typescript
585
+ new DVGatewayClient({
586
+ reconnect: {
587
+ maxAttempts: 10, // 최대 재시도 횟수
588
+ initialDelayMs: 2000, // 첫 재시도 대기 (2초)
589
+ maxDelayMs: 30_000, // 최대 대기 (30초)
590
+ backoffMultiplier: 2.0, // 대기 시간 배수
591
+ },
592
+ });
593
+ ```
594
+
595
+ ## 메트릭
596
+
597
+ ```typescript
598
+ // 레이턴시 통계
599
+ gw.metrics.logSummary();
600
+
601
+ // Prometheus 형식으로 내보내기
602
+ const promText = gw.metrics.toPrometheus();
603
+ ```
604
+
605
+ 수집 메트릭:
606
+ - `dvgw_stt_latency_ms` — STT 응답 시간
607
+ - `dvgw_llm_ttft_ms` — LLM 첫 토큰까지 시간
608
+ - `dvgw_tts_latency_ms` — TTS 첫 오디오까지 시간
609
+ - `dvgw_e2e_latency_ms` — 전체 E2E 레이턴시
610
+ - `dvgw_transcripts_total` — 처리된 발화 수
611
+ - `dvgw_errors_total` — 오류 수
612
+
613
+ ## 라이선스
614
+
615
+ MIT
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Audio Codec Utilities
3
+ *
4
+ * Converts between DVGateway's native slin16 format and Float32 PCM
5
+ * used by most AI STT/TTS APIs.
6
+ *
7
+ * DVGateway audio format:
8
+ * - Sample rate: 16000 Hz
9
+ * - Bit depth: 16-bit signed integer
10
+ * - Endianness: little-endian
11
+ * - Frame size: 640 bytes = 320 samples = 20ms
12
+ * - Channels: 1 (mono)
13
+ */
14
+ /** Convert slin16 Buffer → normalized Float32Array [-1.0, 1.0] */
15
+ export declare function slin16ToFloat32(buf: Buffer): Float32Array;
16
+ /** Convert normalized Float32Array [-1.0, 1.0] → slin16 Buffer */
17
+ export declare function float32ToSlin16(samples: Float32Array): Buffer;
18
+ /** Convert μ-law (ulaw) Buffer → slin16 Buffer */
19
+ export declare function ulawToSlin16(ulaw: Buffer): Buffer;
20
+ /** Convert slin16 Buffer → μ-law (ulaw) Buffer */
21
+ export declare function slin16ToUlaw(slin16: Buffer): Buffer;
22
+ /**
23
+ * Resample Float32Array from one sample rate to another.
24
+ * Uses linear interpolation — suitable for real-time processing.
25
+ *
26
+ * @param samples - Input samples
27
+ * @param fromRate - Source sample rate (e.g. 16000)
28
+ * @param toRate - Target sample rate (e.g. 24000 for ElevenLabs)
29
+ */
30
+ export declare function resample(samples: Float32Array, fromRate: number, toRate: number): Float32Array;
31
+ /**
32
+ * Calculate RMS (Root Mean Square) amplitude of audio samples.
33
+ * Returns a value in [0.0, 1.0].
34
+ */
35
+ export declare function calculateRms(samples: Float32Array): number;
36
+ /**
37
+ * Simple Voice Activity Detection based on RMS threshold.
38
+ * Returns true if the frame likely contains speech.
39
+ *
40
+ * @param samples - Float32 samples
41
+ * @param threshold - RMS threshold (default: 0.01, i.e. -40 dBFS)
42
+ */
43
+ export declare function detectVoiceActivity(samples: Float32Array, threshold?: number): boolean;
44
+ //# sourceMappingURL=codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../src/audio/codec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAYzD;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAW7D;AAED,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASjD;AAED,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAUnD;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,YAAY,CAkBd;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAS1D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,SAAO,GAAG,OAAO,CAEpF"}