From 57c09df1fe822aee4940e5a50d2240f7531c7546 Mon Sep 17 00:00:00 2001 From: Akarin~ <60691961+Asankilp@users.noreply.github.com> Date: Sat, 15 Feb 2025 19:09:00 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E5=85=BC=E5=AE=B9=E6=80=A7=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 更新OpenAI模型列表,重构获取系统提示词逻辑,添加开发者消息类型,兼容 OpenAI o1 以上模型的系统提示词 * 添加 System-As-User 提示词配置,更新相关文档 * 更新使用文档,添加 DeepSeek-R1 模型的 System-As-User Prompt 配置说明 --- docs/en/start/install.md | 4 +++- docs/zh/start/install.md | 4 +++- docs/zh/start/use.md | 10 ++++++++- nonebot_plugin_marshoai/_types.py | 33 ++++++++++++++++++++++++++++ nonebot_plugin_marshoai/config.py | 9 +++++--- nonebot_plugin_marshoai/constants.py | 9 +++++++- nonebot_plugin_marshoai/marsho.py | 19 ++++++---------- nonebot_plugin_marshoai/util.py | 26 ++++++++++++++++------ 8 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 nonebot_plugin_marshoai/_types.py diff --git a/docs/en/start/install.md b/docs/en/start/install.md index 99a3c915..19157281 100644 --- a/docs/en/start/install.md +++ b/docs/en/start/install.md @@ -117,7 +117,8 @@ Add options in the `.env` file from the diagram below in nonebot2 project. | -------------------------------- | ------- | --------------------------------------- | --------------------------------------------------------------------------------------------- | | MARSHOAI_TOKEN | `str` | | The token needed to call AI API | | MARSHOAI_DEFAULT_MODEL | `str` | `gpt-4o-mini` | The default model of Marsho | -| MARSHOAI_PROMPT | `str` | Catgirl Marsho's character prompt | Marsho's basic system prompt **※Some models(o1 and so on) don't support it** | +| MARSHOAI_PROMPT | `str` | Catgirl Marsho's character prompt | Marsho's basic system prompt | +| MARSHOAI_SYSASUSER_PROMPT | `str` | `好的喵~` | Marsho 的 System-As-User 启用时,使用的 Assistant 消息 | | MARSHOAI_ADDITIONAL_PROMPT | `str` | | Marsho's external system prompt | | MARSHOAI_ENFORCE_NICKNAME | `bool` | `true` | Enforce user to set nickname or not | | MARSHOAI_POKE_SUFFIX | `str` | `揉了揉你的猫耳` | When double click Marsho who connected to OneBot adapter, the chat content. When it's empty string, double click function is off. Such as, the default content is `*[昵称]揉了揉你的猫耳。` | @@ -135,6 +136,7 @@ Add options in the `.env` file from the diagram below in nonebot2 project. | MARSHOAI_ENABLE_SUPPORT_IMAGE_TIP | `bool` | `true` | When on, if user send request with photo and model don't support that, remind the user | | MARSHOAI_ENABLE_NICKNAME_TIP | `bool` | `true` | When on, if user haven't set username, remind user to set | | MARSHOAI_ENABLE_PRAISES | `bool` | `true` | Turn on Praise list or not | +| MARSHOAI_ENABLE_SYSASUSER_PROMPT | `bool` | `false` | 是否启用 System-As-User 提示词 | | MARSHOAI_ENABLE_TIME_PROMPT | `bool` | `true` | Turn on real-time date and time (accurate to seconds) and lunar date system prompt | | MARSHOAI_ENABLE_TOOLS | `bool` | `false` | Turn on Marsho Tools or not | | MARSHOAI_ENABLE_PLUGINS | `bool` | `true` | Turn on Marsho Plugins or not diff --git a/docs/zh/start/install.md b/docs/zh/start/install.md index ff5080a3..e35d3f18 100644 --- a/docs/zh/start/install.md +++ b/docs/zh/start/install.md @@ -119,7 +119,8 @@ GitHub Models API 的限制较多,不建议使用,建议通过修改`MARSHOA | -------------------------------- | ------- | --------------------------------------- | --------------------------------------------------------------------------------------------- | | MARSHOAI_TOKEN | `str` | | 调用 AI API 所需的 token | | MARSHOAI_DEFAULT_MODEL | `str` | `gpt-4o-mini` | Marsho 默认调用的模型 | -| MARSHOAI_PROMPT | `str` | 猫娘 Marsho 人设提示词 | Marsho 的基本系统提示词 **※部分模型(o1等)不支持系统提示词。** | +| MARSHOAI_PROMPT | `str` | 猫娘 Marsho 人设提示词 | Marsho 的基本系统提示词 | +| MARSHOAI_SYSASUSER_PROMPT | `str` | `好的喵~` | Marsho 的 System-As-User 启用时,使用的 Assistant 消息 | | MARSHOAI_ADDITIONAL_PROMPT | `str` | | Marsho 的扩展系统提示词 | | MARSHOAI_ENFORCE_NICKNAME | `bool` | `true` | 是否强制用户设置昵称 | | MARSHOAI_POKE_SUFFIX | `str` | `揉了揉你的猫耳` | 对 Marsho 所连接的 OneBot 用户进行双击戳一戳时,构建的聊天内容。此配置项为空字符串时,戳一戳响应功能会被禁用。例如,默认值构建的聊天内容将为`*[昵称]揉了揉你的猫耳。` | @@ -135,6 +136,7 @@ GitHub Models API 的限制较多,不建议使用,建议通过修改`MARSHOA | MARSHOAI_ENABLE_SUPPORT_IMAGE_TIP | `bool` | `true` | 启用后用户发送带图请求时若模型不支持图片,则提示用户 | | MARSHOAI_ENABLE_NICKNAME_TIP | `bool` | `true` | 启用后用户未设置昵称时提示用户设置 | | MARSHOAI_ENABLE_PRAISES | `bool` | `true` | 是否启用夸赞名单功能 | +| MARSHOAI_ENABLE_SYSASUSER_PROMPT | `bool` | `false` | 是否启用 System-As-User 提示词 | | MARSHOAI_ENABLE_TIME_PROMPT | `bool` | `true` | 是否启用实时更新的日期与时间(精确到秒)与农历日期系统提示词 | | MARSHOAI_ENABLE_TOOLS | `bool` | `false` | 是否启用小棉工具 | | MARSHOAI_ENABLE_PLUGINS | `bool` | `true` | 是否启用小棉插件 | diff --git a/docs/zh/start/use.md b/docs/zh/start/use.md index b0a146de..4279a99b 100644 --- a/docs/zh/start/use.md +++ b/docs/zh/start/use.md @@ -23,7 +23,15 @@ title: 使用 ```dotenv MARSHOAI_ADDITIONAL_IMAGE_MODELS=["hunyuan-vision"] ``` - +- 对于本地部署的 DeepSeek-R1 模型: + :::tip + MarshoAI 默认使用 System Prompt 进行人设等的调整,但 DeepSeek-R1 官方推荐**避免**使用 System Prompt(但可以正常使用)。 + 为解决此问题,引入了 System-As-User Prompt 配置,可将 System Prompt 作为用户传入的消息。 + ::: + ```dotenv + MARSHOAI_ENABLE_SYSASUSER_PROMPT=true + MARSHOAI_SYSASUSER_PROMPT="好的喵~" # 假装是模型收到消息后的回答 + ``` ### 使用 DeepSeek-R1 模型 MarshoAI 兼容 DeepSeek-R1 模型,你可通过以下步骤来使用: 1. 获取 API Key diff --git a/nonebot_plugin_marshoai/_types.py b/nonebot_plugin_marshoai/_types.py new file mode 100644 index 00000000..a26a31ba --- /dev/null +++ b/nonebot_plugin_marshoai/_types.py @@ -0,0 +1,33 @@ +# source: https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-inference/azure/ai/inference/models/_models.py +from typing import Any, Literal, Mapping, Optional, overload + +from azure.ai.inference._model_base import rest_discriminator, rest_field +from azure.ai.inference.models import ChatRequestMessage + + +class DeveloperMessage(ChatRequestMessage, discriminator="developer"): + + role: Literal["developer"] = rest_discriminator(name="role") # type: ignore + """The chat role associated with this message, which is always 'developer' for developer messages. + Required.""" + content: Optional[str] = rest_field() + """The content of the message.""" + + @overload + def __init__( + self, + *, + content: Optional[str] = None, + ): ... + + @overload + def __init__(self, mapping: Mapping[str, Any]): + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__( + self, *args: Any, **kwargs: Any + ) -> None: # pylint: disable=useless-super-delegation + super().__init__(*args, role="developer", **kwargs) diff --git a/nonebot_plugin_marshoai/config.py b/nonebot_plugin_marshoai/config.py index 344120ef..3e9634ec 100644 --- a/nonebot_plugin_marshoai/config.py +++ b/nonebot_plugin_marshoai/config.py @@ -28,6 +28,8 @@ class ConfigModel(BaseModel): "当主人给你图片作为问题,并且你确实可以处理图片时,你必须以猫娘的说话方式进行回答," "当主人想要你回复一些有关 LaTeX 公式的时候,你切记一定不可以在公式中包含非 ASCII 字符。" ) + marshoai_sysasuser_prompt: str = "好的喵~" + marshoai_enable_sysasuser_prompt: bool = False marshoai_additional_prompt: str = "" marshoai_poke_suffix: str = "揉了揉你的猫耳" marshoai_enable_richtext_parse: bool = True @@ -146,6 +148,7 @@ if config.marshoai_use_yaml_config: config = ConfigModel(**yaml_config) else: - logger.info( - "MarshoAI 支持新的 YAML 配置系统,若要使用,请将 MARSHOAI_USE_YAML_CONFIG 配置项设置为 true。" - ) + # logger.info( + # "MarshoAI 支持新的 YAML 配置系统,若要使用,请将 MARSHOAI_USE_YAML_CONFIG 配置项设置为 true。" + # ) + pass diff --git a/nonebot_plugin_marshoai/constants.py b/nonebot_plugin_marshoai/constants.py index 594bcf11..9f09513f 100755 --- a/nonebot_plugin_marshoai/constants.py +++ b/nonebot_plugin_marshoai/constants.py @@ -26,7 +26,14 @@ SUPPORT_IMAGE_MODELS: list = [ "llama-3.2-11b-vision-instruct", "gemini-2.0-flash-exp", ] -NO_SYSPROMPT_MODELS: list = ["o1", "o1-preview", "o1-mini"] +OPENAI_NEW_MODELS: list = [ + "o1", + "o1-preview", + "o1-mini", + "o3", + "o3-mini", + "o3-mini-large", +] INTRODUCTION: str = f"""MarshoAI-NoneBot by LiteyukiStudio 你好喵~我是一只可爱的猫娘AI,名叫小棉~🐾! 我的主页在这里哦~↓↓↓ diff --git a/nonebot_plugin_marshoai/marsho.py b/nonebot_plugin_marshoai/marsho.py index ae2337bf..33a74c4f 100644 --- a/nonebot_plugin_marshoai/marsho.py +++ b/nonebot_plugin_marshoai/marsho.py @@ -257,7 +257,7 @@ async def marsho( model_name.lower() in SUPPORT_IMAGE_MODELS + config.marshoai_additional_image_models ) - is_reasoning_model = model_name.lower() in NO_SYSPROMPT_MODELS + is_openai_new_model = model_name.lower() in OPENAI_NEW_MODELS usermsg = [] if is_support_image_model else "" for i in text: # type: ignore if i.type == "text": @@ -285,14 +285,13 @@ async def marsho( backup_context, target.id, target.private ) # 加载历史记录 logger.info(f"已恢复会话 {target.id} 的上下文备份~") - context_msg = context.build(target.id, target.private) - if not is_reasoning_model: - context_msg = [get_prompt()] + context_msg - # o1等推理模型不支持系统提示词, 故不添加 + context_msg = get_prompt(model_name) + context.build(target.id, target.private) + tools_lists = tools.tools_list + list( map(lambda v: v.data(), get_function_calls().values()) ) logger.info(f"正在获取回答,模型:{model_name}") + # logger.info(f"上下文:{context_msg}") response = await make_chat_openai( client=client, model_name=model_name, @@ -304,7 +303,7 @@ async def marsho( # Sprint(choice) # 当tool_calls非空时,将finish_reason设置为TOOL_CALLS if choice.message.tool_calls != None and config.marshoai_fix_toolcalls: - choice.finish_reason = CompletionsFinishReason.TOOL_CALLS + choice.finish_reason = "tool_calls" logger.info(f"完成原因:{choice.finish_reason}") if choice.finish_reason == CompletionsFinishReason.STOPPED: # 当对话成功时,将dict的上下文添加到上下文类中 @@ -460,12 +459,8 @@ with contextlib.suppress(ImportError): # 优化先不做() response = await make_chat_openai( client=client, model_name=model_name, - msg=[ - ( - get_prompt() - if model_name.lower() not in NO_SYSPROMPT_MODELS - else None - ), + msg=get_prompt(model_name) + + [ UserMessage( content=f"*{user_nickname}{config.marshoai_poke_suffix}" ), diff --git a/nonebot_plugin_marshoai/util.py b/nonebot_plugin_marshoai/util.py index 066144cf..dca0b16e 100755 --- a/nonebot_plugin_marshoai/util.py +++ b/nonebot_plugin_marshoai/util.py @@ -3,22 +3,23 @@ import json import mimetypes import re import uuid -from typing import Any, Optional +from typing import Any, Dict, List, Optional import aiofiles # type: ignore import httpx import nonebot_plugin_localstore as store from azure.ai.inference.aio import ChatCompletionsClient -from azure.ai.inference.models import SystemMessage +from azure.ai.inference.models import AssistantMessage, SystemMessage, UserMessage from nonebot import get_driver from nonebot.log import logger from nonebot_plugin_alconna import Image as ImageMsg from nonebot_plugin_alconna import Text as TextMsg from nonebot_plugin_alconna import UniMessage from openai import AsyncOpenAI, NotGiven -from openai.types.chat import ChatCompletionMessage +from openai.types.chat import ChatCompletion, ChatCompletionMessage from zhDateTime import DateTime +from ._types import DeveloperMessage from .config import config from .constants import * from .deal_latex import ConvertLatex @@ -135,7 +136,7 @@ async def make_chat_openai( msg: list, model_name: str, tools: Optional[list] = None, -): +) -> ChatCompletion: """ 使用 Openai SDK 调用ai获取回复 @@ -252,7 +253,7 @@ async def refresh_nickname_json(): logger.error("刷新 nickname_json 表错误:无法载入 nickname.json 文件") -def get_prompt(): +def get_prompt(model: str) -> List[Dict[str, Any]]: """获取系统提示词""" prompts = config.marshoai_additional_prompt if config.marshoai_enable_praises: @@ -271,8 +272,19 @@ def get_prompt(): ) marsho_prompt = config.marshoai_prompt - spell = SystemMessage(content=marsho_prompt + prompts).as_dict() - return spell + sysprompt_content = marsho_prompt + prompts + prompt_list: List[Dict[str, Any]] = [] + if not config.marshoai_enable_sysasuser_prompt: + if model not in OPENAI_NEW_MODELS: + prompt_list += [SystemMessage(content=sysprompt_content).as_dict()] + else: + prompt_list += [DeveloperMessage(content=sysprompt_content).as_dict()] + else: + prompt_list += [UserMessage(content=sysprompt_content).as_dict()] + prompt_list += [ + AssistantMessage(content=config.marshoai_sysasuser_prompt).as_dict() + ] + return prompt_list def suggest_solution(errinfo: str) -> str: