ishaq101 commited on
Commit
986403e
·
1 Parent(s): 38a5904

Feat: STT & TTS Gemini, Creds Gemini Cloud Speech, Generate Buffer Audio, Testing

Browse files

ticket:https://bukittechnology.atlassian.net/browse/KM-500

- develop feat STT using gemini
- debugging gemini speech to text and text to speech credential using
personal account
- testing functionality STT and TTS
- generate 3 buffer audio for buffering response

.env.example CHANGED
@@ -15,19 +15,24 @@ GEMINI_TTS_MODEL=gemini-2.5-flash-preview-tts
15
  GEMINI_TTS_VOICE=Autonoe
16
  GEMINI_TTS_LANGUAGE=id-ID
17
 
18
- # Chatbot agent service
19
- CHATBOT_BASE_URL=http://localhost:8000/chatbot-knowledgemanagement
20
- CHATBOT_TIMEOUT=30
21
 
22
- # Google Cloud Project (untuk referensi Vertex AI di masa depan)
23
  GOOGLE_PROJECT_NAME=
 
24
  GOOGLE_PROJECT_NUMBER=
 
25
 
26
  # Gemini STT
27
  GEMINI_STT_MODEL=gemini-2.0-flash
28
  GEMINI_LIVE_MODEL=gemini-live-2.5-flash-preview
29
  GEMINI_STT_LANGUAGE=id-ID
30
 
31
- # Provider default untuk WebSocket /ws/voice: "deepgram" atau "gemini"
32
- STT_PROVIDER=gemini
 
 
 
 
 
 
33
  TTS_PROVIDER=gemini
 
15
  GEMINI_TTS_VOICE=Autonoe
16
  GEMINI_TTS_LANGUAGE=id-ID
17
 
 
 
 
18
 
19
+ # Google Cloud Project
20
  GOOGLE_PROJECT_NAME=
21
+ GOOGLE_PROJECT_ID=
22
  GOOGLE_PROJECT_NUMBER=
23
+ GOOGLE_CLOUD_LOCATION=
24
 
25
  # Gemini STT
26
  GEMINI_STT_MODEL=gemini-2.0-flash
27
  GEMINI_LIVE_MODEL=gemini-live-2.5-flash-preview
28
  GEMINI_STT_LANGUAGE=id-ID
29
 
30
+ # Chirp 3 STT (Google Cloud Speech-to-Text V2)
31
+ # Membutuhkan GOOGLE_PROJECT_NAME dan Application Default Credentials (ADC)
32
+ # Set GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
33
+ CHIRP3_REGION=us
34
+ CHIRP3_LANGUAGE=id-ID
35
+
36
+ # Provider default untuk WebSocket /ws/voice: "deepgram", "gemini", atau "chirp3"
37
+ STT_PROVIDER=chirp3
38
  TTS_PROVIDER=gemini
.gitignore CHANGED
@@ -23,4 +23,5 @@ test_client.py
23
  convert_audio.py
24
 
25
  API_CONTRACT_CHATBOT.md
26
- API_CONTRACT_VOICE.md
 
 
23
  convert_audio.py
24
 
25
  API_CONTRACT_CHATBOT.md
26
+ API_CONTRACT_VOICE.md
27
+ HIGHLIGHT_VOICE.md
README.md CHANGED
@@ -52,7 +52,10 @@ WAKE_WORD=Hai EMA # Default: "Hai EMA"
52
  ## Run
53
 
54
  ```bash
55
- uv run uvicorn main:app --host 0.0.0.0 --port 7861
 
 
 
56
  ```
57
 
58
  Server akan berjalan di `http://localhost:7861`.
 
52
  ## Run
53
 
54
  ```bash
55
+ `uv run uvicorn main:app --host 0.0.0.0 --port 7861`
56
+ or
57
+ `uv run uvicorn main:app --host 0.0.0.0 --port 7861 --reload`
58
+
59
  ```
60
 
61
  Server akan berjalan di `http://localhost:7861`.
main.py CHANGED
@@ -8,7 +8,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, CHATBOT_BASE_URL, 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
@@ -23,13 +23,20 @@ VERSION = "1.2.0"
23
  app = FastAPI(title="Voice Agent Service", version=VERSION)
24
 
25
 
 
 
 
 
 
 
 
 
26
  @app.get("/health")
27
  async def health() -> JSONResponse:
28
  stt_ready = bool(DEEPGRAM_API_KEY)
29
  tts_ready = bool(CARTESIA_API_KEY and CARTESIA_VOICE_ID)
30
  gemini_ready = bool(GOOGLE_API_KEY)
31
- chatbot_ready = bool(CHATBOT_BASE_URL)
32
- all_ready = stt_ready and tts_ready and chatbot_ready
33
 
34
  body: dict = {
35
  "status": "ok" if all_ready else "degraded",
@@ -38,7 +45,6 @@ async def health() -> JSONResponse:
38
  "tts_ready": tts_ready,
39
  "gemini_tts_ready": gemini_ready,
40
  "gemini_stt_ready": gemini_ready,
41
- "chatbot_ready": chatbot_ready,
42
  }
43
  if not all_ready:
44
  body["message"] = "One or more required configurations are missing. Check your .env file."
@@ -99,21 +105,14 @@ async def text_to_speech(req: TTSRequest) -> StreamingResponse:
99
  @app.websocket("/ws/voice")
100
  async def voice_ws(
101
  ws: WebSocket,
102
- user_id: str = Query(default="anonymous"),
103
- fullname: str = Query(default=""),
104
- company: str = Query(default=""),
105
- function: str = Query(default=""),
106
- site: str = Query(default="HO"),
107
- role: str = Query(default="engineer"),
108
- agent: str = Query(default="analysis"),
109
  stt_provider: str = Query(default=STT_PROVIDER),
110
  tts_provider: str = Query(default=TTS_PROVIDER),
111
  wake_word_enabled: bool = Query(default=WAKE_WORD_ENABLED),
112
  ) -> None:
113
  await ws.accept()
114
  logger.info(
115
- "Client connected: %s (user_id=%s, stt=%s, tts=%s)",
116
- ws.client, user_id, stt_provider, tts_provider,
117
  )
118
 
119
  async def send_audio(chunk: bytes) -> None:
