Просмотр исходного кода

Align thinking format conversions

CassiopeiaCode 2 месяцев назад
Родитель
Сommit
3b29e62ec0
4 измененных файлов с 48 добавлено и 20 удалено
  1. 4 1
      app.py
  2. 16 12
      claude_converter.py
  3. 14 5
      claude_parser.py
  4. 14 2
      claude_stream.py

+ 4 - 1
app.py

@@ -747,6 +747,9 @@ async def claude_messages(req: ClaudeRequest, account: Dict[str, Any] = Depends(
                         if final_content[idx]:
                             if delta.get("type") == "text_delta":
                                 final_content[idx]["text"] += delta.get("text", "")
+                            elif delta.get("type") == "thinking_delta":
+                                final_content[idx].setdefault("thinking", "")
+                                final_content[idx]["thinking"] += delta.get("thinking", "")
                             elif delta.get("type") == "input_json_delta":
                                 if "partial_json" not in final_content[idx]:
                                     final_content[idx]["partial_json"] = ""
@@ -1393,4 +1396,4 @@ async def startup_event():
 @app.on_event("shutdown")
 async def shutdown_event():
     await _close_global_client()
-    await close_db()
+    await close_db()

+ 16 - 12
claude_converter.py

@@ -25,6 +25,12 @@ except ImportError:
 logger = logging.getLogger(__name__)
 
 THINKING_HINT = "<antml:thinking_mode>interleaved</antml:thinking_mode><antml:max_thinking_length>16000</antml:max_thinking_length>"
+THINKING_START_TAG = "<thinking>"
+THINKING_END_TAG = "</thinking>"
+
+def _wrap_thinking_content(thinking_text: str) -> str:
+    """Wrap thinking text with the XML tag expected by Amazon Q."""
+    return f"{THINKING_START_TAG}{thinking_text}{THINKING_END_TAG}"
 
 def is_thinking_mode_enabled(thinking_cfg: Optional[Any]) -> bool:
     """Detect whether the client enabled thinking mode."""
@@ -46,15 +52,6 @@ def is_thinking_mode_enabled(thinking_cfg: Optional[Any]) -> bool:
             return True
     return False
 
-def _contains_tool_content(content: Union[str, List[Dict[str, Any]]]) -> bool:
-    """Return True if the user message content contains tool result/call payloads."""
-    if not isinstance(content, list):
-        return False
-    for block in content:
-        if isinstance(block, dict) and block.get("type") in {"tool_result", "tool_use"}:
-            return True
-    return False
-
 def _append_thinking_hint(text: str, hint: str = THINKING_HINT) -> str:
     """Append the special hint once to the end of the text."""
     text = text or ""
@@ -88,8 +85,11 @@ def extract_text_from_content(content: Union[str, List[Dict[str, Any]]]) -> str:
         parts = []
         for block in content:
             if isinstance(block, dict):
-                if block.get("type") == "text":
+                block_type = block.get("type")
+                if block_type == "text":
                     parts.append(block.get("text", ""))
+                elif block_type == "thinking":
+                    parts.append(_wrap_thinking_content(block.get("thinking", "")))
         return "\n".join(parts)
     return ""
 
@@ -186,7 +186,7 @@ def process_history(messages: List[ClaudeMessage], thinking_enabled: bool = Fals
             text_content = ""
             tool_results = None
             images = extract_images_from_content(content)
-            should_append_hint = thinking_enabled and not _contains_tool_content(content)
+            should_append_hint = thinking_enabled
             
             if isinstance(content, list):
                 text_parts = []
@@ -195,6 +195,8 @@ def process_history(messages: List[ClaudeMessage], thinking_enabled: bool = Fals
                         btype = block.get("type")
                         if btype == "text":
                             text_parts.append(block.get("text", ""))
+                        elif btype == "thinking":
+                            text_parts.append(_wrap_thinking_content(block.get("thinking", "")))
                         elif btype == "tool_result":
                             if tool_results is None:
                                 tool_results = []
@@ -334,6 +336,8 @@ def convert_claude_to_amazonq_request(req: ClaudeRequest, conversation_id: Optio
                     btype = block.get("type")
                     if btype == "text":
                         text_parts.append(block.get("text", ""))
+                    elif btype == "thinking":
+                        text_parts.append(_wrap_thinking_content(block.get("thinking", "")))
                     elif btype == "tool_result":
                         has_tool_result = True
                         if tool_results is None:
@@ -371,7 +375,7 @@ def convert_claude_to_amazonq_request(req: ClaudeRequest, conversation_id: Optio
         else:
             prompt_content = extract_text_from_content(content)
 
-        if thinking_enabled and not _contains_tool_content(content):
+        if thinking_enabled:
             prompt_content = _append_thinking_hint(prompt_content)
             
     # 3. Context

+ 14 - 5
claude_parser.py

@@ -150,19 +150,28 @@ def build_message_start(conversation_id: str, model: str = "claude-sonnet-4.5",
 
 def build_content_block_start(index: int, block_type: str = "text") -> str:
     """Build content_block_start SSE event."""
+    if block_type == "text":
+        block_payload = {"type": "text", "text": ""}
+    elif block_type == "thinking":
+        block_payload = {"type": "thinking", "thinking": ""}
+    else:
+        block_payload = {"type": block_type}
     data = {
         "type": "content_block_start",
         "index": index,
-        "content_block": {"type": block_type, "text": ""} if block_type in ["text", "thinking"] else {"type": block_type}
+        "content_block": block_payload
     }
     return _sse_format("content_block_start", data)
 
-def build_content_block_delta(index: int, text: str) -> str:
-    """Build content_block_delta SSE event (text)."""
+def build_content_block_delta(index: int, text: str, delta_type: str = "text_delta", field_name: str = "text") -> str:
+    """Build content_block_delta SSE event."""
+    delta = {"type": delta_type}
+    if field_name:
+        delta[field_name] = text
     data = {
         "type": "content_block_delta",
         "index": index,
-        "delta": {"type": "text_delta", "text": text}
+        "delta": delta
     }
     return _sse_format("content_block_delta", data)
 
@@ -219,4 +228,4 @@ def build_tool_use_input_delta(index: int, input_json_delta: str) -> str:
             "partial_json": input_json_delta
         }
     }
-    return _sse_format("content_block_delta", data)
+    return _sse_format("content_block_delta", data)

+ 14 - 2
claude_stream.py

@@ -124,6 +124,7 @@ class ClaudeStreamHandler:
                                     yield build_content_block_start(self.content_block_index, "text")
                                     self.content_block_start_sent = True
                                     self.content_block_started = True
+                                    self.content_block_stop_sent = False
                                 self.response_buffer.append(before_text)
                                 yield build_content_block_delta(self.content_block_index, before_text)
 
@@ -148,6 +149,7 @@ class ClaudeStreamHandler:
                                 yield build_content_block_start(self.content_block_index, "text")
                                 self.content_block_start_sent = True
                                 self.content_block_started = True
+                                self.content_block_stop_sent = False
                             self.response_buffer.append(remaining)
                             yield build_content_block_delta(self.content_block_index, remaining)
                             self.think_buffer = ""
@@ -159,7 +161,12 @@ class ClaudeStreamHandler:
                             # Send thinking content
                             thinking_text = self.think_buffer[pos:think_end]
                             if thinking_text:
-                                yield build_content_block_delta(self.content_block_index, thinking_text)
+                                yield build_content_block_delta(
+                                    self.content_block_index,
+                                    thinking_text,
+                                    delta_type="thinking_delta",
+                                    field_name="thinking"
+                                )
 
                             # Close thinking block
                             yield build_content_block_stop(self.content_block_index)
@@ -170,7 +177,12 @@ class ClaudeStreamHandler:
                         else:
                             # No </thinking> yet, send as thinking
                             remaining = self.think_buffer[pos:]
-                            yield build_content_block_delta(self.content_block_index, remaining)
+                            yield build_content_block_delta(
+                                self.content_block_index,
+                                remaining,
+                                delta_type="thinking_delta",
+                                field_name="thinking"
+                            )
                             self.think_buffer = ""
                             break