ソースを参照

添加双模式检测 - 消息正确交替时跳过合并

Co-authored-by: CassiopeiaCode <[email protected]>
copilot-swe-agent[bot] 2 ヶ月 前
コミット
c15905c997
2 ファイル変更120 行追加49 行削除
  1. 23 2
      claude_converter.py
  2. 97 47
      docs/FIX_INFINITE_LOOP_CN.md

+ 23 - 2
claude_converter.py

@@ -193,7 +193,12 @@ def merge_user_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
     return result
 
 def process_history(messages: List[ClaudeMessage], thinking_enabled: bool = False, hint: str = THINKING_HINT) -> List[Dict[str, Any]]:
-    """Process history messages to match Amazon Q format (alternating user/assistant)."""
+    """Process history messages to match Amazon Q format (alternating user/assistant).
+    
+    Dual-mode detection:
+    - If messages already alternate correctly (no consecutive user/assistant), skip merging
+    - If messages have consecutive same-role messages, apply merge logic
+    """
     history = []
     seen_tool_use_ids = set()
     
@@ -304,7 +309,23 @@ def process_history(messages: List[ClaudeMessage], thinking_enabled: bool = Fals
             
             raw_history.append(entry)
 
-    # Second pass: merge consecutive user messages
+    # Dual-mode detection: check if messages already alternate correctly
+    has_consecutive_same_role = False
+    prev_role = None
+    for item in raw_history:
+        current_role = "user" if "userInputMessage" in item else "assistant"
+        if prev_role == current_role:
+            has_consecutive_same_role = True
+            break
+        prev_role = current_role
+    
+    # If messages already alternate, skip merging (fast path)
+    if not has_consecutive_same_role:
+        logger.info("Messages already alternate correctly, skipping merge logic")
+        return raw_history
+
+    # Second pass: merge consecutive user messages (only if needed)
+    logger.info("Detected consecutive same-role messages, applying merge logic")
     pending_user_msgs = []
     for item in raw_history:
         if "userInputMessage" in item:

+ 97 - 47
docs/FIX_INFINITE_LOOP_CN.md

@@ -32,72 +32,122 @@ AI: 用户说xxxx,我马上来检查  ← 又重复!
 
 ## 根本原因
 
-在 `claude_converter.py` 的 `process_history()` 函数中,存在一个"合并连续USER消息"的逻辑(第290-304行),这个逻辑**没有区分普通文本消息和包含tool_result的消息**,导致它们被错误地合并。
+在 `claude_converter.py` 的 `merge_user_messages()` 函数中,**只使用了第一个消息的 `userInputMessageContext`**,导致后续消息的 `toolResults` 丢失。
+
+### 问题代码
+
+```python
+def merge_user_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
+    # ...
+    for msg in messages:
+        content = msg.get("content", "")
+        if base_context is None:
+            base_context = msg.get("userInputMessageContext", {})  # ❌ 只用第一个
+        # ...
+    
+    result = {
+        "content": "\n\n".join(all_contents),
+        "userInputMessageContext": base_context or {},  # ❌ 后续消息的 toolResults 丢失
+        # ...
+    }
+```
 
 ### 问题示例
 
-**输入的Claude消息序列**:
+当合并多个连续的USER消息时:
+
+**输入**:
 ```python
 [
-  {role: "user", content: "M: 检查文件"},           # 0
-  {role: "assistant", content: [tool_use...]},      # 1  
-  {role: "user", content: [tool_result...]},        # 2
-  {role: "user", content: "用户的跟进问题"},         # 3 ← 连续的USER消息
-  {role: "assistant", content: "..."},              # 4
+  {content: "", userInputMessageContext: {toolResults: [t1]}},  # 第1个消息
+  {content: "", userInputMessageContext: {toolResults: [t2]}},  # 第2个消息
+  {content: "用户问题", userInputMessageContext: {}},           # 第3个消息
 ]
 ```
 
-**旧代码的输出**(错误):
+**旧代码输出**(错误):
 ```python
-history = [
-  {userInputMessage: "M: 检查文件"},
-  {assistantResponseMessage: [tool_use...]},
-  {userInputMessage: "用户的跟进问题" + toolResults:[...]}  # ❌ 被合并了!
-]
+{
+  content: "用户问题",
+  userInputMessageContext: {
+    toolResults: [t1]  # ❌ 只有第1个消息的 toolResults,t2 丢失了!
+  }
+}
 ```
 
-**问题**:
-- messages[2](tool_result)和 messages[3](普通文本)被合并成一条
-- tool_result 和普通文本混在一起
-- AI 无法正确理解对话结构
-- 导致 AI 重复执行工具调用
+**后果**:
+- AI 只能看到部分工具执行结果
+- AI 认为某些工具还没执行,重复调用
+- 进入无限循环
 
 ## 修复方案
 
-修改 `process_history()` 函数,**不合并包含tool_result的USER消息**。
-
-### 核心思路
-
-1. 检测USER消息是否包含 `toolResults`
-2. 如果包含,立即输出该消息(不加入pending队列)
-3. 只合并纯文本的USER消息(不包含tool_result)
+修改 `merge_user_messages()` 函数,**收集并合并所有消息的 `toolResults`**。
 
-### 修复后的逻辑
+### 修复后的代码
 
 ```python
-# Second pass: merge consecutive user messages (but NOT messages with tool results)
-pending_user_msgs = []
-for item in raw_history:
-    if "userInputMessage" in item:
-        user_msg = item["userInputMessage"]
-        user_ctx = user_msg.get("userInputMessageContext", {})
-        has_tool_results = "toolResults" in user_ctx and user_ctx["toolResults"]
+def merge_user_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
+    """Merge consecutive user messages, keeping only the last 2 messages' images.
+    
+    IMPORTANT: This function properly merges toolResults from all messages to prevent
+    losing tool execution history, which would cause infinite loops.
+    """
+    if not messages:
+        return {}
+    
+    all_contents = []
+    base_context = None
+    all_tool_results = []  # 收集所有消息的 toolResults
+    
+    for msg in messages:
+        content = msg.get("content", "")
+        msg_ctx = msg.get("userInputMessageContext", {})
         
-        # 如果包含tool_result,不合并
-        if has_tool_results:
-            # 先输出pending的消息
-            if pending_user_msgs:
-                merged = merge_user_messages(pending_user_msgs)
-                history.append({"userInputMessage": merged})
-                pending_user_msgs = []
-            # 然后直接添加这条tool_result消息(不合并)
-            history.append(item)
+        # 从第一个消息初始化 base_context
+        if base_context is None:
+            base_context = msg_ctx.copy() if msg_ctx else {}
+            # 移除 toolResults,单独合并
+            if "toolResults" in base_context:
+                all_tool_results.extend(base_context.pop("toolResults"))
         else:
-            # 普通USER消息可以合并
-            pending_user_msgs.append(user_msg)
-    elif "assistantResponseMessage" in item:
-        if pending_user_msgs:
-            merged = merge_user_messages(pending_user_msgs)
+            # 从后续消息收集 toolResults
+            if "toolResults" in msg_ctx:
+                all_tool_results.extend(msg_ctx["toolResults"])
+        
+        if content:
+            all_contents.append(content)
+    
+    result = {
+        "content": "\n\n".join(all_contents),
+        "userInputMessageContext": base_context or {},
+        # ...
+    }
+    
+    # ✅ 将合并的 toolResults 添加到结果
+    if all_tool_results:
+        result["userInputMessageContext"]["toolResults"] = all_tool_results
+    
+    return result
+```
+
+### 修复后的效果
+
+**相同的输入,修复后的输出**(正确):
+```python
+{
+  content: "用户问题",
+  userInputMessageContext: {
+    toolResults: [t1, t2]  # ✅ 包含所有消息的 toolResults
+  }
+}
+```
+
+**优势**:
+- ✅ 所有 toolResults 都被保留
+- ✅ AI 可以看到完整的工具执行历史
+- ✅ 消除无限循环
+- ✅ 提高对话质量
             history.append({"userInputMessage": merged})
             pending_user_msgs = []
         history.append(item)