Compare commits

...

11 Commits

Author SHA1 Message Date
075a529aa1 新增加载外部工具集的配置项,修复依赖 2024-11-27 13:38:11 +08:00
Akarin~
c7e55cc803 Merge pull request #13 from Twisuki/main
新增了README_TOOLS的英文版
2024-11-26 22:38:58 +08:00
Nya_Twisuki
ef61b4c192 Update README_TOOLS_EN.md 2024-11-26 22:37:36 +08:00
Nya_Twisuki
72839b68c5 Update README_TOOLS_EN.md 2024-11-26 22:34:41 +08:00
Nya_Twisuki
d30a7d1113 Update README_TOOLS_EN.md 2024-11-26 21:17:39 +08:00
Nya_Twisuki
56aa21e279 新增了README_TOOLS的英文版 2024-11-26 21:07:05 +08:00
bd6893ea1e ✍️更新readme_en 2024-11-26 18:01:30 +08:00
Akarin~
6844f034c8 Merge pull request #12 from yuhan2680/main
呐,英文版
2024-11-26 17:14:31 +08:00
47d3df3ad5 Update English version 2024-11-26 14:09:49 +08:00
d6e80d3120 Update README_EN.md 2024-11-26 14:08:30 +08:00
b3fe293df1 Create README_EN.md 2024-11-26 13:00:04 +08:00
8 changed files with 314 additions and 18 deletions

View File

@@ -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
View 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 directoryThis 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
View 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" # Descriptionit 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. 🐾✨
```

View File

@@ -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()

View File

@@ -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。")

View File

@@ -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 的端点。
# 模型参数配置

View File

@@ -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}' 未导入")

View File

@@ -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" }