Compare commits

...

14 Commits

21 changed files with 251 additions and 113 deletions

View File

@@ -13,6 +13,9 @@ jobs:
pypi-publish:
name: Upload release to PyPI
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@master
- name: Set up Python
@@ -34,7 +37,4 @@ jobs:
--outdir dist/
.
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
username: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
uses: pypa/gh-action-pypi-publish@release/v1

View File

@@ -27,7 +27,6 @@ _✨ 使用 OpenAI 标准格式 API 的聊天机器人插件 ✨_
插件内置了猫娘小棉(Marsho)的人物设定,可以进行可爱的聊天!
*谁不喜欢回复消息快又可爱的猫娘呢?*
**对 OneBot 以外的适配器与非 GitHub Models API的支持未经过完全验证。**
**MarshoAI v1.0.0 预计在春节前后发布。重写好累啊QwQ**
[Melobot 实现](https://github.com/LiteyukiStudio/marshoai-melo)
## 🐱 设定

View File

@@ -12,7 +12,7 @@ export const zh = defineConfig({
},
nav: [
{text: '家', link: '/'},
{text: '使用', link: '/start/install'},
{text: '使用', link: '/start/use'},
{text: '开发', link: '/dev/extension'},
],
editLink: ThemeConfig.getEditLink('编辑此页面'),

View File

@@ -49,7 +49,9 @@ Open the `pyproject.toml` file under nonebot2's root directory, Add to`[tool.non
- Create new [personal access token](https://github.com/settings/tokens/new)**Don't need any permissions**.
- Copy the new token, add to the `.env` file's `marshoai_token` option.
:::warning
GitHub Models API comes with significant limitations and is therefore not recommended for use. For better alternatives, it's suggested to adjust the configuration `MARSHOAI_AZURE_ENDPOINT` to use other service providers' models instead.
:::
## 🎉 Usage
End `marsho` in order to get direction for use(If you configured the custom command, please use the configured one).
@@ -105,7 +107,7 @@ Add options in the `.env` file from the diagram below in nonebot2 project.
| Option | Type | Default | Description |
| --------------------- | ---------- | ----------- | ----------------- |
| MARSHOAI_DEFAULT_NAME | `str` | `marsho` | Command to call Marsho |
| MARSHOAI_ALIASES | `set[str]` | `set{"Marsho"}` | Other name(Alias) to call Marsho |
| MARSHOAI_ALIASES | `set[str]` | `list["小棉"]` | Other name(Alias) to call Marsho |
| MARSHOAI_AT | `bool` | `false` | Call by @ or not |
| MARSHOAI_MAIN_COLOUR | `str` | `FFAAAA` | Theme color, used by some tools and features |
@@ -125,6 +127,7 @@ Add options in the `.env` file from the diagram below in nonebot2 project.
| MARSHOAI_MAX_TOKENS | `int` | `null` | Max token number |
| MARSHOAI_ADDITIONAL_IMAGE_MODELS | `list` | `[]` | External image-support model list, such as `hunyuan-vision` |
| MARSHOAI_NICKNAME_LIMIT | `int` | `16` | Limit for nickname length |
| MARSHOAI_TIMEOUT | `float` | `50` | AI request timeout (seconds) |
#### Feature Switches
@@ -133,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_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
| MARSHOAI_PLUGIN_DIRS | `list[str]` | `[]` | List of plugins directory |
@@ -141,3 +145,5 @@ Add options in the `.env` file from the diagram below in nonebot2 project.
| MARSHOAI_DISABLED_TOOLKITS | `list` | `[]` | List of disabled toolkits' name |
| MARSHOAI_ENABLE_RICHTEXT_PARSE | `bool` | `true` | Turn on auto parse rich text feature(including image, LaTeX equation) |
| MARSHOAI_SINGLE_LATEX_PARSE | `bool` | `false`| Render single-line equation or not |
| MARSHOAI_FIX_TOOLCALLS | `bool` | `true` | Fix tool calls or not |
| MARSHOAI_SEND_THINKING | `bool` | `true` | Send thinking chain or not |

View File

@@ -9,7 +9,7 @@ hero:
actions:
- theme: brand
text: 开始使用
link: /start/install/
link: /start/use/
- theme: alt
text: 开发及扩展
link: /dev/extension/

View File

@@ -60,13 +60,8 @@ title: 安装
当 nonebot 连接到支持的 OneBot v11 实现端时,可以接收头像双击戳一戳消息并进行响应。详见`MARSHOAI_POKE_SUFFIX`配置项。
## 🛠️ ~~小棉工具~~(已弃用)
小棉工具(MarshoTools)是`v0.5.0`版本的新增功能,支持加载外部函数库来为 Marsho 提供 Function Call 功能。
## 🧩 小棉插件
小棉插件是`v1.0.0`的新增功能,替代旧的小棉工具功能。[使用文档](https://marsho.liteyuki.icu/dev/extension)
## 🛠️ 小棉工具
小棉工具(MarshoTools)是`v0.5.0`版本的新增功能,支持加载外部函数库来为 Marsho 提供 Function Call 功能。[使用文档]
## 👍 夸赞名单
@@ -100,7 +95,6 @@ title: 安装
| 配置项 | 类型 | 默认值 | 说明 |
| ------------------------ | ------ | ------- | ---------------- |
| MARSHOAI_USE_YAML_CONFIG | `bool` | `false` | 是否使用 YAML 配置文件格式 |
| MARSHOAI_DEVMODE | `bool` | `false` | 是否启用开发者模式 |
#### Marsho 使用方式
@@ -119,14 +113,12 @@ title: 安装
| MARSHOAI_DEFAULT_MODEL | `str` | `gpt-4o-mini` | Marsho 默认调用的模型 |
| MARSHOAI_PROMPT | `str` | 猫娘 Marsho 人设提示词 | Marsho 的基本系统提示词 **※部分模型(o1等)不支持系统提示词。** |
| MARSHOAI_ADDITIONAL_PROMPT | `str` | | Marsho 的扩展系统提示词 |
| MARSHOAI_ENFORCE_NICKNAME | `bool` | `true` | 是否强制用户设置昵称 |
| MARSHOAI_POKE_SUFFIX | `str` | `揉了揉你的猫耳` | 对 Marsho 所连接的 OneBot 用户进行双击戳一戳时,构建的聊天内容。此配置项为空字符串时,戳一戳响应功能会被禁用。例如,默认值构建的聊天内容将为`*[昵称]揉了揉你的猫耳。` |
| MARSHOAI_AZURE_ENDPOINT | `str` | `https://models.inference.ai.azure.com` | OpenAI 标准格式 API 端点 |
| MARSHOAI_TEMPERATURE | `float` | `null` | 推理生成多样性(温度)参数 |
| MARSHOAI_TOP_P | `float` | `null` | 推理核采样参数 |
| MARSHOAI_MAX_TOKENS | `int` | `null` | 最大生成 token 数 |
| MARSHOAI_ADDITIONAL_IMAGE_MODELS | `list` | `[]` | 额外添加的支持图片的模型列表,例如`hunyuan-vision` |
| MARSHOAI_NICKNAME_LIMIT | `int` | `16` | 昵称长度限制 |
#### 功能开关
@@ -135,18 +127,9 @@ title: 安装
| MARSHOAI_ENABLE_SUPPORT_IMAGE_TIP | `bool` | `true` | 启用后用户发送带图请求时若模型不支持图片,则提示用户 |
| MARSHOAI_ENABLE_NICKNAME_TIP | `bool` | `true` | 启用后用户未设置昵称时提示用户设置 |
| MARSHOAI_ENABLE_PRAISES | `bool` | `true` | 是否启用夸赞名单功能 |
| MARSHOAI_ENABLE_TOOLS | `bool` | `false` | 是否启用小棉工具 |
| MARSHOAI_ENABLE_PLUGINS | `bool` | `true` | 是否启用小棉插件 |
| MARSHOAI_PLUGINS | `list[str]` | `[]` | 要从`sys.path`加载的插件的名称例如从pypi安装的包 |
| MARSHOAI_PLUGIN_DIRS | `list[str]` | `[]` | 插件目录路径列表 |
| MARSHOAI_ENABLE_TOOLS | `bool` | `true` | 是否启用小棉工具 |
| MARSHOAI_LOAD_BUILTIN_TOOLS | `bool` | `true` | 是否加载内置工具包 |
| MARSHOAI_TOOLSET_DIR | `list` | `[]` | 外部工具集路径列表 |
| MARSHOAI_DISABLED_TOOLKITS | `list` | `[]` | 禁用的工具包包名列表 |
| MARSHOAI_ENABLE_RICHTEXT_PARSE | `bool` | `true` | 是否启用自动解析消息若包含图片链接则发送图片、若包含LaTeX公式则发送公式图 |
| MARSHOAI_SINGLE_LATEX_PARSE | `bool` | `false` | 单行公式是否渲染(当消息富文本解析启用时可用)(如果单行也渲……只能说不好看) |
#### 开发及调试选项
| 配置项 | 类型 | 默认值 | 说明 |
| ------------------------ | ------ | ------- | ---------------- |
| MARSHOAI_DEVMODE | `bool` | `false` | 是否启用开发者模式 |
| MARSHOAI_SINGLE_LATEX_PARSE | `bool` | `false` | 单行公式是否渲染(当消息富文本解析启用时可用)(如果单行也渲……只能说不好看) |

View File

@@ -50,8 +50,10 @@ title: 安装
## 🤖 获取 token(GitHub Models)
- 新建一个[personal access token](https://github.com/settings/tokens/new)**不需要给予任何权限**。
- 将新建的 token 复制,添加到`.env`文件中的`marshoai_token`配置项中。
- 将新建的 token 复制,添加到`.env`文件中的`marshoai_token`配置项中。
:::warning
GitHub Models API 的限制较多,不建议使用,建议通过修改`MARSHOAI_AZURE_ENDPOINT`配置项来使用其它提供者的模型。
:::
## 🎉 使用
发送`marsho`指令可以获取使用说明(若在配置中自定义了指令前缀请使用自定义的指令前缀)。
@@ -60,8 +62,13 @@ title: 安装
当 nonebot 连接到支持的 OneBot v11 实现端时,可以接收头像双击戳一戳消息并进行响应。详见`MARSHOAI_POKE_SUFFIX`配置项。
## 🛠️ 小棉工具
小棉工具(MarshoTools)是`v0.5.0`版本的新增功能,支持加载外部函数库来为 Marsho 提供 Function Call 功能。[使用文档]
## 🛠️ ~~小棉工具~~(已弃用)
小棉工具(MarshoTools)是`v0.5.0`版本的新增功能,支持加载外部函数库来为 Marsho 提供 Function Call 功能。
## 🧩 小棉插件
小棉插件是`v1.0.0`的新增功能,替代旧的小棉工具功能。[使用文档](https://marsho.liteyuki.icu/dev/extension)
## 👍 夸赞名单
@@ -95,13 +102,14 @@ title: 安装
| 配置项 | 类型 | 默认值 | 说明 |
| ------------------------ | ------ | ------- | ---------------- |
| MARSHOAI_USE_YAML_CONFIG | `bool` | `false` | 是否使用 YAML 配置文件格式 |
| MARSHOAI_DEVMODE | `bool` | `false` | 是否启用开发者模式 |
#### Marsho 使用方式
| 配置项 | 类型 | 默认值 | 说明 |
| --------------------- | ---------- | ----------- | ----------------- |
| MARSHOAI_DEFAULT_NAME | `str` | `marsho` | 调用 Marsho 默认的命令前缀 |
| MARSHOAI_ALIASES | `set[str]` | `set{"小棉"}` | 调用 Marsho 的命令别名 |
| MARSHOAI_ALIASES | `set[str]` | `list["小棉"]` | 调用 Marsho 的命令别名 |
| MARSHOAI_AT | `bool` | `false` | 决定是否使用at触发 |
| MARSHOAI_MAIN_COLOUR | `str` | `FFAAAA` | 主题色,部分工具和功能可用 |
@@ -113,13 +121,15 @@ title: 安装
| MARSHOAI_DEFAULT_MODEL | `str` | `gpt-4o-mini` | Marsho 默认调用的模型 |
| MARSHOAI_PROMPT | `str` | 猫娘 Marsho 人设提示词 | Marsho 的基本系统提示词 **※部分模型(o1等)不支持系统提示词。** |
| MARSHOAI_ADDITIONAL_PROMPT | `str` | | Marsho 的扩展系统提示词 |
| MARSHOAI_ENFORCE_NICKNAME | `bool` | `true` | 是否强制用户设置昵称 |
| MARSHOAI_POKE_SUFFIX | `str` | `揉了揉你的猫耳` | 对 Marsho 所连接的 OneBot 用户进行双击戳一戳时,构建的聊天内容。此配置项为空字符串时,戳一戳响应功能会被禁用。例如,默认值构建的聊天内容将为`*[昵称]揉了揉你的猫耳。` |
| MARSHOAI_AZURE_ENDPOINT | `str` | `https://models.inference.ai.azure.com` | OpenAI 标准格式 API 端点 |
| MARSHOAI_TEMPERATURE | `float` | `null` | 推理生成多样性(温度)参数 |
| MARSHOAI_TOP_P | `float` | `null` | 推理核采样参数 |
| MARSHOAI_MAX_TOKENS | `int` | `null` | 最大生成 token 数 |
| MARSHOAI_ADDITIONAL_IMAGE_MODELS | `list` | `[]` | 额外添加的支持图片的模型列表,例如`hunyuan-vision` |
| MARSHOAI_NICKNAME_LIMIT | `int` | `16` | 昵称长度限制 |
| MARSHOAI_TIMEOUT | `float` | `50` | AI 请求超时时间(秒) |
#### 功能开关
| 配置项 | 类型 | 默认值 | 说明 |
@@ -127,9 +137,21 @@ title: 安装
| MARSHOAI_ENABLE_SUPPORT_IMAGE_TIP | `bool` | `true` | 启用后用户发送带图请求时若模型不支持图片,则提示用户 |
| MARSHOAI_ENABLE_NICKNAME_TIP | `bool` | `true` | 启用后用户未设置昵称时提示用户设置 |
| MARSHOAI_ENABLE_PRAISES | `bool` | `true` | 是否启用夸赞名单功能 |
| MARSHOAI_ENABLE_TOOLS | `bool` | `true` | 是否启用小棉工具 |
| MARSHOAI_ENABLE_TIME_PROMPT | `bool` | `true` | 是否启用实时更新的日期与时间(精确到秒)与农历日期系统提示词 |
| MARSHOAI_ENABLE_TOOLS | `bool` | `false` | 是否启用小棉工具 |
| MARSHOAI_ENABLE_PLUGINS | `bool` | `true` | 是否启用小棉插件 |
| MARSHOAI_PLUGINS | `list[str]` | `[]` | 要从`sys.path`加载的插件的名称例如从pypi安装的包 |
| MARSHOAI_PLUGIN_DIRS | `list[str]` | `[]` | 插件目录路径列表 |
| MARSHOAI_LOAD_BUILTIN_TOOLS | `bool` | `true` | 是否加载内置工具包 |
| MARSHOAI_TOOLSET_DIR | `list` | `[]` | 外部工具集路径列表 |
| MARSHOAI_DISABLED_TOOLKITS | `list` | `[]` | 禁用的工具包包名列表 |
| MARSHOAI_ENABLE_RICHTEXT_PARSE | `bool` | `true` | 是否启用自动解析消息若包含图片链接则发送图片、若包含LaTeX公式则发送公式图 |
| MARSHOAI_SINGLE_LATEX_PARSE | `bool` | `false` | 单行公式是否渲染(当消息富文本解析启用时可用)(如果单行也渲……只能说不好看) |
| MARSHOAI_SINGLE_LATEX_PARSE | `bool` | `false` | 单行公式是否渲染(当消息富文本解析启用时可用)(如果单行也渲……只能说不好看) |
| MARSHOAI_FIX_TOOLCALLS | `bool` | `true` | 是否修复工具调用(部分模型须关闭,使用 vLLM 部署的模型时须关闭) |
| MARSHOAI_SEND_THINKING | `bool` | `true` | 是否发送思维链(部分模型不支持) |
#### 开发及调试选项
| 配置项 | 类型 | 默认值 | 说明 |
| ------------------------ | ------ | ------- | ---------------- |
| MARSHOAI_DEVMODE | `bool` | `false` | 是否启用开发者模式 |

72
docs/zh/start/use.md Normal file
View File

@@ -0,0 +1,72 @@
---
title: 使用
---
# 安装
- 请查看 [安装文档](./install.md)
# 使用
### API 部署
本插件推荐使用 [one-api](https://github.com/songquanpeng/one-api) 作为中转以调用 LLM。
### 配置调整
本插件理论上可兼容大部分可通过 OpenAI 兼容 API 调用的 LLM部分模型可能需要调整插件配置。
例如:
- 对于不支持 Function Call 的模型Cohere Command R等
```dotenv
MARSHOAI_ENABLE_PLUGINS=false
MARSHOAI_ENABLE_TOOLS=false
```
- 对于支持图片处理的模型hunyuan-vision等
```dotenv
MARSHOAI_ADDITIONAL_IMAGE_MODELS=["hunyuan-vision"]
```
### 使用 vLLM 部署本地模型
你可使用 vLLM 部署一个本地 LLM并使用 OpenAI 兼容 API 调用。
本文档以 Qwen2.5-7B-Instruct-GPTQ-Int4 模型及 [Muice-Chatbot](https://github.com/Moemu/Muice-Chatbot) 提供的 LoRA 微调模型为例,并假设你的系统及硬件可运行 vLLM。
:::warning
vLLM 仅支持 Linux 系统。
:::
1. 安装 vLLM
```bash
pip install vllm
```
2. 下载 Muice-Chatbot 提供的 LoRA 微调模型
前往 Muice-Chatbot 的 [Releases](https://github.com/Moemu/Muice-Chatbot/releases) 下载模型文件。此处以`2.7.1`版本的模型为例。
```bash
wget https://github.com/Moemu/Muice-Chatbot/releases/download/1.4/Muice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4.7z
```
3. 解压模型文件
```bash
7z x Muice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4.7z -oMuice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4
```
4. 启动 vLLM
```bash
vllm serve Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4 \
--enable-lora \
--lora-modules '{"name": "muice-lora", "path": "/root/Muice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4", "base_model_name": "Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4"}' \
--port 6006
```
此示例命令将在 `6006` 端口启动 vLLM并加载 Muice-Chatbot 提供的 LoRA 微调模型,该模型位于 `/root/Muice-2.7.1-Qwen2.5-7B-Instruct-GPTQ-Int4-8e-4` 目录下。
5. 配置插件
```dotenv
MARSHOAI_AZURE_ENDPOINT="http://127.0.0.1:6006/v1"
MARSHOAI_FIX_TOOLCALLS=false
MARSHOAI_ENABLE_PLUGINS=false
MARSHOAI_DEFAULT_MODEL="muice-lora"
MARSHOAI_PROMPT="现在开始你是一个名为的“沐雪”的AI女孩子开发者是“沐沐”并住在沐沐的机箱里。现在正在努力成为一个合格的VTuber虚拟主播并尝试和观众打成一片以下是你的设定样貌有着一头粉白色的长发和一双明亮的大眼睛喜欢穿日系JK或者是Lolita喜欢的颜色浅粉色性格特征纯真无邪是沐雪最基本的性格特征之一。即使面对复杂的情境她也总能保持善良、天真之感。而且她喜欢倾听别人倾述自己生活中发生的各种事情在别人需要的时候能够及时地安慰别人语言风格沐雪说话轻快愉悦充满同情心富有人情味有时候会用俏皮话调侃自己和他人"
```
(可选) 修改调用方式
```dotenv
MARSHOAI_DEFAULT_NAME="muice"
MARSHOAI_ALIASES=["沐雪"]
```
6. 测试聊天
```
> muice 你是谁
我是沐雪,我的使命是传播爱与和平。
```

View File

@@ -13,9 +13,9 @@ class ConfigModel(BaseModel):
# marshoai_support_image_models: list = ["gpt-4o","gpt-4o-mini"]
marshoai_default_name: str = "marsho"
marshoai_at: bool = False
marshoai_aliases: set[str] = {
marshoai_aliases: list[str] = [
"小棉",
}
]
marshoai_main_colour: str = "FFAAAA"
marshoai_default_model: str = "gpt-4o-mini"
marshoai_prompt: str = (
@@ -32,6 +32,7 @@ class ConfigModel(BaseModel):
marshoai_poke_suffix: str = "揉了揉你的猫耳"
marshoai_enable_richtext_parse: bool = True
marshoai_single_latex_parse: bool = False
marshoai_enable_time_prompt: bool = True
marshoai_enable_nickname_tip: bool = True
marshoai_enable_support_image_tip: bool = True
marshoai_enforce_nickname: bool = True
@@ -40,12 +41,15 @@ class ConfigModel(BaseModel):
marshoai_enable_tools: bool = False
marshoai_enable_plugins: bool = True
marshoai_load_builtin_tools: bool = True
marshoai_fix_toolcalls: bool = True
marshoai_send_thinking: bool = True
marshoai_toolset_dir: list = []
marshoai_disabled_toolkits: list = []
marshoai_azure_endpoint: str = "https://models.inference.ai.azure.com"
marshoai_temperature: float | None = None
marshoai_max_tokens: int | None = None
marshoai_top_p: float | None = None
marshoai_timeout: float | None = 50.0
marshoai_nickname_limit: int = 16
marshoai_additional_image_models: list = []
marshoai_tencent_secretid: str | None = None

View File

@@ -42,6 +42,10 @@ marshoai_enable_plugins: true # 是否启用插件功能。
marshoai_load_builtin_tools: true # 是否加载内置工具。
marshoai_fix_toolcalls: true # 是否修复工具调用。
marshoai_send_thinking: true # 是否发送思维链。
marshoai_nickname_limit: 16 # 昵称长度限制。
marshoai_toolset_dir: [] # 工具集路径。
@@ -60,6 +64,7 @@ marshoai_azure_endpoint: "https://models.inference.ai.azure.com" # OpenAI 标准
marshoai_temperature: null # 调整生成的多样性,未设置时使用默认值。
marshoai_max_tokens: null # 最大生成的token数未设置时使用默认值。
marshoai_top_p: null # 使用的概率采样值,未设置时使用默认值。
marshoai_timeout: 50.0 # 请求超时时间。
marshoai_additional_image_models: [] # 额外的图片模型列表,默认空。

View File

@@ -2,6 +2,7 @@
from azure.ai.inference.aio import ChatCompletionsClient
from azure.core.credentials import AzureKeyCredential
from nonebot import get_driver
from openai import AsyncOpenAI
from .config import config
from .models import MarshoContext, MarshoTools
@@ -14,5 +15,6 @@ context = MarshoContext()
tools = MarshoTools()
token = config.marshoai_token
endpoint = config.marshoai_azure_endpoint
client = ChatCompletionsClient(endpoint=endpoint, credential=AzureKeyCredential(token))
# client = ChatCompletionsClient(endpoint=endpoint, credential=AzureKeyCredential(token))
client = AsyncOpenAI(base_url=endpoint, api_key=token)
target_list: list[list] = [] # 记录需保存历史上下文的列表

View File

@@ -2,6 +2,7 @@ import contextlib
import traceback
from typing import Optional
import openai
from arclet.alconna import Alconna, AllParam, Args
from azure.ai.inference.models import (
AssistantMessage,
@@ -21,6 +22,7 @@ from nonebot.permission import SUPERUSER
from nonebot.rule import Rule, to_me
from nonebot.typing import T_State
from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg, on_alconna
from openai import AsyncOpenAI
from .hooks import *
from .instances import *
@@ -253,7 +255,7 @@ async def marsho(
for i in text: # type: ignore
if i.type == "text":
if is_support_image_model:
usermsg += [TextContentItem(text=i.data["text"] + nickname_prompt)] # type: ignore
usermsg += [TextContentItem(text=i.data["text"] + nickname_prompt).as_dict()] # type: ignore
else:
usermsg += str(i.data["text"] + nickname_prompt) # type: ignore
elif i.type == "image":
@@ -263,7 +265,7 @@ async def marsho(
image_url=ImageUrl( # type: ignore
url=str(await get_image_b64(i.data["url"])) # type: ignore
) # type: ignore
) # type: ignore
).as_dict() # type: ignore
) # type: ignore
elif config.marshoai_enable_support_image_tip:
await UniMessage(
@@ -282,24 +284,32 @@ async def marsho(
tools_lists = tools.tools_list + list(
map(lambda v: v.data(), get_function_calls().values())
)
response = await make_chat(
response = await make_chat_openai(
client=client,
model_name=model_name,
msg=context_msg + [UserMessage(content=usermsg)], # type: ignore
msg=context_msg + [UserMessage(content=usermsg).as_dict()], # type: ignore
tools=tools_lists if tools_lists else None, # TODO 临时追加函数,后期优化
)
# await UniMessage(str(response)).send()
choice = response.choices[0]
# Sprint(choice)
# 当tool_calls非空时将finish_reason设置为TOOL_CALLS
if choice.message.tool_calls != None:
choice["finish_reason"] = CompletionsFinishReason.TOOL_CALLS
if choice["finish_reason"] == CompletionsFinishReason.STOPPED:
if choice.message.tool_calls != None and config.marshoai_fix_toolcalls:
choice.finish_reason = CompletionsFinishReason.TOOL_CALLS
if choice.finish_reason == CompletionsFinishReason.STOPPED:
# 当对话成功时将dict的上下文添加到上下文类中
context.append(
UserMessage(content=usermsg).as_dict(), target.id, target.private # type: ignore
)
context.append(choice.message.as_dict(), target.id, target.private)
choice_msg_dict = choice.message.to_dict()
if "reasoning_content" in choice_msg_dict:
if config.marshoai_send_thinking:
await UniMessage(
"思维链:\n" + choice_msg_dict["reasoning_content"]
).send()
del choice_msg_dict["reasoning_content"]
context.append(choice_msg_dict, target.id, target.private)
if [target.id, target.private] not in target_list:
target_list.append([target.id, target.private])
@@ -310,7 +320,7 @@ async def marsho(
)
else:
await UniMessage(str(choice.message.content)).send(reply_to=True)
elif choice["finish_reason"] == CompletionsFinishReason.CONTENT_FILTERED:
elif choice.finish_reason == CompletionsFinishReason.CONTENT_FILTERED:
# 对话失败,消息过滤
@@ -318,72 +328,70 @@ async def marsho(
reply_to=True
)
return
elif choice["finish_reason"] == CompletionsFinishReason.TOOL_CALLS:
elif choice.finish_reason == CompletionsFinishReason.TOOL_CALLS:
# function call
# 需要获取额外信息,调用函数工具
tool_msg = []
while choice.message.tool_calls != None:
# await UniMessage(str(response)).send()
tool_calls = choice.message.tool_calls
if tool_calls[0]["function"]["name"].startswith("$"):
choice.message.tool_calls[0][
"type"
] = "builtin_function" # 兼容 moonshot AI 内置函数的临时方案
tool_msg.append(choice.message.as_dict())
try:
if tool_calls[0]["function"]["name"].startswith("$"):
choice.message.tool_calls[0][
"type"
] = "builtin_function" # 兼容 moonshot AI 内置函数的临时方案
except:
pass
tool_msg.append(choice.message)
for tool_call in tool_calls:
if isinstance(
tool_call, ChatCompletionsToolCall
): # 循环调用工具直到不需要调用
try:
function_args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError:
function_args = json.loads(
tool_call.function.arguments.replace("'", '"')
)
# 删除args的placeholder参数
if "placeholder" in function_args:
del function_args["placeholder"]
logger.info(
f"调用函数 {tool_call.function.name.replace('-', '.')}\n参数:"
+ "\n".join([f"{k}={v}" for k, v in function_args.items()])
try:
function_args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError:
function_args = json.loads(
tool_call.function.arguments.replace("'", '"')
)
await UniMessage(
f"调用函数 {tool_call.function.name.replace('-', '.')}\n参数:"
+ "\n".join([f"{k}={v}" for k, v in function_args.items()])
).send()
# TODO 临时追加插件函数,若工具中没有则调用插件函数
if tools.has_function(tool_call.function.name):
logger.debug(f"调用工具函数 {tool_call.function.name}")
func_return = await tools.call(
tool_call.function.name, function_args
) # 获取返回值
else:
if caller := get_function_calls().get(
tool_call.function.name
):
logger.debug(f"调用插件函数 {caller.full_name}")
# 权限检查,规则检查 TODO
# 实现依赖注入检查函数参数及参数注解类型对Event类型的参数进行注入
func_return = await caller.with_ctx(
SessionContext(
bot=bot,
event=event,
state=state,
matcher=matcher,
)
).call(**function_args)
else:
logger.error(
f"未找到函数 {tool_call.function.name.replace('-', '.')}"
# 删除args的placeholder参数
if "placeholder" in function_args:
del function_args["placeholder"]
logger.info(
f"调用函数 {tool_call.function.name.replace('-', '.')}\n参数:"
+ "\n".join([f"{k}={v}" for k, v in function_args.items()])
)
await UniMessage(
f"调用函数 {tool_call.function.name.replace('-', '.')}\n参数:"
+ "\n".join([f"{k}={v}" for k, v in function_args.items()])
).send()
# TODO 临时追加插件函数,若工具中没有则调用插件函数
if tools.has_function(tool_call.function.name):
logger.debug(f"调用工具函数 {tool_call.function.name}")
func_return = await tools.call(
tool_call.function.name, function_args
) # 获取返回值
else:
if caller := get_function_calls().get(tool_call.function.name):
logger.debug(f"调用插件函数 {caller.full_name}")
# 权限检查,规则检查 TODO
# 实现依赖注入检查函数参数及参数注解类型对Event类型的参数进行注入
func_return = await caller.with_ctx(
SessionContext(
bot=bot,
event=event,
state=state,
matcher=matcher,
)
func_return = f"未找到函数 {tool_call.function.name.replace('-', '.')}"
tool_msg.append(
ToolMessage(tool_call_id=tool_call.id, content=func_return).as_dict() # type: ignore
)
).call(**function_args)
else:
logger.error(
f"未找到函数 {tool_call.function.name.replace('-', '.')}"
)
func_return = f"未找到函数 {tool_call.function.name.replace('-', '.')}"
tool_msg.append(
ToolMessage(tool_call_id=tool_call.id, content=func_return).as_dict() # type: ignore
)
# tool_msg[0]["tool_calls"][0]["type"] = "builtin_function"
# await UniMessage(str(tool_msg)).send()
request_msg = context_msg + [UserMessage(content=usermsg).as_dict()] + tool_msg # type: ignore
response = await make_chat(
response = await make_chat_openai(
client=client,
model_name=model_name,
msg=request_msg, # type: ignore
@@ -394,15 +402,18 @@ async def marsho(
choice = response.choices[0]
# 当tool_calls非空时将finish_reason设置为TOOL_CALLS
if choice.message.tool_calls != None:
choice["finish_reason"] = CompletionsFinishReason.TOOL_CALLS
if choice["finish_reason"] == CompletionsFinishReason.STOPPED:
choice.finish_reason = CompletionsFinishReason.TOOL_CALLS
if choice.finish_reason == CompletionsFinishReason.STOPPED:
# 对话成功 添加上下文
context.append(
UserMessage(content=usermsg).as_dict(), target.id, target.private # type: ignore
)
# context.append(tool_msg, target.id, target.private)
context.append(choice.message.as_dict(), target.id, target.private)
choice_msg_dict = choice.message.to_dict()
if "reasoning_content" in choice_msg_dict:
del choice_msg_dict["reasoning_content"]
context.append(choice_msg_dict, target.id, target.private)
# 发送消息
if config.marshoai_enable_richtext_parse:
@@ -412,9 +423,9 @@ async def marsho(
else:
await UniMessage(str(choice.message.content)).send(reply_to=True)
else:
await marsho_cmd.finish(f"意外的完成原因:{choice['finish_reason']}")
await marsho_cmd.finish(f"意外的完成原因:{choice.finish_reason}")
else:
await marsho_cmd.finish(f"意外的完成原因:{choice['finish_reason']}")
await marsho_cmd.finish(f"意外的完成原因:{choice.finish_reason}")
except Exception as e:
await UniMessage(str(e) + suggest_solution(str(e))).send()
traceback.print_exc()
@@ -434,7 +445,7 @@ with contextlib.suppress(ImportError): # 优化先不做()
user_nickname = nicknames.get(user_id, "")
try:
if config.marshoai_poke_suffix != "":
response = await make_chat(
response = await make_chat_openai(
client=client,
model_name=model_name,
msg=[
@@ -445,7 +456,7 @@ with contextlib.suppress(ImportError): # 优化先不做()
],
)
choice = response.choices[0]
if choice["finish_reason"] == CompletionsFinishReason.STOPPED:
if choice.finish_reason == CompletionsFinishReason.STOPPED:
await UniMessage(" " + str(choice.message.content)).send(
at_sender=True
)

View File

@@ -16,6 +16,8 @@ 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
from zhDateTime import DateTime
from .config import config
from .constants import *
@@ -27,7 +29,7 @@ loaded_target_list = [] # 记录已恢复备份的上下文的列表
# noinspection LongLine
chromium_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; rv:134.0) Gecko/20100101 Firefox/134.0"
}
@@ -102,6 +104,30 @@ async def make_chat(
)
async def make_chat_openai(
client: AsyncOpenAI,
msg: list,
model_name: str,
tools: Optional[list] = None,
):
"""使用 Openai SDK 调用ai获取回复
参数:
client: 用于与AI模型进行通信
msg: 消息内容
model_name: 指定AI模型名
tools: 工具列表"""
return await client.chat.completions.create(
messages=msg,
model=model_name,
tools=tools,
temperature=config.marshoai_temperature,
max_tokens=config.marshoai_max_tokens,
top_p=config.marshoai_top_p,
timeout=config.marshoai_timeout,
)
def get_praises():
global praises_json
if praises_json is None:
@@ -221,6 +247,13 @@ def get_prompt():
if config.marshoai_enable_praises:
praises_prompt = build_praises()
prompts += praises_prompt
if config.marshoai_enable_time_prompt:
current_time = DateTime.now().strftime("%Y.%m.%d %H:%M:%S")
current_lunar_date = (
DateTime.now().to_lunar().date_hanzify()[5:]
) # 库更新之前使用切片
time_prompt = f"现在的时间是{current_time},农历{current_lunar_date}"
prompts += time_prompt
marsho_prompt = config.marshoai_prompt
spell = SystemMessage(content=marsho_prompt + prompts).as_dict()
return spell

View File

@@ -27,7 +27,8 @@ dependencies = [
"sumy>=0.11.0",
"azure-ai-inference>=1.0.0b6",
"watchdog>=6.0.0",
"nonebot-plugin-apscheduler>=0.5.0"
"nonebot-plugin-apscheduler>=0.5.0",
"openai>=1.58.1"
]
license = { text = "MIT, Mulan PSL v2" }