From e7f16f07f7a709d556397514735df74f92189906 Mon Sep 17 00:00:00 2001 From: "Paul Gauthier (aider)" Date: Fri, 7 Mar 2025 16:10:21 -0800 Subject: [PATCH] refactor: Extract reasoning tag handling to separate module --- aider/coders/base_coder.py | 49 ++++------------------------ aider/reasoning_tags.py | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 aider/reasoning_tags.py diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index b287ae216..bd1a45f0d 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -30,16 +30,16 @@ from aider.llm import litellm from aider.models import RETRY_TIMEOUT from aider.repo import ANY_GIT_ERROR, GitRepo from aider.repomap import RepoMap +from aider.reasoning_tags import ( + REASONING_TAG, REASONING_START, REASONING_END, + replace_reasoning_tags, format_reasoning_content, detect_reasoning_tag +) from aider.run_cmd import run_cmd from aider.utils import format_content, format_messages, format_tokens, is_image_file from ..dump import dump # noqa: F401 from .chat_chunks import ChatChunks -REASONING_TAG = "thinking-content-" + "7bbeb8e1441453ad999a0bbba8a46d4b" -REASONING_START = "> Thinking ..." -REASONING_END = "> ... done thinking.\n\n------" - class UnknownEditFormat(ValueError): def __init__(self, edit_format, valid_formats): @@ -1697,39 +1697,7 @@ class Coder: if args: self.io.ai_output(json.dumps(args, indent=4)) - def replace_reasoning_tags(self, text): - """ - Replace opening and closing reasoning tags with standard formatting. - Ensures exactly one blank line before START and END markers. - - Args: - text (str): The text containing the tags - - Returns: - str: Text with reasoning tags replaced with standard format - """ - if not text: - return text - - # Helper function to ensure exactly one blank line before replacement - def ensure_one_blank_line(match): - content_before = match.string[: match.start()].rstrip() - # If we're not at the start of the text, add exactly one blank line - if content_before: - return f"{content_before}\n\n{match.group(1)}" - # If we're at the start, don't add extra newlines - return match.group(1) - - # Replace opening tag with proper spacing - text = re.sub(f"\\s*<{self.reasoning_tag_name}>\\s*", f"\n{REASONING_START}\n\n", text) - - # Replace closing tag with proper spacing - text = re.sub(f"\\s*\\s*", f"\n\n{REASONING_END}\n\n", text) - - # Clean up any excessive newlines (more than 2 consecutive) - text = re.sub(r"\n{3,}", "\n\n", text) - - return text + # Moved to aider/reasoning_tags.py def show_send_output(self, completion): if self.verbose: @@ -1774,12 +1742,7 @@ class Coder: show_resp = self.render_incremental_response(True) if reasoning_content: - formatted_reasoning = ( - f"<{self.reasoning_tag_name}>\n\n" - + reasoning_content - + f"\n\n" - ) - formatted_reasoning = self.replace_reasoning_tags(formatted_reasoning) + "\n\n" + formatted_reasoning = format_reasoning_content(reasoning_content, self.reasoning_tag_name) + "\n\n" show_resp = formatted_reasoning + show_resp self.io.assistant_output(show_resp, pretty=self.show_pretty()) diff --git a/aider/reasoning_tags.py b/aider/reasoning_tags.py new file mode 100644 index 000000000..fae4914a0 --- /dev/null +++ b/aider/reasoning_tags.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +import re + +# Standard tag identifier +REASONING_TAG = "thinking-content-" + "7bbeb8e1441453ad999a0bbba8a46d4b" +# Output formatting +REASONING_START = "> Thinking ..." +REASONING_END = "> ... done thinking.\n\n------" + +def replace_reasoning_tags(text, tag_name): + """ + Replace opening and closing reasoning tags with standard formatting. + Ensures exactly one blank line before START and END markers. + + Args: + text (str): The text containing the tags + tag_name (str): The name of the tag to replace + + Returns: + str: Text with reasoning tags replaced with standard format + """ + if not text: + return text + + # Replace opening tag with proper spacing + text = re.sub(f"\\s*<{tag_name}>\\s*", f"\n{REASONING_START}\n\n", text) + + # Replace closing tag with proper spacing + text = re.sub(f"\\s*\\s*", f"\n\n{REASONING_END}\n\n", text) + + # Clean up any excessive newlines (more than 2 consecutive) + text = re.sub(r"\n{3,}", "\n\n", text) + + return text + +def format_reasoning_content(reasoning_content, tag_name): + """ + Format reasoning content with appropriate tags. + + Args: + reasoning_content (str): The content to format + tag_name (str): The tag name to use + + Returns: + str: Formatted reasoning content with tags + """ + if not reasoning_content: + return "" + + formatted = f"<{tag_name}>\n\n{reasoning_content}\n\n" + return replace_reasoning_tags(formatted, tag_name) + +def detect_reasoning_tag(text, tag_name): + """ + Detect if text contains reasoning tags. + + Args: + text (str): The text to check + tag_name (str): The tag name to look for + + Returns: + bool: True if text contains reasoning tags + """ + opening_pattern = f"<{tag_name}>" + return opening_pattern in text