把回复消息中的 图片 全部插入消息体;

Black格式化代码
This commit is contained in:
2024-11-30 03:40:10 +08:00
parent 075a529aa1
commit a888442311
11 changed files with 277 additions and 93 deletions

View File

@ -1,5 +1,6 @@
import contextlib
import uuid
import traceback
import contextlib
from typing import Optional
from pathlib import Path
@ -15,15 +16,21 @@ from azure.ai.inference.models import (
ChatCompletionsToolCall,
)
from azure.core.credentials import AzureKeyCredential
from nonebot import on_command, on_message, logger
from nonebot import on_command, on_message, logger, get_driver
from nonebot.adapters import Message, Event
from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER
from nonebot.rule import Rule, to_me
from nonebot_plugin_alconna import on_alconna, MsgTarget
from nonebot_plugin_alconna.uniseg import UniMessage, UniMsg
from nonebot_plugin_alconna import (
on_alconna,
MsgTarget,
UniMessage,
UniMsg,
Text as TextMsg,
Image as ImageMsg,
)
import nonebot_plugin_localstore as store
from nonebot import get_driver
from .constants import *
from .metadata import metadata
@ -37,15 +44,23 @@ async def at_enable():
driver = get_driver()
changemodel_cmd = on_command("changemodel", permission=SUPERUSER, priority=10, block=True)
changemodel_cmd = on_command(
"changemodel", permission=SUPERUSER, priority=10, block=True
)
resetmem_cmd = on_command("reset", priority=10, block=True)
# setprompt_cmd = on_command("prompt",permission=SUPERUSER)
praises_cmd = on_command("praises", permission=SUPERUSER, priority=10, block=True)
add_usermsg_cmd = on_command("usermsg", permission=SUPERUSER, priority=10, block=True)
add_assistantmsg_cmd = on_command("assistantmsg", permission=SUPERUSER, priority=10, block=True)
add_assistantmsg_cmd = on_command(
"assistantmsg", permission=SUPERUSER, priority=10, block=True
)
contexts_cmd = on_command("contexts", permission=SUPERUSER, priority=10, block=True)
save_context_cmd = on_command("savecontext", permission=SUPERUSER, priority=10, block=True)
load_context_cmd = on_command("loadcontext", permission=SUPERUSER, priority=10, block=True)
save_context_cmd = on_command(
"savecontext", permission=SUPERUSER, priority=10, block=True
)
load_context_cmd = on_command(
"loadcontext", permission=SUPERUSER, priority=10, block=True
)
marsho_cmd = on_alconna(
Alconna(
config.marshoai_default_name,
@ -53,18 +68,20 @@ marsho_cmd = on_alconna(
),
aliases=config.marshoai_aliases,
priority=10,
block=True
block=True,
)
marsho_at = on_message(rule=to_me()&at_enable, priority=11)
marsho_at = on_message(rule=to_me() & at_enable, priority=11)
nickname_cmd = on_alconna(
Alconna(
"nickname",
Args["name?", str],
),
priority = 10,
block = True
priority=10,
block=True,
)
refresh_data_cmd = on_command(
"refresh_data", permission=SUPERUSER, priority=10, block=True
)
refresh_data_cmd = on_command("refresh_data", permission=SUPERUSER, priority=10, block=True)
command_start = driver.config.command_start
model_name = config.marshoai_default_model
@ -86,7 +103,9 @@ async def _preload_tools():
tools.load_tools(store.get_plugin_data_dir() / "tools")
for tool_dir in config.marshoai_toolset_dir:
tools.load_tools(tool_dir)
logger.info("如果启用小棉工具后使用的模型出现报错,请尝试将 MARSHOAI_ENABLE_TOOLS 设为 false。")
logger.info(
"如果启用小棉工具后使用的模型出现报错,请尝试将 MARSHOAI_ENABLE_TOOLS 设为 false。"
)
@add_usermsg_cmd.handle()
@ -132,7 +151,9 @@ async def save_context(target: MsgTarget, arg: Message = CommandArg()):
@load_context_cmd.handle()
async def load_context(target: MsgTarget, arg: Message = CommandArg()):
if msg := arg.extract_plain_text():
await get_backup_context(target.id, target.private) # 为了将当前会话添加到"已恢复过备份"的列表而添加防止上下文被覆盖好奇怪QwQ
await get_backup_context(
target.id, target.private
) # 为了将当前会话添加到"已恢复过备份"的列表而添加防止上下文被覆盖好奇怪QwQ
context.set_context(
await load_context_from_json(msg, "contexts"), target.id, target.private
)
@ -178,11 +199,86 @@ async def refresh_data():
await refresh_data_cmd.finish("已刷新数据")
"""
以下函数依照 Apache 2.0 协议授权
函数: get_back_uuidcodeblock、send_markdown
版权所有 © 2024 金羿ELS
Copyright (R) 2024 Eilles(EillesWan@outlook.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
async def get_back_uuidcodeblock(msg: str, code_blank_uuid_map: list[tuple[str, str]]):
for torep, rep in code_blank_uuid_map:
msg = msg.replace(torep, rep)
return msg
async def send_markdown(msg: str):
"""
人工智能给出的回答一般不会包含 HTML 嵌入其中,但是包含图片或者 LaTeX 公式、代码块,都很正常。
这个函数会把这些都以图片形式嵌入消息体。
"""
result_msg = UniMessage()
code_blank_uuid_map = [
(uuid.uuid4().hex, cbp.group()) for cbp in CODE_BLOCK_PATTERN.finditer(msg)
]
# 代码块渲染麻烦,先不处理
for rep, torep in code_blank_uuid_map:
msg = msg.replace(torep, rep)
# 插入图片
for each_img_tag in IMG_TAG_PATTERN.finditer(msg):
img_tag = await get_back_uuidcodeblock(
each_img_tag.group(), code_blank_uuid_map
)
image_description = img_tag[2 : img_tag.find("]")]
image_url = img_tag[img_tag.find("(") + 1 : -1]
result_msg.append(
TextMsg(
await get_back_uuidcodeblock(
msg[: msg.find(img_tag)], code_blank_uuid_map
)
)
)
result_msg.append(ImageMsg(url=image_url, name=image_description + ".png"))
result_msg.append(TextMsg("{}".format(image_description)))
await result_msg.send(reply_to=True)
"""
Apache 2.0 协议授权部分结束
"""
@marsho_at.handle()
@marsho_cmd.handle()
async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None):
global target_list
if event.get_message().extract_plain_text() and (not text and event.get_message().extract_plain_text() != config.marshoai_default_name):
if event.get_message().extract_plain_text() and (
not text
and event.get_message().extract_plain_text() != config.marshoai_default_name
):
text = event.get_message()
if not text:
# 发送说明
@ -204,7 +300,10 @@ async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None)
"*你未设置自己的昵称。推荐使用'nickname [昵称]'命令设置昵称来获得个性化(可能)回答。"
).send()
is_support_image_model = model_name.lower() in SUPPORT_IMAGE_MODELS + config.marshoai_additional_image_models
is_support_image_model = (
model_name.lower()
in SUPPORT_IMAGE_MODELS + config.marshoai_additional_image_models
)
is_reasoning_model = model_name.lower() in REASONING_MODELS
usermsg = [] if is_support_image_model else ""
for i in text:
@ -217,14 +316,18 @@ async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None)
if is_support_image_model:
usermsg.append(
ImageContentItem(
image_url=ImageUrl(url=str(await get_image_b64(i.data["url"])))
image_url=ImageUrl(
url=str(await get_image_b64(i.data["url"]))
)
)
)
elif config.marshoai_enable_support_image_tip:
await UniMessage("*此模型不支持图片处理。").send()
backup_context = await get_backup_context(target.id, target.private)
if backup_context:
context.set_context(backup_context, target.id, target.private) # 加载历史记录
context.set_context(
backup_context, target.id, target.private
) # 加载历史记录
logger.info(f"已恢复会话 {target.id} 的上下文备份~")
context_msg = context.build(target.id, target.private)
if not is_reasoning_model:
@ -234,46 +337,74 @@ async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None)
client=client,
model_name=model_name,
msg=context_msg + [UserMessage(content=usermsg)],
tools=tools.get_tools_list()
tools=tools.get_tools_list(),
)
# await UniMessage(str(response)).send()
choice = response.choices[0]
if (choice["finish_reason"] == CompletionsFinishReason.STOPPED): # 当对话成功时将dict的上下文添加到上下文类中
if choice["finish_reason"] == CompletionsFinishReason.STOPPED:
# 当对话成功时将dict的上下文添加到上下文类中
context.append(
UserMessage(content=usermsg).as_dict(), target.id, target.private
)
context.append(choice.message.as_dict(), target.id, target.private)
if [target.id, target.private] not in target_list:
target_list.append([target.id, target.private])
await UniMessage(str(choice.message.content)).send(reply_to=True)
# 对话成功发送消息
await send_markdown(str(choice.message.content))
elif choice["finish_reason"] == CompletionsFinishReason.CONTENT_FILTERED:
await UniMessage("*已被内容过滤器过滤。请调整聊天内容后重试。").send(reply_to=True)
# 对话失败,消息过滤
await UniMessage("*已被内容过滤器过滤。请调整聊天内容后重试。").send(
reply_to=True
)
return
elif choice["finish_reason"] == CompletionsFinishReason.TOOL_CALLS:
# 需要获取额外信息,调用函数工具
tool_msg = []
while choice.message.tool_calls != None:
tool_msg.append(AssistantMessage(tool_calls=response.choices[0].message.tool_calls))
tool_msg.append(
AssistantMessage(tool_calls=response.choices[0].message.tool_calls)
)
for tool_call in choice.message.tool_calls:
if isinstance(tool_call, ChatCompletionsToolCall): # 循环调用工具直到不需要调用
function_args = json.loads(tool_call.function.arguments.replace("'", '"'))
logger.info(f"调用函数 {tool_call.function.name} ,参数为 {function_args}")
await UniMessage(f"调用函数 {tool_call.function.name} ,参数为 {function_args}").send()
func_return = await tools.call(tool_call.function.name, function_args) # 获取返回值
tool_msg.append(ToolMessage(tool_call_id=tool_call.id, content=func_return))
if isinstance(
tool_call, ChatCompletionsToolCall
): # 循环调用工具直到不需要调用
function_args = json.loads(
tool_call.function.arguments.replace("'", '"')
)
logger.info(
f"调用函数 {tool_call.function.name} ,参数为 {function_args}"
)
await UniMessage(
f"调用函数 {tool_call.function.name} ,参数为 {function_args}"
).send()
func_return = await tools.call(
tool_call.function.name, function_args
) # 获取返回值
tool_msg.append(
ToolMessage(tool_call_id=tool_call.id, content=func_return)
)
response = await make_chat(
client=client,
model_name=model_name,
msg=context_msg + [UserMessage(content=usermsg)] + tool_msg,
tools=tools.get_tools_list()
tools=tools.get_tools_list(),
)
choice = response.choices[0]
if choice["finish_reason"] == CompletionsFinishReason.STOPPED:
# 对话成功 添加上下文
context.append(
UserMessage(content=usermsg).as_dict(), target.id, target.private
)
# context.append(tool_msg, target.id, target.private)
# context.append(tool_msg, target.id, target.private)
context.append(choice.message.as_dict(), target.id, target.private)
await UniMessage(str(choice.message.content)).send(reply_to=True)
# 发送消息
await send_markdown(str(choice.message.content))
else:
await marsho_cmd.finish(f"意外的完成原因:{choice['finish_reason']}")
else:
@ -288,7 +419,6 @@ with contextlib.suppress(ImportError): # 优化先不做()
import nonebot.adapters.onebot.v11 # type: ignore
from .azure_onebot import poke_notify
@poke_notify.handle()
async def poke(event: Event):
@ -327,5 +457,7 @@ async def auto_backup_context():
target_uid = "private_" + target_id
else:
target_uid = "group_" + target_id
await save_context_to_json(f"back_up_context_{target_uid}", contexts_data, "contexts/backup")
await save_context_to_json(
f"back_up_context_{target_uid}", contexts_data, "contexts/backup"
)
logger.info(f"已保存会话 {target_id} 的上下文备份,将在下次对话时恢复~")