Spaces:
Running
Running
xieli commited on
Commit ·
463162b
1
Parent(s): 501ca3e
feat: fix
Browse files- stepfun_api.py +129 -40
stepfun_api.py
CHANGED
|
@@ -159,7 +159,16 @@ class StepFunAPIClient:
|
|
| 159 |
Raises:
|
| 160 |
Exception: 如果转录失败
|
| 161 |
"""
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
# 读取音频文件并转换为base64
|
| 165 |
try:
|
|
@@ -167,13 +176,9 @@ class StepFunAPIClient:
|
|
| 167 |
audio_data = base64.b64encode(audio_file.read()).decode('utf-8')
|
| 168 |
except Exception as e:
|
| 169 |
logger.error(f"❌ Failed to read audio file: {e}")
|
| 170 |
-
|
| 171 |
-
yield f"[读取文件失败: {e}]"
|
| 172 |
-
return
|
| 173 |
-
else:
|
| 174 |
-
raise Exception(f"Failed to read audio file: {e}")
|
| 175 |
|
| 176 |
-
# 构建请求payload
|
| 177 |
payload = {
|
| 178 |
"audio": {
|
| 179 |
"data": audio_data,
|
|
@@ -229,66 +234,150 @@ class StepFunAPIClient:
|
|
| 229 |
accumulated_text += delta
|
| 230 |
logger.debug(f"📝 ASR delta: {delta}")
|
| 231 |
|
| 232 |
-
# 处理增量更新
|
| 233 |
-
if
|
| 234 |
-
yield accumulated_text
|
| 235 |
-
elif progress_callback:
|
| 236 |
progress_callback(accumulated_text)
|
| 237 |
|
| 238 |
elif event_type == "transcript.text.done":
|
| 239 |
final_text = event_data.get("text", accumulated_text)
|
| 240 |
logger.info(f"✅ ASR transcription complete: {final_text}")
|
| 241 |
-
|
| 242 |
-
if streaming:
|
| 243 |
-
yield final_text
|
| 244 |
-
return
|
| 245 |
-
else:
|
| 246 |
-
break
|
| 247 |
|
| 248 |
elif event_type == "error":
|
| 249 |
error_msg = event_data.get("message", "Unknown ASR error")
|
| 250 |
logger.error(f"❌ ASR error: {error_msg}")
|
| 251 |
-
|
| 252 |
-
if streaming:
|
| 253 |
-
yield f"[ASR错误: {error_msg}]"
|
| 254 |
-
return
|
| 255 |
-
else:
|
| 256 |
-
raise Exception(f"ASR API error: {error_msg}")
|
| 257 |
|
| 258 |
except json.JSONDecodeError as e:
|
| 259 |
logger.warning(f"⚠️ Failed to parse SSE line: {line}, error: {e}")
|
| 260 |
continue
|
| 261 |
except Exception as e:
|
| 262 |
logger.error(f"❌ Error processing SSE event: {e}")
|
| 263 |
-
|
| 264 |
-
yield f"[处理错误: {str(e)}]"
|
| 265 |
-
return
|
| 266 |
-
else:
|
| 267 |
-
raise
|
| 268 |
|
| 269 |
# 如果没有获得final_text,使用accumulated_text
|
| 270 |
if not final_text:
|
| 271 |
final_text = accumulated_text
|
| 272 |
|
| 273 |
if not final_text:
|
| 274 |
-
|
| 275 |
-
if streaming:
|
| 276 |
-
yield f"[转录无结果]"
|
| 277 |
-
return
|
| 278 |
-
else:
|
| 279 |
-
raise Exception(error_msg)
|
| 280 |
|
| 281 |
logger.info(f"🎯 Final transcription: {final_text}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
except Exception as e:
|
| 287 |
logger.error(f"❌ ASR transcription failed: {e}")
|
| 288 |
-
|
| 289 |
-
yield f"[转录失败: {str(e)}]"
|
| 290 |
-
else:
|
| 291 |
-
raise Exception(f"ASR transcription failed: {e}")
|
| 292 |
|
| 293 |
|
| 294 |
# Global API client instance (singleton)
|
|
|
|
| 159 |
Raises:
|
| 160 |
Exception: 如果转录失败
|
| 161 |
"""
|
| 162 |
+
if streaming:
|
| 163 |
+
return self._transcribe_audio_sse_streaming(audio_path, progress_callback)
|
| 164 |
+
else:
|
| 165 |
+
return self._transcribe_audio_sse_sync(audio_path, progress_callback)
|
| 166 |
+
|
| 167 |
+
def _transcribe_audio_sse_sync(self, audio_path: str, progress_callback=None) -> str:
|
| 168 |
+
"""
|
| 169 |
+
同步ASR转录,返回最终文本
|
| 170 |
+
"""
|
| 171 |
+
url = f"{API_BASE_URL}/audio/asr"
|
| 172 |
|
| 173 |
# 读取音频文件并转换为base64
|
| 174 |
try:
|
|
|
|
| 176 |
audio_data = base64.b64encode(audio_file.read()).decode('utf-8')
|
| 177 |
except Exception as e:
|
| 178 |
logger.error(f"❌ Failed to read audio file: {e}")
|
| 179 |
+
raise Exception(f"Failed to read audio file: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
+
# 构建请求payload
|
| 182 |
payload = {
|
| 183 |
"audio": {
|
| 184 |
"data": audio_data,
|
|
|
|
| 234 |
accumulated_text += delta
|
| 235 |
logger.debug(f"📝 ASR delta: {delta}")
|
| 236 |
|
| 237 |
+
# 处理增量更新回调
|
| 238 |
+
if progress_callback:
|
|
|
|
|
|
|
| 239 |
progress_callback(accumulated_text)
|
| 240 |
|
| 241 |
elif event_type == "transcript.text.done":
|
| 242 |
final_text = event_data.get("text", accumulated_text)
|
| 243 |
logger.info(f"✅ ASR transcription complete: {final_text}")
|
| 244 |
+
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
|
| 246 |
elif event_type == "error":
|
| 247 |
error_msg = event_data.get("message", "Unknown ASR error")
|
| 248 |
logger.error(f"❌ ASR error: {error_msg}")
|
| 249 |
+
raise Exception(f"ASR API error: {error_msg}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
|
| 251 |
except json.JSONDecodeError as e:
|
| 252 |
logger.warning(f"⚠️ Failed to parse SSE line: {line}, error: {e}")
|
| 253 |
continue
|
| 254 |
except Exception as e:
|
| 255 |
logger.error(f"❌ Error processing SSE event: {e}")
|
| 256 |
+
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
# 如果没有获得final_text,使用accumulated_text
|
| 259 |
if not final_text:
|
| 260 |
final_text = accumulated_text
|
| 261 |
|
| 262 |
if not final_text:
|
| 263 |
+
raise Exception("No transcription result received")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
|
| 265 |
logger.info(f"🎯 Final transcription: {final_text}")
|
| 266 |
+
return final_text
|
| 267 |
+
|
| 268 |
+
except Exception as e:
|
| 269 |
+
logger.error(f"❌ ASR transcription failed: {e}")
|
| 270 |
+
raise Exception(f"ASR transcription failed: {e}")
|
| 271 |
+
|
| 272 |
+
def _transcribe_audio_sse_streaming(self, audio_path: str, progress_callback=None):
|
| 273 |
+
"""
|
| 274 |
+
流式ASR转录,返回生成器
|
| 275 |
+
"""
|
| 276 |
+
url = f"{API_BASE_URL}/audio/asr"
|
| 277 |
+
|
| 278 |
+
# 读取音频文件并转换为base64
|
| 279 |
+
try:
|
| 280 |
+
with open(audio_path, 'rb') as audio_file:
|
| 281 |
+
audio_data = base64.b64encode(audio_file.read()).decode('utf-8')
|
| 282 |
+
except Exception as e:
|
| 283 |
+
logger.error(f"❌ Failed to read audio file: {e}")
|
| 284 |
+
yield f"[读取文件失败: {e}]"
|
| 285 |
+
return
|
| 286 |
+
|
| 287 |
+
# 构建请求payload
|
| 288 |
+
payload = {
|
| 289 |
+
"audio": {
|
| 290 |
+
"data": audio_data,
|
| 291 |
+
"input": {
|
| 292 |
+
"transcription": {
|
| 293 |
+
"language": "zh",
|
| 294 |
+
"prompt": "请记录下你所听到的语音内容。",
|
| 295 |
+
"model": "step-asr",
|
| 296 |
+
"full_rerun_on_commit": True,
|
| 297 |
+
"enable_itn": True
|
| 298 |
+
},
|
| 299 |
+
"format": {
|
| 300 |
+
"type": "pcm",
|
| 301 |
+
"codec": "pcm_s16le",
|
| 302 |
+
"rate": 16000,
|
| 303 |
+
"bits": 16,
|
| 304 |
+
"channel": 1
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
headers = {
|
| 311 |
+
**self.base_headers,
|
| 312 |
+
"Content-Type": "application/json",
|
| 313 |
+
"Accept": "text/event-stream"
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
logger.info("🎙️ Starting ASR transcription...")
|
| 317 |
+
|
| 318 |
+
try:
|
| 319 |
+
response = requests.post(url, headers=headers, data=json.dumps(payload), stream=True, timeout=120)
|
| 320 |
+
response.raise_for_status()
|
| 321 |
+
|
| 322 |
+
final_text = ""
|
| 323 |
+
accumulated_text = ""
|
| 324 |
|
| 325 |
+
for line in response.iter_lines(decode_unicode=True):
|
| 326 |
+
if line:
|
| 327 |
+
line = line.strip()
|
| 328 |
+
if line.startswith("data: "):
|
| 329 |
+
try:
|
| 330 |
+
# 解析SSE数据
|
| 331 |
+
data_str = line[6:] # 去掉 "data: " 前缀
|
| 332 |
+
if data_str == "[DONE]":
|
| 333 |
+
break
|
| 334 |
+
|
| 335 |
+
event_data = json.loads(data_str)
|
| 336 |
+
event_type = event_data.get("type")
|
| 337 |
+
|
| 338 |
+
if event_type == "transcript.text.delta":
|
| 339 |
+
delta = event_data.get("delta", "")
|
| 340 |
+
accumulated_text += delta
|
| 341 |
+
logger.debug(f"📝 ASR delta: {delta}")
|
| 342 |
+
|
| 343 |
+
# 流式更新
|
| 344 |
+
yield accumulated_text
|
| 345 |
+
if progress_callback:
|
| 346 |
+
progress_callback(accumulated_text)
|
| 347 |
+
|
| 348 |
+
elif event_type == "transcript.text.done":
|
| 349 |
+
final_text = event_data.get("text", accumulated_text)
|
| 350 |
+
logger.info(f"✅ ASR transcription complete: {final_text}")
|
| 351 |
+
yield final_text
|
| 352 |
+
return
|
| 353 |
+
|
| 354 |
+
elif event_type == "error":
|
| 355 |
+
error_msg = event_data.get("message", "Unknown ASR error")
|
| 356 |
+
logger.error(f"❌ ASR error: {error_msg}")
|
| 357 |
+
yield f"[ASR错误: {error_msg}]"
|
| 358 |
+
return
|
| 359 |
+
|
| 360 |
+
except json.JSONDecodeError as e:
|
| 361 |
+
logger.warning(f"⚠️ Failed to parse SSE line: {line}, error: {e}")
|
| 362 |
+
continue
|
| 363 |
+
except Exception as e:
|
| 364 |
+
logger.error(f"❌ Error processing SSE event: {e}")
|
| 365 |
+
yield f"[处理错误: {str(e)}]"
|
| 366 |
+
return
|
| 367 |
+
|
| 368 |
+
# 如果没有获得final_text,使用accumulated_text
|
| 369 |
+
if not final_text:
|
| 370 |
+
final_text = accumulated_text
|
| 371 |
+
|
| 372 |
+
if not final_text:
|
| 373 |
+
yield f"[转录无结果]"
|
| 374 |
+
return
|
| 375 |
+
|
| 376 |
+
logger.info(f"🎯 Final transcription: {final_text}")
|
| 377 |
|
| 378 |
except Exception as e:
|
| 379 |
logger.error(f"❌ ASR transcription failed: {e}")
|
| 380 |
+
yield f"[转录失败: {str(e)}]"
|
|
|
|
|
|
|
|
|
|
| 381 |
|
| 382 |
|
| 383 |
# Global API client instance (singleton)
|