Files
wehub-resource-sync adf0d17497
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:17:32 +08:00

381 lines
16 KiB
Python

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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "<thinking>Let me think about this.</thinking>\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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "<thinking>First thought.</thinking>\nFirst response.\n<thinking>Second thought.</thinking>\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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "<thinking>Only thinking here.</thinking>",
}
]
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 <reasoning>"""
chatbot = gr.Chatbot(
reasoning_tags=[
("<thinking>", "</thinking>"),
("<reasoning>", "</reasoning>"),
]
)
messages = [
{
"role": "assistant",
"content": "<thinking>Thinking block.</thinking>\nFirst response.\n<reasoning>Reasoning block.</reasoning>\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=[("<thinking>", "</thinking>")])
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": "<thinking>This should not be extracted.</thinking>\nRegular response.",
}
]
result = chatbot.postprocess(messages).model_dump() # type: ignore
assert len(result) == 1
assert (
result[0]["content"][0]["text"]
== "<thinking>This should not be extracted.</thinking>\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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "Intro.\n<thinking>Think 1.</thinking>\nMiddle 1.\n<thinking>Think 2.</thinking>\nMiddle 2.\n<thinking>Think 3.</thinking>\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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "<thinking>Think 1.</thinking><thinking>Think 2.</thinking>Prose.<thinking>Think 3.</thinking><thinking>Think 4.</thinking>",
}
]
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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "Prose 1. Prose 2.<thinking>Think 1.</thinking>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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "Starting with prose.<thinking>Then thinking.</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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "Starting with prose.<thinking>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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "Starting with prose.<thinking>Then thinking</thinking><thinking>More 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=[("<thinking>", "</thinking>")])
messages = [
{
"role": "assistant",
"content": "<thinking>Think 1.</thinking><thinking>Think 2.</thinking><thinking>Think 3.</thinking>",
}
]
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"}