Files
jarvis-models/server.py
2025-07-30 18:13:10 +08:00

136 lines
5.8 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.

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
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
injector = Injector()
blackbox_factory = injector.get(BlackboxFactory)
logger = LokiLogger(
tags={"application": "Server", "env": "Development", "source": "python-fastapi-app"},
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.get("/trigger-error")
async def trigger_error():
"""
这个端点会故意抛出一个异常来测试全局异常处理器。
"""
logger.info("尝试触发错误...")
raise ValueError("这是一个测试错误,用于演示全局异常处理。")
@app.post("/")
@app.get("/")
async def blackbox(blackbox_name: Union[str, None] = None, request: Request = None):
if not blackbox_name:
return await JSONResponse(content={"error": "blackbox_name is required"}, status_code=status.HTTP_400_BAD_REQUEST)
try:
box = blackbox_factory.get_blackbox(blackbox_name)
except ValueError:
return await 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:
response_content_bytes = await response.body()
decoded_content = response_content_bytes.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 handler 内部抛出异常: URL={request.url}, Method={request.method}, 异常: {e}", exc_info=True)
raise e
# @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)