ishaq101 commited on
Commit
4be6d55
·
1 Parent(s): 986403e

Feat: update stt chirp3

Browse files
Files changed (3) hide show
  1. .gitignore +2 -1
  2. main.py +24 -5
  3. src/stt/chirp3_client.py +45 -0
.gitignore CHANGED
@@ -24,4 +24,5 @@ convert_audio.py
24
 
25
  API_CONTRACT_CHATBOT.md
26
  API_CONTRACT_VOICE.md
27
- HIGHLIGHT_VOICE.md
 
 
24
 
25
  API_CONTRACT_CHATBOT.md
26
  API_CONTRACT_VOICE.md
27
+ HIGHLIGHT_VOICE.md
28
+ HIGHLIGHT_STT_TTS.md
main.py CHANGED
@@ -2,16 +2,18 @@ import json
2
  import logging
3
  import uvicorn
4
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect, File, Form, UploadFile, HTTPException, Query
 
5
  from fastapi.responses import JSONResponse, StreamingResponse
6
  from pydantic import BaseModel
7
  from src.pipeline import VoicePipeline
8
  from typing import Literal
9
  from src.config import (
10
  DEEPGRAM_API_KEY, CARTESIA_API_KEY, CARTESIA_VOICE_ID,
11
- SAMPLE_RATE, GOOGLE_API_KEY, STT_PROVIDER, TTS_PROVIDER, WAKE_WORD_ENABLED,
12
  )
13
  from src.stt.deepgram_rest import transcribe_audio as deepgram_transcribe
14
  from src.stt.gemini_stt import transcribe_audio as gemini_stt_transcribe
 
15
  from src.tts.cartesia_client import synthesize_stream as cartesia_synthesize
16
  from src.tts.gemini_client import synthesize_stream as gemini_synthesize, GEMINI_SAMPLE_RATE
17
 
@@ -22,6 +24,13 @@ VERSION = "1.2.0"
22
 
23
  app = FastAPI(title="Voice Agent Service", version=VERSION)
24
 
 
 
 
 
 
 
 
25
 
26
  @app.get("/")
27
  async def health() -> JSONResponse:
@@ -60,14 +69,18 @@ class TTSRequest(BaseModel):
60
  @app.post("/stt")
61
  async def speech_to_text(
62
  audio: UploadFile = File(...),
63
- provider: str = Form(default="gemini"),
64
  ) -> JSONResponse:
65
  data = await audio.read()
66
  if not data:
67
  raise HTTPException(status_code=400, detail="Audio file is empty.")
68
  mimetype = audio.content_type or "audio/wav"
69
 
70
- if provider == "gemini":
 
 
 
 
71
  if not GOOGLE_API_KEY:
72
  raise HTTPException(status_code=503, detail="Gemini STT not configured: missing GOOGLE_API_KEY.")
73
  result = await gemini_stt_transcribe(data, mimetype=mimetype)
@@ -116,10 +129,16 @@ async def voice_ws(
116
  )
117
 
118
  async def send_audio(chunk: bytes) -> None:
119
- await ws.send_bytes(chunk)
 
 
 
120
 
121
  async def send_event(event: dict) -> None:
122
- await ws.send_text(json.dumps(event))
 
 
 
123
 
124
  tts_sample_rate = GEMINI_SAMPLE_RATE if tts_provider == "gemini" else SAMPLE_RATE
