fraq-plugin-chatsalt
v0.16.0
Published
通用 AI 对话插件
Readme
fraq-plugin-chatsalt
通用 AI 对话插件
安装与配置
将插件添加到 fraq.yml 的 plugins 字段下:
plugins:
# 本插件依赖 fraqjs/ai 和 fraqjs/kysely 插件,因此需要一并安装
fraqjs/ai:
# 在这里传入 AiPlugin 的配置选项
fraqjs/kysely:
# 在这里传入 KyselyPlugin 的配置选项
# 推荐配合 fraqjs/message-store 插件使用,以便在数据库中持久化消息,减少远程拉取消息上下文的次数
fraqjs/message-store:
# 在这里传入 MessageStorePlugin 的配置选项
# 可选,安装后 chatsalt 会自动注册 WebUI,未安装时不影响对话功能
fraqjs/webui-gateway:
# 在这里传入 WebuiGateway 的配置选项
chatsalt:
# 角色设定文本,这里表示从 persona.md 文件中读取,见下方「编写人设」一节
persona: ${{ text:persona.md }}
# 主人(管理员)列表
owners: [10001, 10002]
# 消息中包含这些关键词时触发对话
trigger:
keywords: [salt, 机器人]以上就是一个可以运行的配置,未列出的选项都会使用默认值。完整选项见下方「配置参考」一节。
安装并配置 fraqjs/webui-gateway 后,可通过 /webui/chatsalt/ 查看对话、警告和记忆记录。WebUI 由
Gateway 统一鉴权;如果未安装 Gateway,Chatsalt 不会注册任何 WebUI 路由。
配置参考
以下选项都写在 chatsalt 字段下,嵌套的选项用 . 表示层级,例如 memory.enabled 对应:
chatsalt:
memory:
enabled: true表情和 @(richText)、自动模式(auto)的配置分别见对应章节。
基础
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| persona | 必填 | 角色设定文本,会在每次对话开始时注入上下文。推荐使用 Markdown 格式,并用 ${{ text:persona.md }} 从另一个文件读取,以保持配置文件可读性 |
| chatModel | ctx.ai.model() | 用于对话的语言模型,可以指定在 fraqjs/ai 插件中配置的模型名称或别名,例如 google/gemini-3.5-flash |
| visionModel | 同 chatModel | 用于图像识别的语言模型,取值同上 |
| owners | [] | 主人(管理员)QQ 号列表,可用于执行部分命令,但不会将相关信息注入提示词 |
| temperature | 0.8 | 对话的温度参数 |
| maxToolSteps | 10 | 调用工具时最多允许的步骤数 |
| trigger.keywords | [] | 触发对话的关键词列表,消息中包含任一关键词即触发对话 |
上下文
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| contextWindow | 20 | 上下文窗口大小,即提供给大模型的最少消息数 |
| contextStep | 10 | 上下文窗口起点的移动步长,见下文 |
| maxForwardDepth | 0 | 上下文中包含合并转发消息时最多展开的层数,0 表示不展开 |
窗口起点每隔 contextStep 条消息才向后移动一次,期间的对话只在上下文末尾追加消息,便于命中模型提供商的提示词缓存。因此实际提供的消息数在 contextWindow 到 contextWindow + contextStep - 1 之间浮动(撤回等缺失的消息不计入)。设为 1 则每条新消息都会移动窗口起点,即固定提供 contextWindow 条消息,但几乎无法命中缓存。
记忆
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| memory.enabled | true | 是否启用记忆功能 |
| memory.maxWindow | 20 | 在对话中最多注入的记忆条数 |
| memory.maxScopeCount | 50 | 每个对话场景最多允许的记忆条数 |
工具
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| builtinWebSearch.enabled | false | 是否启用模型内置的网页搜索工具,见下文 |
| externalWebSearch.enabled | false | 是否启用外部网页搜索工具,即由另一个模型执行搜索 |
| externalWebSearch.model | 同 chatModel | 执行外部网页搜索的模型,同样只支持下文列出的三种提供商 |
| webPage.enabled | false | 是否允许模型读取上下文中明确提供的 HTTP 或 HTTPS URL |
| webPage.timeoutMs | 10000 | 单次请求超时时间(毫秒) |
| webPage.maxResponseBytes | 2097152(2 MiB) | 最大响应正文大小 |
| webPage.maxContentLength | 20000 | 返回给模型的最大正文字符数 |
| webPage.maxRedirects | 3 | 最大重定向次数,每次重定向都会重新校验目标地址 |
| github.enabled | false | 是否启用 GitHub 工具 |
| github.token | — | GitHub 访问令牌,启用 GitHub 工具时必填,例如 ${{ env:GITHUB_TOKEN }} |
模型内置网页搜索目前仅支持以下 AI SDK 提供商的模型:@ai-sdk/anthropic、@ai-sdk/google、@ai-sdk/openai。启用前请确认你使用的模型本身支持网页搜索工具,否则可能导致模型无法正常工作。
网页读取支持 HTML、JSON、XML、Markdown、CSV 和纯文本;XML 只作为文本读取,不会解析外部实体。
WebUI
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| webui.enabled | true | 是否注册 WebUI(需要安装 fraqjs/webui-gateway) |
| webui.conversationLimit | 100 | 内存中最多保留的对话记录数 |
| webui.warningLimit | 100 | 内存中最多保留的警告记录数 |
| webui.memoryLimit | 500 | WebUI 最多查询的记忆条数 |
| webui.autoDecisionLimit | 300 | 内存中最多保留的自动判断记录数 |
编写人设
角色设定文本是指为大模型提供的角色设定信息,通常包括但不限于以下内容:
- 身份:姓名、年龄、性别、职业等基本信息
- 性格:是否开朗/内向、是否善于表达、是否有幽默感等
- 语言风格:口头禅、口癖、常用词汇等,可以包含例句
- 背景故事:家庭、成长经历、教育背景、人际关系、兴趣爱好等
- 知识边界:对于什么样的话题应该回答/给出模糊回答/拒绝回答等
- 互动目标:在角色视角看来,用户的身份、场景、目的等
一个好的角色设定文本可以让大模型更好地生成更符合预期的回答。仓库中包含一个示例角色设定文本,可以作为参考。
开始对话
通过在消息中 @ 机器人、回复机器人的消息或使用触发关键词来开始对话。机器人会根据角色设定文本和上下文信息生成回答。例如,对于上面的示例角色设定文本中的角色【纱露朵】,一个可能的对话是这样的:
用户: @纱露朵 你好呀~
纱露朵: 你好呀,我是纱露朵,请多关照的说喵~表情和 @
启用 richText 后,模型可以在回复中写类似 CQ 码的 milky 码,发送前会被转换成对应的 Milky 消息段:
| milky 码 | 效果 |
| --- | --- |
| [milky:face-表情ID] | 发送一个 QQ 表情,例如 [milky:face-264] 是「捂脸」 |
| [milky:mention-QQ号] | @ 某人,例如 [milky:mention-10001] |
用户: @纱露朵 我又寸了
纱露朵: 下次一定能过的喵[milky:face-111]转换规则:
- 表情可以使用插件内置表情表中的任意 ID。
recommendedFaces只决定提示词里推荐哪些表情,并不限制模型发送其他表情,比如它在上下文里看到的表情。 - @ 只在群聊中有效,并且只能 @ 当前上下文中出现过的人(发过消息或被 @ 过);不能 @ 机器人自己,也不能 @ 全体成员。
- 格式错误、表情 ID 不存在或不允许的 milky 码会被直接删除,不会原样发到群里,同时在 WebUI 中记录一条警告。如果删除后回复为空,则不发送。
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| richText.enabled | true | 是否允许模型在回复中发送 QQ 表情和 @ |
| richText.recommendedFaces | [111, 479, 478, 264, 181, 277, 344, 297] | 在提示词中推荐给模型的表情 ID 列表,默认依次为可怜、不对不对、对的对的、捂脸、戳一戳、汪汪、大怨种、拜谢。填写不存在的表情 ID 会在启动时报错 |
自动模式(实验性)
启用后,机器人会在白名单群里自己判断是否开口,不需要 @。判断可使用 TypeSafe 的 System One API 或 OpenAI 的 Decisions API,只返回分类、概率和评分。
机器人只会在三种情况下开口:
- 被叫到:没有 @,但叫了
names中的名字并且在跟机器人说话。 - 接话:机器人刚说过话,有人没引用就接着说。
- 主动插话:最近几条消息中,有一条的类型在
interject里(默认是提问和分享成就),话题属于expertise,并且不是别人之间的私聊。这种情况受budget配额限制。
此外:
- 群里刷屏时,机器人会等安静下来再判断,同一批消息最多回复一条。
- 敏感话题一律沉默。如果有人嫌机器人烦,自动模式会在该群静音一段时间。
- 对话模型仍可以输出
no_reply (理由)放弃发言,理由会显示在 WebUI 的对话记录里。在自动模式下,这不会产生警告。 - 主动插话时,对话模型会按消息类型得到不同的规范:提问时只给确切答案;分享成就时可以直接恭喜,了解情况才带细节;分享挫折时只安慰,不随意指点;讨论只在能补充具体内容时才接话。所有类型都不允许编造事实,也不允许因为没听说过就断言某样东西不存在。
- 自动模式下机器人不会给消息加表情回应。主动插话回复的是最新消息时,不会引用原消息。
建议先开启 shadow: true 运行一段时间,在 WebUI 的「自动判断」页查看每条消息的各项概率和最终决定,确认没问题后再关闭影子模式。切换供应商后建议重新观察概率分布并校准阈值。
自动模式会把白名单群中的消息发送给所配置的决策服务进行判断,请在启用前告知群成员。
自动模式配置
一个最小的自动模式配置如下,以舞萌(maimai DX)群为例:
chatsalt:
auto:
enabled: true
groups: [123456789]
shadow: true
decisions:
type: systemone
apiKey: ${{ env:TYPESAFE_API_KEY }}
model: jev-1.14.0
names: [纱露朵, 盐, bot, 机器人]
expertise: 'maimai DX (舞萌) rhythm game: charts, difficulty constants, rating, song unlocks, play technique.'
intentHints:
share_success: ['我把 xx 收了', '鸟加了', 'AP 了', 'rating 推到 15000 了']
share_setback: ['又寸了', '这谱根本打不过']
question: ['这谱怎么打', '定数多少']使用 OpenAI 时,将上述 auto.decisions 替换为:
decisions:
type: decisions
apiKey: ${{ env:OPENAI_API_KEY }}
model: gpt-6-luna基本选项
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| auto.enabled | false | 是否启用自动模式 |
| auto.groups | 必填 | 启用自动模式的群号白名单 |
| auto.shadow | false | 影子模式:照常判断和生成回复,但不发送,只记录到 WebUI,用于测试参数 |
| auto.decisions.type | 必填 | systemone(TypeSafe)或 decisions(OpenAI) |
| auto.decisions.apiKey | 必填 | 所选服务的 API Key |
| auto.decisions.model | 必填 | 所用的决策模型,例如 jev-1.14.0、gpt-6-luna |
| auto.decisions.baseURL | 随类型选择 | systemone 默认 https://api.typesafe.ai/v1,decisions 默认 https://api.openai.com/v1。必须以 /v1 结尾,分别追加 /systemone 或 /decisions,可使用兼容对应协议的代理 |
| auto.decisions.timeoutMs | 5000 | 单次请求超时(毫秒);最终失败时不发言 |
| auto.decisions.maxRetries | 2 | 网络错误、超时或可重试 HTTP 错误的额外重试次数,0 表示不重试 |
| auto.names | [] | 机器人的名字和别名,用于识别「没有 @ 但在叫机器人」的消息 |
| auto.expertise | 必填 | 机器人擅长的领域,只有在这个范围内才会主动插话。推荐用英文描述,可以夹带中文专有名词 |
| auto.avoidTopics | [politics, history, romance or sexual content] | 不参与的话题 |
| auto.intentHints | {} | 各消息类型在本领域中的典型说法,会追加到决策模型的判断标准中。内置标准只包含通用的中文例子,圈内黑话(模型不一定认识)需要在这里补充 |
主动插话
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| auto.interject | { question: 0.6, share_success: 0.6 } | 对哪些类型的消息主动插话,见下文 |
| auto.interjectWithin | 3 | 主动插话的消息必须在群里最近几条消息之内 |
| auto.budget.perHour | 3 | 每个群每小时最多主动插话次数 |
| auto.budget.minIntervalMs | 600000(10 分钟) | 两次主动插话的最小间隔 |
| auto.budget.perUserIntervalMs | 1800000(30 分钟) | 对同一个群成员的消息两次主动插话的最小间隔 |
budget 只限制主动插话,被叫到和接话不受限制。
interject 的每一项可以写成一个数字,表示决策模型判定为该类型的最低概率,话题门槛沿用 thresholds.topicFit;也可以写成 { probability, topicFit },为该类型单独设置话题门槛(0-3,设为 0 表示不要求擅长领域)。未列出的类型不会触发主动插话。可选类型:
| 类型 | 含义 |
| --- | --- |
| chatter | 闲聊、玩梗、表情、日常 |
| share_setback | 分享自己的失败、倒霉或烦恼,没有求助 |
| share_success | 分享自己的成就、进展或好运 |
| discussion | 分享信息,或发表看法、发起讨论 |
| question | 提问、求助、求推荐 |
对话模型拿到的是决策模型判断出的最可能类型:如果这个类型在 interject 中列出,模型会得到正面的回应规范(怎样接得好);如果没有列出(消息是靠别的类型的规则通过的),模型会得到偏谨慎的规范,大多会放弃发言。例如设置 question: 0 后,擅长领域内的闲聊也会进入生成,但模型会被告知「这只是闲聊,并不需要你回应」。
时机与静音
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| auto.stateMessages | 8 | 提供给决策模型 的上下文消息条数 |
| auto.debounceMs | 3000 | 群里安静多久后才做决定(毫秒) |
| auto.maxBurstWaitMs | 10000 | 刷屏时最多等待多久后做决定(毫秒) |
| auto.muteOnComplaintMs | 21600000(6 小时) | 有人嫌机器人烦时,自动模式在该群静音的时长 |
判断阈值
一般不需要修改,可在 WebUI「自动判断」页观察概率后调整。
| 选项 | 默认值 | 说明 |
| --- | --- | --- |
| auto.thresholds.addressed | 0.75 | 判定「在叫机器人」的最低概率 |
| auto.thresholds.continuation | 0.7 | 判定「在接机器人的话」的最低概率 |
| auto.thresholds.topicFit | 2.3 | 主动插话要求的话题相关度(0-3) |
| auto.thresholds.privateExchange | 0.4 | 高于此值视为别人在私聊,不插话 |
| auto.thresholds.sensitive | 0.6 | 高于此值视为敏感话题,一律沉默 |
| auto.thresholds.wantsBotQuiet | 0.7 | 高于此值视为嫌机器人烦,触发静音 |
配置一个爱插话的机器人
默认配置偏安静:只在有人提问、或有人分享成就时插话,并且要求话题在 expertise 范围内。如果你希望机器人更像一个活跃的群友,建议:
- 在
interject中加入chatter、share_setback、discussion等类型。列出的类型会得到正面的回应规范,模型不会被劝退。 - 为闲聊类的类型设置
topicFit: 0,让它们不受擅长领域限制。提问和讨论仍然只限于擅长领域(提示词里也会这样要求),以免不懂装懂。 - 主要通过
budget控制发言频率,而不是靠提高概率阈值。
auto:
interject:
question: 0.6
discussion: 0.6
share_success: { probability: 0.6, topicFit: 0 }
share_setback: { probability: 0.7, topicFit: 0 }
chatter: { probability: 0.6, topicFit: 0 }
budget:
perHour: 6
minIntervalMs: 180000
perUserIntervalMs: 600000无论怎样配置,以下规则都不会放宽:不编造事实、一到两句话、不复述别人说过的话、话题过去了就不再回应、敏感话题一律沉默、有人嫌机器人烦时自动静音。
使用 chatsalt 命令
chatsalt 可以提供一些除对话之外的功能,目前有如下用法:
chatsalt inspect:查看插件的版本、对话模型和视觉模型等信息。chatsalt rename <新名片>:更改机器人在当前群的群名片。
