Skip to content

Maisaka 推理引擎

Maisaka 是 MaiBot 的核心 AI 运行时,负责对话推理、节奏控制和工具调用。本文详述其内部架构、状态机和执行流程。

架构总览

MaisakaHeartFlowChatting

源码位置:src/maisaka/runtime.py

每个聊天会话对应一个 MaisakaHeartFlowChatting 实例,由 HeartflowManager 管理生命周期。

状态机

运行时具有三种状态:

  • running — 正在执行推理循环
  • wait — 等待状态,wait 工具设定了超时时间
  • stop — 空闲状态,等待新的外部消息触发

核心属性

  • session_id str — 会话 ID
  • _chat_history list[LLMContextMessage] — 内部上下文历史
  • message_cache list[SessionMessage] — 待处理消息缓存
  • _internal_turn_queue asyncio.Queue — 内部循环触发队列("message" / "timeout")
  • _tool_registry ToolRegistry — 统一工具注册表
  • _reasoning_engine MaisakaReasoningEngine — 推理引擎
  • _chat_loop_service MaisakaChatLoopService — 对话循环服务
  • _max_internal_rounds int — 最大内部轮次(默认 10)
  • _max_context_size int — 最大上下文消息数
  • _message_debounce_seconds float — 消息防抖秒数(默认 1.0)
  • _talk_frequency_adjust float — 说话频率倍率
  • deferred_tool_specs_by_name dict[str, ToolSpec] — 延迟发现工具池
  • discovered_tool_names set[str] — 已发现的延迟工具

消息触发机制

触发阈值计算:

python
effective_frequency = talk_value * _talk_frequency_adjust  # 回复频率
trigger_threshold = ceil(1.0 / effective_frequency)  # 所需消息数

空窗补偿:当新消息数不足但空窗时间较长时,按最近平均回复时长折算等价消息数。

强制 continue 机制

当检测到 @ 或提及时,_arm_force_next_timing_continue() 设置标记,使下一次 Timing Gate 直接返回 continue,确保 bot 回应直接呼叫。

MaisakaReasoningEngine

源码位置:src/maisaka/reasoning_engine.py

核心推理引擎,负责内部思考循环和工具执行。