125
  await send_event({
 
2
  import logging
3
  import uvicorn
4
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect, File, Form, UploadFile, HTTPException, Query
5
+ from fastapi.middleware.cors import CORSMiddleware
6
  from fastapi.responses import JSONResponse, StreamingResponse
7
  from pydantic import BaseModel
8
  from src.pipeline import VoicePipeline
9
  from typing import Literal
10
  from src.config import (
11
  DEEPGRAM_API_KEY, CARTESIA_API_KEY, CARTESIA_VOICE_ID,
12
+ SAMPLE_RATE, GOOGLE_API_KEY, GOOGLE_PROJECT_ID, STT_PROVIDER, TTS_PROVIDER, WAKE_WORD_ENABLED,
13
  )
14
  from src.stt.deepgram_rest import transcribe_audio as deepgram_transcribe
15
  from src.stt.gemini_stt import transcribe_audio as gemini_stt_transcribe
16
+ from src.stt.chirp3_client import transcribe_audio as chirp3_transcribe
17
  from src.tts.cartesia_client import synthesize_stream as cartesia_synthesize
18
  from src.tts.gemini_client import synthesize_stream as gemini_synthesize, GEMINI_SAMPLE_RATE
19
 
 
24
 
25
  app = FastAPI(title="Voice Agent Service", version=VERSION)
26
 
27
+ app.add_middleware(
28
+ CORSMiddleware,
29
+ allow_origins=["*"],
30
+ allow_methods=["*"],
31
+ allow_headers=["*"],
32
+ )
33
+
34
 
35
  @app.get("/")
36
  async def health() -> JSONResponse:
 
69
  @app.post("/stt")
70
  async def speech_to_text(
71
  audio: UploadFile = File(...),
72
+ provider: str = Form(default="chirp3"),
73
  ) -> JSONResponse:
74
  data = await audio.read()
75
  if not data:
76
  raise HTTPException(status_code=400, detail="Audio file is empty.")
77
  mimetype = audio.content_type or "audio/wav"
78
 
79
+ if provider == "chirp3":
80
+ if not GOOGLE_PROJECT_ID:
81
+ raise HTTPException(status_code=503, detail="Chirp3 STT not configured: missing GOOGLE_PROJECT_ID.")
82
+ result = await chirp3_transcribe(data, mimetype=mimetype)
83
+ elif provider == "gemini":
84
  if not GOOGLE_API_KEY:
85
  raise HTTPException(status_code=503, detail="Gemini STT not configured: missing GOOGLE_API_KEY.")
86
  result = await gemini_stt_transcribe(data, mimetype=mimetype)
 
129
  )
130
 
131
  async def send_audio(chunk: bytes) -> None:
132
+ try:
133
+ await ws.send_bytes(chunk)
134
+ except WebSocketDisconnect:
135
+ pass
136
 
137
  async def send_event(event: dict) -> None:
138
+ try:
139
+ await ws.send_text(json.dumps(event))
140
+ except WebSocketDisconnect:
141
+ pass
142
 
143
  tts_sample_rate = GEMINI_SAMPLE_RATE if tts_provider == "gemini" else SAMPLE_RATE
144
  await send_event({
src/stt/chirp3_client.py CHANGED
@@ -17,6 +17,51 @@ OnTranscriptCallback = Callable[[str], Awaitable[None]]
17
  _CHUNK_SIZE = 24 * 1024 # stay under the 25 KB gRPC streaming limit
18
 
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  class Chirp3STTStreamer:
21
  """
22
  Push-to-talk STT using Google Cloud Speech-to-Text V2 streaming with Chirp 3 model.
 
17
  _CHUNK_SIZE = 24 * 1024 # stay under the 25 KB gRPC streaming limit
18
 
19
 
20
+ async def transcribe_audio(data: bytes, mimetype: str = "audio/wav") -> dict:
21
+ """Transcribes a full audio file using Chirp 3 (Google Cloud Speech-to-Text V2)."""
22
+ def _run() -> str:
23
+ client = SpeechClient(
24
+ client_options=ClientOptions(
25
+ api_endpoint=f"{CHIRP3_REGION}-speech.googleapis.com"
26
+ )
27
+ )
28
+ chunks = [data[i: i + _CHUNK_SIZE] for i in range(0, len(data), _CHUNK_SIZE)]
29
+ audio_requests = (
30
+ cloud_speech_types.StreamingRecognizeRequest(audio=chunk)
31
+ for chunk in chunks
32
+ )
33
+ recognition_config = cloud_speech_types.RecognitionConfig(
34
+ auto_decoding_config=cloud_speech_types.AutoDetectDecodingConfig(),
35
+ language_codes=[CHIRP3_LANGUAGE],
36
+ model="chirp_3",
37
+ )
38
+ streaming_config = cloud_speech_types.StreamingRecognitionConfig(
39
+ config=recognition_config
40
+ )
41
+ config_request = cloud_speech_types.StreamingRecognizeRequest(
42
+ recognizer=f"projects/{GOOGLE_PROJECT_ID}/locations/{CHIRP3_REGION}/recognizers/_",
43
+ streaming_config=streaming_config,
44
+ )
45
+
46
+ def requests():
47
+ yield config_request
48
+ yield from audio_requests
49
+
50
+ transcripts: list[str] = []
51
+ for response in client.streaming_recognize(requests=requests()):
52
+ for result in response.results:
53
+ if result.alternatives:
54
+ transcripts.append(result.alternatives[0].transcript)
55
+
56
+ transcript = " ".join(transcripts).strip()
57
+ if transcript:
58
+ logger.info("transcribe_audio/transcript: %s", transcript[:80])
59
+ return transcript
60
+
61
+ text = await asyncio.get_event_loop().run_in_executor(None, _run)
62
+ return {"text": text, "language": CHIRP3_LANGUAGE, "duration": None}
63
+
64
+
65
  class Chirp3STTStreamer:
66
  """
67
  Push-to-talk STT using Google Cloud Speech-to-Text V2 streaming with Chirp 3 model.