feat: add LLM direct follow-up reply gate#1850
Conversation
Walkthrough新增了直接接话判定配置、多语言提示词和运行时 LLM 门控,并补充了上下文判定与 schema 测试。 Changes直接接话判定
Sequence Diagram(s)sequenceDiagram
participant MaisakaHeartFlowChatting
participant LLMServiceClient
participant 外部LLM服务
MaisakaHeartFlowChatting->>LLMServiceClient: generate_response(direct_followup_gate, chat_context)
LLMServiceClient->>外部LLM服务: prompt + generation options
外部LLM服务-->>LLMServiceClient: JSON 结果
LLMServiceClient-->>MaisakaHeartFlowChatting: 响应文本
MaisakaHeartFlowChatting->>MaisakaHeartFlowChatting: _parse_direct_followup_gate_result
alt direct_followup = true
MaisakaHeartFlowChatting->>MaisakaHeartFlowChatting: _arm_force_next_timing_continue(trigger_reason="接话消息")
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/maisaka/runtime.py (1)
1053-1060: 🎯 Functional Correctness | 🔴 Critical修复强制回复逻辑中的提前返回问题
当
reply_probability_boost >= 1.0单独命中时,由于 Line 1058 的后半部分条件(not message.is_at and not message.is_mentioned)为真,会导致函数提前返回,从而丢失了强制继续的逻辑。该判断条件应予以修正,仅在明确不应强制回复时才允许提前返回。should_force_reply = ( reply_probability_boost >= 1.0 or (message.is_at and global_config.chat.inevitable_at_reply) or (message.is_mentioned and global_config.chat.mentioned_bot_reply) ) # 修正:只有当 not should_force_reply 时才执行 fallback 逻辑 if not should_force_reply: await self._maybe_arm_direct_followup_continue(message) return🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/maisaka/runtime.py` around lines 1053 - 1060, The forced-reply guard in runtime logic is too broad and causes an early return even when reply_probability_boost >= 1.0 should force continuation. Update the conditional around should_force_reply in the relevant reply-handling flow so that _maybe_arm_direct_followup_continue(message) is called only when should_force_reply is false, and remove the extra message.is_at/message.is_mentioned check that overrides the forced-reply path.
🧹 Nitpick comments (1)
src/maisaka/runtime.py (1)
1166-1168: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win更新函数注释以覆盖 direct-followup 触发。
这个函数现在也服务于“接话消息”,但 docstring 仍写成只处理
@/提及,后续维护容易误删trigger_reason分支。按项目规则,重构时应保留并更新注释准确性。建议修复
- """在检测到 @ 或提及时,要求下一次 Timing Gate 直接 continue。""" + """在检测到强制回复触发时,要求下一次 Timing Gate 直接 continue。"""As per coding guidelines, “Maintain good comments in the code. When refactoring, preserve existing comments unless the code is deleted; update comments for accuracy but do not remove them”.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/maisaka/runtime.py` around lines 1166 - 1168, Update the docstring for the function around trigger_reason so it accurately describes all supported trigger types, including direct-followup/接话消息 in addition to @ and 提及消息. Keep the existing trigger_reason branching in place and revise the comment near the logic that sets trigger_reason so future refactors don’t remove the direct-followup path by mistake.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/maisaka/runtime.py`:
- Around line 1109-1117: Make the gate check fully fail-closed by wrapping
prompt loading, the LLM call in generate_response, and the response parsing in
one guarded flow so any load or runtime error returns a safe non-match instead
of bubbling up and blocking message registration. Update the parser used around
the direct_followup/reason extraction to validate that the decoded JSON is a
dict before accessing keys, and keep the existing invalid_json warning/false
fallback path in the same handling around the relevant runtime helpers.
- Around line 709-710: The message handling flow in runtime should not wait for
the direct-followup LLM gate before adding the new message to cache and
continuing local processing. In the message ingestion path around
_update_message_trigger_state and message_cache.append, move the cache append
and any local `@/mention` marking ahead of the external gate, then run the
direct-followup check in a background task with a timeout. If the gate later
passes, call _schedule_message_turn() from that async follow-up path; apply the
same reordering to the related logic in the other referenced block as well.
- Around line 1074-1075: The is_bot_self check is being called with the whole
SessionMessage object instead of the required platform and user_id values, which
can raise a TypeError in register_message when direct-followup is enabled and
text is present. Update the call at the message handling path in runtime.py to
pass the same platform and user_id fields used elsewhere around
register_message, so the bot-self filter works with the correct signature and
the function can continue normally.
---
Outside diff comments:
In `@src/maisaka/runtime.py`:
- Around line 1053-1060: The forced-reply guard in runtime logic is too broad
and causes an early return even when reply_probability_boost >= 1.0 should force
continuation. Update the conditional around should_force_reply in the relevant
reply-handling flow so that _maybe_arm_direct_followup_continue(message) is
called only when should_force_reply is false, and remove the extra
message.is_at/message.is_mentioned check that overrides the forced-reply path.
---
Nitpick comments:
In `@src/maisaka/runtime.py`:
- Around line 1166-1168: Update the docstring for the function around
trigger_reason so it accurately describes all supported trigger types, including
direct-followup/接话消息 in addition to @ and 提及消息. Keep the existing trigger_reason
branching in place and revise the comment near the logic that sets
trigger_reason so future refactors don’t remove the direct-followup path by
mistake.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 23e32a71-9949-4fc7-8fea-97e8e9712cf4
📒 Files selected for processing (10)
dashboard/src/routes/config/bot/types.tsprompts/en-US/.meta.tomlprompts/en-US/maisaka_direct_followup_gate.promptprompts/ja-JP/.meta.tomlprompts/ja-JP/maisaka_direct_followup_gate.promptprompts/zh-CN/.meta.tomlprompts/zh-CN/maisaka_direct_followup_gate.promptpytests/webui/test_config_schema.pysrc/config/official_configs.pysrc/maisaka/runtime.py
| await self._update_message_trigger_state(message) | ||
| self.message_cache.append(message) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
不要在消息入缓存前等待 direct-followup LLM 门控。
Line 709 会在 message_cache.append() 之前等待一次外部 LLM 调用;如果模型慢、排队或无响应,这条新消息不会入缓存、不会广播监控、也不会进入调度。建议先完成本地 @/提及标记和消息入队,再把 direct-followup gate 放到带超时的后台任务中;命中后再 _schedule_message_turn()。
Also applies to: 1125-1138
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/maisaka/runtime.py` around lines 709 - 710, The message handling flow in
runtime should not wait for the direct-followup LLM gate before adding the new
message to cache and continuing local processing. In the message ingestion path
around _update_message_trigger_state and message_cache.append, move the cache
append and any local `@/mention` marking ahead of the external gate, then run the
direct-followup check in a background task with a timeout. If the gate later
passes, call _schedule_message_turn() from that async follow-up path; apply the
same reordering to the related logic in the other referenced block as well.
| try: | ||
| payload = json.loads(normalized_text) | ||
| except json.JSONDecodeError: | ||
| logger.warning(f"接话必回复判定结果不是有效 JSON,将按未命中处理: {response_text[:200]}") | ||
| return False, "invalid_json" | ||
|
|
||
| direct_followup = bool(payload.get("direct_followup")) | ||
| reason = str(payload.get("reason") or "").strip() | ||
| return direct_followup, reason |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
让门控解析和 prompt 加载也 fail-closed。
当前只有 generate_response 在 try 内;load_prompt 失败,或 LLM 返回合法 JSON 但顶层不是对象时,异常会向上传播并打断消息注册。把 prompt 加载、调用、解析放进同一个 fail-closed 块,并在解析器里校验 dict。
建议修复
try:
+ prompt = load_prompt(
+ "maisaka_direct_followup_gate",
+ bot_name=global_config.bot.nickname,
+ chat_context=context_text,
+ )
result = await self._direct_followup_gate_client.generate_response(
prompt,
LLMGenerationOptions(temperature=0.0, max_tokens=180),
)
+ direct_followup, reason = self._parse_direct_followup_gate_result(result.response or "")
except Exception:
logger.exception(f"{self.log_prefix} 接话必回复 LLM 判定失败,按未命中处理")
return
- direct_followup, reason = self._parse_direct_followup_gate_result(result.response or "") try:
payload = json.loads(normalized_text)
except json.JSONDecodeError:
logger.warning(f"接话必回复判定结果不是有效 JSON,将按未命中处理: {response_text[:200]}")
return False, "invalid_json"
+ if not isinstance(payload, dict):
+ logger.warning(f"接话必回复判定结果 JSON 顶层不是对象,将按未命中处理: {response_text[:200]}")
+ return False, "invalid_json_shape"
direct_followup = bool(payload.get("direct_followup"))Also applies to: 1129-1143
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/maisaka/runtime.py` around lines 1109 - 1117, Make the gate check fully
fail-closed by wrapping prompt loading, the LLM call in generate_response, and
the response parsing in one guarded flow so any load or runtime error returns a
safe non-match instead of bubbling up and blocking message registration. Update
the parser used around the direct_followup/reason extraction to validate that
the decoded JSON is a dict before accessing keys, and keep the existing
invalid_json warning/false fallback path in the same handling around the
relevant runtime helpers.
5b1c118 to
f5ea8a9
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/maisaka/runtime.py`:
- Around line 1121-1123: The direct_followup parsing in the payload handling is
too loose because bool() treats non-empty strings like "false" as true. Update
the logic in the relevant runtime parser around the direct_followup/reason
extraction to strictly interpret only real boolean values (or explicit string
forms like true/false) and default everything else safely, so the follow-up flag
cannot be accidentally enabled by an LLM string response.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 180816ad-de7c-4db7-b1e9-7897c4d38af0
📒 Files selected for processing (11)
dashboard/src/routes/config/bot/types.tsprompts/en-US/.meta.tomlprompts/en-US/maisaka_direct_followup_gate.promptprompts/ja-JP/.meta.tomlprompts/ja-JP/maisaka_direct_followup_gate.promptprompts/zh-CN/.meta.tomlprompts/zh-CN/maisaka_direct_followup_gate.promptpytests/maisaka_test/test_direct_followup_context.pypytests/webui/test_config_schema.pysrc/config/official_configs.pysrc/maisaka/runtime.py
✅ Files skipped from review due to trivial changes (2)
- prompts/ja-JP/.meta.toml
- prompts/en-US/.meta.toml
🚧 Files skipped from review as they are similar to previous changes (7)
- dashboard/src/routes/config/bot/types.ts
- prompts/ja-JP/maisaka_direct_followup_gate.prompt
- pytests/webui/test_config_schema.py
- prompts/en-US/maisaka_direct_followup_gate.prompt
- src/config/official_configs.py
- prompts/zh-CN/maisaka_direct_followup_gate.prompt
- prompts/zh-CN/.meta.toml
| direct_followup = bool(payload.get("direct_followup")) | ||
| reason = str(payload.get("reason") or "").strip() | ||
| return direct_followup, reason |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
严格解析 direct_followup 布尔值,避免 "false" 被当成命中。
bool("false") 会得到 True,LLM 若返回字符串 "false" 会误触发强制接话。
建议修复
- direct_followup = bool(payload.get("direct_followup"))
+ direct_followup_value = payload.get("direct_followup")
+ if not isinstance(direct_followup_value, bool):
+ logger.warning(
+ f"接话必回复判定 direct_followup 字段不是布尔值,将按未命中处理: "
+ f"{response_text[:200]}"
+ )
+ return False, "invalid_direct_followup_type"
+ direct_followup = direct_followup_value📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| direct_followup = bool(payload.get("direct_followup")) | |
| reason = str(payload.get("reason") or "").strip() | |
| return direct_followup, reason | |
| direct_followup_value = payload.get("direct_followup") | |
| if not isinstance(direct_followup_value, bool): | |
| logger.warning( | |
| f"接话必回复判定 direct_followup 字段不是布尔值,将按未命中处理: " | |
| f"{response_text[:200]}" | |
| ) | |
| return False, "invalid_direct_followup_type" | |
| direct_followup = direct_followup_value | |
| reason = str(payload.get("reason") or "").strip() | |
| return direct_followup, reason |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/maisaka/runtime.py` around lines 1121 - 1123, The direct_followup parsing
in the payload handling is too loose because bool() treats non-empty strings
like "false" as true. Update the logic in the relevant runtime parser around the
direct_followup/reason extraction to strictly interpret only real boolean values
(or explicit string forms like true/false) and default everything else safely,
so the follow-up flag cannot be accidentally enabled by an LLM string response.
Summary
Tests
Summary by CodeRabbit
enable_direct_followup_reply、以及相关“必回复”选项)以控制该行为。