外观
触发器桥接(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 能否正确调用你的触发器。必须包含:
- 一句话功能描述
- 用户在什么自然语言场景下会需要这个功能
- 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_return,bot.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 无法获取有效信息 | 改造价值低 |
完整示例
详见 完整示例 - 触发器桥接。