@@ -135,13 +134,6 @@ async def voice_ws(
135
  pipeline = VoicePipeline(
136
  send_audio=send_audio,
137
  send_event=send_event,
138
- user_id=user_id,
139
- fullname=fullname,
140
- company=company,
141
- function=function,
142
- site=site,
143
- role=role,
144
- agent=agent,
145
  stt_provider=stt_provider,
146
  tts_provider=tts_provider,
147
  wake_word_enabled=wake_word_enabled,
@@ -163,12 +155,19 @@ async def voice_ws(
163
  await ws.send_text(json.dumps({"event": "pong"}))
164
  elif action == "interrupt":
165
  await pipeline.interrupt()
 
 
 
 
166
  except json.JSONDecodeError:
167
  pass
168
  except WebSocketDisconnect:
169
  logger.info("Client disconnected: %s", ws.client)
170
  finally:
171
- pipeline.stop()
 
 
 
172
 
173
 
174
  if __name__ == "__main__":
 
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
 
23
  app = FastAPI(title="Voice Agent Service", version=VERSION)
24
 
25
 
26
+ @app.get("/")
27
+ async def health() -> JSONResponse:
28
+ body: dict = {
29
+ "message": "Welcome to the Voice Agent Service! Please use the /health endpoint to check service status.",
30
+ "version": VERSION,
31
+ }
32
+ return JSONResponse(status_code=200, content=body)
33
+
34
  @app.get("/health")
35
  async def health() -> JSONResponse:
36
  stt_ready = bool(DEEPGRAM_API_KEY)
37
  tts_ready = bool(CARTESIA_API_KEY and CARTESIA_VOICE_ID)
38
  gemini_ready = bool(GOOGLE_API_KEY)
39
+ all_ready = stt_ready and tts_ready
 
40
 
41
  body: dict = {
42
  "status": "ok" if all_ready else "degraded",
 
45
  "tts_ready": tts_ready,
46
  "gemini_tts_ready": gemini_ready,
47
  "gemini_stt_ready": gemini_ready,
 
48
  }
49
  if not all_ready:
50
  body["message"] = "One or more required configurations are missing. Check your .env file."
 
105
  @app.websocket("/ws/voice")
106
  async def voice_ws(
107
  ws: WebSocket,
 
 
 
 
 
 
 
108
  stt_provider: str = Query(default=STT_PROVIDER),
109
  tts_provider: str = Query(default=TTS_PROVIDER),
110
  wake_word_enabled: bool = Query(default=WAKE_WORD_ENABLED),
111
  ) -> None:
112
  await ws.accept()
113
  logger.info(
114
+ "Client connected: %s (stt=%s, tts=%s)",
115
+ ws.client, stt_provider, tts_provider,
116
  )
117
 
118
  async def send_audio(chunk: bytes) -> None:
 
134
  pipeline = VoicePipeline(
135
  send_audio=send_audio,
136
  send_event=send_event,
 
 
 
 
 
 
 
137
  stt_provider=stt_provider,
138
  tts_provider=tts_provider,
139
  wake_word_enabled=wake_word_enabled,
 
155
  await ws.send_text(json.dumps({"event": "pong"}))
156
  elif action == "interrupt":
157
  await pipeline.interrupt()
158
+ elif action == "speak":
159
+ text = msg.get("text", "").strip()
160
+ if text:
161
+ await pipeline.speak(text)
162
  except json.JSONDecodeError:
163
  pass
164
  except WebSocketDisconnect:
165
  logger.info("Client disconnected: %s", ws.client)
166
  finally:
167
+ try:
168
+ await pipeline.stop_async()
169
+ except Exception:
170
+ logger.exception("Error during pipeline stop")
171
 
172
 
173
  if __name__ == "__main__":
pyproject.toml CHANGED
@@ -13,6 +13,10 @@ dependencies = [
13
  "deepgram-sdk>=6.1.1",
14
  "python-multipart>=0.0.26",
15
  "google-genai>=1.0.0",
 
 
 
 
16
  ]
17
 
18
  [project.optional-dependencies]
@@ -26,3 +30,8 @@ build-backend = "hatchling.build"
26
 
27
  [tool.hatch.build.targets.wheel]
28
  packages = ["src"]
 
 
 
 
 
 
13
  "deepgram-sdk>=6.1.1",
14
  "python-multipart>=0.0.26",
15
  "google-genai>=1.0.0",
16
+ "google-generativeai>=0.8.6",
17
+ "google-cloud-texttospeech>=2.29.0",
18
+ "google-cloud-speech>=2.0.0",
19
+ "numpy>=1.26.0",
20
  ]
21
 
22
  [project.optional-dependencies]
 
30
 
31
  [tool.hatch.build.targets.wheel]
32
  packages = ["src"]
33
+
34
+ [dependency-groups]
35
+ dev = [
36
+ "sounddevice>=0.5.5",
37
+ ]
src/chatbot/__init__.py DELETED
File without changes
src/chatbot/client.py DELETED
@@ -1,60 +0,0 @@
1
- import uuid
2
- import logging
3
- import httpx
4
- from src.config import CHATBOT_BASE_URL, CHATBOT_TIMEOUT
5
-
6
- logger = logging.getLogger(__name__)
7
-
8
-
9
- async def call_agent(
10
- question: str,
11
- chat_history: list[dict],
12
- user_id: str = "anonymous",
13
- fullname: str = "",
14
- company: str = "",
15
- function: str = "",
16
- site: str = "HO",
17
- role: str = "engineer",
18
- agent: str = "analysis",
19
- room_id: str | None = None,
20
- chat_id: str | None = None,
21
- ) -> str:
22
- """
23
- Call POST /api/agents/call and return audio_text (fallback to content).
24
-
25
- Raises RuntimeError if CHATBOT_BASE_URL is not configured or the agent
26
- returns a failed status.
27
- """
28
- if not CHATBOT_BASE_URL:
29
- raise RuntimeError("CHATBOT_BASE_URL is not configured.")
30
-
31
- history = chat_history + [{"role": "human", "content": question}]
32
-
33
- payload = {
34
- "user_id": user_id,
35
- "fullname": fullname,
36
- "company": company,
37
- "function": function,
38
- "site": site,
39
- "role": role,
40
- "agent": agent,
41
- "room_id": room_id,
42
- "chat_id": chat_id or str(uuid.uuid4()),
43
- "req_plot_object": False,
44
- "chat_history": history,
45
- }
46
-
47
- async with httpx.AsyncClient(timeout=CHATBOT_TIMEOUT) as client:
48
- resp = await client.post(f"{CHATBOT_BASE_URL}/api/agents/call", json=payload)
49
- resp.raise_for_status()
50
- data = resp.json()
51
-
52
- status = data.get("status") or {}
53
- if status.get("status") == "failed":
54
- raise RuntimeError(status.get("message", "Agent call failed"))
55
-
56
- answer = data.get("answer") or {}
57
- audio_text: str | None = answer.get("audio_text")
58
- content: str = answer.get("content", "")
59
-
60
- return audio_text or content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/config.py CHANGED
@@ -14,9 +14,11 @@ CARTESIA_MODEL: str = os.getenv("CARTESIA_MODEL", "sonic-3")
14
 
15
  GOOGLE_API_KEY: str = os.getenv("GOOGLE_API_KEY", "")
16
  GOOGLE_PROJECT_NAME: str = os.getenv("GOOGLE_PROJECT_NAME", "")
 
17
  GOOGLE_PROJECT_NUMBER: str = os.getenv("GOOGLE_PROJECT_NUMBER", "")
 
18
 
19
- GEMINI_TTS_MODEL: str = os.getenv("GEMINI_TTS_MODEL", "gemini-2.5-flash-preview-tts")
20
  GEMINI_TTS_VOICE: str = os.getenv("GEMINI_TTS_VOICE", "Autonoe")
21
  GEMINI_TTS_LANGUAGE: str = os.getenv("GEMINI_TTS_LANGUAGE", "id-ID")
22
 
@@ -29,5 +31,5 @@ DEEPGRAM_LANGUAGE: str = os.getenv("DEEPGRAM_LANGUAGE", "id")
29
  DEEPGRAM_ENDPOINTING_MS: int = int(os.getenv("DEEPGRAM_ENDPOINTING_MS", "300"))
30
  DEEPGRAM_UTTERANCE_END_MS: int = int(os.getenv("DEEPGRAM_UTTERANCE_END_MS", "2000"))
31
 
32
- CHATBOT_BASE_URL: str = os.getenv("CHATBOT_BASE_URL", "")
33
- CHATBOT_TIMEOUT: int = int(os.getenv("CHATBOT_TIMEOUT", "30"))
 
14
 
15
  GOOGLE_API_KEY: str = os.getenv("GOOGLE_API_KEY", "")
16
  GOOGLE_PROJECT_NAME: str = os.getenv("GOOGLE_PROJECT_NAME", "")
17
+ GOOGLE_PROJECT_ID: str = os.getenv("GOOGLE_PROJECT_ID", "")
18
  GOOGLE_PROJECT_NUMBER: str = os.getenv("GOOGLE_PROJECT_NUMBER", "")
19
+ GOOGLE_CLOUD_LOCATION: str = os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1")
20
 
21
+ GEMINI_TTS_MODEL: str = os.getenv("GEMINI_TTS_MODEL", "gemini-2.5-flash-tts")
22
  GEMINI_TTS_VOICE: str = os.getenv("GEMINI_TTS_VOICE", "Autonoe")
23
  GEMINI_TTS_LANGUAGE: str = os.getenv("GEMINI_TTS_LANGUAGE", "id-ID")
24
 
 
31
  DEEPGRAM_ENDPOINTING_MS: int = int(os.getenv("DEEPGRAM_ENDPOINTING_MS", "300"))
32
  DEEPGRAM_UTTERANCE_END_MS: int = int(os.getenv("DEEPGRAM_UTTERANCE_END_MS", "2000"))
33
 
34
+ CHIRP3_REGION: str = os.getenv("CHIRP3_REGION", "us")
35
+ CHIRP3_LANGUAGE: str = os.getenv("CHIRP3_LANGUAGE", "id-ID")
src/pipeline.py CHANGED
@@ -1,4 +1,3 @@
1
- import uuid
2
  import asyncio
3
  import logging
4
  from typing import Callable, Awaitable
@@ -6,7 +5,6 @@ from src.config import WAKE_WORDS, WAKE_WORD_ENABLED
6
  from src.stt.deepgram_client import DeepgramStreamer
7
  from src.tts.cartesia_client import synthesize_stream as cartesia_synthesize_stream
8
  from src.tts.gemini_client import synthesize_stream as gemini_synthesize_stream, GEMINI_SAMPLE_RATE
9
- from src.chatbot.client import call_agent
10
 
11
  logger = logging.getLogger(__name__)
12
 
@@ -27,16 +25,10 @@ class VoicePipeline:
27
  self,
28
  send_audio: SendAudioCallback,
29
  send_event: SendEventCallback,
30
- user_id: str = "anonymous",
31
- fullname: str = "",
32
- company: str = "",
33
- function: str = "",
34
- site: str = "HO",
35
- role: str = "engineer",
36
- agent: str = "analysis",
37
  stt_provider: str = "deepgram",
38
  tts_provider: str = "cartesia",
39
  wake_word_enabled: bool = WAKE_WORD_ENABLED,
 
40
  ):
41
  self._send_audio = send_audio
42
  self._send_event = send_event
@@ -44,27 +36,18 @@ class VoicePipeline:
44
  self._tts_provider = tts_provider
45
  self._wake_word_enabled = wake_word_enabled
46
 
47
- self._user_id = user_id
48
- self._fullname = fullname
49
- self._company = company
50
- self._function = function
51
- self._site = site
52
- self._role = role
53
- self._agent = agent
54
-
55
- # Stable identifiers for this session
56
- self._room_id = str(uuid.uuid4())
57
- self._chat_id = str(uuid.uuid4())
58
-
59
- # Running chat history for multi-turn context
60
- self._chat_history: list[dict] = []
61
-
62
  if stt_provider == "gemini":
63
  from src.stt.gemini_stt import GeminiSTTStreamer
64
  self._stt = GeminiSTTStreamer(
65
  on_final_transcript=self._on_final_transcript,
66
  loop=self._loop,
67
  )
 
 
 
 
 
 
68
  else:
69
  self._stt = DeepgramStreamer(
70
  on_final_transcript=self._on_final_transcript,
@@ -80,21 +63,18 @@ class VoicePipeline:
80
  self._stt.send_audio(chunk)
81
 
82
  async def _on_final_transcript(self, text: str) -> None:
83
- await self._send_event({"event": "transcript", "text": text})
84
-
85
  if self._wake_word_enabled:
86
  question = self._extract_question(text)
87
  if question is None:
88
  logger.debug("No wake word detected, ignoring: %s", text)
89
  return
90
  logger.info("Wake word detected, question: %s", question)
 
91
  else:
92
- question = text.strip()
93
- if not question:
94
  return
95
- logger.info("Processing transcript (wake word disabled): %s", question)
96
-
97
- self._tts_task = asyncio.create_task(self._answer_and_speak(question))
98
 
99
  def _extract_question(self, transcript: str) -> str | None:
100
  lower = transcript.lower()
@@ -109,36 +89,9 @@ class VoicePipeline:
109
  question = transcript[idx:].strip(" ,.")
110
  return question if question else None
111
 
112
- async def _answer_and_speak(self, question: str) -> None:
113
- try:
114
- reply_text = await call_agent(
115
- question=question,
116
- chat_history=self._chat_history,
117
- user_id=self._user_id,
118
- fullname=self._fullname,
119
- company=self._company,
120
- function=self._function,
121
- site=self._site,
122
- role=self._role,
123
- agent=self._agent,
124
- room_id=self._room_id,
125
- chat_id=self._chat_id,
126
- )
127
- except Exception:
128
- logger.exception("Chatbot call failed for question: %s", question)
129
- await self._send_event({
130
- "event": "error",
131
- "code": "CHATBOT_ERROR",
132
- "message": "Failed to get response from chatbot agent.",
133
- })
134
- return
135
-
136
- # Update history for multi-turn context
137
- self._chat_history.append({"role": "human", "content": question})
138
- self._chat_history.append({"role": "ai", "content": reply_text})
139
-
140
- await self._send_event({"event": "reply", "text": reply_text})
141
- await self._speak(reply_text)
142
 
143
  async def _speak(self, text: str) -> None:
144
  async with self._tts_lock:
@@ -176,6 +129,14 @@ class VoicePipeline:
176
  self._tts_task.cancel()
177
  self._stt.stop()
178
 
 
 
 
 
 
 
 
 
179
 
180
  # Backward-compatible alias
181
  EchoPipeline = VoicePipeline
 
 
1
  import asyncio
2
  import logging
3
  from typing import Callable, Awaitable
 
5
  from src.stt.deepgram_client import DeepgramStreamer
6
  from src.tts.cartesia_client import synthesize_stream as cartesia_synthesize_stream
7
  from src.tts.gemini_client import synthesize_stream as gemini_synthesize_stream, GEMINI_SAMPLE_RATE
 
8
 
9
  logger = logging.getLogger(__name__)
10
 
 
25
  self,
26
  send_audio: SendAudioCallback,
27
  send_event: SendEventCallback,
 
 
 
 
 
 
 
28
  stt_provider: str = "deepgram",
29
  tts_provider: str = "cartesia",
30
  wake_word_enabled: bool = WAKE_WORD_ENABLED,
31
+ **kwargs,
32
  ):
33
  self._send_audio = send_audio
34
  self._send_event = send_event
 
36
  self._tts_provider = tts_provider
37
  self._wake_word_enabled = wake_word_enabled
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  if stt_provider == "gemini":
40
  from src.stt.gemini_stt import GeminiSTTStreamer
41
  self._stt = GeminiSTTStreamer(
42
  on_final_transcript=self._on_final_transcript,
43
  loop=self._loop,
44
  )
45
+ elif stt_provider == "chirp3":
46
+ from src.stt.chirp3_client import Chirp3STTStreamer
47
+ self._stt = Chirp3STTStreamer(
48
+ on_final_transcript=self._on_final_transcript,
49
+ loop=self._loop,
50
+ )
51
  else:
52
  self._stt = DeepgramStreamer(
53
  on_final_transcript=self._on_final_transcript,
 
63
  self._stt.send_audio(chunk)
64
 
65
  async def _on_final_transcript(self, text: str) -> None:
 
 
66
  if self._wake_word_enabled:
67
  question = self._extract_question(text)
68
  if question is None:
69
  logger.debug("No wake word detected, ignoring: %s", text)
70
  return
71
  logger.info("Wake word detected, question: %s", question)
72
+ await self._send_event({"event": "transcript", "text": question})
73
  else:
74
+ if not text.strip():
 
75
  return
76
+ logger.info("Transcript: %s", text)
77
+ await self._send_event({"event": "transcript", "text": text})
 
78
 
79
  def _extract_question(self, transcript: str) -> str | None:
80
  lower = transcript.lower()
 
89
  question = transcript[idx:].strip(" ,.")
90
  return question if question else None
91
 
92
+ async def speak(self, text: str) -> None:
93
+ """Called by the WebSocket handler when the FE sends a speak action."""
94
+ self._tts_task = asyncio.create_task(self._speak(text))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  async def _speak(self, text: str) -> None:
97
  async with self._tts_lock:
 
129
  self._tts_task.cancel()
130
  self._stt.stop()
131
 
132
+ async def stop_async(self) -> None:
133
+ if self._tts_task and not self._tts_task.done():
134
+ self._tts_task.cancel()
135
+ if hasattr(self._stt, "stop_and_transcribe"):
136
+ await self._stt.stop_and_transcribe()
137
+ else:
138
+ self._stt.stop()
139
+
140
 
141
  # Backward-compatible alias
142
  EchoPipeline = VoicePipeline
src/stt/chirp3_client.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import wave
3
+ import asyncio
4
+ import logging
5
+ from typing import Callable, Awaitable
6
+
7
+ from google.cloud.speech_v2 import SpeechClient
8
+ from google.cloud.speech_v2.types import cloud_speech as cloud_speech_types
9
+ from google.api_core.client_options import ClientOptions
10
+
11
+ from src.config import GOOGLE_PROJECT_ID, CHIRP3_REGION, CHIRP3_LANGUAGE
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ OnTranscriptCallback = Callable[[str], Awaitable[None]]
16
+
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.
23
+
24
+ Interface matches GeminiSTTStreamer: start(), send_audio(chunk), stop(), stop_and_transcribe().
25
+ Audio chunks (PCM Linear16 16kHz mono) are buffered, then streamed to Chirp 3 in 24 KB
26
+ pieces via streaming_recognize() on stop_and_transcribe().
27
+
28
+ Authentication: Application Default Credentials (ADC).
29
+ Set GOOGLE_APPLICATION_CREDENTIALS or run: gcloud auth application-default login
30
+ """
31
+
32
+ def __init__(self, on_final_transcript: OnTranscriptCallback, loop: asyncio.AbstractEventLoop):
33
+ self._on_final_transcript = on_final_transcript
34
+ self._loop = loop
35
+ self._audio_chunks: list[bytes] = []
36
+
37
+ def start(self) -> None:
38
+ self._audio_chunks = []
39
+ logger.info("Chirp 3 STT ready (region=%s, language=%s)", CHIRP3_REGION, CHIRP3_LANGUAGE)
40
+
41
+ def send_audio(self, chunk: bytes) -> None:
42
+ self._audio_chunks.append(chunk)
43
+
44
+ def stop(self) -> None:
45
+ logger.info("Chirp 3 STT stopped")
46
+
47
+ async def stop_and_transcribe(self) -> None:
48
+ audio_data = b"".join(self._audio_chunks)
49
+ self._audio_chunks = []
50
+
51
+ if not audio_data:
52
+ logger.info("Chirp 3 STT: no audio received, skipping transcription")
53
+ return
54
+
55
+ logger.info("Chirp 3 STT transcribing %d bytes of audio...", len(audio_data))
56
+ wav_data = self._pcm_to_wav(audio_data)
57
+
58
+ try:
59
+ text = await asyncio.get_event_loop().run_in_executor(
60
+ None, self._recognize, wav_data
61
+ )
62
+ if text:
63
+ await self._on_final_transcript(text)
64
+ except Exception:
65
+ logger.exception("Chirp 3 STT transcription failed")
66
+
67
+ def _recognize(self, wav_data: bytes) -> str:
68
+ client = SpeechClient(
69
+ client_options=ClientOptions(
70
+ api_endpoint=f"{CHIRP3_REGION}-speech.googleapis.com"
71
+ )
72
+ )
73
+
74
+ chunks = [
75
+ wav_data[i : i + _CHUNK_SIZE]
76
+ for i in range(0, len(wav_data), _CHUNK_SIZE)
77
+ ]
78
+ audio_requests = (
79
+ cloud_speech_types.StreamingRecognizeRequest(audio=chunk)
80
+ for chunk in chunks
81
+ )
82
+
83
+ recognition_config = cloud_speech_types.RecognitionConfig(
84
+ auto_decoding_config=cloud_speech_types.AutoDetectDecodingConfig(),
85
+ language_codes=[CHIRP3_LANGUAGE],
86
+ model="chirp_3",
87
+ )
88
+ streaming_config = cloud_speech_types.StreamingRecognitionConfig(
89
+ config=recognition_config
90
+ )
91
+ config_request = cloud_speech_types.StreamingRecognizeRequest(
92
+ recognizer=f"projects/{GOOGLE_PROJECT_ID}/locations/{CHIRP3_REGION}/recognizers/_",
93
+ streaming_config=streaming_config,
94
+ )
95
+
96
+ def requests():
97
+ yield config_request
98
+ yield from audio_requests
99
+
100
+ transcripts: list[str] = []
101
+ for response in client.streaming_recognize(requests=requests()):
102
+ for result in response.results:
103
+ if result.alternatives:
104
+ transcripts.append(result.alternatives[0].transcript)
105
+
106
+ transcript = " ".join(transcripts).strip()
107
+ if transcript:
108
+ logger.info("Chirp 3 STT transcript: %s", transcript[:80])
109
+ return transcript
110
+
111
+ @staticmethod
112
+ def _pcm_to_wav(pcm_data: bytes) -> bytes:
113
+ buf = io.BytesIO()
114
+ with wave.open(buf, "wb") as wf:
115
+ wf.setnchannels(1)
116
+ wf.setsampwidth(2)
117
+ wf.setframerate(16000)
118
+ wf.writeframes(pcm_data)
119
+ return buf.getvalue()
src/stt/gemini_stt.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import asyncio
2
  import logging
3
  from typing import Callable, Awaitable
@@ -7,10 +9,10 @@ from google.genai import types
7
 
8
  from src.config import (
9
  GOOGLE_API_KEY,
 
 
10
  GEMINI_STT_MODEL,
11
- GEMINI_LIVE_MODEL,
12
  GEMINI_STT_LANGUAGE,
13
- DEEPGRAM_UTTERANCE_END_MS,
14
  )
15
 
16
  logger = logging.getLogger(__name__)
@@ -23,7 +25,10 @@ async def transcribe_audio(data: bytes, mimetype: str = "audio/wav") -> dict:
23
  Transcribes a full audio file using Gemini multimodal API.
24
  Returns dict with 'text', 'language', and 'duration'.
25
  """
26
- client = genai.Client(api_key=GOOGLE_API_KEY)
 
 
 
27
  response = await client.aio.models.generate_content(
28
  model=GEMINI_STT_MODEL,
29
  contents=[
@@ -39,99 +44,53 @@ async def transcribe_audio(data: bytes, mimetype: str = "audio/wav") -> dict:
39
 
40
  class GeminiSTTStreamer:
41
  """
42
- Real-time streaming STT using the Gemini Live API.
43
-
44
- Interface matches DeepgramStreamer: start(), send_audio(chunk), stop().
45
- Audio chunks must be PCM Linear16 16kHz mono bytes.
46
 
47
- Final transcript segments are buffered and flushed after DEEPGRAM_UTTERANCE_END_MS
48
- of silence (reusing the same configurable delay).
 
49
  """
50
 
51
  def __init__(self, on_final_transcript: OnTranscriptCallback, loop: asyncio.AbstractEventLoop):
52
  self._on_final_transcript = on_final_transcript
53
  self._loop = loop
54
- self._audio_queue: asyncio.Queue[bytes | None] = None # type: ignore[assignment]
55
- self._session_task: asyncio.Task | None = None
56
- self._transcript_buffer: list[str] = []
57
- self._flush_handle: asyncio.TimerHandle | None = None
58
 
59
  def start(self) -> None:
60
- self._audio_queue = asyncio.Queue()
61
- self._session_task = asyncio.ensure_future(self._run_session(), loop=self._loop)
62
- logger.info("Gemini Live STT starting...")
63
 
64
  def send_audio(self, chunk: bytes) -> None:
65
- if self._audio_queue is not None:
66
- self._audio_queue.put_nowait(chunk)
67
 
68
  def stop(self) -> None:
69
- if self._session_task and not self._session_task.done():
70
- self._session_task.cancel()
71
- if self._audio_queue is not None:
72
- # Unblock the send loop if it's waiting on the queue
73
- self._audio_queue.put_nowait(None)
74
- if self._flush_handle is not None:
75
- self._flush_handle.cancel()
76
- self._flush_handle = None
77
- logger.info("Gemini Live STT stopped")
78
-
79
- async def _run_session(self) -> None:
80
- client = genai.Client(api_key=GOOGLE_API_KEY)
81
- config = types.LiveConnectConfig(
82
- response_modalities=["TEXT"],
83
- input_audio_transcription=types.AudioTranscriptionConfig(),
84
- )
85
- try:
86
- async with client.aio.live.connect(model=GEMINI_LIVE_MODEL, config=config) as session:
87
- logger.info("Gemini Live STT connected")
88
- await asyncio.gather(
89
- self._send_loop(session),
90
- self._receive_loop(session),
91
- )
92
- except asyncio.CancelledError:
93
- pass
94
- except Exception:
95
- logger.exception("Gemini Live session error")
96
 
97
- async def _send_loop(self, session) -> None:
98
- try:
99
- while True:
100
- chunk = await self._audio_queue.get()
101
- if chunk is None:
102
- break
103
- await session.send_realtime_input(
104
- audio=types.Blob(data=chunk, mime_type="audio/pcm;rate=16000")
105
- )
106
- except asyncio.CancelledError:
107
- raise
108
-
109
- async def _receive_loop(self, session) -> None:
110
  try:
111
- async for response in session.receive():
112
- if response.server_content is None:
113
- continue
114
- transcription = response.server_content.input_transcription
115
- if transcription and transcription.text:
116
- self._buffer_transcript(transcription.text)
117
- except asyncio.CancelledError:
118
- raise
119
  except Exception:
120
- logger.exception("Gemini Live receive error")
121
-
122
- def _buffer_transcript(self, text: str) -> None:
123
- self._transcript_buffer.append(text)
124
- logger.debug("Buffered segment: %s", text)
125
- if self._flush_handle is not None:
126
- self._flush_handle.cancel()
127
- delay = DEEPGRAM_UTTERANCE_END_MS / 1000.0
128
- self._flush_handle = self._loop.call_later(delay, self._flush_buffer)
129
-
130
- def _flush_buffer(self) -> None:
131
- if not self._transcript_buffer:
132
- return
133
- full_text = " ".join(self._transcript_buffer)
134
- self._transcript_buffer.clear()
135
- self._flush_handle = None
136
- logger.info("Final transcript: %s", full_text)
137
- asyncio.ensure_future(self._on_final_transcript(full_text), loop=self._loop)
 
1
+ import io
2
+ import wave
3
  import asyncio
4
  import logging
5
  from typing import Callable, Awaitable
 
9
 
10
  from src.config import (
11
  GOOGLE_API_KEY,
12
+ GOOGLE_PROJECT_NAME,
13
+ GOOGLE_CLOUD_LOCATION,
14
  GEMINI_STT_MODEL,
 
15
  GEMINI_STT_LANGUAGE,
 
16
  )
17
 
18
  logger = logging.getLogger(__name__)
 
25
  Transcribes a full audio file using Gemini multimodal API.
26
  Returns dict with 'text', 'language', and 'duration'.
27
  """
28
+ if GOOGLE_API_KEY:
29
+ client = genai.Client(api_key=GOOGLE_API_KEY)
30
+ else:
31
+ client = genai.Client(vertexai=True, project=GOOGLE_PROJECT_NAME, location=GOOGLE_CLOUD_LOCATION)
32
  response = await client.aio.models.generate_content(
33
  model=GEMINI_STT_MODEL,
34
  contents=[
 
44
 
45
  class GeminiSTTStreamer:
46
  """
47
+ Push-to-talk STT using Gemini batch transcription.
 
 
 
48
 
49
+ Interface matches DeepgramStreamer: start(), send_audio(chunk), stop_and_transcribe().
50
+ Audio chunks (PCM Linear16 16kHz mono) are buffered during the session.
51
+ On stop_and_transcribe(), all buffered audio is sent to Gemini as a single WAV request.
52
  """
53
 
54
  def __init__(self, on_final_transcript: OnTranscriptCallback, loop: asyncio.AbstractEventLoop):
55
  self._on_final_transcript = on_final_transcript
56
  self._loop = loop
57
+ self._audio_chunks: list[bytes] = []
 
 
 
58
 
59
  def start(self) -> None:
60
+ self._audio_chunks = []
61
+ logger.info("Gemini STT ready")
 
62
 
63
  def send_audio(self, chunk: bytes) -> None:
64
+ self._audio_chunks.append(chunk)
 
65
 
66
  def stop(self) -> None:
67
+ logger.info("Gemini STT stopped")
68
+
69
+ async def stop_and_transcribe(self) -> None:
70
+ audio_data = b"".join(self._audio_chunks)
71
+ self._audio_chunks = []
72
+
73
+ if not audio_data:
74
+ logger.info("Gemini STT: no audio received, skipping transcription")
75
+ return
76
+
77
+ logger.info("Gemini STT transcribing %d bytes of audio...", len(audio_data))
78
+ wav_data = self._pcm_to_wav(audio_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  try:
81
+ result = await transcribe_audio(wav_data, mimetype="audio/wav")
82
+ text = result.get("text", "").strip()
83
+ if text:
84
+ await self._on_final_transcript(text)
 
 
 
 
85
  except Exception:
86
+ logger.exception("Gemini STT batch transcription failed")
87
+
88
+ @staticmethod
89
+ def _pcm_to_wav(pcm_data: bytes) -> bytes:
90
+ buf = io.BytesIO()
91
+ with wave.open(buf, "wb") as wf:
92
+ wf.setnchannels(1)
93
+ wf.setsampwidth(2)
94
+ wf.setframerate(16000)
95
+ wf.writeframes(pcm_data)
96
+ return buf.getvalue()
 
 
 
 
 
 
 
src/tts/gemini_client.py CHANGED
@@ -1,39 +1,39 @@
 
1
  import logging
2
  from typing import AsyncIterator
3
- from google import genai
4
- from google.genai import types
5
- from src.config import GOOGLE_API_KEY, GEMINI_TTS_MODEL, GEMINI_TTS_VOICE, GEMINI_TTS_LANGUAGE
 
6
 
7
  logger = logging.getLogger(__name__)
8
 
9
- GEMINI_SAMPLE_RATE = 24000 # Gemini outputs PCM Linear16 at 24kHz
10
 
11
 
12
  async def synthesize_stream(text: str) -> AsyncIterator[bytes]:
13
- """Calls Gemini TTS and yields raw PCM Linear16 chunks at 24kHz."""
14
- client = genai.Client(api_key=GOOGLE_API_KEY)
15
  logger.info("Gemini TTS: synthesizing '%s'", text[:60])
16
 
17
- response_iter = await client.aio.models.generate_content_stream(
18
- model=GEMINI_TTS_MODEL,
19
- contents=text,
20
- config=types.GenerateContentConfig(
21
- response_modalities=["AUDIO"],
22
- speech_config=types.SpeechConfig(
23
- language_code=GEMINI_TTS_LANGUAGE,
24
- voice_config=types.VoiceConfig(
25
- prebuilt_voice_config=types.PrebuiltVoiceConfig(
26
- voice_name=GEMINI_TTS_VOICE,
27
- )
28
- ),
29
- ),
30
- ),
31
- )
32
-
33
- async for chunk in response_iter:
34
- if (
35
- chunk.candidates
36
- and chunk.candidates[0].content.parts
37
- and chunk.candidates[0].content.parts[0].inline_data
38
- ):
39
- yield chunk.candidates[0].content.parts[0].inline_data.data
 
1
+ import asyncio
2
  import logging
3
  from typing import AsyncIterator
4
+
5
+ from google.cloud import texttospeech
6
+
7
+ from src.config import GEMINI_TTS_MODEL, GEMINI_TTS_VOICE, GEMINI_TTS_LANGUAGE
8
 
9
  logger = logging.getLogger(__name__)
10
 
11
+ GEMINI_SAMPLE_RATE = 24000 # Cloud TTS Gemini model outputs PCM Linear16 at 24kHz
12
 
13
 
14
  async def synthesize_stream(text: str) -> AsyncIterator[bytes]:
15
+ """Calls Cloud TTS Gemini model and yields raw PCM Linear16 chunks at 24kHz."""
 
16
  logger.info("Gemini TTS: synthesizing '%s'", text[:60])
17
 
18
+ def _collect() -> list[bytes]:
19
+ client = texttospeech.TextToSpeechClient()
20
+ config_req = texttospeech.StreamingSynthesizeRequest(
21
+ streaming_config=texttospeech.StreamingSynthesizeConfig(
22
+ voice=texttospeech.VoiceSelectionParams(
23
+ name=GEMINI_TTS_VOICE,
24
+ language_code=GEMINI_TTS_LANGUAGE,
25
+ model_name=GEMINI_TTS_MODEL,
26
+ )
27
+ )
28
+ )
29
+
30
+ def _gen():
31
+ yield config_req
32
+ yield texttospeech.StreamingSynthesizeRequest(
33
+ input=texttospeech.StreamingSynthesisInput(text=text)
34
+ )
35
+
36
+ return [r.audio_content for r in client.streaming_synthesize(_gen())]
37
+
38
+ for chunk in await asyncio.to_thread(_collect):
39
+ yield chunk
 
uv.lock CHANGED
@@ -1,6 +1,11 @@
1
  version = 1
2
  revision = 2
3
  requires-python = ">=3.11"
 
 
 
 
 
4
 
5
  [[package]]
6
  name = "aiohappyeyeballs"
@@ -571,6 +576,90 @@ wheels = [
571
  { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
572
  ]
573
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
  [[package]]
575
  name = "google-auth"
576
  version = "2.49.2"
@@ -584,6 +673,53 @@ wheels = [
584
  { url = "https://files.pythonhosted.org/packages/73/76/d241a5c927433420507215df6cac1b1fa4ac0ba7a794df42a84326c68da8/google_auth-2.49.2-py3-none-any.whl", hash = "sha256:c2720924dfc82dedb962c9f52cabb2ab16714fd0a6a707e40561d217574ed6d5", size = 240638, upload-time = "2026-04-10T00:41:14.501Z" },
585
  ]
586
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  [[package]]
588
  name = "google-genai"
589
  version = "1.2.0"
@@ -599,6 +735,102 @@ wheels = [
599
  { url = "https://files.pythonhosted.org/packages/0d/ed/985f2d2e2b5fbd912ab0fdb11d6dc48c22553a6c4edffabb8146d53b974a/google_genai-1.2.0-py3-none-any.whl", hash = "sha256:609d61bee73f1a6ae5b47e9c7dd4b469d50318f050c5ceacf835b0f80f79d2d9", size = 130744, upload-time = "2025-02-12T16:40:03.601Z" },
600
  ]
601
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  [[package]]
603
  name = "h11"
604
  version = "0.16.0"
@@ -621,6 +853,18 @@ wheels = [
621
  { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
622
  ]
623
 
 
 
 
 
 
 
 
 
 
 
 
 
624
  [[package]]
625
  name = "httptools"
626
  version = "0.7.1"
@@ -808,6 +1052,85 @@ wheels = [
808
  { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" },
809
  ]
810
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  [[package]]
812
  name = "propcache"
813
  version = "0.4.1"
@@ -907,6 +1230,32 @@ wheels = [
907
  { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" },
908
  ]
909
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
910
  [[package]]
911
  name = "pyasn1"
912
  version = "0.6.3"
@@ -1054,6 +1403,15 @@ wheels = [
1054
  { url = "https://files.pythonhosted.org/packages/ef/d2/d2b0025246481aa2ce6db8ba196e29b92063343ac76e675b3a1fa478ed4d/pydantic_core-2.46.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:375cfdd2a1049910c82ba2ff24f948e93599a529e0fdb066d747975ca31fc663", size = 2190970, upload-time = "2026-04-15T14:49:33.111Z" },
1055
  ]
1056
 
 
 
 
 
 
 
 
 
 
1057
  [[package]]
1058
  name = "python-dotenv"
1059
  version = "1.0.1"
@@ -1151,6 +1509,22 @@ wheels = [
1151
  { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
1152
  ]
1153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1154
  [[package]]
1155
  name = "starlette"
1156
  version = "0.38.6"
@@ -1163,6 +1537,18 @@ wheels = [
1163
  { url = "https://files.pythonhosted.org/packages/b7/9c/93f7bc03ff03199074e81974cc148908ead60dcf189f68ba1761a0ee35cf/starlette-0.38.6-py3-none-any.whl", hash = "sha256:4517a1409e2e73ee4951214ba012052b9e16f60e90d73cfb06192c19203bbb05", size = 71451, upload-time = "2024-09-22T17:01:43.076Z" },
1164
  ]
1165
 
 
 
 
 
 
 
 
 
 
 
 
 
1166
  [[package]]
1167
  name = "typing-extensions"
1168
  version = "4.15.0"
@@ -1184,6 +1570,15 @@ wheels = [
1184
  { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
1185
  ]
1186
 
 
 
 
 
 
 
 
 
 
1187
  [[package]]
1188
  name = "urllib3"
1189
  version = "2.6.3"
@@ -1264,8 +1659,12 @@ dependencies = [
1264
  { name = "cartesia" },
1265
  { name = "deepgram-sdk" },
1266
  { name = "fastapi" },
 
 
1267
  { name = "google-genai" },
 
1268
  { name = "httpx" },
 
1269
  { name = "python-dotenv" },
1270
  { name = "python-multipart" },
1271
  { name = "uvicorn", extra = ["standard"] },
@@ -1277,14 +1676,23 @@ dev = [
1277
  { name = "websockets" },
1278
  ]
1279
 
 
 
 
 
 
1280
  [package.metadata]
1281
  requires-dist = [
1282
  { name = "assemblyai", specifier = "==0.33.0" },
1283
  { name = "cartesia", specifier = "==1.3.1" },
1284
  { name = "deepgram-sdk", specifier = ">=6.1.1" },
1285
  { name = "fastapi", specifier = "==0.115.0" },
 
 
1286
  { name = "google-genai", specifier = ">=1.0.0" },
 
1287
  { name = "httpx", specifier = "==0.27.2" },
 
1288
  { name = "python-dotenv", specifier = "==1.0.1" },
1289
  { name = "python-multipart", specifier = ">=0.0.26" },
1290
  { name = "uvicorn", extras = ["standard"], specifier = "==0.30.6" },
@@ -1293,6 +1701,9 @@ requires-dist = [
1293
  ]
1294
  provides-extras = ["dev"]
1295
 
 
 
 
1296
  [[package]]
1297
  name = "watchfiles"
1298
  version = "1.1.1"
 
1
  version = 1
2
  revision = 2
3
  requires-python = ">=3.11"
4
+ resolution-markers = [
5
+ "python_full_version >= '3.14'",
6
+ "python_full_version == '3.13.*'",
7
+ "python_full_version < '3.13'",
8
+ ]
9
 
10
  [[package]]
11
  name = "aiohappyeyeballs"
 
576
  { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
577
  ]
578
 
579
+ [[package]]
580
+ name = "google-ai-generativelanguage"
581
+ version = "0.6.15"
582
+ source = { registry = "https://pypi.org/simple" }
583
+ dependencies = [
584
+ { name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" }, extra = ["grpc"], marker = "python_full_version >= '3.14'" },
585
+ { name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" }, extra = ["grpc"], marker = "python_full_version < '3.14'" },
586
+ { name = "google-auth" },
587
+ { name = "proto-plus" },
588
+ { name = "protobuf" },
589
+ ]
590
+ sdist = { url = "https://files.pythonhosted.org/packages/11/d1/48fe5d7a43d278e9f6b5ada810b0a3530bbeac7ed7fcbcd366f932f05316/google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3", size = 1375443, upload-time = "2025-01-13T21:50:47.459Z" }
591
+ wheels = [
592
+ { url = "https://files.pythonhosted.org/packages/7c/a3/67b8a6ff5001a1d8864922f2d6488dc2a14367ceb651bc3f09a947f2f306/google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c", size = 1327356, upload-time = "2025-01-13T21:50:44.174Z" },
593
+ ]
594
+
595
+ [[package]]
596
+ name = "google-api-core"
597
+ version = "2.25.2"
598
+ source = { registry = "https://pypi.org/simple" }
599
+ resolution-markers = [
600
+ "python_full_version >= '3.14'",
601
+ ]
602
+ dependencies = [
603
+ { name = "google-auth", marker = "python_full_version >= '3.14'" },
604
+ { name = "googleapis-common-protos", marker = "python_full_version >= '3.14'" },
605
+ { name = "proto-plus", marker = "python_full_version >= '3.14'" },
606
+ { name = "protobuf", marker = "python_full_version >= '3.14'" },
607
+ { name = "requests", marker = "python_full_version >= '3.14'" },
608
+ ]
609
+ sdist = { url = "https://files.pythonhosted.org/packages/09/cd/63f1557235c2440fe0577acdbc32577c5c002684c58c7f4d770a92366a24/google_api_core-2.25.2.tar.gz", hash = "sha256:1c63aa6af0d0d5e37966f157a77f9396d820fba59f9e43e9415bc3dc5baff300", size = 166266, upload-time = "2025-10-03T00:07:34.778Z" }
610
+ wheels = [
611
+ { url = "https://files.pythonhosted.org/packages/c8/d8/894716a5423933f5c8d2d5f04b16f052a515f78e815dab0c2c6f1fd105dc/google_api_core-2.25.2-py3-none-any.whl", hash = "sha256:e9a8f62d363dc8424a8497f4c2a47d6bcda6c16514c935629c257ab5d10210e7", size = 162489, upload-time = "2025-10-03T00:07:32.924Z" },
612
+ ]
613
+
614
+ [package.optional-dependencies]
615
+ grpc = [
616
+ { name = "grpcio", marker = "python_full_version >= '3.14'" },
617
+ { name = "grpcio-status", marker = "python_full_version >= '3.14'" },
618
+ ]
619
+
620
+ [[package]]
621
+ name = "google-api-core"
622
+ version = "2.30.3"
623
+ source = { registry = "https://pypi.org/simple" }
624
+ resolution-markers = [
625
+ "python_full_version == '3.13.*'",
626
+ "python_full_version < '3.13'",
627
+ ]
628
+ dependencies = [
629
+ { name = "google-auth", marker = "python_full_version < '3.14'" },
630
+ { name = "googleapis-common-protos", marker = "python_full_version < '3.14'" },
631
+ { name = "proto-plus", marker = "python_full_version < '3.14'" },
632
+ { name = "protobuf", marker = "python_full_version < '3.14'" },
633
+ { name = "requests", marker = "python_full_version < '3.14'" },
634
+ ]
635
+ sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" }
636
+ wheels = [
637
+ { url = "https://files.pythonhosted.org/packages/03/15/e56f351cf6ef1cfea58e6ac226a7318ed1deb2218c4b3cc9bd9e4b786c5a/google_api_core-2.30.3-py3-none-any.whl", hash = "sha256:a85761ba72c444dad5d611c2220633480b2b6be2521eca69cca2dbb3ffd6bfe8", size = 173274, upload-time = "2026-04-09T22:57:16.198Z" },
638
+ ]
639
+
640
+ [package.optional-dependencies]
641
+ grpc = [
642
+ { name = "grpcio", marker = "python_full_version < '3.14'" },
643
+ { name = "grpcio-status", marker = "python_full_version < '3.14'" },
644
+ ]
645
+
646
+ [[package]]
647
+ name = "google-api-python-client"
648
+ version = "2.194.0"
649
+ source = { registry = "https://pypi.org/simple" }
650
+ dependencies = [
651
+ { name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
652
+ { name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
653
+ { name = "google-auth" },
654
+ { name = "google-auth-httplib2" },
655
+ { name = "httplib2" },
656
+ { name = "uritemplate" },
657
+ ]
658
+ sdist = { url = "https://files.pythonhosted.org/packages/60/ab/e83af0eb043e4ccc49571ca7a6a49984e9d00f4e9e6e6f1238d60bc84dce/google_api_python_client-2.194.0.tar.gz", hash = "sha256:db92647bd1a90f40b79c9618461553c2b20b6a43ce7395fa6de07132dc14f023", size = 14443469, upload-time = "2026-04-08T23:07:35.757Z" }
659
+ wheels = [
660
+ { url = "https://files.pythonhosted.org/packages/b0/34/5a624e49f179aa5b0cb87b2ce8093960299030ff40423bfbde09360eb908/google_api_python_client-2.194.0-py3-none-any.whl", hash = "sha256:61eaaac3b8fc8fdf11c08af87abc3d1342d1b37319cc1b57405f86ef7697e717", size = 15016514, upload-time = "2026-04-08T23:07:33.093Z" },
661
+ ]
662
+
663
  [[package]]
664
  name = "google-auth"
665
  version = "2.49.2"
 
673
  { url = "https://files.pythonhosted.org/packages/73/76/d241a5c927433420507215df6cac1b1fa4ac0ba7a794df42a84326c68da8/google_auth-2.49.2-py3-none-any.whl", hash = "sha256:c2720924dfc82dedb962c9f52cabb2ab16714fd0a6a707e40561d217574ed6d5", size = 240638, upload-time = "2026-04-10T00:41:14.501Z" },
674
  ]
675
 
676
+ [[package]]
677
+ name = "google-auth-httplib2"
678
+ version = "0.3.1"
679
+ source = { registry = "https://pypi.org/simple" }
680
+ dependencies = [
681
+ { name = "google-auth" },
682
+ { name = "httplib2" },
683
+ ]
684
+ sdist = { url = "https://files.pythonhosted.org/packages/ed/99/107612bef8d24b298bb5a7c8466f908ecda791d43f9466f5c3978f5b24c1/google_auth_httplib2-0.3.1.tar.gz", hash = "sha256:0af542e815784cb64159b4469aa5d71dd41069ba93effa006e1916b1dcd88e55", size = 11152, upload-time = "2026-03-30T22:50:26.766Z" }
685
+ wheels = [
686
+ { url = "https://files.pythonhosted.org/packages/97/e9/93afb14d23a949acaa3f4e7cc51a0024671174e116e35f42850764b99634/google_auth_httplib2-0.3.1-py3-none-any.whl", hash = "sha256:682356a90ef4ba3d06548c37e9112eea6fc00395a11b0303a644c1a86abc275c", size = 9534, upload-time = "2026-03-30T22:49:03.384Z" },
687
+ ]
688
+
689
+ [[package]]
690
+ name = "google-cloud-speech"
691
+ version = "2.38.0"
692
+ source = { registry = "https://pypi.org/simple" }
693
+ dependencies = [
694
+ { name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" }, extra = ["grpc"], marker = "python_full_version >= '3.14'" },
695
+ { name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" }, extra = ["grpc"], marker = "python_full_version < '3.14'" },
696
+ { name = "google-auth" },
697
+ { name = "grpcio" },
698
+ { name = "proto-plus" },
699
+ { name = "protobuf" },
700
+ ]
701
+ sdist = { url = "https://files.pythonhosted.org/packages/21/1f/d0122ad8af8c0608fb3168bd5030e62ce0a1fcc09c730487bc8be541874a/google_cloud_speech-2.38.0.tar.gz", hash = "sha256:1854b51cbb7957273b6ba61f4a6cf49dec8d09ec450991587897e50267eaca51", size = 406015, upload-time = "2026-03-26T22:18:54.434Z" }
702
+ wheels = [
703
+ { url = "https://files.pythonhosted.org/packages/01/96/008365cddc78720d65475091be929466fb16c62b47283546f8eab5ff4445/google_cloud_speech-2.38.0-py3-none-any.whl", hash = "sha256:dbccb340a750a409b0e70c48c16c8d7d5d48a87c70cce2add50f3d571f5375a0", size = 346013, upload-time = "2026-03-26T22:13:50.88Z" },
704
+ ]
705
+
706
+ [[package]]
707
+ name = "google-cloud-texttospeech"
708
+ version = "2.36.0"
709
+ source = { registry = "https://pypi.org/simple" }
710
+ dependencies = [
711
+ { name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" }, extra = ["grpc"], marker = "python_full_version >= '3.14'" },
712
+ { name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" }, extra = ["grpc"], marker = "python_full_version < '3.14'" },
713
+ { name = "google-auth" },
714
+ { name = "grpcio" },
715
+ { name = "proto-plus" },
716
+ { name = "protobuf" },
717
+ ]
718
+ sdist = { url = "https://files.pythonhosted.org/packages/1a/fe/670610dce6852c8fbdd723101a2ffb497d196bc242cb04a1312339f49d67/google_cloud_texttospeech-2.36.0.tar.gz", hash = "sha256:6c605af7e4774c1bac99fcaaf4538f152b10bba7738a23f42184557f444dc6b7", size = 196558, upload-time = "2026-04-02T21:23:42.236Z" }
719
+ wheels = [
720
+ { url = "https://files.pythonhosted.org/packages/76/1a/0317eaa1ccd6e3645d570851574bb7c1a1cfa3504bb114b7211db59b154d/google_cloud_texttospeech-2.36.0-py3-none-any.whl", hash = "sha256:03f76162543e9d77ecbab823c1cc3728c42ef40547353bcfdbd9ac0e71cb8121", size = 200086, upload-time = "2026-04-02T21:23:13.444Z" },
721
+ ]
722
+
723
  [[package]]
724
  name = "google-genai"
725
  version = "1.2.0"
 
735
  { url = "https://files.pythonhosted.org/packages/0d/ed/985f2d2e2b5fbd912ab0fdb11d6dc48c22553a6c4edffabb8146d53b974a/google_genai-1.2.0-py3-none-any.whl", hash = "sha256:609d61bee73f1a6ae5b47e9c7dd4b469d50318f050c5ceacf835b0f80f79d2d9", size = 130744, upload-time = "2025-02-12T16:40:03.601Z" },
736
  ]
737
 
738
+ [[package]]
739
+ name = "google-generativeai"
740
+ version = "0.8.6"
741
+ source = { registry = "https://pypi.org/simple" }
742
+ dependencies = [
743
+ { name = "google-ai-generativelanguage" },
744
+ { name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
745
+ { name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
746
+ { name = "google-api-python-client" },
747
+ { name = "google-auth" },
748
+ { name = "protobuf" },
749
+ { name = "pydantic" },
750
+ { name = "tqdm" },
751
+ { name = "typing-extensions" },
752
+ ]
753
+ wheels = [
754
+ { url = "https://files.pythonhosted.org/packages/97/0f/ef33b5bb71437966590c6297104c81051feae95d54b11ece08533ef937d3/google_generativeai-0.8.6-py3-none-any.whl", hash = "sha256:37a0eaaa95e5bbf888828e20a4a1b2c196cc9527d194706e58a68ff388aeb0fa", size = 155098, upload-time = "2025-12-16T17:53:58.61Z" },
755
+ ]
756
+
757
+ [[package]]
758
+ name = "googleapis-common-protos"
759
+ version = "1.74.0"
760
+ source = { registry = "https://pypi.org/simple" }
761
+ dependencies = [
762
+ { name = "protobuf" },
763
+ ]
764
+ sdist = { url = "https://files.pythonhosted.org/packages/20/18/a746c8344152d368a5aac738d4c857012f2c5d1fd2eac7e17b647a7861bd/googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1", size = 151254, upload-time = "2026-04-02T21:23:26.679Z" }
765
+ wheels = [
766
+ { url = "https://files.pythonhosted.org/packages/b6/b0/be5d3329badb9230b765de6eea66b73abd5944bdeb5afb3562ddcd80ae84/googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5", size = 300743, upload-time = "2026-04-02T21:22:49.108Z" },
767
+ ]
768
+
769
+ [[package]]
770
+ name = "grpcio"
771
+ version = "1.80.0"
772
+ source = { registry = "https://pypi.org/simple" }
773
+ dependencies = [
774
+ { name = "typing-extensions" },
775
+ ]
776
+ sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" }
777
+ wheels = [
778
+ { url = "https://files.pythonhosted.org/packages/5d/db/1d56e5f5823257b291962d6c0ce106146c6447f405b60b234c4f222a7cde/grpcio-1.80.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:dfab85db094068ff42e2a3563f60ab3dddcc9d6488a35abf0132daec13209c8a", size = 6055009, upload-time = "2026-03-30T08:46:46.265Z" },
779
+ { url = "https://files.pythonhosted.org/packages/6e/18/c83f3cad64c5ca63bca7e91e5e46b0d026afc5af9d0a9972472ceba294b3/grpcio-1.80.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5c07e82e822e1161354e32da2662f741a4944ea955f9f580ec8fb409dd6f6060", size = 12035295, upload-time = "2026-03-30T08:46:49.099Z" },
780
+ { url = "https://files.pythonhosted.org/packages/0f/8e/e14966b435be2dda99fbe89db9525ea436edc79780431a1c2875a3582644/grpcio-1.80.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba0915d51fd4ced2db5ff719f84e270afe0e2d4c45a7bdb1e8d036e4502928c2", size = 6610297, upload-time = "2026-03-30T08:46:52.123Z" },
781
+ { url = "https://files.pythonhosted.org/packages/cc/26/d5eb38f42ce0e3fdc8174ea4d52036ef8d58cc4426cb800f2610f625dd75/grpcio-1.80.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3cb8130ba457d2aa09fa6b7c3ed6b6e4e6a2685fce63cb803d479576c4d80e21", size = 7300208, upload-time = "2026-03-30T08:46:54.859Z" },
782
+ { url = "https://files.pythonhosted.org/packages/25/51/bd267c989f85a17a5b3eea65a6feb4ff672af41ca614e5a0279cc0ea381c/grpcio-1.80.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09e5e478b3d14afd23f12e49e8b44c8684ac3c5f08561c43a5b9691c54d136ab", size = 6813442, upload-time = "2026-03-30T08:46:57.056Z" },
783
+ { url = "https://files.pythonhosted.org/packages/9e/d9/d80eef735b19e9169e30164bbf889b46f9df9127598a83d174eb13a48b26/grpcio-1.80.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:00168469238b022500e486c1c33916acf2f2a9b2c022202cf8a1885d2e3073c1", size = 7414743, upload-time = "2026-03-30T08:46:59.682Z" },
784
+ { url = "https://files.pythonhosted.org/packages/de/f2/567f5bd5054398ed6b0509b9a30900376dcf2786bd936812098808b49d8d/grpcio-1.80.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8502122a3cc1714038e39a0b071acb1207ca7844208d5ea0d091317555ee7106", size = 8426046, upload-time = "2026-03-30T08:47:02.474Z" },
785
+ { url = "https://files.pythonhosted.org/packages/62/29/73ef0141b4732ff5eacd68430ff2512a65c004696997f70476a83e548e7e/grpcio-1.80.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ce1794f4ea6cc3ca29463f42d665c32ba1b964b48958a66497917fe9069f26e6", size = 7851641, upload-time = "2026-03-30T08:47:05.462Z" },
786
+ { url = "https://files.pythonhosted.org/packages/46/69/abbfa360eb229a8623bab5f5a4f8105e445bd38ce81a89514ba55d281ad0/grpcio-1.80.0-cp311-cp311-win32.whl", hash = "sha256:51b4a7189b0bef2aa30adce3c78f09c83526cf3dddb24c6a96555e3b97340440", size = 4154368, upload-time = "2026-03-30T08:47:08.027Z" },
787
+ { url = "https://files.pythonhosted.org/packages/6f/d4/ae92206d01183b08613e846076115f5ac5991bae358d2a749fa864da5699/grpcio-1.80.0-cp311-cp311-win_amd64.whl", hash = "sha256:02e64bb0bb2da14d947a49e6f120a75e947250aebe65f9629b62bb1f5c14e6e9", size = 4894235, upload-time = "2026-03-30T08:47:10.839Z" },
788
+ { url = "https://files.pythonhosted.org/packages/5c/e8/a2b749265eb3415abc94f2e619bbd9e9707bebdda787e61c593004ec927a/grpcio-1.80.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:c624cc9f1008361014378c9d776de7182b11fe8b2e5a81bc69f23a295f2a1ad0", size = 6015616, upload-time = "2026-03-30T08:47:13.428Z" },
789
+ { url = "https://files.pythonhosted.org/packages/3e/97/b1282161a15d699d1e90c360df18d19165a045ce1c343c7f313f5e8a0b77/grpcio-1.80.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f49eddcac43c3bf350c0385366a58f36bed8cc2c0ec35ef7b74b49e56552c0c2", size = 12014204, upload-time = "2026-03-30T08:47:15.873Z" },
790
+ { url = "https://files.pythonhosted.org/packages/6e/5e/d319c6e997b50c155ac5a8cb12f5173d5b42677510e886d250d50264949d/grpcio-1.80.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d334591df610ab94714048e0d5b4f3dd5ad1bee74dfec11eee344220077a79de", size = 6563866, upload-time = "2026-03-30T08:47:18.588Z" },
791
+ { url = "https://files.pythonhosted.org/packages/ae/f6/fdd975a2cb4d78eb67769a7b3b3830970bfa2e919f1decf724ae4445f42c/grpcio-1.80.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0cb517eb1d0d0aaf1d87af7cc5b801d686557c1d88b2619f5e31fab3c2315921", size = 7273060, upload-time = "2026-03-30T08:47:21.113Z" },
792
+ { url = "https://files.pythonhosted.org/packages/db/f0/a3deb5feba60d9538a962913e37bd2e69a195f1c3376a3dd44fe0427e996/grpcio-1.80.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e78c4ac0d97dc2e569b2f4bcbbb447491167cb358d1a389fc4af71ab6f70411", size = 6782121, upload-time = "2026-03-30T08:47:23.827Z" },
793
+ { url = "https://files.pythonhosted.org/packages/ca/84/36c6dcfddc093e108141f757c407902a05085e0c328007cb090d56646cdf/grpcio-1.80.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2ed770b4c06984f3b47eb0517b1c69ad0b84ef3f40128f51448433be904634cd", size = 7383811, upload-time = "2026-03-30T08:47:26.517Z" },
794
+ { url = "https://files.pythonhosted.org/packages/7c/ef/f3a77e3dc5b471a0ec86c564c98d6adfa3510d38f8ee99010410858d591e/grpcio-1.80.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:256507e2f524092f1473071a05e65a5b10d84b82e3ff24c5b571513cfaa61e2f", size = 8393860, upload-time = "2026-03-30T08:47:29.439Z" },
795
+ { url = "https://files.pythonhosted.org/packages/9b/8d/9d4d27ed7f33d109c50d6b5ce578a9914aa68edab75d65869a17e630a8d1/grpcio-1.80.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a6284a5d907c37db53350645567c522be314bac859a64a7a5ca63b77bb7958f", size = 7830132, upload-time = "2026-03-30T08:47:33.254Z" },
796
+ { url = "https://files.pythonhosted.org/packages/14/e4/9990b41c6d7a44e1e9dee8ac11d7a9802ba1378b40d77468a7761d1ad288/grpcio-1.80.0-cp312-cp312-win32.whl", hash = "sha256:c71309cfce2f22be26aa4a847357c502db6c621f1a49825ae98aa0907595b193", size = 4140904, upload-time = "2026-03-30T08:47:35.319Z" },
797
+ { url = "https://files.pythonhosted.org/packages/2f/2c/296f6138caca1f4b92a31ace4ae1b87dab692fc16a7a3417af3bb3c805bf/grpcio-1.80.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe648599c0e37594c4809d81a9e77bd138cc82eb8baa71b6a86af65426723ff", size = 4880944, upload-time = "2026-03-30T08:47:37.831Z" },
798
+ { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" },
799
+ { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" },
800
+ { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" },
801
+ { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" },
802
+ { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" },
803
+ { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" },
804
+ { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" },
805
+ { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" },
806
+ { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" },
807
+ { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" },
808
+ { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" },
809
+ { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" },
810
+ { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" },
811
+ { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" },
812
+ { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" },
813
+ { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" },
814
+ { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" },
815
+ { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" },
816
+ { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" },
817
+ { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" },
818
+ ]
819
+
820
+ [[package]]
821
+ name = "grpcio-status"
822
+ version = "1.71.2"
823
+ source = { registry = "https://pypi.org/simple" }
824
+ dependencies = [
825
+ { name = "googleapis-common-protos" },
826
+ { name = "grpcio" },
827
+ { name = "protobuf" },
828
+ ]
829
+ sdist = { url = "https://files.pythonhosted.org/packages/fd/d1/b6e9877fedae3add1afdeae1f89d1927d296da9cf977eca0eb08fb8a460e/grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50", size = 13677, upload-time = "2025-06-28T04:24:05.426Z" }
830
+ wheels = [
831
+ { url = "https://files.pythonhosted.org/packages/67/58/317b0134129b556a93a3b0afe00ee675b5657f0155509e22fcb853bafe2d/grpcio_status-1.71.2-py3-none-any.whl", hash = "sha256:803c98cb6a8b7dc6dbb785b1111aed739f241ab5e9da0bba96888aa74704cfd3", size = 14424, upload-time = "2025-06-28T04:23:42.136Z" },
832
+ ]
833
+
834
  [[package]]
835
  name = "h11"
836
  version = "0.16.0"
 
853
  { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
854
  ]
855
 
856
+ [[package]]
857
+ name = "httplib2"
858
+ version = "0.31.2"
859
+ source = { registry = "https://pypi.org/simple" }
860
+ dependencies = [
861
+ { name = "pyparsing" },
862
+ ]
863
+ sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" }
864
+ wheels = [
865
+ { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" },
866
+ ]
867
+
868
  [[package]]
869
  name = "httptools"
870
  version = "0.7.1"
 
1052
  { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" },
1053
  ]
1054
 
1055
+ [[package]]
1056
+ name = "numpy"
1057
+ version = "2.4.4"
1058
+ source = { registry = "https://pypi.org/simple" }
1059
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" }
1060
+ wheels = [
1061
+ { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" },
1062
+ { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" },
1063
+ { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" },
1064
+ { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" },
1065
+ { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" },
1066
+ { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" },
1067
+ { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" },
1068
+ { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" },
1069
+ { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" },
1070
+ { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" },
1071
+ { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" },
1072
+ { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" },
1073
+ { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" },
1074
+ { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" },
1075
+ { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" },
1076
+ { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" },
1077
+ { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" },
1078
+ { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" },
1079
+ { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" },
1080
+ { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" },
1081
+ { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" },
1082
+ { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" },
1083
+ { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" },
1084
+ { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" },
1085
+ { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" },
1086
+ { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" },
1087
+ { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" },
1088
+ { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" },
1089
+ { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" },
1090
+ { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" },
1091
+ { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" },
1092
+ { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" },
1093
+ { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" },
1094
+ { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" },
1095
+ { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" },
1096
+ { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" },
1097
+ { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" },
1098
+ { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" },
1099
+ { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" },
1100
+ { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" },
1101
+ { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" },
1102
+ { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" },
1103
+ { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" },
1104
+ { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" },
1105
+ { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" },
1106
+ { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" },
1107
+ { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" },
1108
+ { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" },
1109
+ { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" },
1110
+ { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" },
1111
+ { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" },
1112
+ { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" },
1113
+ { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" },
1114
+ { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" },
1115
+ { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" },
1116
+ { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" },
1117
+ { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" },
1118
+ { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" },
1119
+ { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" },
1120
+ { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" },
1121
+ { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" },
1122
+ { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" },
1123
+ { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" },
1124
+ { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" },
1125
+ { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" },
1126
+ { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" },
1127
+ { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" },
1128
+ { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" },
1129
+ { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" },
1130
+ { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" },
1131
+ { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" },
1132
+ ]
1133
+
1134
  [[package]]
1135
  name = "propcache"
1136
  version = "0.4.1"
 
1230
  { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" },
1231
  ]
1232
 
1233
+ [[package]]
1234
+ name = "proto-plus"
1235
+ version = "1.27.2"
1236
+ source = { registry = "https://pypi.org/simple" }
1237
+ dependencies = [
1238
+ { name = "protobuf" },
1239
+ ]
1240
+ sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204, upload-time = "2026-03-26T22:18:57.174Z" }
1241
+ wheels = [
1242
+ { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450, upload-time = "2026-03-26T22:13:42.927Z" },
1243
+ ]
1244
+
1245
+ [[package]]
1246
+ name = "protobuf"
1247
+ version = "5.29.6"
1248
+ source = { registry = "https://pypi.org/simple" }
1249
+ sdist = { url = "https://files.pythonhosted.org/packages/7e/57/394a763c103e0edf87f0938dafcd918d53b4c011dfc5c8ae80f3b0452dbb/protobuf-5.29.6.tar.gz", hash = "sha256:da9ee6a5424b6b30fd5e45c5ea663aef540ca95f9ad99d1e887e819cdf9b8723", size = 425623, upload-time = "2026-02-04T22:54:40.584Z" }
1250
+ wheels = [
1251
+ { url = "https://files.pythonhosted.org/packages/d4/88/9ee58ff7863c479d6f8346686d4636dd4c415b0cbeed7a6a7d0617639c2a/protobuf-5.29.6-cp310-abi3-win32.whl", hash = "sha256:62e8a3114992c7c647bce37dcc93647575fc52d50e48de30c6fcb28a6a291eb1", size = 423357, upload-time = "2026-02-04T22:54:25.805Z" },
1252
+ { url = "https://files.pythonhosted.org/packages/1c/66/2dc736a4d576847134fb6d80bd995c569b13cdc7b815d669050bf0ce2d2c/protobuf-5.29.6-cp310-abi3-win_amd64.whl", hash = "sha256:7e6ad413275be172f67fdee0f43484b6de5a904cc1c3ea9804cb6fe2ff366eda", size = 435175, upload-time = "2026-02-04T22:54:28.592Z" },
1253
+ { url = "https://files.pythonhosted.org/packages/06/db/49b05966fd208ae3f44dcd33837b6243b4915c57561d730a43f881f24dea/protobuf-5.29.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5a169e664b4057183a34bdc424540e86eea47560f3c123a0d64de4e137f9269", size = 418619, upload-time = "2026-02-04T22:54:30.266Z" },
1254
+ { url = "https://files.pythonhosted.org/packages/b7/d7/48cbf6b0c3c39761e47a99cb483405f0fde2be22cf00d71ef316ce52b458/protobuf-5.29.6-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:a8866b2cff111f0f863c1b3b9e7572dc7eaea23a7fae27f6fc613304046483e6", size = 320284, upload-time = "2026-02-04T22:54:31.782Z" },
1255
+ { url = "https://files.pythonhosted.org/packages/e3/dd/cadd6ec43069247d91f6345fa7a0d2858bef6af366dbd7ba8f05d2c77d3b/protobuf-5.29.6-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:e3387f44798ac1106af0233c04fb8abf543772ff241169946f698b3a9a3d3ab9", size = 320478, upload-time = "2026-02-04T22:54:32.909Z" },
1256
+ { url = "https://files.pythonhosted.org/packages/5a/cb/e3065b447186cb70aa65acc70c86baf482d82bf75625bf5a2c4f6919c6a3/protobuf-5.29.6-py3-none-any.whl", hash = "sha256:6b9edb641441b2da9fa8f428760fc136a49cf97a52076010cf22a2ff73438a86", size = 173126, upload-time = "2026-02-04T22:54:39.462Z" },
1257
+ ]
1258
+
1259
  [[package]]
1260
  name = "pyasn1"
1261
  version = "0.6.3"
 
1403
  { url = "https://files.pythonhosted.org/packages/ef/d2/d2b0025246481aa2ce6db8ba196e29b92063343ac76e675b3a1fa478ed4d/pydantic_core-2.46.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:375cfdd2a1049910c82ba2ff24f948e93599a529e0fdb066d747975ca31fc663", size = 2190970, upload-time = "2026-04-15T14:49:33.111Z" },
1404
  ]
1405
 
1406
+ [[package]]
1407
+ name = "pyparsing"
1408
+ version = "3.3.2"
1409
+ source = { registry = "https://pypi.org/simple" }
1410
+ sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" }
1411
+ wheels = [
1412
+ { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" },
1413
+ ]
1414
+
1415
  [[package]]
1416
  name = "python-dotenv"
1417
  version = "1.0.1"
 
1509
  { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
1510
  ]
1511
 
1512
+ [[package]]
1513
+ name = "sounddevice"
1514
+ version = "0.5.5"
1515
+ source = { registry = "https://pypi.org/simple" }
1516
+ dependencies = [
1517
+ { name = "cffi" },
1518
+ ]
1519
+ sdist = { url = "https://files.pythonhosted.org/packages/2a/f9/2592608737553638fca98e21e54bfec40bf577bb98a61b2770c912aab25e/sounddevice-0.5.5.tar.gz", hash = "sha256:22487b65198cb5bf2208755105b524f78ad173e5ab6b445bdab1c989f6698df3", size = 143191, upload-time = "2026-01-23T18:36:43.529Z" }
1520
+ wheels = [
1521
+ { url = "https://files.pythonhosted.org/packages/1e/0a/478e441fd049002cf308520c0d62dd8333e7c6cc8d997f0dda07b9fbcc46/sounddevice-0.5.5-py3-none-any.whl", hash = "sha256:30ff99f6c107f49d25ad16a45cacd8d91c25a1bcdd3e81a206b921a3a6405b1f", size = 32807, upload-time = "2026-01-23T18:36:35.649Z" },
1522
+ { url = "https://files.pythonhosted.org/packages/56/f9/c037c35f6d0b6bc3bc7bfb314f1d6f1f9a341328ef47cd63fc4f850a7b27/sounddevice-0.5.5-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:05eb9fd6c54c38d67741441c19164c0dae8ce80453af2d8c4ad2e7823d15b722", size = 108557, upload-time = "2026-01-23T18:36:37.41Z" },
1523
+ { url = "https://files.pythonhosted.org/packages/88/a1/d19dd9889cd4bce2e233c4fac007cd8daaf5b9fe6e6a5d432cf17be0b807/sounddevice-0.5.5-py3-none-win32.whl", hash = "sha256:1234cc9b4c9df97b6cbe748146ae0ec64dd7d6e44739e8e42eaa5b595313a103", size = 317765, upload-time = "2026-01-23T18:36:39.047Z" },
1524
+ { url = "https://files.pythonhosted.org/packages/c3/0e/002ed7c4c1c2ab69031f78989d3b789fee3a7fba9e586eb2b81688bf4961/sounddevice-0.5.5-py3-none-win_amd64.whl", hash = "sha256:cfc6b2c49fb7f555591c78cb8ecf48d6a637fd5b6e1db5fec6ed9365d64b3519", size = 365324, upload-time = "2026-01-23T18:36:40.496Z" },
1525
+ { url = "https://files.pythonhosted.org/packages/4e/39/a61d4b83a7746b70d23d9173be688c0c6bfc7173772344b7442c2c155497/sounddevice-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3861901ddd8230d2e0e8ae62ac320cdd4c688d81df89da036dcb812f757bb3e6", size = 317115, upload-time = "2026-01-23T18:36:42.235Z" },
1526
+ ]
1527
+
1528
  [[package]]
1529
  name = "starlette"
1530
  version = "0.38.6"
 
1537
  { url = "https://files.pythonhosted.org/packages/b7/9c/93f7bc03ff03199074e81974cc148908ead60dcf189f68ba1761a0ee35cf/starlette-0.38.6-py3-none-any.whl", hash = "sha256:4517a1409e2e73ee4951214ba012052b9e16f60e90d73cfb06192c19203bbb05", size = 71451, upload-time = "2024-09-22T17:01:43.076Z" },
1538
  ]
1539
 
1540
+ [[package]]
1541
+ name = "tqdm"
1542
+ version = "4.67.3"
1543
+ source = { registry = "https://pypi.org/simple" }
1544
+ dependencies = [
1545
+ { name = "colorama", marker = "sys_platform == 'win32'" },
1546
+ ]
1547
+ sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" }
1548
+ wheels = [
1549
+ { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
1550
+ ]
1551
+
1552
  [[package]]
1553
  name = "typing-extensions"
1554
  version = "4.15.0"
 
1570
  { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
1571
  ]
1572
 
1573
+ [[package]]
1574
+ name = "uritemplate"
1575
+ version = "4.2.0"
1576
+ source = { registry = "https://pypi.org/simple" }
1577
+ sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" }
1578
+ wheels = [
1579
+ { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" },
1580
+ ]
1581
+
1582
  [[package]]
1583
  name = "urllib3"
1584
  version = "2.6.3"
 
1659
  { name = "cartesia" },
1660
  { name = "deepgram-sdk" },
1661
  { name = "fastapi" },
1662
+ { name = "google-cloud-speech" },
1663
+ { name = "google-cloud-texttospeech" },
1664
  { name = "google-genai" },
1665
+ { name = "google-generativeai" },
1666
  { name = "httpx" },
1667
+ { name = "numpy" },
1668
  { name = "python-dotenv" },
1669
  { name = "python-multipart" },
1670
  { name = "uvicorn", extra = ["standard"] },
 
1676
  { name = "websockets" },
1677
  ]
1678
 
1679
+ [package.dev-dependencies]
1680
+ dev = [
1681
+ { name = "sounddevice" },
1682
+ ]
1683
+
1684
  [package.metadata]
1685
  requires-dist = [
1686
  { name = "assemblyai", specifier = "==0.33.0" },
1687
  { name = "cartesia", specifier = "==1.3.1" },
1688
  { name = "deepgram-sdk", specifier = ">=6.1.1" },
1689
  { name = "fastapi", specifier = "==0.115.0" },
1690
+ { name = "google-cloud-speech", specifier = ">=2.0.0" },
1691
+ { name = "google-cloud-texttospeech", specifier = ">=2.29.0" },
1692
  { name = "google-genai", specifier = ">=1.0.0" },
1693
+ { name = "google-generativeai", specifier = ">=0.8.6" },
1694
  { name = "httpx", specifier = "==0.27.2" },
1695
+ { name = "numpy", specifier = ">=1.26.0" },
1696
  { name = "python-dotenv", specifier = "==1.0.1" },
1697
  { name = "python-multipart", specifier = ">=0.0.26" },
1698
  { name = "uvicorn", extras = ["standard"], specifier = "==0.30.6" },
 
1701
  ]
1702
  provides-extras = ["dev"]
1703
 
1704
+ [package.metadata.requires-dev]
1705
+ dev = [{ name = "sounddevice", specifier = ">=0.5.5" }]
1706
+
1707
  [[package]]
1708
  name = "watchfiles"
1709
  version = "1.1.1"