关键常量

  • TIMING_GATE_CONTEXT_LIMIT_max_context_size(可配置) · Timing Gate 上下文消息上限(读取 global_config.chat.max_context_size / max_private_context_size
  • TIMING_GATE_MAX_TOKENS — 384 · Timing Gate 最大输出 token
  • TIMING_GATE_TOOL_NAMES{"continue", "no_action", "wait"} · Timing Gate 可用工具
  • ACTION_HIDDEN_TOOL_NAMES{"continue", "no_action"} · Action Loop 隐藏的工具
  • MAX_INTERNAL_ROUNDS — 10 · 最大内部思考轮次

run_loop 主循环

python
async def run_loop(self) -> None:
    while runtime._running:
        # 1. 等待触发信号
        queued_trigger = await runtime._internal_turn_queue.get()
        message_triggered, timeout_triggered = _drain_ready_turn_triggers(queued_trigger)

        # 2. 消息防抖
        if message_triggered:
            await runtime._wait_for_message_quiet_period()

        # 3. 收集待处理消息
        cached_messages = runtime._collect_pending_messages()

        # 4. 消息注入历史
        await _ingest_messages(cached_messages)

        # 5. 内部思考循环
        for round_index in range(max_internal_rounds):
            # 5a. Timing Gate(如果需要)
            if timing_gate_required:
                timing_action = await _run_timing_gate(anchor_message)
            if timing_action != "continue":
                break  # wait 或 no_action,结束本轮

            # 5b. Planner(Action Loop)
            response = await _run_interruptible_planner()

            # 5c. 相似度检测
            if _should_replace_reasoning(response.content):
                # 替换为重新思考提示
                response.content = "我应该根据我上面思考的内容进行反思..."

            # 5d. 工具执行
            if response.tool_calls:
                should_pause, summaries, monitors = await _handle_tool_calls(...)
                if should_pause:
                    break
                continue  # 工具执行后有新信息,继续循环

            break  # 无工具调用且无内容,结束

Timing Gate

Timing Gate 是一个独立的子代理,决定对话节奏:

Timing Gate 系统提示词:

  • 优先从 maisaka_timing_gate 模板加载
  • 兜底提示词强调 只调用一个工具,不要输出普通文本
  • 可用工具仅 waitno_actioncontinue 三个

Planner(Action Loop)

Planner 是主要的推理和工具执行阶段:

  1. 构建工具定义_build_action_tool_definitions()

    • 过滤 ACTION_HIDDEN_TOOL_NAMES(continue、no_action)
    • 内置 Action 工具直接暴露
    • 默认第三方/插件工具放入 deferred 池,通过 tool_search 发现;声明 core_tool=Truevisibility="visible" 的插件工具会直接暴露
  2. 运行可打断 Planner_run_interruptible_planner()

    • 绑定 asyncio.Event 中断标记
    • 新消息到达时设置标记 → LLM 请求中止(ReqAbortException
    • 连续打断有上限(planner_interrupt_max_consecutive_count
  3. 思考去重_should_replace_reasoning()

    • 当前后思考与上一轮相似度 > 90% 时
    • 替换为"重新思考"提示,避免循环空转

Planner 打断机制

打断后行为:

  • 如果 has_pending_messages 且未达最大轮次 → 跳过 Timing Gate,重新进入 Planner
  • 否则 → 结束当前循环

工具执行

工具调用通过统一 ToolRegistry 路由:

内置工具定义

源码位置:src/maisaka/builtin_tool/

Timing Gate 工具

  • continuecontinue_tool.py · 允许继续进入下一轮思考 · 关键参数:无
  • no_actionno_action.py · 停止当前循环,等待新外部消息 · 关键参数:无
  • waitwait.py · 暂停对话 N 秒后重新判断 · 关键参数:seconds(默认 30)

Action 工具

  • replyreply.py · 生成并发送回复消息 · 关键参数:msg_idset_quotereference_info
  • send_emojisend_emoji.py · 发送表情包 · 关键参数:无(自动根据上下文选择)
  • finishfinish.py · 结束当前思考轮次 · 关键参数:无
  • query_jargonquery_jargon.py · 查询黑话/词条 · 关键参数:words
  • query_memoryquery_memory.py · 查询长期记忆 · 关键参数:querymodelimit
  • query_person_profilequery_person_profile.py · 查询人物画像 · 关键参数:person_name
  • view_complex_messageview_complex_message.py · 查看完整转发消息 · 关键参数:message_id
  • tool_searchtool_search.py · 搜索延迟发现的工具 · 关键参数:querylimit

Deferred Tool 发现机制

Action Loop 中,普通第三方/插件工具默认不直接暴露给 Planner,而是通过两步发现:

  1. tool_search:搜索 deferred 工具池,匹配到的工具名标记为"已发现"
  2. 下一轮 Planner:已发现的工具加入可见工具列表

这减少了 Planner 一次看到的工具数量,避免选择困难。

插件 Tool 如果声明了 core_tool=Truevisibility="visible",会跳过 deferred 发现流程,直接进入 Planner 可见工具列表。该机制适合高频、低风险、上下文强相关的工具;普通插件工具仍建议保持默认 deferred 行为。

MaisakaChatLoopService

源码位置:src/maisaka/chat_loop_service.py

负责单步 LLM 请求的封装,包括上下文选择、Prompt 构建和 Hook 触发。

chat_loop_step 流程

上下文选择策略

select_llm_context_messages() 从历史中选择给 LLM 的上下文:

  1. request_kind 过滤(planner 请求隐藏 Timing Gate 工具链)
  2. 从末尾向前遍历,选择能成功转换为 LLM 消息的条目
  3. 计数仅计算 count_in_context=True 的消息(ToolResultMessageReferenceMessage 不占窗口)
  4. 达到 max_context_size 后停止
  5. 隐藏最早 50% 的 assistant 文本消息(保留工具调用链路)

Hook Specs

  • maisaka.planner.before_request — 可中止 ✗ · 可改写 ✓ · 可改写消息列表和工具定义
  • maisaka.planner.after_response — 可中止 ✗ · 可改写 ✓ · 可调整文本结果和工具调用列表
  • maisaka.replyer.before_request — 可中止 ✗ · 可改写 ✓ · 可改写 replyer 任务名、指定模型、额外提示和 reply_tool_args
  • maisaka.replyer.before_model_request — 可中止 ✗ · 可改写 ✓ · 可改写 replyer 已构造完成、即将发给模型的 messages
  • maisaka.replyer.after_response — 可中止 ✗ · 可改写 ✓ · 可改写回复文本或要求 replyer 重生成

上下文消息类型

源码位置:src/maisaka/context_messages.py

ReferenceMessageType

  • custom — 自定义参考消息
  • jargon — 黑话/词条查询结果
  • memory — 长期记忆检索结果
  • tool_hint — 工具提示信息(如 deferred tools 提醒)

上下文窗口占用

  • SessionBackedMessage — 占用窗口 ✓ · 真实用户消息
  • ComplexSessionMessage — 占用窗口 ✓ · 复杂/转发消息
  • ReferenceMessage — 占用窗口 ✗ · 参考信息(不占用窗口)
  • AssistantMessage (assistant) — 占用窗口 ✓ · 内部思考文本
  • AssistantMessage (perception) — 占用窗口 ✗ · 感知类文本(打断提示等)
  • ToolResultMessage — 占用窗口 ✗ · 工具执行结果

Planner 消息前缀

源码位置:src/maisaka/planner_message_utils.py

每条用户消息注入 Planner 时,会添加结构化前缀:

[时间]HH:MM:SS
[用户名]nickname
[用户群昵称]group_card
[msg_id]message_id
[发言内容]实际消息文本

build_planner_prefix() 构建前缀,build_planner_user_prefix_from_session_message()SessionMessage 提取参数。

监控事件

源码位置:src/maisaka/monitor_events.py

通过 WebSocket 向前端监控面板广播事件:

  • session.start — 运行时启动 · 关键数据:session_id, session_name
  • message.ingested — 消息注入历史 · 关键数据:speaker_name, content, message_id
  • cycle.start — 思考循环开始 · 关键数据:cycle_id, round_index, max_rounds
  • timing_gate.result — Timing Gate 决策完成 · 关键数据:action, content, tool_calls, prompt_tokens
  • planner.finalized — 规划器完成 · 关键数据:完整 cycle 数据、token 统计、耗时

完整推理流程示例

以群聊中用户发送一条 @bot 消息为例: