Files
xiaozhi-esp32-server/main/xiaozhi-server/core/api/vision_handler.py
T

122 lines
4.6 KiB
Python

import json
from aiohttp import web
from config.logger import setup_logging
from core.utils.util import get_vision_url, is_valid_image_file
from core.utils.vllm import create_instance
import base64
TAG = __name__
# 设置最大文件大小为5MB
MAX_FILE_SIZE = 5 * 1024 * 1024
class VisionHandler:
def __init__(self, config: dict):
self.config = config
self.logger = setup_logging()
def _create_error_response(self, message: str) -> dict:
"""创建统一的错误响应格式"""
return {"success": False, "message": message}
async def handle_post(self, request):
"""处理 MCP Vision POST 请求"""
try:
# 获取请求头信息
device_id = request.headers.get("Device-Id", "")
client_id = request.headers.get("Client-Id", "")
self.logger.bind(tag=TAG).debug(f"Device-Id: {device_id}")
self.logger.bind(tag=TAG).debug(f"Client-Id: {client_id}")
# 解析multipart/form-data请求
reader = await request.multipart()
# 读取question字段
question_field = await reader.next()
if question_field is None:
raise ValueError("缺少问题字段")
question = await question_field.text()
self.logger.bind(tag=TAG).debug(f"Question: {question}")
# 读取图片文件
image_field = await reader.next()
if image_field is None:
raise ValueError("缺少图片文件")
# 读取图片数据
image_data = await image_field.read()
if not image_data:
raise ValueError("图片数据为空")
# 检查文件大小
if len(image_data) > MAX_FILE_SIZE:
raise ValueError(
f"图片大小超过限制,最大允许{MAX_FILE_SIZE/1024/1024}MB"
)
# 检查文件格式
if not is_valid_image_file(image_data):
raise ValueError(
"不支持的文件格式,请上传有效的图片文件(支持JPEG、PNG、GIF、BMP、TIFF、WEBP格式)"
)
# 将图片转换为base64编码
image_base64 = base64.b64encode(image_data).decode("utf-8")
vllm_config = self.config["VLLM"]["ChatGLMVLLM"]
provider = create_instance("openai", vllm_config)
response = provider.response(question, image_base64)
return_json = {
"success": True,
"result": response,
}
response = web.Response(
text=json.dumps(return_json, separators=(",", ":")),
content_type="application/json",
)
except ValueError as e:
self.logger.bind(tag=TAG).error(f"MCP Vision POST请求异常: {e}")
return_json = self._create_error_response(str(e))
response = web.Response(
text=json.dumps(return_json, separators=(",", ":")),
content_type="application/json",
)
except Exception as e:
self.logger.bind(tag=TAG).error(f"MCP Vision POST请求异常: {e}")
return_json = self._create_error_response("处理请求时发生错误")
response = web.Response(
text=json.dumps(return_json, separators=(",", ":")),
content_type="application/json",
)
finally:
self._add_cors_headers(response)
return response
async def handle_get(self, request):
"""处理 MCP Vision GET 请求"""
try:
vision_explain = get_vision_url(self.config)
message = f"MCP Vision 接口运行正常,视觉解释接口地址是:{vision_explain}"
response = web.Response(text=message, content_type="text/plain")
except Exception as e:
self.logger.bind(tag=TAG).error(f"MCP Vision GET请求异常: {e}")
return_json = self._create_error_response("服务器内部错误")
response = web.Response(
text=json.dumps(return_json, separators=(",", ":")),
content_type="application/json",
)
finally:
self._add_cors_headers(response)
return response
def _add_cors_headers(self, response):
"""添加CORS头信息"""
response.headers["Access-Control-Allow-Headers"] = (
"client-id, content-type, device-id"
)
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Origin"] = "*"