import gradio as gr from gradio import utils class TestChatbot: def test_component_functions(self): """ Postprocess, get_config """ chatbot = gr.Chatbot() assert chatbot.postprocess( # type: ignore [ # type: ignore {"role": "user", "content": "You are **cool**\nand fun"}, {"role": "assistant", "content": "so are *you*"}, ] ).model_dump() == [ { "role": "user", "content": [{"type": "text", "text": "You are **cool**\nand fun"}], "metadata": None, "options": None, }, { "role": "assistant", "content": [{"type": "text", "text": "so are *you*"}], "metadata": None, "options": None, }, ] multimodal_msg = [ {"role": "user", "content": {"path": "test/test_files/video_sample.mp4"}}, {"role": "assistant", "content": "cool video"}, {"role": "user", "content": {"path": "test/test_files/audio_sample.wav"}}, {"role": "assistant", "content": "cool audio"}, { "role": "user", "content": {"path": "test/test_files/bus.png", "alt_text": "A bus"}, }, {"role": "assistant", "content": "cool pic"}, {"role": "user", "content": {"path": "test/test_files/video_sample.mp4"}}, {"role": "assistant", "content": "cool video"}, {"role": "user", "content": {"path": "test/test_files/audio_sample.wav"}}, {"role": "assistant", "content": "cool audio"}, { "role": "user", "content": { "file": {"path": "test/test_files/bus.png", "alt_text": "A bus"} }, }, {"role": "assistant", "content": "cool pic"}, ] postprocessed_multimodal_msg = chatbot.postprocess(multimodal_msg).model_dump() # type: ignore for msg in postprocessed_multimodal_msg: if msg["role"] == "user": assert "file" in msg["content"][0] assert msg["content"][0]["file"]["path"].split(".")[-1] in { "mp4", "wav", "png", } if msg["content"][0]["alt_text"]: assert msg["content"][0]["alt_text"] == "A bus" elif msg["role"] == "assistant": assert msg["content"][0]["text"] in { "cool video", "cool audio", "cool pic", } else: raise ValueError(f"Unexpected role: {msg['role']}") assert chatbot.get_config() == { "value": [], "label": None, "show_label": True, "name": "chatbot", "visible": True, "elem_id": None, "elem_classes": [], "editable": None, "container": True, "min_width": 160, "group_consecutive_messages": True, "scale": None, "placeholder": None, "height": 400, "feedback_options": ["Like", "Dislike"], "feedback_value": None, "resizable": False, "max_height": None, "min_height": None, "autoscroll": True, "proxy_url": None, "_selectable": False, "_retryable": False, "_undoable": False, "allow_file_downloads": True, "key": None, "preserved_by_key": ["value"], "latex_delimiters": [{"display": True, "left": "$$", "right": "$$"}], "likeable": False, "rtl": False, "buttons": ["share", "copy", "copy_all"], "avatar_images": [None, None], "sanitize_html": True, "render_markdown": True, "line_breaks": True, "layout": None, "allow_tags": True, "examples": None, "watermark": None, "reasoning_tags": None, "like_user_message": False, } def test_avatar_images_are_moved_to_cache(self): chatbot = gr.Chatbot(avatar_images=("test/test_files/bus.png", None)) assert chatbot.avatar_images[0] assert utils.is_in_or_equal( chatbot.avatar_images[0]["path"], chatbot.GRADIO_CACHE ) assert chatbot.avatar_images[1] is None def test_reasoning_tags_single_block(self): """Test reasoning_tags with a single thinking block""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Let me think about this.\nHere is my response.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 2 assert result[0]["content"][0]["text"] == "Let me think about this." assert result[0]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[1]["content"][0]["text"] == "Here is my response." assert result[1]["metadata"] is None def test_reasoning_tags_multiple_blocks(self): """Test reasoning_tags with multiple thinking blocks""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "First thought.\nFirst response.\nSecond thought.\nSecond response.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 4 assert result[0]["content"][0]["text"] == "First thought." assert result[0]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[1]["content"][0]["text"] == "First response." assert result[1]["metadata"] is None assert result[2]["content"][0]["text"] == "Second thought." assert result[2]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[3]["content"][0]["text"] == "Second response." assert result[3]["metadata"] is None def test_reasoning_tags_only_thinking(self): """Test reasoning_tags with only thinking content, no prose""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Only thinking here.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 1 assert result[0]["content"][0]["text"] == "Only thinking here." assert result[0]["metadata"] == {"title": "Reasoning", "status": "done"} def test_reasoning_tags_multiple_tag_types(self): """Test reasoning_tags with multiple tag types like """ chatbot = gr.Chatbot( reasoning_tags=[ ("", ""), ("", ""), ] ) messages = [ { "role": "assistant", "content": "Thinking block.\nFirst response.\nReasoning block.\nSecond response.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 4 assert result[0]["content"][0]["text"] == "Thinking block." assert result[0]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[1]["content"][0]["text"] == "First response." assert result[1]["metadata"] is None assert result[2]["content"][0]["text"] == "Reasoning block." assert result[2]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[3]["content"][0]["text"] == "Second response." assert result[3]["metadata"] is None def test_reasoning_tags_no_thinking_tags(self): """Test reasoning_tags when no thinking tags are present""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Just a regular response with no thinking.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 1 assert ( result[0]["content"][0]["text"] == "Just a regular response with no thinking." ) assert result[0]["metadata"] is None def test_reasoning_tags_disabled(self): """Test that reasoning_tags=None doesn't extract anything""" chatbot = gr.Chatbot(reasoning_tags=None) messages = [ { "role": "assistant", "content": "This should not be extracted.\nRegular response.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 1 assert ( result[0]["content"][0]["text"] == "This should not be extracted.\nRegular response." ) assert result[0]["metadata"] is None def test_reasoning_tags_preserves_order(self): """Test that reasoning_tags preserves the order of thinking and prose""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Intro.\nThink 1.\nMiddle 1.\nThink 2.\nMiddle 2.\nThink 3.\nConclusion.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 7 assert result[0]["content"][0]["text"] == "Intro." assert result[0]["metadata"] is None assert result[1]["content"][0]["text"] == "Think 1." assert result[1]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[2]["content"][0]["text"] == "Middle 1." assert result[2]["metadata"] is None assert result[3]["content"][0]["text"] == "Think 2." assert result[3]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[4]["content"][0]["text"] == "Middle 2." assert result[4]["metadata"] is None assert result[5]["content"][0]["text"] == "Think 3." assert result[5]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[6]["content"][0]["text"] == "Conclusion." assert result[6]["metadata"] is None def test_reasoning_tags_consecutive_thinking_blocks(self): """Test consecutive thinking blocks with no prose between them""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Think 1.Think 2.Prose.Think 3.Think 4.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 5 assert result[0]["content"][0]["text"] == "Think 1." assert result[0]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[1]["content"][0]["text"] == "Think 2." assert result[1]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[2]["content"][0]["text"] == "Prose." assert result[2]["metadata"] is None assert result[3]["content"][0]["text"] == "Think 3." assert result[3]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[4]["content"][0]["text"] == "Think 4." assert result[4]["metadata"] == {"title": "Reasoning", "status": "done"} def test_reasoning_tags_consecutive_prose_sections(self): """Test that consecutive prose sections get merged into one message""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Prose 1. Prose 2.Think 1.Prose 3. Prose 4.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 3 assert result[0]["content"][0]["text"] == "Prose 1. Prose 2." assert result[0]["metadata"] is None assert result[1]["content"][0]["text"] == "Think 1." assert result[1]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[2]["content"][0]["text"] == "Prose 3. Prose 4." assert result[2]["metadata"] is None def test_reasoning_tags_prose_before_thinking(self): """Test prose at the start followed by thinking blocks""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Starting with prose.Then thinking.Then more prose.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 3 assert result[0]["content"][0]["text"] == "Starting with prose." assert result[0]["metadata"] is None assert result[1]["content"][0]["text"] == "Then thinking." assert result[1]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[2]["content"][0]["text"] == "Then more prose." assert result[2]["metadata"] is None def test_reasoning_tags_sets_pending_correctly(self): """Test prose at the start followed by thinking blocks""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Starting with prose.Then thinking", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 2 assert result[0]["content"][0]["text"] == "Starting with prose." assert result[0]["metadata"] is None assert result[1]["content"][0]["text"] == "Then thinking" assert result[1]["metadata"] == {"title": "Reasoning", "status": "pending"} def test_reasoning_tags_sets_pending_correctly_multiple_thinking(self): """Test prose at the start followed by thinking blocks""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Starting with prose.Then thinkingMore thinking", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 3 assert result[0]["content"][0]["text"] == "Starting with prose." assert result[0]["metadata"] is None assert result[1]["content"][0]["text"] == "Then thinking" assert result[1]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[2]["content"][0]["text"] == "More thinking" assert result[2]["metadata"] == {"title": "Reasoning", "status": "pending"} def test_reasoning_tags_multiple_thinking_only(self): """Test multiple consecutive thinking blocks with no prose at all""" chatbot = gr.Chatbot(reasoning_tags=[("", "")]) messages = [ { "role": "assistant", "content": "Think 1.Think 2.Think 3.", } ] result = chatbot.postprocess(messages).model_dump() # type: ignore assert len(result) == 3 assert result[0]["content"][0]["text"] == "Think 1." assert result[0]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[1]["content"][0]["text"] == "Think 2." assert result[1]["metadata"] == {"title": "Reasoning", "status": "done"} assert result[2]["content"][0]["text"] == "Think 3." assert result[2]["metadata"] == {"title": "Reasoning", "status": "done"}