简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

交互会话系统

Note

本章内容需要 ErisPulse **2.8.0+**。

ErisPulse 把"与用户的持续交互"做成了框架级基础设施:从一条 wait_reply, 到定时提醒、多路等待、会话互斥、重启恢复,全部由统一的 交互会话管理器(Core/Event/interaction.py,sdk.interaction)调度。

{!--< tips >!--} 本文覆盖的每一项能力都自带归属(owner):交互等待、租约、定时器全部记录 注册时的模块名,模块卸载 / 适配器关闭时由框架自动清理,等待方立即得到通知 而非干等超时——这是归属权系统在交互维度的延伸(见 归属权系统)。 {!--< /tips >!--}

等待回复:wait_reply

wait_reply 是交互会话的基石——挂起当前协程,等待目标用户在下一条消息中"回复"。

from ErisPulse.Core.Event import command

@command("ask")
async def ask_command(event):
    reply = await event.wait_reply(prompt="请输入你的名字:", timeout=30)
    if reply is None:
        await event.reply("超时了")
        return
    await event.reply(f"你好,{reply.get_text()}!")

全参数一览

参数 说明 默认
prompt 挂起前发送的提示消息 None
timeout 等待超时(秒) 60
pattern glob 过滤(* / ? / [seq]),不匹配继续等待 None
regex 正则过滤(与 pattern 同时给定时须都匹配),不匹配继续等待 None
validator 校验函数(接收 Event,返回 bool),失败继续等待 None
callback 收到回复时的回调(替代返回值式的另一种写法) None
method prompt 的发送方法 "Text"
session 会话级等待:同会话(群 / 频道)中任何人的回复均可命中 False
# 只接受数字金额,否则继续等
reply = await event.wait_reply("请输入金额:", regex=r"\d+\s*元", timeout=30)

# 会话级等待:群协作场景,任何群友回答均可
reply = await event.wait_reply(session=True, prompt="哪位大佬帮忙答一下?")

等待会在什么时候被取消

等待不再是"只能等超时"——以下情况会让等待立即终止(wait_reply 返回 None), 而不是让调用方干等到超时:

触发 取消原因(InteractionCancelled.reason) 说明
归属模块被卸载 / 禁用 owner_unload 归属清理:谁注册的等待,谁消失时一并回收
适配器关闭 / 重启 platform_stop 该平台挂起的等待全部取消
同会话被新的等待 / 租约取代 conflict 见下方"会话仲裁"
回复者被拉黑 / owner 模块被解绑 revoked 回复命中的权限复查:scope 身份维度 + 模块维度
用户回复命中 —— 正常路径,返回回复事件

底层异常为 InteractionCancelled(挂在 InteractionError 异常体系下), wait_reply 已将其转换为返回 None;需要原因的调用方可直接使用 sdk.interaction.register() 低层 API。

回复命中的完整判定链

一条回复消息到达时,交互管理器按以下顺序判定(在命令匹配之前执行, 对话连续性优先——即使消息已被其他高优先级处理器认领,挂起的对话也能完成):

会话键命中(精确 user 维度 → 会话级回退)
  → pattern / regex 文本过滤(不匹配继续等)
  → validator 校验(失败继续等)
  → 权限复查(scope 身份维度 + owner 模块维度,失败则终止等待)
  → 唤醒等待方 + 认领事件(mark_processed)

会话定时器:remind / escalate

把"超时"从返回值变成可编排的原语。定时器挂在交互会话上, 随模块卸载 / 适配器关闭自动取消,单会话活跃 remind 上限 5 个。

remind:没回复就提醒

@command("ticket")
async def ticket_command(event):
    await event.reply("工单已提交,处理结果会在这里通知")
    # 5 分钟无回复则温和催一次;用户任何回复都会自动取消它
    event.remind(300, "还在吗?有结果了会第一时间告诉你")
    reply = await event.wait_reply(timeout=3600)
    ...

escalate:到点必达的升级

event.escalate(1800, lambda e: notify_master(f"工单 30 分钟未处理:{event.get_command_args()}"))

与 remind 的唯一区别:不被用户回复取消——升级动作(通知主人、转人工) 是"超时必达"承诺,仅手动 cancel() / 模块卸载 / 适配器关闭才取消。

remind escalate
到期行为 发文本 / 执行 callback 执行 callback
用户回复 自动取消 不受影响
归属清理(卸载 / 关平台) 取消 取消
单会话上限 5 不限(随归属清理兜底)

多路等待:expect + select

同时挂起多条期望,先到先得——典型场景:等管理员审批的同时等用户撤回、 多人协作投票。

which, reply = await event.select(
    event.expect(pattern="同意*", user="10001"),
    event.expect(pattern="拒绝*", user="10002"),
    event.expect(validator=lambda e: e.get_text() == "搁置", session=True),
    timeout=60,
)
if which is None:
    await event.reply("60 秒内未收到任何审批结果")
elif which == 0:
    await event.reply("已同意")
elif which == 1:
    await event.reply("已拒绝")

{!--< tips >!--} select 与多线程 asyncio.wait 手工编排相比:期望未命中时自动清理、 命中事件自动认领、权限复查与归属清理全部生效——不需要自己管任何 Future。 {!--< /tips >!--}

会话互斥:acquire / hold / get_owner_of

归属权从"资源"走向"会话"——"这个用户当前正被谁占用"成为一等查询。

# 查询:这个会话正被谁交互?(空闲返回 None)
owner = sdk.interaction.get_owner_of(event)
if owner and owner != "MyModule":
    return  # 其他模块正在对话中,避免打扰

# 互斥租约:独占会话(deny 策略,被占用返回 None)
lease = sdk.interaction.acquire(event)          # 默认 TTL 1 小时,可传 ttl=
if lease is None:
    return  # 已被占用
try:
    ...  # 独占交互
finally:
    lease.release()

上下文管理器形式(获取失败抛 SessionOccupiedError):

with sdk.interaction.hold(event) as lease:
    ...  # 退出自动释放

租约支持 renew(ttl) 续期;TTL 惰性过期——过期的租约在下次访问时自动清理。

Conversation.resume() 恢复对话时框架会自动 acquire 租约(见 Conversation 多轮对话的"恢复即接管")—— 恢复的对话天然持有会话,其他模块不会插入。

会话收件箱:event.history

每会话近期消息流的统一记录(用户 + 机器人双方),作为 AI 上下文、 防复读、行为分析类模块的共享事实底座——各模块不再各自存历史。

messages = await event.history(20)   # 当前会话最近 20 条,时间升序
for m in messages:
    print(m["role"], ":", m["text"])  # role: "user" / "bot"

消息事务:message_tx

事务内的所有出站发送自动记账;异常退出时逆序自动撤回已发送的消息 (适配器未实现 delete_message 时跳过,账本仍正常记录)。

async with event.message_tx():
    await event.reply("正在处理,请稍候")
    result = await do_something()          # 这里抛异常 →
    await event.reply(f"完成: {result}")   # 前面的"处理中"自动撤回

事务外发送不记账(零开销);get_send_receipts() 可查看当前事务已发送的回执。

链路追踪:trace-id

每个入站事件自动获得追踪 ID(复用 event["id"],缺失则生成),贯穿:

一条消息被多个模块接力处理时,全链路可用同一 ID 串联(日志 / 慢查询 / 审计)。

与其他系统的关系

相关文档