sales-assistant-py-new/app/asr/asr_client.py

137 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
import logging
from typing import Optional
import httpx
from app.conf.app_config import ASRConfig, app_config
logger = logging.getLogger(__name__)
class Qwen3ASRClient:
def __init__(self, config: ASRConfig):
self.config = config
self._session: Optional[httpx.AsyncClient] = None
@property
def session(self) -> httpx.AsyncClient:
if self._session is None:
timeout = httpx.Timeout(120.0, connect=10.0, read=60.0)
headers = {}
if self.config.api_key and self.config.api_key.strip():
headers["Authorization"] = f"Bearer {self.config.api_key.strip()}"
self._session = httpx.AsyncClient(
timeout=timeout,
follow_redirects=True,
headers=headers
)
return self._session
def _get_submit_url(self) -> str:
return f"{self.config.base_url}/api/v1/services/audio/asr/transcription"
def _get_task_url(self, task_id: str) -> str:
return f"{self.config.base_url}/api/v1/tasks/{task_id}"
async def _submit_task(self, audio_url: str) -> str:
url = self._get_submit_url()
payload = {
"model": "qwen3-asr-flash-filetrans",
"input": {
"file_url": audio_url
},
"parameters": {
"enable_itn": False
}
}
headers = {
"Content-Type": "application/json",
"X-DashScope-Async": "enable"
}
response = await self.session.post(url, json=payload, headers=headers)
if response.status_code >= 400:
logger.error(f"ASR任务提交失败状态码: {response.status_code}, 响应: {response.text}")
response.raise_for_status()
result = response.json()
if "output" in result and "task_id" in result["output"]:
task_id = result["output"]["task_id"]
logger.info(f"ASR任务提交成功task_id: {task_id}")
return task_id
else:
logger.error(f"ASR任务提交失败响应: {result}")
raise ValueError(f"Failed to submit ASR task: {result}")
async def _poll_task_result(self, task_id: str, max_attempts: int = 60, interval: float = 2.0) -> str:
url = self._get_task_url(task_id)
for attempt in range(max_attempts):
response = await self.session.get(url)
response.raise_for_status()
result = response.json()
task_status = result.get("output", {}).get("task_status", "")
if task_status == "SUCCEEDED":
logger.info(f"ASR任务完成task_id: {task_id}")
output = result.get("output", {})
result_data = output.get("result", {})
transcription_url = result_data.get("transcription_url", "")
if transcription_url:
logger.info(f"下载识别结果: {transcription_url}")
trans_response = await self.session.get(transcription_url)
trans_response.raise_for_status()
trans_data = trans_response.json()
texts = []
if "transcripts" in trans_data:
for item in trans_data["transcripts"]:
if "text" in item:
texts.append(item["text"])
elif "text" in trans_data:
texts.append(trans_data["text"])
text = "\n".join(texts).strip()
else:
text = str(output)
logger.info(f"识别结果长度: {len(text)}")
return text
elif task_status in ("FAILED", "UNKNOWN"):
error_msg = result.get("output", {}).get("message", f"Task {task_status}")
logger.error(f"ASR任务失败task_id: {task_id}, 错误: {error_msg}")
raise RuntimeError(f"ASR task failed: {error_msg}")
logger.debug(f"ASR任务进行中task_id: {task_id}, 状态: {task_status}, 第{attempt + 1}次轮询")
await asyncio.sleep(interval)
raise TimeoutError(f"ASR task timed out after {max_attempts} attempts")
async def recognize_from_url_direct(self, audio_url: str) -> str:
logger.info(f"开始ASR URL直传识别URL: {audio_url}")
try:
task_id = await self._submit_task(audio_url)
result = await self._poll_task_result(task_id)
return result
except Exception as e:
logger.error(f"ASR URL直传识别失败: {str(e)}", exc_info=True)
raise
async def recognize(self, audio_data: bytes, format: str = "wav", **kwargs) -> str:
raise NotImplementedError("Audio data upload not implemented yet, use recognize_from_url_direct")
async def recognize_from_file(self, file_path: str) -> str:
raise NotImplementedError("Local file upload not implemented yet, use recognize_from_url_direct")
async def close(self):
if self._session:
await self._session.aclose()
self._session = None
asr_client = Qwen3ASRClient(app_config.asr)