mirror of
https://github.com/LiteyukiStudio/nonebot-plugin-marshoai.git
synced 2024-11-23 01:27:38 +08:00
✨ 添加Marsho AI插件,整合Azure服务,更新配置和元数据,修复startup加载两次的问题
This commit is contained in:
parent
9dc5951079
commit
8ac81e358f
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,6 +3,9 @@ __pycache__/
|
|||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
|
#nonebot dev
|
||||||
|
.env
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
|
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"python.testing.pytestArgs": [
|
||||||
|
"."
|
||||||
|
],
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true
|
||||||
|
}
|
@ -1,23 +1,15 @@
|
|||||||
from nonebot.plugin import PluginMetadata, inherit_supported_adapters, require
|
from nonebot.plugin import PluginMetadata, inherit_supported_adapters, require
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
require("nonebot_plugin_localstore")
|
require("nonebot_plugin_localstore")
|
||||||
from .azure import *
|
from .azure import *
|
||||||
from nonebot import get_driver, logger
|
from nonebot import get_driver, logger
|
||||||
from .config import ConfigModel, config
|
from .config import config
|
||||||
from .constants import USAGE
|
from .metadata import metadata
|
||||||
import nonebot_plugin_localstore as store
|
import nonebot_plugin_localstore as store
|
||||||
|
|
||||||
__author__ = "Asankilp"
|
__author__ = "Asankilp"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = metadata
|
||||||
name="Marsho AI插件",
|
|
||||||
description="接入Azure服务的AI聊天插件",
|
|
||||||
usage=USAGE,
|
|
||||||
type="application",
|
|
||||||
config=ConfigModel,
|
|
||||||
homepage="https://github.com/LiteyukiStudio/nonebot-plugin-marshoai",
|
|
||||||
supported_adapters=inherit_supported_adapters("nonebot_plugin_alconna"),
|
|
||||||
extra={"License":"MIT","Author":"Asankilp"}
|
|
||||||
)
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,14 +12,16 @@ from azure.ai.inference.aio import ChatCompletionsClient
|
|||||||
from azure.ai.inference.models import (
|
from azure.ai.inference.models import (
|
||||||
UserMessage,
|
UserMessage,
|
||||||
AssistantMessage,
|
AssistantMessage,
|
||||||
|
ContentItem,
|
||||||
TextContentItem,
|
TextContentItem,
|
||||||
ImageContentItem,
|
ImageContentItem,
|
||||||
ImageUrl,
|
ImageUrl,
|
||||||
CompletionsFinishReason,
|
CompletionsFinishReason,
|
||||||
)
|
)
|
||||||
from azure.core.credentials import AzureKeyCredential
|
from azure.core.credentials import AzureKeyCredential
|
||||||
from typing import Optional
|
from typing import Any, Optional
|
||||||
from .__init__ import __plugin_meta__
|
|
||||||
|
from .metadata import metadata
|
||||||
from .config import config
|
from .config import config
|
||||||
from .models import MarshoContext
|
from .models import MarshoContext
|
||||||
from .constants import *
|
from .constants import *
|
||||||
@ -126,15 +128,12 @@ async def nickname(event: Event, name=None):
|
|||||||
@marsho_cmd.handle()
|
@marsho_cmd.handle()
|
||||||
async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None):
|
async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None):
|
||||||
if not text:
|
if not text:
|
||||||
await UniMessage(
|
await UniMessage(metadata.usage + "\n当前使用的模型:" + model_name).send()
|
||||||
__plugin_meta__.usage + "\n当前使用的模型:" + model_name
|
|
||||||
).send()
|
|
||||||
await marsho_cmd.finish(INTRODUCTION)
|
await marsho_cmd.finish(INTRODUCTION)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
is_support_image_model = model_name.lower() in SUPPORT_IMAGE_MODELS
|
|
||||||
usermsg = [] if is_support_image_model else ""
|
|
||||||
user_id = event.get_user_id()
|
user_id = event.get_user_id()
|
||||||
nicknames = await get_nicknames()
|
nicknames = await get_nicknames()
|
||||||
nickname = nicknames.get(user_id, "")
|
nickname = nicknames.get(user_id, "")
|
||||||
@ -146,22 +145,18 @@ async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None)
|
|||||||
await UniMessage(
|
await UniMessage(
|
||||||
"*你未设置自己的昵称。推荐使用'nickname [昵称]'命令设置昵称来获得个性化(可能)回答。"
|
"*你未设置自己的昵称。推荐使用'nickname [昵称]'命令设置昵称来获得个性化(可能)回答。"
|
||||||
).send()
|
).send()
|
||||||
|
|
||||||
|
usermsg: list[ContentItem] = []
|
||||||
for i in text:
|
for i in text:
|
||||||
if i.type == "image":
|
if i.type == "text":
|
||||||
if is_support_image_model:
|
usermsg += [TextContentItem(text=i.data["text"] + nickname_prompt)]
|
||||||
imgurl = i.data["url"]
|
elif i.type == "image" and model_name.lower() in SUPPORT_IMAGE_MODELS:
|
||||||
picmsg = ImageContentItem(
|
usermsg.append(
|
||||||
image_url=ImageUrl(url=str(await get_image_b64(imgurl)))
|
ImageContentItem(
|
||||||
|
image_url=ImageUrl(url=str(await get_image_b64(i.data["url"])))
|
||||||
)
|
)
|
||||||
usermsg.append(picmsg)
|
)
|
||||||
else:
|
|
||||||
await UniMessage("*此模型不支持图片处理。").send()
|
|
||||||
elif i.type == "text":
|
|
||||||
clean_text = i.data["text"]
|
|
||||||
if is_support_image_model:
|
|
||||||
usermsg.append(TextContentItem(text=clean_text + nickname_prompt))
|
|
||||||
else:
|
|
||||||
usermsg += str(clean_text + nickname_prompt)
|
|
||||||
response = await make_chat(
|
response = await make_chat(
|
||||||
client=client,
|
client=client,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
from nonebot import on_type
|
from nonebot import on_type
|
||||||
from nonebot.rule import to_me
|
from nonebot.rule import to_me
|
||||||
from nonebot.adapters.onebot.v11 import PokeNotifyEvent
|
from nonebot.adapters.onebot.v11 import PokeNotifyEvent # type: ignore
|
||||||
poke_notify = on_type(
|
|
||||||
(PokeNotifyEvent,),
|
|
||||||
rule=to_me()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
poke_notify = on_type((PokeNotifyEvent,), rule=to_me())
|
||||||
|
14
nonebot_plugin_marshoai/metadata.py
Normal file
14
nonebot_plugin_marshoai/metadata.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
||||||
|
from .config import ConfigModel, config
|
||||||
|
from .constants import USAGE
|
||||||
|
|
||||||
|
metadata = PluginMetadata(
|
||||||
|
name="Marsho AI插件",
|
||||||
|
description="接入Azure服务的AI聊天插件",
|
||||||
|
usage=USAGE,
|
||||||
|
type="application",
|
||||||
|
config=ConfigModel,
|
||||||
|
homepage="https://github.com/LiteyukiStudio/nonebot-plugin-marshoai",
|
||||||
|
supported_adapters=inherit_supported_adapters("nonebot_plugin_alconna"),
|
||||||
|
extra={"License": "MIT", "Author": "Asankilp"},
|
||||||
|
)
|
@ -2,55 +2,67 @@ import base64
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
from typing import Any
|
||||||
import httpx
|
import httpx
|
||||||
import nonebot_plugin_localstore as store
|
import nonebot_plugin_localstore as store
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from zhDateTime import DateTime
|
from zhDateTime import DateTime # type: ignore
|
||||||
from azure.ai.inference.aio import ChatCompletionsClient
|
from azure.ai.inference.aio import ChatCompletionsClient
|
||||||
from azure.ai.inference.models import SystemMessage
|
from azure.ai.inference.models import SystemMessage
|
||||||
from .config import config
|
from .config import config
|
||||||
|
|
||||||
|
|
||||||
async def get_image_b64(url):
|
async def get_image_b64(url):
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||||
}
|
}
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(url, headers=headers)
|
response = await client.get(url, headers=headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# 获取图片数据
|
# 获取图片数据
|
||||||
image_data = response.content
|
image_data = response.content
|
||||||
content_type = response.headers.get('Content-Type')
|
content_type = response.headers.get("Content-Type")
|
||||||
if not content_type:
|
if not content_type:
|
||||||
content_type = mimetypes.guess_type(url)[0]
|
content_type = mimetypes.guess_type(url)[0]
|
||||||
image_format = content_type.split('/')[1] if content_type else 'jpeg'
|
image_format = content_type.split("/")[1] if content_type else "jpeg"
|
||||||
base64_image = base64.b64encode(image_data).decode('utf-8')
|
base64_image = base64.b64encode(image_data).decode("utf-8")
|
||||||
data_url = f"data:{content_type};base64,{base64_image}"
|
data_url = f"data:{content_type};base64,{base64_image}"
|
||||||
return data_url
|
return data_url
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def make_chat(client: ChatCompletionsClient, msg, model_name: str):
|
async def make_chat(client: ChatCompletionsClient, msg, model_name: str):
|
||||||
return await client.complete(
|
return await client.complete(
|
||||||
messages=msg,
|
messages=msg,
|
||||||
model=model_name,
|
model=model_name,
|
||||||
temperature=config.marshoai_temperature,
|
temperature=config.marshoai_temperature,
|
||||||
max_tokens=config.marshoai_max_tokens,
|
max_tokens=config.marshoai_max_tokens,
|
||||||
top_p=config.marshoai_top_p
|
top_p=config.marshoai_top_p,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_praises():
|
def get_praises():
|
||||||
praises_file = store.get_plugin_data_file("praises.json") # 夸赞名单文件使用localstore存储
|
praises_file = store.get_plugin_data_file(
|
||||||
|
"praises.json"
|
||||||
|
) # 夸赞名单文件使用localstore存储
|
||||||
if not os.path.exists(praises_file):
|
if not os.path.exists(praises_file):
|
||||||
init_data = {
|
init_data = {
|
||||||
"like": [
|
"like": [
|
||||||
{"name":"Asankilp","advantages":"赋予了Marsho猫娘人格,使用vim与vscode为Marsho写了许多代码,使Marsho更加可爱"}
|
{
|
||||||
]
|
"name": "Asankilp",
|
||||||
}
|
"advantages": "赋予了Marsho猫娘人格,使用vim与vscode为Marsho写了许多代码,使Marsho更加可爱",
|
||||||
with open(praises_file,"w",encoding="utf-8") as f:
|
}
|
||||||
json.dump(init_data,f,ensure_ascii=False,indent=4)
|
]
|
||||||
with open(praises_file,"r",encoding="utf-8") as f:
|
}
|
||||||
|
with open(praises_file, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(init_data, f, ensure_ascii=False, indent=4)
|
||||||
|
with open(praises_file, "r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def build_praises():
|
def build_praises():
|
||||||
praises = get_praises()
|
praises = get_praises()
|
||||||
result = ["你喜欢以下几个人物,他们有各自的优点:"]
|
result = ["你喜欢以下几个人物,他们有各自的优点:"]
|
||||||
@ -58,41 +70,47 @@ def build_praises():
|
|||||||
result.append(f"名字:{item['name']},优点:{item['advantages']}")
|
result.append(f"名字:{item['name']},优点:{item['advantages']}")
|
||||||
return "\n".join(result)
|
return "\n".join(result)
|
||||||
|
|
||||||
async def save_context_to_json(name: str, context: str):
|
|
||||||
|
async def save_context_to_json(name: str, context: Any):
|
||||||
context_dir = store.get_plugin_data_dir() / "contexts"
|
context_dir = store.get_plugin_data_dir() / "contexts"
|
||||||
os.makedirs(context_dir, exist_ok=True)
|
os.makedirs(context_dir, exist_ok=True)
|
||||||
file_path = os.path.join(context_dir, f"{name}.json")
|
file_path = os.path.join(context_dir, f"{name}.json")
|
||||||
with open(file_path, 'w', encoding='utf-8') as json_file:
|
with open(file_path, "w", encoding="utf-8") as json_file:
|
||||||
json.dump(context, json_file, ensure_ascii=False, indent=4)
|
json.dump(context, json_file, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
|
||||||
async def load_context_from_json(name: str):
|
async def load_context_from_json(name: str):
|
||||||
context_dir = store.get_plugin_data_dir() / "contexts"
|
context_dir = store.get_plugin_data_dir() / "contexts"
|
||||||
os.makedirs(context_dir, exist_ok=True)
|
os.makedirs(context_dir, exist_ok=True)
|
||||||
file_path = os.path.join(context_dir, f"{name}.json")
|
file_path = os.path.join(context_dir, f"{name}.json")
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'r', encoding='utf-8') as json_file:
|
with open(file_path, "r", encoding="utf-8") as json_file:
|
||||||
return json.load(json_file)
|
return json.load(json_file)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def set_nickname(user_id: str, name: str):
|
async def set_nickname(user_id: str, name: str):
|
||||||
filename = store.get_plugin_data_file("nickname.json")
|
filename = store.get_plugin_data_file("nickname.json")
|
||||||
if not os.path.exists(filename):
|
if not os.path.exists(filename):
|
||||||
data = {}
|
data = {}
|
||||||
else:
|
else:
|
||||||
with open(filename,'r', encoding='utf-8') as f:
|
with open(filename, "r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
data[user_id] = name
|
data[user_id] = name
|
||||||
with open(filename, 'w', encoding='utf-8') as f:
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
|
||||||
async def get_nicknames():
|
async def get_nicknames():
|
||||||
filename = store.get_plugin_data_file("nickname.json")
|
filename = store.get_plugin_data_file("nickname.json")
|
||||||
try:
|
try:
|
||||||
with open(filename, 'r', encoding='utf-8') as f:
|
with open(filename, "r", encoding="utf-8") as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def get_prompt():
|
def get_prompt():
|
||||||
prompts = ""
|
prompts = ""
|
||||||
prompts += config.marshoai_additional_prompt
|
prompts += config.marshoai_additional_prompt
|
||||||
@ -100,14 +118,17 @@ def get_prompt():
|
|||||||
praises_prompt = build_praises()
|
praises_prompt = build_praises()
|
||||||
prompts += praises_prompt
|
prompts += praises_prompt
|
||||||
if config.marshoai_enable_time_prompt:
|
if config.marshoai_enable_time_prompt:
|
||||||
current_time = datetime.now().strftime('%Y.%m.%d %H:%M:%S')
|
current_time = datetime.now().strftime("%Y.%m.%d %H:%M:%S")
|
||||||
current_lunar_date = DateTime.now().to_lunar().date_hanzify()[5:] #库更新之前使用切片
|
current_lunar_date = (
|
||||||
|
DateTime.now().to_lunar().date_hanzify()[5:]
|
||||||
|
) # 库更新之前使用切片
|
||||||
time_prompt = f"现在的时间是{current_time},农历{current_lunar_date}。"
|
time_prompt = f"现在的时间是{current_time},农历{current_lunar_date}。"
|
||||||
prompts += time_prompt
|
prompts += time_prompt
|
||||||
marsho_prompt = config.marshoai_prompt
|
marsho_prompt = config.marshoai_prompt
|
||||||
spell = SystemMessage(content=marsho_prompt+prompts).as_dict()
|
spell = SystemMessage(content=marsho_prompt + prompts).as_dict()
|
||||||
return spell
|
return spell
|
||||||
|
|
||||||
|
|
||||||
def suggest_solution(errinfo: str) -> str:
|
def suggest_solution(errinfo: str) -> str:
|
||||||
suggestions = {
|
suggestions = {
|
||||||
"content_filter": "消息已被内容过滤器过滤。请调整聊天内容后重试。",
|
"content_filter": "消息已被内容过滤器过滤。请调整聊天内容后重试。",
|
||||||
@ -116,11 +137,11 @@ def suggest_solution(errinfo: str) -> str:
|
|||||||
"content_length_limit": "请求体过大。请重置上下文。",
|
"content_length_limit": "请求体过大。请重置上下文。",
|
||||||
"unauthorized": "Azure凭据无效。请联系Bot管理员。",
|
"unauthorized": "Azure凭据无效。请联系Bot管理员。",
|
||||||
"invalid type: parameter messages.content is of type array but should be of type string.": "聊天请求体包含此模型不支持的数据类型。请重置上下文。",
|
"invalid type: parameter messages.content is of type array but should be of type string.": "聊天请求体包含此模型不支持的数据类型。请重置上下文。",
|
||||||
"At most 1 image(s) may be provided in one request.": "此模型只能在上下文中包含1张图片。如果此前的聊天已经发送过图片,请重置上下文。"
|
"At most 1 image(s) may be provided in one request.": "此模型只能在上下文中包含1张图片。如果此前的聊天已经发送过图片,请重置上下文。",
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, suggestion in suggestions.items():
|
for key, suggestion in suggestions.items():
|
||||||
if key in errinfo:
|
if key in errinfo:
|
||||||
return f"\n{suggestion}"
|
return f"\n{suggestion}"
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
@ -12,7 +12,7 @@ dependencies = [
|
|||||||
"azure-ai-inference>=1.0.0b4",
|
"azure-ai-inference>=1.0.0b4",
|
||||||
"zhDatetime>=1.1.1",
|
"zhDatetime>=1.1.1",
|
||||||
"aiohttp>=3.9",
|
"aiohttp>=3.9",
|
||||||
"httpx>=0.27.0"
|
"httpx>=0.27.0",
|
||||||
]
|
]
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|
||||||
@ -22,6 +22,9 @@ Homepage = "https://github.com/LiteyukiStudio/nonebot-plugin-marshoai"
|
|||||||
|
|
||||||
[tool.nonebot]
|
[tool.nonebot]
|
||||||
plugins = ["nonebot_plugin_marshoai"]
|
plugins = ["nonebot_plugin_marshoai"]
|
||||||
|
adapters = [
|
||||||
|
{ name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" },
|
||||||
|
]
|
||||||
|
|
||||||
[tool.pdm]
|
[tool.pdm]
|
||||||
distribution = true
|
distribution = true
|
||||||
@ -34,9 +37,7 @@ path = "nonebot_plugin_marshoai/constants.py"
|
|||||||
includes = []
|
includes = []
|
||||||
|
|
||||||
[tool.pdm.dev-dependencies]
|
[tool.pdm.dev-dependencies]
|
||||||
dev = [
|
dev = ["nb-cli>=1.4.2"]
|
||||||
"nb-cli>=1.4.2",
|
|
||||||
]
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["pdm-backend"]
|
requires = ["pdm-backend"]
|
||||||
build-backend = "pdm.backend"
|
build-backend = "pdm.backend"
|
||||||
|
Loading…
Reference in New Issue
Block a user