通用 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.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 的决策模型 Jev 完成,它只返回概率,不生成文字,每条消息的判断成本极低。
机器人只会在三种情况下开口:
- 被叫到:没有 @,但叫了
names中的名字并且在跟机器人说话。 - 接话:机器人刚说过话,有人没引用就接着说。
- 主动插话:最近几条消息中,有一条的类型在
interject里(默认是提问和分享成就),话题属于expertise,并且不是别人之间的私聊。这种情况受budget配额限制。
此外:
- 群里刷屏时,机器人会等安静下来再判断,同一批消息最多回复一条。
- 敏感话题一律沉默。如果有人嫌机器人烦,自动模式会在该群静音一段时间。
- 对话模型仍可以输出
no_reply (理由)放弃发言,理由会显示在 WebUI 的对话记录里。在自动模式下,这不会产生警告。 - 主动插话时,对话模型会按消息类型得到不同的规范:提问时只给确切答案;分享成就时可以直接恭喜,了解情况才带细节;分享挫折时只安慰,不随意指点;讨论只在能补充具体内容时才接话。所有类型都不允许编造事实,也不允许因为没听说过就断言某样东西不存在。
- 自动模式下机器人不会给消息加表情回应。主动插话回复的是最新消息时,不会引用原消息。
建议先开启 shadow: true 运行一段时间,在 WebUI 的「自动判断」页查看每条消息的各项概率和最终决定,确认没问题后再关闭影子模式。也可以先用 pnpm tsx test/jev-probe.ts(需要设置 TYPESAFE_API_KEY)查看 Jev 对几条示例消息的判断。
自动模式会把白名单群中的消息发送给 TypeSafe 进行判断,请在启用前告知群成员。
一个最小的自动模式配置如下,以舞萌(maimai DX)群为例:
chatsalt:
auto:
enabled: true
groups: [123456789]
shadow: true
jev:
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: ['这谱怎么打', '定数多少']| 选项 | 默认值 | 说明 |
|---|---|---|
auto.enabled |
false |
是否启用自动模式 |
auto.groups |
必填 | 启用自动模式的群号白名单 |
auto.shadow |
false |
影子模式:照常判断和生成回复,但不发送,只记录到 WebUI,用于测试参数 |
auto.jev.apiKey |
必填 | TypeSafe API Key |
auto.jev.model |
必填 | 使用的 Jev 模型,例如 jev-1.14.0 |
auto.jev.baseURL |
https://api.typesafe.ai/v1 |
TypeSafe API 地址,必须以 /v1 结尾。可替换为兼容 System One 接口的代理或自建服务,请求会发送到 <baseURL>/systemone |
auto.jev.timeoutMs |
5000 |
单次请求超时(毫秒),超时或出错时视为不发言 |
auto.names |
[] |
机器人的名字和别名,用于识别「没有 @ 但在叫机器人」的消息 |
auto.expertise |
必填 | 机器人擅长的领域,只有在这个范围内才会主动插话。Jev 以英文训练为主,推荐用英文描述,可以夹带中文专有名词 |
auto.avoidTopics |
[politics, history, romance or sexual content] |
不参与的话题 |
auto.intentHints |
{} |
各消息类型在本领域中的典型说法,会追加到 Jev 的判断标准中。内置标准只包含通用的中文例子,圈内黑话(Jev 不一定认识)需要在这里补充 |
| 选项 | 默认值 | 说明 |
|---|---|---|
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 的每一项可以写成一个数字,表示 Jev 判定为该类型的最低概率,话题门槛沿用 thresholds.topicFit;也可以写成 { probability, topicFit },为该类型单独设置话题门槛(0-3,设为 0 表示不要求擅长领域)。未列出的类型不会触发主动插话。可选类型:
| 类型 | 含义 |
|---|---|
chatter |
闲聊、玩梗、表情、日常 |
share_setback |
分享自己的失败、倒霉或烦恼,没有求助 |
share_success |
分享自己的成就、进展或好运 |
discussion |
分享信息,或发表看法、发起讨论 |
question |
提问、求助、求推荐 |
对话模型拿到的是 Jev 判断出的最可能类型:如果这个类型在 interject 中列出,模型会得到正面的回应规范(怎样接得好);如果没有列出(消息是靠别的类型的规则通过的),模型会得到偏谨慎的规范,大多会放弃发言。例如设置 question: 0 后,擅长领域内的闲聊也会进入生成,但模型会被告知「这只是闲聊,并不需要你回应」。
| 选项 | 默认值 | 说明 |
|---|---|---|
auto.stateMessages |
8 |
提供给 Jev 的上下文消息条数 |
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 inspect:查看插件的版本、对话模型和视觉模型等信息。chatsalt rename <新名片>:更改机器人在当前群的群名片。