Skip to content

About

通用 AI 对话插件

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

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 的决策模型 Jev 完成,它只返回概率,不生成文字,每条消息的判断成本极低。

机器人只会在三种情况下开口:

  1. 被叫到:没有 @,但叫了 names 中的名字并且在跟机器人说话。
  2. 接话:机器人刚说过话,有人没引用就接着说。
  3. 主动插话:最近几条消息中,有一条的类型在 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 可以提供一些除对话之外的功能,目前有如下用法:

  • chatsalt inspect:查看插件的版本、对话模型和视觉模型等信息。
  • chatsalt rename <新名片>:更改机器人在当前群的群名片。

About

通用 AI 对话插件

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

Generated from fraqjs/plugin-template