Archime commited on
Commit
87f1f7d
·
1 Parent(s): 62b768a

Merge stream_utils and utils files

Browse files
Files changed (3) hide show
  1. app.py +8 -10
  2. app/stream_utils.py +0 -231
  3. app/utils.py +239 -4
app.py CHANGED
@@ -13,7 +13,11 @@ import os
13
  from gradio.utils import get_space
14
 
15
  from app.utils import (
16
- raise_function
 
 
 
 
17
  )
18
  from app.session_utils import (
19
  on_load,
@@ -36,13 +40,6 @@ from app.ui_utils import (
36
  on_file_load
37
  )
38
 
39
- from app.stream_utils import (
40
- generate_coturn_config,
41
- read_and_stream_audio,
42
- stop_streaming,
43
- task
44
- )
45
-
46
  # --------------------------------------------------------
47
  # Initialization
48
  # --------------------------------------------------------
@@ -219,7 +216,6 @@ with gr.Blocks(theme=theme, css=css_style) as demo:
219
  # === STEP 4 ===
220
  with gr.Step("Task", id=3) as task_step:
221
  gr.Markdown("## Step 4: Start the Task")
222
-
223
  with gr.Group():
224
  with gr.Column():
225
  status_slider = gr.Slider(
@@ -235,8 +231,10 @@ with gr.Blocks(theme=theme, css=css_style) as demo:
235
  label="Transcription / Translation Result",
236
  placeholder="Waiting for output...",
237
  lines=10,
 
238
  interactive=False,
239
- visible=True
 
240
  )
241
 
242
  start_task_button = gr.Button("Start Task", visible=True)
 
13
  from gradio.utils import get_space
14
 
15
  from app.utils import (
16
+ raise_function,
17
+ generate_coturn_config,
18
+ read_and_stream_audio,
19
+ stop_streaming,
20
+ task
21
  )
22
  from app.session_utils import (
23
  on_load,
 
40
  on_file_load
41
  )
42
 
 
 
 
 
 
 
 
43
  # --------------------------------------------------------
44
  # Initialization
45
  # --------------------------------------------------------
 
216
  # === STEP 4 ===
217
  with gr.Step("Task", id=3) as task_step:
218
  gr.Markdown("## Step 4: Start the Task")
 
219
  with gr.Group():
220
  with gr.Column():
221
  status_slider = gr.Slider(
 
231
  label="Transcription / Translation Result",
232
  placeholder="Waiting for output...",
233
  lines=10,
234
+ max_lines= 10,
235
  interactive=False,
236
+ visible=True,
237
+ autoscroll=True
238
  )
239
 
240
  start_task_button = gr.Button("Start Task", visible=True)
app/stream_utils.py DELETED
@@ -1,231 +0,0 @@
1
-
2
- from app.logger_config import logger as logging
3
- from fastrtc.utils import AdditionalOutputs
4
- from pydub import AudioSegment
5
- import asyncio
6
- import os
7
- import time
8
- import numpy as np
9
- import spaces
10
- import hmac
11
- import hashlib
12
- import base64
13
- import os
14
- import time
15
- import random
16
-
17
- from app.session_utils import (
18
- get_active_task_flag_file,
19
- get_folder_chunks
20
- )
21
-
22
- # --------------------------------------------------------
23
- # Utility functions
24
- # --------------------------------------------------------
25
- def generate_coturn_config():
26
- """
27
- Génère une configuration Coturn complète avec authentification dynamique (use-auth-secret).
28
- Returns:
29
- dict: Objet coturn_config prêt à être utilisé côté client WebRTC.
30
- """
31
-
32
- secret_key = os.getenv("TURN_SECRET_KEY", "your_secret_key")
33
- ttl = int(os.getenv("TURN_TTL", 3600))
34
- turn_url = os.getenv("TURN_URL", "turn:*******")
35
- turn_s_url = os.getenv("TURN_S_URL", "turns:*****")
36
- user = os.getenv("TURN_USER", "client")
37
-
38
- timestamp = int(time.time()) + ttl
39
- username = f"{timestamp}:{user}"
40
- password = base64.b64encode(
41
- hmac.new(secret_key.encode(), username.encode(), hashlib.sha1).digest()
42
- ).decode()
43
-
44
- coturn_config = {
45
- "iceServers": [
46
- {
47
- "urls": [
48
- f"{turn_url}",
49
- f"{turn_s_url}",
50
- ],
51
- "username": username,
52
- "credential": password,
53
- }
54
- ]
55
- }
56
- return coturn_config
57
-
58
-
59
-
60
- def read_and_stream_audio(filepath_to_stream: str, session_id: str, stop_streaming_flags: dict):
61
- """
62
- Read an audio file and stream it chunk by chunk (1s per chunk).
63
- Handles errors safely and reports structured messages to the client.
64
- """
65
- if not session_id:
66
- yield from handle_stream_error("unknown", "No session_id provided.", stop_streaming_flags)
67
- return
68
-
69
- if not filepath_to_stream or not os.path.exists(filepath_to_stream):
70
- yield from handle_stream_error(session_id, f"Audio file not found: {filepath_to_stream}", stop_streaming_flags)
71
- return
72
- transcribe_flag = get_active_task_flag_file(session_id)
73
- try:
74
- segment = AudioSegment.from_file(filepath_to_stream)
75
- chunk_duration_ms = 1000
76
- total_chunks = len(segment) // chunk_duration_ms + 1
77
- logging.info(f"[{session_id}] Starting audio streaming {filepath_to_stream} ({total_chunks} chunks).")
78
-
79
- for i, chunk in enumerate(segment[::chunk_duration_ms]):
80
-
81
-
82
- frame_rate = chunk.frame_rate
83
- samples = np.array(chunk.get_array_of_samples()).reshape(1, -1)
84
- progress = round(((i + 1) / total_chunks) * 100, 2)
85
- if _is_stop_requested(stop_streaming_flags):
86
- logging.info(f"[{session_id}] Stop signal received. Terminating stream.")
87
- yield ((frame_rate, samples), AdditionalOutputs({"stoped": True, "value": "STREAM_STOPED"} ) )
88
- break
89
-
90
- yield ((frame_rate, samples), AdditionalOutputs({"progressed": True, "value": progress} ))
91
- logging.debug(f"[{session_id}] Sent chunk {i+1}/{total_chunks} ({progress}%).")
92
-
93
- time.sleep(1)
94
- # Save only if transcription is active
95
- if os.path.exists(transcribe_flag) :
96
- chunk_dir = get_folder_chunks(session_id)
97
- if not os.path.exists(chunk_dir) :
98
- os.makedirs(chunk_dir, exist_ok=True)
99
- npz_path = os.path.join(chunk_dir, f"chunk_{i:05d}.npz")
100
- chunk_array = np.array(chunk.get_array_of_samples(), dtype=np.int16)
101
- np.savez_compressed(npz_path, data=chunk_array, rate=frame_rate)
102
- logging.debug(f"[{session_id}] Saved chunk {i}/{total_chunks} (transcribe active)")
103
-
104
- # raise_function() # Optional injected test exception
105
-
106
- logging.info(f"[{session_id}] Audio streaming completed successfully.")
107
-
108
- except asyncio.CancelledError:
109
- yield from handle_stream_error(session_id, "Streaming cancelled by user.", stop_streaming_flags)
110
- except FileNotFoundError as e:
111
- yield from handle_stream_error(session_id, e, stop_streaming_flags)
112
- except Exception as e:
113
- yield from handle_stream_error(session_id, e, stop_streaming_flags)
114
-
115
- finally:
116
- if isinstance(stop_streaming_flags, dict):
117
- stop_streaming_flags["stop"] = False
118
- logging.info(f"[{session_id}] Stop flag reset.")
119
-
120
-
121
-
122
- def handle_stream_error(session_id: str, error: Exception | str, stop_streaming_flags: dict | None = None):
123
- """
124
- Handle streaming errors:
125
- - Log the error
126
- - Send structured info to client
127
- - Reset stop flag
128
- """
129
- if isinstance(error, Exception):
130
- msg = f"{type(error).__name__}: {str(error)}"
131
- else:
132
- msg = str(error)
133
-
134
- logging.error(f"[{session_id}] Streaming error: {msg}", exc_info=isinstance(error, Exception))
135
-
136
- if isinstance(stop_streaming_flags, dict):
137
- stop_streaming_flags["stop"] = False
138
-
139
- yield ((16000,np.zeros(16000, dtype=np.float32).reshape(1, -1)), AdditionalOutputs({"errored": True, "value": msg}))
140
-
141
-
142
-
143
- def _is_stop_requested(stop_streaming_flags: dict) -> bool:
144
- """Check if the stop signal was requested."""
145
- if not isinstance(stop_streaming_flags, dict):
146
- return False
147
- return bool(stop_streaming_flags.get("stop", False))
148
-
149
-
150
- @spaces.GPU
151
- def task(session_id: str):
152
- """Continuously read and delete .npz chunks while task is active."""
153
- active_flag = get_active_task_flag_file(session_id)
154
- with open(active_flag, "w") as f:
155
- f.write("1")
156
- chunk_dir = get_folder_chunks(session_id)
157
- logging.info(f"[{session_id}] task started. {chunk_dir}")
158
-
159
-
160
- try:
161
- logging.info(f"[{session_id}] task loop started.")
162
- yield f"Task started for session {session_id}\n\n"
163
- while os.path.exists(active_flag):
164
- if not os.path.exists(chunk_dir):
165
- logging.warning(f"[{session_id}] No chunk directory found for task.")
166
- yield "No audio chunks yet... waiting for stream.\n"
167
- time.sleep(0.25)
168
- continue
169
- files = sorted(f for f in os.listdir(chunk_dir) if f.endswith(".npz"))
170
- if not files:
171
- time.sleep(0.25)
172
- continue
173
-
174
- for fname in files:
175
- fpath = os.path.join(chunk_dir, fname)
176
- try:
177
- npz = np.load(fpath)
178
- samples = npz["data"]
179
- rate = int(npz["rate"])
180
-
181
- text = f"Transcribed {fname}: {len(samples)} samples @ {rate}Hz"
182
- yield f"{text}\n"
183
- logging.debug(f"[{session_id}] {text}")
184
-
185
- os.remove(fpath)
186
- logging.debug(f"[{session_id}] Deleted processed chunk: {fname}")
187
- except Exception as e:
188
- logging.error(f"[{session_id}] Error processing {fname}: {e}")
189
- yield f"Error processing {fname}: {e}\n"
190
- continue
191
-
192
- time.sleep(0.25)
193
- # raise_function()
194
- yield "\nTask stopped by user or stream ended.\n"
195
- logging.info(f"[{session_id}] task loop ended (flag removed).")
196
-
197
- except Exception as e:
198
- logging.error(f"[{session_id}] task error: {e}", exc_info=True)
199
- yield f"Unexpected error: {e}\n"
200
- finally:
201
- # active_flag = os.path.join(TMP_DIR, f"transcribe_active_{session_id}.txt")
202
- if os.path.exists(active_flag):
203
- os.remove(active_flag)
204
- logging.info(f"[{session_id}] task stopped.")
205
- try:
206
- if os.path.exists(chunk_dir) and not os.listdir(chunk_dir):
207
- os.rmdir(chunk_dir)
208
- logging.debug(f"[{session_id}] Cleaned up empty chunk dir.")
209
- except Exception as e:
210
- logging.error(f"[{session_id}] Cleanup error: {e}")
211
- yield "\nCleanup error: {e}"
212
- logging.info(f"[{session_id}] Exiting task loop.")
213
- yield "\nTask finished and cleaned up.\n"
214
-
215
-
216
-
217
-
218
- def stop_streaming(session_id: str, stop_streaming_flags: dict):
219
- """Trigger the stop flag for active streaming."""
220
- logging.info(f"[{session_id}] Stop button clicked — sending stop signal.")
221
- if not isinstance(stop_streaming_flags, dict):
222
- stop_streaming_flags = {"stop": True}
223
- else:
224
- stop_streaming_flags["stop"] = True
225
- return stop_streaming_flags
226
-
227
-
228
- def raise_function():
229
- """Raise an error randomly (1 out of 10 times)."""
230
- if random.randint(1, 10) == 1:
231
- raise RuntimeError("Random failure triggered!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/utils.py CHANGED
@@ -1,6 +1,244 @@
1
- import torch
2
  from app.logger_config import logger as logging
 
 
 
 
 
 
 
 
 
 
 
 
3
  import random
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  def debug_current_device():
5
  """Safely logs GPU or CPU information without crashing on stateless GPU."""
6
  logging.debug("=== Debugging current device ===")
@@ -49,9 +287,6 @@ def get_current_device():
49
 
50
 
51
 
52
-
53
-
54
-
55
  def raise_function():
56
  """Raise an error randomly (1 out of 10 times)."""
57
  if random.randint(1, 50) == 1:
 
 
1
  from app.logger_config import logger as logging
2
+ from fastrtc.utils import AdditionalOutputs
3
+ from pydub import AudioSegment
4
+ import asyncio
5
+ import os
6
+ import time
7
+ import numpy as np
8
+ import spaces
9
+ import hmac
10
+ import hashlib
11
+ import base64
12
+ import os
13
+ import time
14
  import random
15
+
16
+ from app.session_utils import (
17
+ get_active_task_flag_file,
18
+ get_folder_chunks
19
+ )
20
+
21
+
22
+ # --------------------------------------------------------
23
+ # Utility functions
24
+ # --------------------------------------------------------
25
+ def generate_coturn_config():
26
+ """
27
+ Génère une configuration Coturn complète avec authentification dynamique (use-auth-secret).
28
+ Returns:
29
+ dict: Objet coturn_config prêt à être utilisé côté client WebRTC.
30
+ """
31
+
32
+ secret_key = os.getenv("TURN_SECRET_KEY", "your_secret_key")
33
+ ttl = int(os.getenv("TURN_TTL", 3600))
34
+ turn_url = os.getenv("TURN_URL", "turn:*******")
35
+ turn_s_url = os.getenv("TURN_S_URL", "turns:*****")
36
+ user = os.getenv("TURN_USER", "client")
37
+
38
+ timestamp = int(time.time()) + ttl
39
+ username = f"{timestamp}:{user}"
40
+ password = base64.b64encode(
41
+ hmac.new(secret_key.encode(), username.encode(), hashlib.sha1).digest()
42
+ ).decode()
43
+
44
+ coturn_config = {
45
+ "iceServers": [
46
+ {
47
+ "urls": [
48
+ f"{turn_url}",
49
+ f"{turn_s_url}",
50
+ ],
51
+ "username": username,
52
+ "credential": password,
53
+ }
54
+ ]
55
+ }
56
+ return coturn_config
57
+
58
+
59
+
60
+ def read_and_stream_audio(filepath_to_stream: str, session_id: str, stop_streaming_flags: dict):
61
+ """
62
+ Read an audio file and stream it chunk by chunk (1s per chunk).
63
+ Handles errors safely and reports structured messages to the client.
64
+ """
65
+ if not session_id:
66
+ yield from handle_stream_error("unknown", "No session_id provided.", stop_streaming_flags)
67
+ return
68
+
69
+ if not filepath_to_stream or not os.path.exists(filepath_to_stream):
70
+ yield from handle_stream_error(session_id, f"Audio file not found: {filepath_to_stream}", stop_streaming_flags)
71
+ return
72
+ transcribe_flag = get_active_task_flag_file(session_id)
73
+ try:
74
+ segment = AudioSegment.from_file(filepath_to_stream)
75
+ chunk_duration_ms = 1000
76
+ total_chunks = len(segment) // chunk_duration_ms + 1
77
+ logging.info(f"[{session_id}] Starting audio streaming {filepath_to_stream} ({total_chunks} chunks).")
78
+
79
+ for i, chunk in enumerate(segment[::chunk_duration_ms]):
80
+
81
+
82
+ frame_rate = chunk.frame_rate
83
+ samples = np.array(chunk.get_array_of_samples()).reshape(1, -1)
84
+ progress = round(((i + 1) / total_chunks) * 100, 2)
85
+ if _is_stop_requested(stop_streaming_flags):
86
+ logging.info(f"[{session_id}] Stop signal received. Terminating stream.")
87
+ yield ((frame_rate, samples), AdditionalOutputs({"stoped": True, "value": "STREAM_STOPED"} ) )
88
+ break
89
+
90
+ yield ((frame_rate, samples), AdditionalOutputs({"progressed": True, "value": progress} ))
91
+ logging.debug(f"[{session_id}] Sent chunk {i+1}/{total_chunks} ({progress}%).")
92
+
93
+ time.sleep(1)
94
+ # Save only if transcription is active
95
+ if os.path.exists(transcribe_flag) :
96
+ chunk_dir = get_folder_chunks(session_id)
97
+ if not os.path.exists(chunk_dir) :
98
+ os.makedirs(chunk_dir, exist_ok=True)
99
+ npz_path = os.path.join(chunk_dir, f"chunk_{i:05d}.npz")
100
+ chunk_array = np.array(chunk.get_array_of_samples(), dtype=np.int16)
101
+ np.savez_compressed(npz_path, data=chunk_array, rate=frame_rate)
102
+ logging.debug(f"[{session_id}] Saved chunk {i}/{total_chunks} (transcribe active)")
103
+
104
+ # raise_function() # Optional injected test exception
105
+
106
+ logging.info(f"[{session_id}] Audio streaming completed successfully.")
107
+
108
+ except asyncio.CancelledError:
109
+ yield from handle_stream_error(session_id, "Streaming cancelled by user.", stop_streaming_flags)
110
+ except FileNotFoundError as e:
111
+ yield from handle_stream_error(session_id, e, stop_streaming_flags)
112
+ except Exception as e:
113
+ yield from handle_stream_error(session_id, e, stop_streaming_flags)
114
+
115
+ finally:
116
+ if isinstance(stop_streaming_flags, dict):
117
+ stop_streaming_flags["stop"] = False
118
+ logging.info(f"[{session_id}] Stop flag reset.")
119
+
120
+
121
+
122
+ def handle_stream_error(session_id: str, error: Exception | str, stop_streaming_flags: dict | None = None):
123
+ """
124
+ Handle streaming errors:
125
+ - Log the error
126
+ - Send structured info to client
127
+ - Reset stop flag
128
+ """
129
+ if isinstance(error, Exception):
130
+ msg = f"{type(error).__name__}: {str(error)}"
131
+ else:
132
+ msg = str(error)
133
+
134
+ logging.error(f"[{session_id}] Streaming error: {msg}", exc_info=isinstance(error, Exception))
135
+
136
+ if isinstance(stop_streaming_flags, dict):
137
+ stop_streaming_flags["stop"] = False
138
+
139
+ yield ((16000,np.zeros(16000, dtype=np.float32).reshape(1, -1)), AdditionalOutputs({"errored": True, "value": msg}))
140
+
141
+
142
+
143
+ def _is_stop_requested(stop_streaming_flags: dict) -> bool:
144
+ """Check if the stop signal was requested."""
145
+ if not isinstance(stop_streaming_flags, dict):
146
+ return False
147
+ return bool(stop_streaming_flags.get("stop", False))
148
+
149
+ # --- Decorator compatibility layer ---
150
+ if os.environ.get("SPACE_ID", "").startswith("zero-gpu"):
151
+ logging.warning("Running on ZeroGPU — disabling @spaces.GPU")
152
+ def gpu_decorator(f): return f
153
+ else:
154
+ gpu_decorator = spaces.GPU
155
+
156
+ # --- Audio Stream Function ---
157
+ @gpu_decorator
158
+ def task(session_id: str):
159
+ """Continuously read and delete .npz chunks while task is active."""
160
+ active_flag = get_active_task_flag_file(session_id)
161
+ with open(active_flag, "w") as f:
162
+ f.write("1")
163
+ chunk_dir = get_folder_chunks(session_id)
164
+ logging.info(f"[{session_id}] task started. {chunk_dir}")
165
+
166
+
167
+ try:
168
+ logging.info(f"[{session_id}] task loop started.")
169
+ yield f"Task started for session {session_id}\n\n"
170
+ while os.path.exists(active_flag):
171
+ if not os.path.exists(chunk_dir):
172
+ logging.warning(f"[{session_id}] No chunk directory found for task.")
173
+ yield "No audio chunks yet... waiting for stream.\n"
174
+ time.sleep(0.25)
175
+ continue
176
+ files = sorted(f for f in os.listdir(chunk_dir) if f.endswith(".npz"))
177
+ if not files:
178
+ time.sleep(0.25)
179
+ continue
180
+
181
+ for fname in files:
182
+ fpath = os.path.join(chunk_dir, fname)
183
+ try:
184
+ npz = np.load(fpath)
185
+ samples = npz["data"]
186
+ rate = int(npz["rate"])
187
+
188
+ text = f"Transcribed {fname}: {len(samples)} samples @ {rate}Hz"
189
+ yield f"{text}\n"
190
+ logging.debug(f"[{session_id}] {text}")
191
+
192
+ os.remove(fpath)
193
+ logging.debug(f"[{session_id}] Deleted processed chunk: {fname}")
194
+ except Exception as e:
195
+ logging.error(f"[{session_id}] Error processing {fname}: {e}")
196
+ yield f"Error processing {fname}: {e}\n"
197
+ continue
198
+
199
+ time.sleep(0.25)
200
+ # raise_function()
201
+ yield "\nTask stopped by user or stream ended.\n"
202
+ logging.info(f"[{session_id}] task loop ended (flag removed).")
203
+
204
+ except Exception as e:
205
+ logging.error(f"[{session_id}] task error: {e}", exc_info=True)
206
+ yield f"Unexpected error: {e}\n"
207
+ finally:
208
+ # active_flag = os.path.join(TMP_DIR, f"transcribe_active_{session_id}.txt")
209
+ if os.path.exists(active_flag):
210
+ os.remove(active_flag)
211
+ logging.info(f"[{session_id}] task stopped.")
212
+ try:
213
+ if os.path.exists(chunk_dir) and not os.listdir(chunk_dir):
214
+ os.rmdir(chunk_dir)
215
+ logging.debug(f"[{session_id}] Cleaned up empty chunk dir.")
216
+ except Exception as e:
217
+ logging.error(f"[{session_id}] Cleanup error: {e}")
218
+ yield "\nCleanup error: {e}"
219
+ logging.info(f"[{session_id}] Exiting task loop.")
220
+ yield "\nTask finished and cleaned up.\n"
221
+
222
+
223
+
224
+
225
+ def stop_streaming(session_id: str, stop_streaming_flags: dict):
226
+ """Trigger the stop flag for active streaming."""
227
+ logging.info(f"[{session_id}] Stop button clicked — sending stop signal.")
228
+ if not isinstance(stop_streaming_flags, dict):
229
+ stop_streaming_flags = {"stop": True}
230
+ else:
231
+ stop_streaming_flags["stop"] = True
232
+ return stop_streaming_flags
233
+
234
+
235
+ def raise_function():
236
+ """Raise an error randomly (1 out of 10 times)."""
237
+ if random.randint(1, 10) == 1:
238
+ raise RuntimeError("Random failure triggered!")
239
+
240
+
241
+
242
  def debug_current_device():
243
  """Safely logs GPU or CPU information without crashing on stateless GPU."""
244
  logging.debug("=== Debugging current device ===")
 
287
 
288
 
289
 
 
 
 
290
  def raise_function():
291
  """Raise an error randomly (1 out of 10 times)."""
292
  if random.randint(1, 50) == 1: