fix(agent): add configurable repeated-reply convergence guard#6921
fix(agent): add configurable repeated-reply convergence guard#6921Jacobinwwey wants to merge 2 commits intoAstrBotDevs:masterfrom
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求旨在解决 Agent 在某些异常任务路径中可能连续输出相同文本,导致对话噪声和用户体验不佳的问题。通过引入一个轻量且可配置的“连续相同回复收敛保护”机制,Agent 能够在检测到连续重复回复达到预设阈值时,主动停止重复行为,禁用工具调用,并被引导直接给出最终答复,从而有效避免无限循环,提升 Agent 的稳定性和用户交互质量。 Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The normalization logic for
repeat_reply_guard_thresholdis duplicated between_normalize_repeat_reply_guard_thresholdinastr_agent_run_util.pyand theinitializemethod ininternal.py; consider reusing the helper (or centralizing this logic) to keep behavior consistent and easier to maintain.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The normalization logic for `repeat_reply_guard_threshold` is duplicated between `_normalize_repeat_reply_guard_threshold` in `astr_agent_run_util.py` and the `initialize` method in `internal.py`; consider reusing the helper (or centralizing this logic) to keep behavior consistent and easier to maintain.
## Individual Comments
### Comment 1
<location path="astrbot/core/astr_agent_run_util.py" line_range="90-95" />
<code_context>
return status_msg
+def _normalize_repeat_reply_guard_threshold(value: int) -> int:
+ try:
+ parsed = int(value)
+ except (TypeError, ValueError):
+ return 0
+ return max(0, parsed)
+
+
</code_context>
<issue_to_address>
**suggestion:** Consider reusing this normalization logic where the threshold is parsed in the pipeline stage to avoid divergence.
`internal.py.initialize` manually coerces `repeat_reply_guard_threshold` to a non‑negative int using logic that overlaps with `_normalize_repeat_reply_guard_threshold`. To avoid drift and make future changes safer, consider using a single normalization helper (or moving this one to a shared module) so both call sites share the same coercion rules.
Suggested implementation:
```python
def normalize_repeat_reply_guard_threshold(value) -> int:
```
```python
def normalize_repeat_reply_guard_threshold(value) -> int:
try:
parsed = int(value)
except (TypeError, ValueError):
return 0
return max(0, parsed)
```
To fully implement the suggestion, you should also:
1. In `astrbot/core/internal.py` (or wherever `initialize` is defined and the pipeline stage parses `repeat_reply_guard_threshold`):
- Import the helper:
- `from astrbot.core.astr_agent_run_util import normalize_repeat_reply_guard_threshold`
- Replace the manual coercion logic for `repeat_reply_guard_threshold` with:
- `repeat_reply_guard_threshold = normalize_repeat_reply_guard_threshold(repeat_reply_guard_threshold)`
2. Remove any now-redundant bounds checking or `int()` parsing around `repeat_reply_guard_threshold` in that initializer to ensure there is a single source of truth for this normalization.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request introduces a 'repeat reply guard' mechanism to prevent agents from getting stuck in loops by repeatedly outputting the same LLM result. It adds a repeat_reply_guard_threshold parameter to run_agent and related functions, along with configuration in default.py and its schema. New helper functions _normalize_repeat_reply_guard_threshold and _build_chain_signature are added, and the run_agent logic is updated to implement the guard. Unit tests for this new functionality are also included. The review comments suggest that the normalization logic for repeat_reply_guard_threshold is duplicated and can be simplified, specifically recommending the removal of the _normalize_repeat_reply_guard_threshold function in astr_agent_run_util.py and a more concise implementation of the normalization within InternalAgentSubStage.initialize.
astrbot/core/astr_agent_run_util.py
Outdated
| def _normalize_repeat_reply_guard_threshold(value: int) -> int: | ||
| try: | ||
| parsed = int(value) | ||
| except (TypeError, ValueError): | ||
| return 0 | ||
| return max(0, parsed) |
astrbot/core/astr_agent_run_util.py
Outdated
| guard_threshold = _normalize_repeat_reply_guard_threshold( | ||
| repeat_reply_guard_threshold | ||
| ) |
| if isinstance(self.repeat_reply_guard_threshold, bool): | ||
| self.repeat_reply_guard_threshold = 3 | ||
| try: | ||
| self.repeat_reply_guard_threshold = int(self.repeat_reply_guard_threshold) | ||
| except (TypeError, ValueError): | ||
| self.repeat_reply_guard_threshold = 3 | ||
| if self.repeat_reply_guard_threshold < 0: | ||
| self.repeat_reply_guard_threshold = 0 |
There was a problem hiding this comment.
这部分用于规范化 repeat_reply_guard_threshold 的逻辑有些冗长,可以简化以提高可读性。
| if isinstance(self.repeat_reply_guard_threshold, bool): | |
| self.repeat_reply_guard_threshold = 3 | |
| try: | |
| self.repeat_reply_guard_threshold = int(self.repeat_reply_guard_threshold) | |
| except (TypeError, ValueError): | |
| self.repeat_reply_guard_threshold = 3 | |
| if self.repeat_reply_guard_threshold < 0: | |
| self.repeat_reply_guard_threshold = 0 | |
| try: | |
| if isinstance(self.repeat_reply_guard_threshold, bool): | |
| raise TypeError | |
| parsed_val = int(self.repeat_reply_guard_threshold) | |
| self.repeat_reply_guard_threshold = max(0, parsed_val) | |
| except (TypeError, ValueError): | |
| self.repeat_reply_guard_threshold = 3 |
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The normalization logic for
repeat_reply_guard_thresholdininternal.initializeduplicates the new_normalize_repeat_reply_guard_thresholdhelper; consider reusing the helper there (or centralizing normalization in one place) to keep behavior consistent and easier to maintain. - The injected convergence prompt string in the guard is currently hard-coded in Chinese inside
run_agent; if you expect multilingual deployments, it may be worth routing this through your existing i18n/config mechanism or at least centralizing the message text for easier adjustment.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The normalization logic for `repeat_reply_guard_threshold` in `internal.initialize` duplicates the new `_normalize_repeat_reply_guard_threshold` helper; consider reusing the helper there (or centralizing normalization in one place) to keep behavior consistent and easier to maintain.
- The injected convergence prompt string in the guard is currently hard-coded in Chinese inside `run_agent`; if you expect multilingual deployments, it may be worth routing this through your existing i18n/config mechanism or at least centralizing the message text for easier adjustment.
## Individual Comments
### Comment 1
<location path="astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py" line_range="67-76" />
<code_context>
self.tool_schema_mode = "full"
if isinstance(self.max_step, bool): # workaround: #2622
self.max_step = 30
+ self.repeat_reply_guard_threshold: int = settings.get(
+ "repeat_reply_guard_threshold", 3
+ )
+ if isinstance(self.repeat_reply_guard_threshold, bool):
+ self.repeat_reply_guard_threshold = 3
+ try:
+ self.repeat_reply_guard_threshold = int(self.repeat_reply_guard_threshold)
+ except (TypeError, ValueError):
+ self.repeat_reply_guard_threshold = 3
+ if self.repeat_reply_guard_threshold < 0:
+ self.repeat_reply_guard_threshold = 0
self.show_tool_use: bool = settings.get("show_tool_use_status", True)
</code_context>
<issue_to_address>
**suggestion:** Repeat-reply threshold normalization is duplicated instead of reusing the shared helper.
This normalization logic (bool handling, int cast, clamping negatives) closely matches `_normalize_repeat_reply_guard_threshold` in `astr_agent_run_util.py`. To keep behavior consistent and avoid future drift, please refactor to reuse that helper here, or extract a shared utility that both call into.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py
Outdated
Show resolved
Hide resolved
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The normalization logic for
repeat_reply_guard_thresholdis duplicated between_normalize_repeat_reply_guard_thresholdinastr_agent_run_util.pyand the initialization inagent_sub_stages/internal.py; consider reusing the helper or centralizing this normalization to avoid divergent behavior on edge inputs. repeat_reply_guard_threshold's default value3is hard-coded in several places (config default, provider template, stage initialize, and therun_agent/run_live_agentsignatures); it would be more maintainable to derive these from a single configuration source or constant to prevent future inconsistencies.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The normalization logic for `repeat_reply_guard_threshold` is duplicated between `_normalize_repeat_reply_guard_threshold` in `astr_agent_run_util.py` and the initialization in `agent_sub_stages/internal.py`; consider reusing the helper or centralizing this normalization to avoid divergent behavior on edge inputs.
- `repeat_reply_guard_threshold`'s default value `3` is hard-coded in several places (config default, provider template, stage initialize, and the `run_agent` / `run_live_agent` signatures); it would be more maintainable to derive these from a single configuration source or constant to prevent future inconsistencies.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The injected convergence prompt in
run_agentis hard-coded Chinese text; consider routing this through your existing localization/prompting mechanism or at least aligning its language with the conversation/user locale. - The chain signature for repeat detection is based on
get_plain_text(with_other_comps_mark=True)with whitespace normalized; if messages can contain dynamic or non-deterministic components (timestamps, IDs, etc.), consider whether they should be stripped or normalized further to avoid missing genuinely repeated replies.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The injected convergence prompt in `run_agent` is hard-coded Chinese text; consider routing this through your existing localization/prompting mechanism or at least aligning its language with the conversation/user locale.
- The chain signature for repeat detection is based on `get_plain_text(with_other_comps_mark=True)` with whitespace normalized; if messages can contain dynamic or non-deterministic components (timestamps, IDs, etc.), consider whether they should be stripped or normalized further to avoid missing genuinely repeated replies.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
“硬编码中文提示词”和“重复检测签名是否要进一步归一化”,都不属于必须处理的新增 unresolved 线程;而且在没有失败测试、maintainer 明确要求或生产风险的新证据前,也不应再次切换同一 concern 的策略。 |
Motivation / 动机
在部分异常任务路径中,Agent 可能连续多步输出几乎完全相同的文本(用户未要求重复),造成对话噪声和“看起来像卡死”的体验。
当前虽然有
max_agent_step全局上限,但在上限较高时,重复输出仍会持续较久。本 PR 增加一个轻量、可配置的“连续相同回复收敛保护”,用于更早阻断这类重复循环。
Modifications / 改动点
核心行为增强:
astrbot/core/astr_agent_run_util.pyllm_result文本签名计数)。0关闭。配置项新增:
astrbot/core/config/default.pyprovider_settings.repeat_reply_guard_threshold(默认3)。调用链透传:
astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.pyrepeat_reply_guard_threshold。run_agent/run_live_agent。测试覆盖:
新增
tests/unit/test_astr_agent_run_util.pyThis is NOT a breaking change. / 这不是一个破坏性变更。
Overlap Check / 业务重叠检查
已检查当前
author:Jacobinwwey的 open PR:#6872关注 subagent handoff 调用次数限制;#6259关注 tool result dedup 与 tool error loop guard。本 PR 聚焦 LLM 文本输出层 的“连续相同回复收敛”,不改 handoff 次数护栏与 tool-result dedup 语义,业务目标独立。
Test Results / 测试结果
本地执行:
结果:
py_compile: passedruff check: All checks passedpytest:3 passedChecklist / 检查清单
requirements.txt和pyproject.toml文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations inrequirements.txtandpyproject.toml.🤖 Generated with Codex
Summary by Sourcery
Add a configurable guard to detect and converge repeated agent replies, wiring it through the agent pipeline and configuration with accompanying tests.
New Features:
Enhancements:
Tests: