Files
voice-chat-web/server.py

150 lines
4.4 KiB
Python

"""
语音交互网页后端
代理转发到 Qwen2-Audio 模型服务
"""
import os
import logging
from typing import Optional
from datetime import datetime
import aiohttp
from fastapi import FastAPI, UploadFile, File, HTTPException, Form
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
# 配置
MODEL_SERVICE_URL = os.getenv("MODEL_SERVICE_URL", "http://localhost:19018")
PORT = int(os.getenv("PORT", "19019"))
# 日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(
title="Voice Chat Web",
description="语音交互网页后端",
version="1.0.0"
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class VoiceResponse(BaseModel):
"""语音响应"""
reply: str
conversation_id: str
timestamp: str
class StatusResponse(BaseModel):
"""状态响应"""
status: str
model_service: str
model_online: bool
@app.get("/")
async def root():
"""根路径返回状态"""
return {"status": "ok", "service": "voice-chat-web"}
@app.get("/status", response_model=StatusResponse)
async def get_status():
"""检查服务状态"""
try:
async with aiohttp.ClientSession() as session:
async with session.get(f"{MODEL_SERVICE_URL}/", timeout=aiohttp.ClientTimeout(total=5)) as resp:
if resp.status == 200:
data = await resp.json()
return StatusResponse(
status="ok",
model_service=MODEL_SERVICE_URL,
model_online=True
)
except Exception as e:
logger.warning(f"Model service check failed: {e}")
return StatusResponse(
status="partial",
model_service=MODEL_SERVICE_URL,
model_online=False
)
@app.post("/voice/chat", response_model=VoiceResponse)
async def voice_chat(
audio: UploadFile = File(..., description="音频文件"),
conversation_id: Optional[str] = Form(None, description="对话ID")
):
"""
语音聊天接口
转发到模型服务
"""
try:
# 读取音频数据
audio_bytes = await audio.read()
# 转发到模型服务
async with aiohttp.ClientSession() as session:
form = aiohttp.FormData()
form.add_field(
'audio',
audio_bytes,
filename=audio.filename or 'audio.wav',
content_type=audio.content_type or 'audio/wav'
)
if conversation_id:
form.add_field('conversation_id', conversation_id)
async with session.post(
f"{MODEL_SERVICE_URL}/api/voice/inference",
data=form,
timeout=aiohttp.ClientTimeout(total=120) # 模型推理可能较慢
) as resp:
if resp.status != 200:
error_text = await resp.text()
logger.error(f"Model service error: {error_text}")
raise HTTPException(status_code=resp.status, detail=error_text)
data = await resp.json()
return VoiceResponse(
reply=data["reply"],
conversation_id=data["conversation_id"],
timestamp=data.get("timestamp", datetime.now().isoformat())
)
except aiohttp.ClientError as e:
logger.error(f"Connection error: {e}")
raise HTTPException(status_code=503, detail="模型服务连接失败")
except Exception as e:
logger.error(f"Voice chat error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/conversation/{conversation_id}")
async def delete_conversation(conversation_id: str):
"""删除对话"""
try:
async with aiohttp.ClientSession() as session:
async with session.delete(
f"{MODEL_SERVICE_URL}/api/voice/conversation/{conversation_id}",
timeout=aiohttp.ClientTimeout(total=10)
) as resp:
return await resp.json()
except Exception as e:
logger.error(f"Delete conversation error: {e}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=PORT)