Skip to content

触发器桥接(to_ai)简单

字数统计:1.6k 字
阅读时长:5 分钟

💡 提醒

触发器桥接是 GsCore 中将现有插件命令零成本开放给 AI 调用的核心机制。

只需在 on_xxx 装饰器上添加 to_ai 参数,AI 就能自动识别并调用你的插件功能,无需修改任何原有业务逻辑

概述

to_ai 参数允许插件开发者将现有的触发器函数自动注册为 AI 工具,无需编写重复的 @ai_tools 函数。AI 调用时使用 MockBot 拦截 bot.send(),将图片/消息内容收集而非真正发送,由 AI 决定是否真正发给用户。

核心模块: gsuid_core/ai_core/trigger_bridge.py

to_ai 参数

在所有 on_xxx 装饰器上新增 to_ai: str = "" 参数:

参数值行为
""(默认)不注册为 AI 工具,行为完全不变
非空字符串将该字符串作为 AI 工具的 docstring,自动注册到 _TOOL_REGISTRY["by_trigger"]

基础用法示例

python
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.ai_core.trigger_bridge import ai_return

sv = SV("股票插件")

@sv.on_command(
    "个股",
    to_ai="""
    查询指定股票或ETF的K线图或分时图。
    当用户询问某只股票/ETF今天走势、分时图、日K、周K、月K时调用。

    Args:
        text: 查询内容,格式为 "[周期前缀] 股票名称或代码"
              - 无前缀:默认显示分时图,例如 "证券ETF"
              - "日k": 日K线,例如 "日k 证券ETF"
              - 多个标的以空格分隔,例如 "证券ETF 白酒ETF"
    """,
)
async def send_stock_img(bot: Bot, ev: Event):
    content = ev.text.strip().lower()
    if not content:
        ai_return("错误:未提供股票代码")
        return await bot.send("请后跟股票代码使用")
    # ... 原有逻辑完全不变 ...
    await bot.send(im)

to_ai 的 docstring 写法规范

to_ai 写得好不好,决定 AI 能否正确调用你的触发器。必须包含

  1. 一句话功能描述
  2. 用户在什么自然语言场景下会需要这个功能
  3. Args 部分:参数格式、可选前缀/后缀、至少两个具体例子
python
# ✅ 有参数的命令
to_ai="""查询指定游戏角色的培养详情和属性数据。
当用户询问某个角色的命座、圣遗物、天赋、属性面板时调用。
需要用户已绑定 UID。

Args:
    text: 角色名称,支持昵称。
          例如 "雷电将军"、"雷神"(等同于雷电将军)、"胡桃"
"""

# ✅ 无参数的 fullmatch
to_ai="""查看当前用户绑定的游戏 UID。
当用户询问"我绑定了什么"、"我的UID是多少"时调用。
无需参数。

Args:
    text: 无需参数,留空即可
"""

ai_return() — 向 AI 返回中间文本

在触发器函数内调用,向 AI 返回纯文本中间结果(如错误提示、进度信息)。

python
from gsuid_core.ai_core.trigger_bridge import ai_return

@sv.on_command("查角色", to_ai="查询角色详情...")
async def get_char_info(bot: Bot, ev: Event):
    char_name = ev.text.strip()
    if not char_name:
        ai_return("错误:未提供角色名称")
        return await bot.send("请输入角色名")

    data = await fetch_char_data(char_name)
    # 在数据拿到后、图片生成前注入 AI 文本摘要
    ai_return(f"【{char_name}】等级: {data['level']}  命座: {data['constellation']}命")
    im = await render_image(data)
    await bot.send(im)
调用场景行为
真实用户触发静默忽略,不影响触发器正常执行
AI 工具调用文本被收集,最终作为工具返回值返回给 AI

ai_return 应该包含什么内容

AI 拿到工具返回值后,会用这段文字来理解执行结果,并决定如何回复用户。

数据类型应提取哪些字段
游戏角色/装备名称、等级、核心属性数值(至少3个)、关键装备
排行榜/列表前 5 名 + 后 5 名 + 总计统计
行情/走势名称、最新价、涨跌幅、开/高/低、关键指标
错误情况错误原因,例如 ai_return("错误:未找到角色 xxx")
写操作成功不需要额外 ai_returnbot.send 的文字会被收集

MockBot — AI 调用时的消息拦截

AI 调用触发器时,包装函数使用 MockBot 代理真实 Bot:

方法行为
send(str)存入 bot_messages 列表
send(bytes)存入 images 列表(视为图片数据)
reply()send()
其他属性代理到真实 Bot(如 bot_self_id

send_trigger_images 工具

配套工具,由 AI 决定是否将拦截到的图片真正发送给用户:

python
# AI 工具返回值示例:
# "[已生成 1 张图片,存储在本次工具上下文中。请调用 send_trigger_images 工具将图片发送给用户]"

AI 可以:

  • 调用 send_trigger_images() → 图片真正发出
  • 不调用 → 图片丢弃(用户只收到 AI 的文字回复)

交互流程对比

场景bot 对象bot.send 行为
用户直接触发真实 Bot立即发图给用户
AI 调用,AI 决定发MockBot存储 → send_trigger_images → 真正发出
AI 调用,AI 决定不发MockBot存储 → AI 不调 send_trigger_images → 图片丢弃

交互流程图

用户直接触发:
  用户 → 触发器匹配 → handler(real_bot, ev) → bot.send(im) → 直接发图 ✅

AI 调用(AI 决定发图):
  AI → 调用触发器工具(text="证券ETF")
    → MockBot 拦截 send(im),图片存入 pending_trigger_images
    → 工具返回 "[已生成1张图片...请调用 send_trigger_images...]"
  AI → 调用 send_trigger_images() → real_bot.send(im) → 图片发出 ✅

AI 调用(AI 决定不发图):
  AI → 调用触发器工具(text="证券ETF")
    → 工具返回含图片标记
  AI → 判断用户只要数据 → 不调用 send_trigger_images → 图片丢弃 ✅

注册时序

插件加载阶段 (cached_import)

    ├── 执行 @sv.on_command("个股", to_ai="...")
    │       │
    │       └── _on() 检测 to_ai 非空
    │               │
    │               └── _register_trigger_as_ai_tool(func, keyword, to_ai_doc, sv, trigger_type)
    │                       │
    │                       └── 写入 _TOOL_REGISTRY["by_trigger"]["send_stock_img"]

    └── 插件加载完成

哪些触发器不适合加 to_ai

情况原因
管理员/超级用户专用命令不应让 AI 绕过权限
危险操作(清数据、重载配置)AI 不应独立执行破坏性操作
需要多轮 Response 会话的命令当前不支持
on_file 文件接收命令AI 无法构建文件输入
功能单一且 AI 无法获取有效信息改造价值低

完整示例

详见 完整示例 - 触发器桥接

相关文档