mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-17 09:56:06 +00:00
feat: 模型视觉多模态支持
This commit is contained in:
@@ -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")
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
Reference in New Issue
Block a user