CoolFace
Apppublic

Akshanshsensei/PDF-Constrained-Conversational-Agent

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
test_thinking_transforms.py161 linesDownload Raw Back to tests
1"""2tests/test_thinking_transforms.py — Unit Tests for Thinking Tag Transforms3===========================================================================4 5Tests for the two pure helper functions added to app.py:6  - transform_thinking_tags(text) → Converts <thinking> tags to <details> accordions7  - strip_thinking(text)         → Strips <thinking> blocks before Redis memory storage8"""9 10import sys11import os12import re13import unittest14 15# ---------------------------------------------------------------------------16# Pull the two helpers out of app.py without importing the full Gradio app17# (which would trigger backend init, Redis connections, etc.)18# ---------------------------------------------------------------------------19 20def transform_thinking_tags(text: str) -> str:21    """Inline copy of the helper — kept in sync with app.py."""22    # Guard: closing tag partially streamed — check ALL partial prefixes23    _CLOSING = "</thinking>"24    if any(_CLOSING[:i] in text for i in range(2, len(_CLOSING))) and _CLOSING not in text:25        return text26    # Guard: opening tag partially streamed (e.g. "<thinki" or "<thinking" without ">")27    if "<thinking" in text and "<thinking>" not in text:28        return text29    text = text.replace(30        "<thinking>",31        "<details open>\n<summary>Agent Thinking...</summary>\n\n"32    )33    text = text.replace("</thinking>", "\n</details>\n\n")34    return text35 36 37def strip_thinking(text: str) -> str:38    """Inline copy of the helper — kept in sync with app.py."""39    return re.sub(r"<thinking>.*?</thinking>", "", text, flags=re.DOTALL).strip()40 41 42# ===========================================================================43# Tests for transform_thinking_tags44# ===========================================================================45 46class TestTransformThinkingTags(unittest.TestCase):47 48    def test_complete_tags_converted(self):49        """Complete <thinking>...</thinking> is replaced with <details open>."""50        result = transform_thinking_tags("<thinking>foo</thinking>bar")51        self.assertIn("<details open>", result)52        self.assertIn("</details>", result)53        self.assertIn("bar", result)54        self.assertNotIn("<thinking>", result)55        self.assertNotIn("</thinking>", result)56 57    def test_partial_opening_tag_unchanged(self):58        """Partial opening tag (no closing >) must be returned unchanged."""59        partial = "<thinki"60        self.assertEqual(transform_thinking_tags(partial), partial)61 62    def test_partial_opening_tag_with_content_unchanged(self):63        """'<thinking' without '>' is partial — must be returned unchanged."""64        partial = "<thinking"65        self.assertEqual(transform_thinking_tags(partial), partial)66 67    def test_partial_closing_tag_unchanged(self):68        """Partial closing tag must not produce broken HTML."""69        partial = "<thinking>foo</thinki"70        self.assertEqual(transform_thinking_tags(partial), partial)71 72    def test_partial_closing_tag_no_gt_unchanged(self):73        """'</thinking' without '>' is partial — must be returned unchanged."""74        partial = "<thinking>foo</thinking"75        self.assertEqual(transform_thinking_tags(partial), partial)76 77    def test_empty_string(self):78        """Empty input returns empty string."""79        self.assertEqual(transform_thinking_tags(""), "")80 81    def test_no_tags_passthrough(self):82        """Text with no thinking tags is returned unchanged."""83        text = "just an answer"84        self.assertEqual(transform_thinking_tags(text), text)85 86    def test_phrase_with_thinking_word_unchanged(self):87        """'thinking' as a natural word (no tag) must not be altered."""88        text = "I was thinking about it"89        self.assertEqual(transform_thinking_tags(text), text)90 91    def test_tool_call_response_unchanged(self):92        """A raw tool call response must never be altered."""93        text = '<tool_call>{"name": "get_page_count", "args": {}}</tool_call>'94        self.assertEqual(transform_thinking_tags(text), text)95 96    def test_summary_label_present(self):97        """The accordion summary label must contain the 🧠 emoji."""98        result = transform_thinking_tags("<thinking>reasoning</thinking>answer")99        self.assertIn("Agent Thinking...", result)100 101    def test_details_open_attribute(self):102        """Accordion must use <details open> so it auto-expands during streaming."""103        result = transform_thinking_tags("<thinking>r</thinking>a")104        self.assertIn("<details open>", result)105 106    def test_multiple_thinking_blocks(self):107        """Multiple thinking blocks are all converted."""108        text = "<thinking>r1</thinking>a1<thinking>r2</thinking>a2"109        result = transform_thinking_tags(text)110        self.assertEqual(result.count("<details open>"), 2)111        self.assertEqual(result.count("</details>"), 2)112 113 114# ===========================================================================115# Tests for strip_thinking116# ===========================================================================117 118class TestStripThinking(unittest.TestCase):119 120    def test_strips_complete_block(self):121        """A complete <thinking>...</thinking> block is removed."""122        text = "<thinking>my reasoning</thinking>Final answer."123        self.assertEqual(strip_thinking(text), "Final answer.")124 125    def test_strips_block_with_newlines(self):126        """Multi-line thinking blocks are fully stripped."""127        text = "<thinking>\nline one\nline two\n</thinking>\nFinal answer."128        self.assertEqual(strip_thinking(text), "Final answer.")129 130    def test_no_thinking_block_unchanged(self):131        """Text with no thinking block is returned as-is."""132        text = "Just a plain answer."133        self.assertEqual(strip_thinking(text), text)134 135    def test_strips_multiple_thinking_blocks(self):136        """Multiple thinking blocks (from multi-turn if ever concatenated) are all removed."""137        text = "<thinking>r1</thinking>A1. <thinking>r2</thinking>A2."138        result = strip_thinking(text)139        self.assertNotIn("<thinking>", result)140        self.assertNotIn("</thinking>", result)141        self.assertIn("A1.", result)142        self.assertIn("A2.", result)143 144    def test_leading_trailing_whitespace_stripped(self):145        """Result is trimmed of leading/trailing whitespace."""146        text = "<thinking>r</thinking>   answer   "147        self.assertEqual(strip_thinking(text), "answer")148 149    def test_empty_thinking_block(self):150        """An empty thinking block is stripped cleanly."""151        text = "<thinking></thinking>answer"152        self.assertEqual(strip_thinking(text), "answer")153 154    def test_empty_string(self):155        """Empty input returns empty string."""156        self.assertEqual(strip_thinking(""), "")157 158 159if __name__ == "__main__":160    unittest.main()161