mirror of
https://github.com/LiteyukiStudio/nonebot-plugin-marshoai.git
synced 2025-12-24 14:26:42 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 075a529aa1 | |||
|
|
c7e55cc803 | ||
|
|
ef61b4c192 | ||
|
|
72839b68c5 | ||
|
|
d30a7d1113 | ||
|
|
56aa21e279 | ||
| bd6893ea1e | |||
|
|
6844f034c8 | ||
| 47d3df3ad5 | |||
| d6e80d3120 | |||
| b3fe293df1 |
@@ -168,7 +168,7 @@ _✨ 使用 OpenAI 标准格式 API 的聊天机器人插件 ✨_
|
||||
| MARSHOAI_ENABLE_PRAISES | `bool` | `true` | 是否启用夸赞名单功能 |
|
||||
| MARSHOAI_ENABLE_TOOLS | `bool` | `true` | 是否启用小棉工具 |
|
||||
| MARSHOAI_LOAD_BUILTIN_TOOLS | `bool` | `true` | 是否加载内置工具包 |
|
||||
|
||||
| MARSHOAI_TOOLSET_DIR | `list` | `[]` | 外部工具集路径列表 |
|
||||
|
||||
## ❤ 鸣谢&版权说明
|
||||
|
||||
|
||||
193
README_EN.md
Normal file
193
README_EN.md
Normal file
@@ -0,0 +1,193 @@
|
||||
<!--suppress LongLine -->
|
||||
<div align="center">
|
||||
<a href="https://v2.nonebot.dev/store"><img src="https://raw.githubusercontent.com/LiteyukiStudio/nonebot-plugin-marshoai/refs/heads/main/resources/marsho-new.svg" width="800" height="430" alt="NoneBotPluginLogo"></a>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
# nonebot-plugin-marshoai
|
||||
|
||||
_✨ A chat bot plugin which use OpenAI standard API ✨_
|
||||
|
||||
<a href="./LICENSE">
|
||||
<img src="https://img.shields.io/github/license/LiteyukiStudio/nonebot-plugin-marshoai.svg" alt="license">
|
||||
</a>
|
||||
<a href="https://pypi.python.org/pypi/nonebot-plugin-marshoai">
|
||||
<img src="https://img.shields.io/pypi/v/nonebot-plugin-marshoai.svg" alt="pypi">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
|
||||
|
||||
</div>
|
||||
|
||||
## 📖 Indroduction
|
||||
|
||||
A plugin made by call OpenAI standard API(Such as GitHub Models API)
|
||||
|
||||
Plugin internally installed the catgirl character of Marsho, is able to have a cute conversation!
|
||||
|
||||
*Who don't like a cute catgirl with fast answer speed?*
|
||||
|
||||
**Support for adapters other than OneBot and non-Github Models APIs is not fully verified.**
|
||||
|
||||
[Melobot implementation](https://github.com/LiteyukiStudio/marshoai-melo)
|
||||
|
||||
## 🐱 Character setting
|
||||
|
||||
#### Basic information
|
||||
|
||||
- Name : Marsho
|
||||
- Birthday : September 6th
|
||||
|
||||
#### Hobbies
|
||||
|
||||
- 🌞 Melt in sunshine
|
||||
- 🤱 Coquetry~ who don't like that~
|
||||
- 🍫 Eating snacks! Meat is yummy!
|
||||
- 🐾 Play! I like play with friends!
|
||||
|
||||
## 💿 Install
|
||||
|
||||
<details open>
|
||||
<summary>Install with nb-cli</summary>
|
||||
|
||||
Open shell under the root directory of nonebot2, input the command below.
|
||||
|
||||
nb plugin install nonebot-plugin-marshoai
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Install with pack manager</summary>
|
||||
|
||||
Open shell under the plugin directory of nonebot2, input corresponding command according to your pack manager.
|
||||
|
||||
<details>
|
||||
<summary>pip</summary>
|
||||
|
||||
pip install nonebot-plugin-marshoai
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>pdm</summary>
|
||||
|
||||
pdm add nonebot-plugin-marshoai
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>poetry</summary>
|
||||
|
||||
poetry add nonebot-plugin-marshoai
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary>conda</summary>
|
||||
|
||||
conda install nonebot-plugin-marshoai
|
||||
|
||||
</details>
|
||||
|
||||
Open the `pyproject.toml` file under nonebot2's root directory, Add to`[tool.nonebot]`.
|
||||
|
||||
plugins = ["nonebot_plugin_marshoai"]
|
||||
|
||||
</details>
|
||||
|
||||
## 🤖 Get token(GitHub Models)
|
||||
|
||||
- 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.
|
||||
|
||||
## 🎉 Usage
|
||||
|
||||
End `marsho` in order to get direction for use(If you configured the custom command, please use the configured one).
|
||||
|
||||
#### 👉 Double click avatar
|
||||
|
||||
When nonebot linked to OneBot v11 adapter, can recieve double click and response to it. More detail in the `MARSHOAI_POKE_SUFFIX` option.
|
||||
|
||||
## 🛠️ MarshoTools
|
||||
|
||||
MarshoTools is a feature added in `v0.5.0`, support loading external function library to provide Function Call for Marsho. [Documentation](./README_TOOLS_EN.md)
|
||||
|
||||
## 👍 Praise list
|
||||
|
||||
Praise list stored in the `praises.json` in plugin directory(This directory will putput to log when Bot start), it'll automatically generate when option is `true`, include character name and advantage two basic data.
|
||||
|
||||
The character stored in it would be “know” and “like” by Marsho.
|
||||
|
||||
It's structure is similar to:
|
||||
|
||||
```json
|
||||
{
|
||||
"like": [
|
||||
{
|
||||
"name": "Asankilp",
|
||||
"advantages": "赋予了Marsho猫娘人格,使用vim与vscode为Marsho写了许多代码,使Marsho更加可爱"
|
||||
},
|
||||
{
|
||||
"name": "神羽(snowykami)",
|
||||
"advantages": "人脉很广,经常找小伙伴们开银趴,很会写后端代码"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## ⚙️ Configurable options
|
||||
|
||||
Add options in the `.env` file from the diagram below in nonebot2 project.
|
||||
|
||||
#### plugin behaviour
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| ------------------------ | ------ | ------- | ---------------- |
|
||||
| MARSHOAI_USE_YAML_CONFIG | `bool` | `false` | Use YAML config format |
|
||||
|
||||
#### Marsho usage
|
||||
|
||||
| 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_AT | `bool` | `false` | Call by @ or not |
|
||||
|
||||
#### AI call
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| -------------------------------- | ------- | --------------------------------------- | --------------------------------------------------------------------------------------------- |
|
||||
| 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_ADDITIONAL_PROMPT | `str` | | Marsho's external system prompt |
|
||||
| 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 `*[昵称]揉了揉你的猫耳。` |
|
||||
| MARSHOAI_AZURE_ENDPOINT | `str` | `https://models.inference.ai.azure.com` | OpenAI standard API |
|
||||
| MARSHOAI_TEMPERATURE | `float` | `null` | temperature parameter |
|
||||
| MARSHOAI_TOP_P | `float` | `null` | Nucleus Sampling parameter |
|
||||
| MARSHOAI_MAX_TOKENS | `int` | `null` | Max token number |
|
||||
| MARSHOAI_ADDITIONAL_IMAGE_MODELS | `list` | `[]` | External image-support model list, such as `hunyuan-vision` |
|
||||
|
||||
#### Feature Switches
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| --------------------------------- | ------ | ------ | -------------------------- |
|
||||
| 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_TOOLS | `bool` | `true` | Turn on Marsho Tools or not |
|
||||
| MARSHOAI_LOAD_BUILTIN_TOOLS | `bool` | `true` | Loading the built-in tool pack or not |
|
||||
|
||||
|
||||
## ❤ Thanks&Copyright
|
||||
|
||||
"Marsho" logo contributed by [@Asankilp](https://github.com/Asankilp),
|
||||
licensed under [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) lisense.
|
||||
|
||||
"nonebot-plugin-marshoai" is licensed under [MIT](./LICENSE) license.
|
||||
|
||||
## 🕊️ TODO
|
||||
|
||||
- [x] [Melobot](https://github.com/Meloland/melobot) implementation
|
||||
- [x] Congize chat initiator(know who are chatting with Marsho) (Initially implement)
|
||||
- [ ] Optimize API (Not only GitHub Models)
|
||||
- [ ] Persistent storage context by database
|
||||
90
README_TOOLS_EN.md
Normal file
90
README_TOOLS_EN.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 🛠️MarshoTools
|
||||
MarshoTools is a simple module loader. It allows to load kits and its function from `tools` in plugin directory, for AI to use.
|
||||
More information for Function Call, please refr to [OpenAI Offical Documentation](https://platform.openai.com/docs/guides/function-calling)
|
||||
|
||||
## ✍️ Writing Tools
|
||||
### 📁 Directory Structure
|
||||
`tools` in plugin directory is called **Toolset**. It contains many **Toolkit**, Toolkit is similar with **Packages** in Python in structure. It need `__init__.py` file and `tools.json` definition file in it. They are used to store and define functions.
|
||||
|
||||
A directory structure of Toolkit:
|
||||
```
|
||||
tools/ # Toolset Directory
|
||||
└── marshoai-example/ # Toolkit Directory, Named as Packages' name
|
||||
└── __init__.py # Tool Module
|
||||
└── tools.json # Function Definition File
|
||||
```
|
||||
In this directory tree:
|
||||
- **Toolset Directory** is named as `marshoai-xxxxx`, its name is the name of Toolset. Please follow this naming standard.
|
||||
- ***Tool Module* could contain many callable **Asynchronous** function, it could have parameters or be parameterless and the return value should be supported by AI model. Generally speaking, the `str` type is accepted to most model.
|
||||
- **Function Definition File** is for AI model to know how to call these function.
|
||||
### Function Writing
|
||||
Let's write a function for getting the weather and one for getting time.
|
||||
###### **\_\_init\_\_.py**
|
||||
```python
|
||||
from datetime import datetime
|
||||
|
||||
async def get_weather(location: str):
|
||||
return f"The temperature of {location} is 114514℃。" #To simulate the return value of weather.
|
||||
|
||||
async def get_current_time():
|
||||
current_time = datetime.now().strftime("%Y.%m.%d %H:%M:%S")
|
||||
time_prompt = f"Now is {current_time}。"
|
||||
return time_prompt
|
||||
```
|
||||
In this example, we define two functions, `get_weather` and `get_current_time`. The former accepts a `str` typed parameter. Let AI to know the existence of these funxtions, you shuold write **Function Definition File**
|
||||
###### **tools.json**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "marshoai-example__get_weather", # Function Call Name
|
||||
"description": "Get the weather of a specified locatin.", # Description, it need to descripte the usage of this Functin
|
||||
"parameters": { # Define the parameters
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": { # 'location' is the name that _init__.py had defined.
|
||||
"type": "string", # the Type of patameters
|
||||
"description": "City or district. Such as Beijing, Hangzhou, Yuhang District" # Description,it need to descripte the type or example of Actual Parameter
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [ # Define the Required Parameters
|
||||
"location"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "marshoai-example__get_current_time",
|
||||
"description": "Get time",
|
||||
"parameters": {} # No parameter requried, so it is blanked
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
In this file, we defined tow function. This Function Definition File will be typed into AI model, for letting AI to know when and how to call these function.
|
||||
**Function Call Name** is specific required. Using weather-getting as an example, this Function Call Name, `marshoai-example__get_weather`, contain these information.
|
||||
- **marshoai-example** is the name of its Toolkit.
|
||||
- **get_weather** is the name of function.
|
||||
- Two **underscores** are used as a separator.
|
||||
|
||||
Using this Naming Standard, it could be compatible with more APIs in the standard format of OpenAI. So don't use two underscores as the name of Toolkit or Function.
|
||||
### Function Testing
|
||||
After developing the Tools, start the Bot. There loading information of Toolkit in Nonebot Logs.
|
||||
This is the test example:
|
||||
```
|
||||
> marsho What's the weather like in Shenzhen?
|
||||
Meow! The temperature in Shenzhen is currently an astonishing 114514°C! That's super hot! Make sure to keep cool and stay hydrated! 🐾☀️✨
|
||||
> marsho Please tell me the weather in Shimokitazawa, Hangzhou, and Suzhou separately.
|
||||
Meow! Here's the weather for each place:
|
||||
|
||||
- Shimokitazawa: The temperature is 114514°C.
|
||||
- Hangzhou: The temperature is also 114514°C.
|
||||
- Suzhou: The temperature is again 114514°C.
|
||||
|
||||
That's super hot everywhere! Please stay cool and take care! 🐾☀️✨
|
||||
> marsho What time is it now?
|
||||
Meow! The current time is 1:15 PM on November 26, 2024. 🐾✨
|
||||
```
|
||||
@@ -80,9 +80,13 @@ target_list = [] # 记录需保存历史上下文的列表
|
||||
async def _preload_tools():
|
||||
tools_dir = store.get_plugin_data_dir() / "tools"
|
||||
os.makedirs(tools_dir, exist_ok=True)
|
||||
if config.marshoai_load_builtin_tools:
|
||||
tools.load_tools(Path(__file__).parent / "tools")
|
||||
tools.load_tools(store.get_plugin_data_dir() / "tools")
|
||||
if config.marshoai_enable_tools:
|
||||
if config.marshoai_load_builtin_tools:
|
||||
tools.load_tools(Path(__file__).parent / "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。")
|
||||
|
||||
|
||||
@add_usermsg_cmd.handle()
|
||||
@@ -250,11 +254,11 @@ async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None)
|
||||
while choice.message.tool_calls != None:
|
||||
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):
|
||||
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)
|
||||
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,
|
||||
@@ -263,12 +267,17 @@ async def marsho(target: MsgTarget, event: Event, text: Optional[UniMsg] = None)
|
||||
tools=tools.get_tools_list()
|
||||
)
|
||||
choice = response.choices[0]
|
||||
context.append(
|
||||
UserMessage(content=usermsg).as_dict(), target.id, target.private
|
||||
)
|
||||
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(choice.message.as_dict(), target.id, target.private)
|
||||
await UniMessage(str(choice.message.content)).send(reply_to=True)
|
||||
context.append(choice.message.as_dict(), target.id, target.private)
|
||||
await UniMessage(str(choice.message.content)).send(reply_to=True)
|
||||
else:
|
||||
await marsho_cmd.finish(f"意外的完成原因:{choice['finish_reason']}")
|
||||
else:
|
||||
await marsho_cmd.finish(f"意外的完成原因:{choice['finish_reason']}")
|
||||
except Exception as e:
|
||||
await UniMessage(str(e) + suggest_solution(str(e))).send()
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -33,6 +33,7 @@ class ConfigModel(BaseModel):
|
||||
marshoai_enable_time_prompt: bool = True
|
||||
marshoai_enable_tools: bool = True
|
||||
marshoai_load_builtin_tools: bool = True
|
||||
marshoai_toolset_dir: list = []
|
||||
marshoai_azure_endpoint: str = "https://models.inference.ai.azure.com"
|
||||
marshoai_temperature: float | None = None
|
||||
marshoai_max_tokens: int | None = None
|
||||
@@ -117,4 +118,4 @@ 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。")
|
||||
|
||||
@@ -34,6 +34,8 @@ marshoai_enable_tools: true # 是否启用工具支持。
|
||||
|
||||
marshoai_load_builtin_tools: true # 是否加载内置工具。
|
||||
|
||||
marshoai_toolset_dir: [] # 工具集路径。
|
||||
|
||||
marshoai_azure_endpoint: "https://models.inference.ai.azure.com" # OpenAI 标准格式 API 的端点。
|
||||
|
||||
# 模型参数配置
|
||||
|
||||
@@ -87,7 +87,7 @@ class MarshoTools:
|
||||
package = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(package)
|
||||
self.imported_packages[package_name] = package
|
||||
logger.info(f"成功加载工具包 {package_name}")
|
||||
logger.success(f"成功加载工具包 {package_name}")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"解码 JSON {json_path} 时发生错误: {e}")
|
||||
except Exception as e:
|
||||
@@ -114,10 +114,10 @@ class MarshoTools:
|
||||
try:
|
||||
function = getattr(package, function_name)
|
||||
return await function(**args)
|
||||
except AttributeError:
|
||||
logger.error(f"函数 '{function_name}' 在 '{package_name}' 中找不到。")
|
||||
except TypeError as e:
|
||||
logger.error(f"调用函数 '{function_name}' 时发生错误: {e}")
|
||||
except Exception as e:
|
||||
errinfo = f"调用函数 '{function_name}'时发生错误:{e}"
|
||||
logger.error(errinfo)
|
||||
return errinfo
|
||||
else:
|
||||
logger.error(f"工具包 '{package_name}' 未导入")
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ dependencies = [
|
||||
"zhDatetime>=1.1.1",
|
||||
"aiohttp>=3.9",
|
||||
"httpx>=0.27.0",
|
||||
"ruamel.yaml>=0.18.6"
|
||||
"ruamel.yaml>=0.18.6",
|
||||
"pyyaml>=6.0.2"
|
||||
|
||||
]
|
||||
license = { text = "MIT" }
|
||||
|
||||
Reference in New Issue
Block a user