feat: 模型视觉多模态支持

This commit is contained in:
RockChinQ
2024-05-15 21:40:18 +08:00
parent 8807f02f36
commit d5b5d667a5
32 changed files with 596 additions and 72 deletions
View File
+85
View File
@@ -0,0 +1,85 @@
from __future__ import annotations
import aiohttp
import typing
from urllib.parse import urlparse, parse_qs
import ssl
from . import service as osssv
from ..core import app
from .services import aliyun
class OSSServiceManager:
ap: app.Application
service: osssv.OSSService = None
def __init__(self, ap: app.Application):
self.ap = ap
async def initialize(self):
"""初始化
"""
mapping = {}
for svcls in osssv.preregistered_services:
mapping[svcls.name] = svcls
for sv in self.ap.system_cfg.data['oss']:
if sv['enable']:
if sv['type'] not in mapping:
raise Exception(f"未知的OSS服务类型: {sv['type']}")
self.service = mapping[sv['type']](self.ap, sv)
await self.service.initialize()
break
def available(self) -> bool:
"""是否可用
Returns:
bool: 是否可用
"""
return self.service is not None
async def fetch_image(self, image_url: str) -> bytes:
parsed = urlparse(image_url)
query = parse_qs(parsed.query)
# Flatten the query dictionary
query = {k: v[0] for k, v in query.items()}
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
async with aiohttp.ClientSession(trust_env=False) as session:
async with session.get(
f"http://{parsed.netloc}{parsed.path}",
params=query,
ssl=ssl_context
) as resp:
resp.raise_for_status() # 检查HTTP错误
file_bytes = await resp.read()
return file_bytes
async def upload_url_image(
self,
image_url: str,
) -> str:
"""上传URL图片
Args:
image_url (str): 图片URL
Returns:
str: 文件URL
"""
file_bytes = await self.fetch_image(image_url)
return await self.service.upload(file_bytes=file_bytes, ext=".jpg")
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
import typing
import abc
from ..core import app
preregistered_services: list[typing.Type[OSSService]] = []
def service_class(
name: str
) -> typing.Callable[[typing.Type[OSSService]], typing.Type[OSSService]]:
"""OSS服务类装饰器
Args:
name (str): 服务名称
Returns:
typing.Callable[[typing.Type[OSSService]], typing.Type[OSSService]]: 装饰器
"""
def decorator(cls: typing.Type[OSSService]) -> typing.Type[OSSService]:
assert issubclass(cls, OSSService)
cls.name = name
preregistered_services.append(cls)
return cls
return decorator
class OSSService(metaclass=abc.ABCMeta):
"""OSS抽象类"""
name: str
ap: app.Application
cfg: dict
def __init__(self, ap: app.Application, cfg: dict) -> None:
self.ap = ap
self.cfg = cfg
async def initialize(self):
pass
@abc.abstractmethod
async def upload(
self,
local_file: str=None,
file_bytes: bytes=None,
ext: str=None,
) -> str:
"""上传文件
Args:
local_file (str, optional): 本地文件路径. Defaults to None.
file_bytes (bytes, optional): 文件字节. Defaults to None.
ext (str, optional): 文件扩展名. Defaults to None.
Returns:
str: 文件URL
"""
pass
View File
+48
View File
@@ -0,0 +1,48 @@
from __future__ import annotations
import uuid
import oss2
from .. import service as osssv
@osssv.service_class('aliyun')
class AliyunOSSService(osssv.OSSService):
"""阿里云OSS服务"""
auth: oss2.Auth
bucket: oss2.Bucket
async def initialize(self):
self.auth = oss2.Auth(
self.cfg['access-key-id'],
self.cfg['access-key-secret']
)
self.bucket = oss2.Bucket(
self.auth,
self.cfg['endpoint'],
self.cfg['bucket']
)
async def upload(
self,
local_file: str=None,
file_bytes: bytes=None,
ext: str=None,
) -> str:
if local_file is not None:
with open(local_file, 'rb') as f:
file_bytes = f.read()
if file_bytes is None:
raise Exception("缺少文件内容")
name = str(uuid.uuid1())
key = f"{self.cfg['prefix']}/{name}{ext}"
self.bucket.put_object(key, file_bytes)
return f"{self.cfg['public-read-base-url']}/{key}"