Преглед изворни кода

Fix tool_result ordering to match tool_uses order

When Claude Code sends parallel tool calls, the tool_results might be
returned in a different order than the tool_uses were made. This causes
the model to get confused (e.g., seeing Edit result before Read result
even though Read was called first).

Key changes:
1. Add _reorder_tool_results_by_tool_uses() helper function
2. Track tool_use order from assistant messages in process_history()
3. Reorder tool_results in user messages to match the preceding
   assistant message's tool_uses order
4. Apply same fix to current message processing in
   convert_claude_to_amazonq_request()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
jerry-271828 пре 1 месец
родитељ
комит
8a9df15519
1 измењених фајлова са 70 додато и 7 уклоњено
  1. 70 7
      claude_converter.py

+ 70 - 7
claude_converter.py

@@ -322,15 +322,50 @@ def merge_user_messages(messages: List[Dict[str, Any]], hint: str = THINKING_HIN
 
     return result
 
+def _reorder_tool_results_by_tool_uses(tool_results: List[Dict[str, Any]], tool_use_order: List[str]) -> List[Dict[str, Any]]:
+    """Reorder tool_results to match the order of tool_uses from the preceding assistant message.
+
+    This is critical for preventing model confusion when parallel tool calls return results
+    in a different order than they were called.
+
+    Args:
+        tool_results: List of tool_result dicts with toolUseId
+        tool_use_order: List of toolUseIds in the order they appeared in the assistant message
+
+    Returns:
+        Reordered list of tool_results
+    """
+    if not tool_use_order or not tool_results:
+        return tool_results
+
+    result_by_id = {r["toolUseId"]: r for r in tool_results}
+    ordered_results = []
+
+    # Add results in the order of tool_uses
+    for tool_use_id in tool_use_order:
+        if tool_use_id in result_by_id:
+            ordered_results.append(result_by_id.pop(tool_use_id))
+
+    # Add any remaining results not in the original order (shouldn't happen normally)
+    ordered_results.extend(result_by_id.values())
+
+    return ordered_results
+
+
 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).
 
     Dual-mode detection:
     - If messages already alternate correctly (no consecutive user/assistant), skip merging
     - If messages have consecutive same-role messages, apply merge logic
+
+    Key fix: Track tool_use order from assistant messages and reorder tool_results in user
+    messages to match. This prevents model confusion when parallel tool calls return results
+    in a different order than they were called.
     """
     history = []
     seen_tool_use_ids = set()  # Track tool_use IDs in assistant messages
+    last_tool_use_order = []  # Track order of tool_uses from the last assistant message
 
     raw_history = []
 
@@ -369,10 +404,15 @@ def process_history(messages: List[ClaudeMessage], thinking_enabled: bool = Fals
                 text_content = "\n".join(text_parts)
             else:
                 text_content = extract_text_from_content(content)
-            
+
             if should_append_hint:
                 text_content = _append_thinking_hint(text_content, hint)
-            
+
+            # Reorder tool_results to match the order of tool_uses from the preceding assistant message
+            if tool_results and last_tool_use_order:
+                tool_results = _reorder_tool_results_by_tool_uses(tool_results, last_tool_use_order)
+                logger.info(f"Reordered {len(tool_results)} tool_results to match tool_uses order")
+
             user_ctx = {
                 "envState": {
                     "operatingSystem": "macos",
@@ -389,20 +429,22 @@ def process_history(messages: List[ClaudeMessage], thinking_enabled: bool = Fals
             }
             if images:
                 u_msg["images"] = images
-                
+
             raw_history.append({"userInputMessage": u_msg})
-            
+
         elif msg.role == "assistant":
             content = msg.content
             text_content = extract_text_from_content(content)
-            
+
             entry = {
                 "assistantResponseMessage": {
                     "messageId": str(uuid.uuid4()),
                     "content": text_content
                 }
             }
-            
+
+            # Track tool_use order for reordering tool_results in the next user message
+            last_tool_use_order = []
             if isinstance(content, list):
                 tool_uses = []
                 for block in content:
@@ -410,6 +452,7 @@ def process_history(messages: List[ClaudeMessage], thinking_enabled: bool = Fals
                         tid = block.get("id")
                         if tid and tid not in seen_tool_use_ids:
                             seen_tool_use_ids.add(tid)
+                            last_tool_use_order.append(tid)  # Track order
                             tool_uses.append({
                                 "toolUseId": tid,
                                 "name": block.get("name"),
@@ -417,7 +460,7 @@ def process_history(messages: List[ClaudeMessage], thinking_enabled: bool = Fals
                             })
                 if tool_uses:
                     entry["assistantResponseMessage"]["toolUses"] = tool_uses
-            
+
             raw_history.append(entry)
 
     # Dual-mode detection: check if messages already alternate correctly
@@ -589,6 +632,26 @@ def convert_claude_to_amazonq_request(req: ClaudeRequest, conversation_id: Optio
         else:
             prompt_content = extract_text_from_content(content)
 
+    # Get tool_use order from the last assistant message for reordering current message's tool_results
+    last_tool_use_order = []
+    if len(req.messages) >= 2:
+        # Find the last assistant message before the current user message
+        for i in range(len(req.messages) - 2, -1, -1):
+            if req.messages[i].role == "assistant":
+                assistant_content = req.messages[i].content
+                if isinstance(assistant_content, list):
+                    for block in assistant_content:
+                        if isinstance(block, dict) and block.get("type") == "tool_use":
+                            tid = block.get("id")
+                            if tid:
+                                last_tool_use_order.append(tid)
+                break
+
+    # Reorder tool_results to match the order of tool_uses from the preceding assistant message
+    if tool_results and last_tool_use_order:
+        tool_results = _reorder_tool_results_by_tool_uses(tool_results, last_tool_use_order)
+        logger.info(f"Reordered {len(tool_results)} current message tool_results to match tool_uses order")
+
     # 3. Context
     user_ctx = {
         "envState": {