Files
jarvis-models/server.py
2025-08-01 17:39:00 +08:00

139 lines
5.9 KiB
Python
Raw Permalink 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.

from typing import Union
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse, Response, StreamingResponse
from src.blackbox.blackbox_factory import BlackboxFactory
from src.log.loki_config import LokiLogger
import logging
from fastapi.middleware.cors import CORSMiddleware
from injector import Injector
import yaml
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
injector = Injector()
blackbox_factory = injector.get(BlackboxFactory)
with open(".env.yaml", "r") as f:
config = yaml.safe_load(f)
logger = LokiLogger(
# url=config["loki"]["url"],
# username=config["loki"]["username"],
# password=config["loki"]["password"],
tags=config["loki"]["labels"],
logger_name=__name__,
level=logging.DEBUG
).get_logger()
@app.exception_handler(Exception)
async def catch_all_exceptions(request: Request, exc: Exception):
"""
捕获所有在 ASGI 应用中未被处理的异常,并记录到 Loki。
"""
logger.error(
f"Unhandled exception in ASGI application for path: {request.url.path}, method: {request.method} - {exc}",
exc_info=True, # 必须为 True才能获取完整的堆栈信息
extra={
"path": request.url.path,
"method": request.method,
"error_type": type(exc).__name__,
"error_message": str(exc)
}
)
return JSONResponse(
status_code=500,
content={"message": "Internal Server Error", "detail": "An unexpected error occurred."}
)
@app.post("/")
@app.get("/")
async def blackbox(blackbox_name: Union[str, None] = None, request: Request = None):
if not blackbox_name:
return JSONResponse(content={"error": "blackbox_name is required"}, status_code=status.HTTP_400_BAD_REQUEST)
try:
box = blackbox_factory.get_blackbox(blackbox_name)
except ValueError as e:
logger.error(f"获取 blackbox 失败: {blackbox_name} - {e}", exc_info=True)
return JSONResponse(content={"error": "value error"}, status_code=status.HTTP_400_BAD_REQUEST)
try:
response = await box.fast_api_handler(request)
# 检查响应的状态码,如果 >= 400则认为是错误响应并记录
if response.status_code >= 400:
try:
decoded_content = response.body.decode('utf-8', errors='ignore')
logger.error(f"Blackbox 返回错误响应: URL={request.url}, Method={request.method}, Status={response.status_code}, Content={decoded_content}")
except Exception as body_exc:
logger.error(f"Blackbox {blackbox_name} 返回错误响应: URL={request.url}, Method={request.method}, Status={response.status_code}, 无法解析响应内容: {body_exc}")
return response
except Exception as e:
logger.error(f"Blackbox 内部抛出异常: URL={request.url}, Method={request.method}, 异常报错: {type(e).__name__}: {e}", exc_info=True)
return JSONResponse(
status_code=500,
content={"message": "Internal Server Error", "detail": "An unexpected error occurred."}
)
# @app.get("/audio/{filename}")
# async def serve_audio(filename: str):
# import os
# # 确保文件存在
# if os.path.exists(filename):
# with open(filename, 'rb') as f:
# audio_data = f.read()
# if filename.endswith(".mp3"):
# # 对于 MP3 文件,设置为 inline 以在浏览器中播放
# return Response(content=audio_data, media_type="audio/mpeg", headers={"Content-Disposition": f"inline; filename={filename}"})
# elif filename.endswith(".wav"):
# # 对于 WAV 文件,设置为 inline 以在浏览器中播放
# return Response(content=audio_data, media_type="audio/wav", headers={"Content-Disposition": f"inline; filename={filename}"})
# else:
# return JSONResponse(content={"error": f"{filename} not found"}, status_code=status.HTTP_404_NOT_FOUND)
@app.get("/audio/audio_files/{filename}")
async def serve_audio(filename: str):
import os
import aiofiles
filename = os.path.join("audio_files", filename)
# 确保文件存在
if os.path.exists(filename):
try:
# 使用 aiofiles 异步读取文件
async with aiofiles.open(filename, 'rb') as f:
audio_data = await f.read()
# 根据文件类型返回正确的音频响应
if filename.endswith(".mp3"):
return Response(content=audio_data, media_type="audio/mpeg", headers={"Content-Disposition": f"inline; filename={filename}"})
elif filename.endswith(".wav"):
return Response(content=audio_data, media_type="audio/wav", headers={"Content-Disposition": f"inline; filename={filename}"})
else:
return JSONResponse(content={"error": "Unsupported audio format"}, status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
except asyncio.CancelledError:
# 处理任务被取消的情况
return JSONResponse(content={"error": "Request was cancelled"}, status_code=status.HTTP_400_BAD_REQUEST)
except Exception as e:
# 捕获其他异常
return JSONResponse(content={"error": str(e)}, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
else:
return JSONResponse(content={"error": f"{filename} not found"}, status_code=status.HTTP_404_NOT_FOUND)
# @app.get("/audio/{filename}")
# async def serve_audio(filename: str):
# import os
# file_path = f"audio/{filename}"
# # 检查文件是否存在
# if os.path.exists(file_path):
# return StreamingResponse(open(file_path, "rb"), media_type="audio/mpeg" if filename.endswith(".mp3") else "audio/wav")
# else:
# return JSONResponse(content={"error": f"{filename} not found"}, status_code=404)