@@ -181,43 +181,45 @@ class ActionTracker: |
| 181 | 181 | self._response_history.pop(0) |
| 182 | 182 | |
| 183 | 183 | def detect_text_loop(self, response: str) -> tuple[bool, str]: |
| 184 | | - if len(self._response_history) < 1: |
| 184 | + if len(self._response_history) < 2: |
| 185 | 185 | return False, "" |
| 186 | 186 | |
| 187 | 187 | normalized = self._normalize_response(response) |
| 188 | 188 | exact_matches = sum(1 for r in self._response_history if r == normalized) |
| 189 | | - if exact_matches >= 1: |
| 189 | + if exact_matches >= 2: |
| 190 | 190 | return True, f"Agent repeated the same response {exact_matches + 1} times" |
| 191 | 191 | |
| 192 | 192 | repetitive_phrases = [ |
| 193 | 193 | "apologies for any confusion", |
| 194 | 194 | "let me proceed", |
| 195 | 195 | "i will now use the", |
| 196 | | - "let's proceed with creating", |
| 197 | | - "i'll create the", |
| 198 | 196 | ] |
| 199 | 197 | response_lower = response.lower() |
| 200 | 198 | for phrase in repetitive_phrases: |
| 201 | 199 | if phrase in response_lower: |
| 202 | 200 | phrase_count = sum(1 for r in self._response_history if phrase in r) |
| 203 | | - if phrase_count >= 1: |
| 201 | + if phrase_count >= 2: |
| 204 | 202 | return True, f"Agent is stuck repeating '{phrase}'" |
| 205 | 203 | |
| 206 | 204 | current_words = set(normalized.split()) |
| 207 | 205 | similarity_matches = 0 |
| 208 | 206 | for prev in self._response_history[-3:]: |
| 209 | 207 | prev_words = set(prev.split()) |
| 210 | | - if len(current_words) > 5 and len(prev_words) > 5: |
| 208 | + if len(current_words) > 10 and len(prev_words) > 10: |
| 211 | 209 | overlap = len(current_words & prev_words) |
| 212 | 210 | similarity = overlap / max(len(current_words), len(prev_words)) |
| 213 | | - if similarity > 0.7: |
| 211 | + if similarity > 0.85: |
| 214 | 212 | similarity_matches += 1 |
| 215 | 213 | |
| 216 | | - if similarity_matches >= 1: |
| 214 | + if similarity_matches >= 2: |
| 217 | 215 | return True, "Agent responses are highly repetitive" |
| 218 | 216 | |
| 219 | 217 | return False, "" |
| 220 | 218 | |
| 219 | + def reset_response_history(self) -> None: |
| 220 | + """Clear response history between turns to prevent cross-turn false positives.""" |
| 221 | + self._response_history.clear() |
| 222 | + |
| 221 | 223 | |
| 222 | 224 | @dataclass |
| 223 | 225 | class ValidationResult: |