ProfillyBot / tests /test_chat_history.py
MinhDS's picture
Deploy ProfillyBot: Gradio ZeroGPU + CV RAG (Qwen2.5-3B)
0828c2c verified
Raw
History Blame Contribute Delete
26.6 kB
"""Tests for chat history feature in RAG pipeline."""
from unittest.mock import MagicMock, patch
import pytest
from src.rag_pipeline import RAGPipeline
class TestFormatChatHistory:
"""Test _format_chat_history method."""
def test_format_chat_history_empty(self):
"""Test formatting empty chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
result = pipeline._format_chat_history(None)
assert result == ""
result = pipeline._format_chat_history([])
assert result == ""
def test_format_chat_history_single_user_message(self):
"""Test formatting single user message."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"role": "user", "content": "Hello"}]
result = pipeline._format_chat_history(history)
assert "PREVIOUS CONVERSATION" in result
assert "User: Hello" in result
def test_format_chat_history_single_assistant_message(self):
"""Test formatting single assistant message."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"role": "assistant", "content": "Hi there!"}]
result = pipeline._format_chat_history(history)
assert "PREVIOUS CONVERSATION" in result
assert "Assistant: Hi there!" in result
def test_format_chat_history_conversation(self):
"""Test formatting full conversation."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [
{"role": "user", "content": "What is your name?"},
{"role": "assistant", "content": "I'm an AI assistant."},
{"role": "user", "content": "Tell me more."},
]
result = pipeline._format_chat_history(history)
assert "PREVIOUS CONVERSATION" in result
assert "User: What is your name?" in result
assert "Assistant: I'm an AI assistant." in result
assert "User: Tell me more." in result
def test_format_chat_history_missing_role(self):
"""Test formatting with missing role field."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"content": "Some message"}]
result = pipeline._format_chat_history(history)
# Should return empty string if role is missing
assert result == ""
def test_format_chat_history_missing_content(self):
"""Test formatting with missing content field."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"role": "user", "content": ""}]
result = pipeline._format_chat_history(history)
# Function formats empty content (doesn't filter it out)
assert "PREVIOUS CONVERSATION" in result
assert "User: " in result
def test_format_chat_history_invalid_role(self):
"""Test formatting with invalid role."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"role": "system", "content": "System message"}]
result = pipeline._format_chat_history(history)
# Should return empty string for non-user/assistant roles
assert result == ""
class TestQueryWithChatHistory:
"""Test query method with chat history."""
def test_query_without_chat_history(self):
"""Test query without chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test response"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
response = pipeline.query("What is AI?")
assert "result" in response
assert isinstance(response["result"], str)
assert len(response["result"]) > 0
def test_query_with_chat_history(self):
"""Test query with chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test response"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi!"},
]
response = pipeline.query("What is AI?", chat_history=history)
assert "result" in response
assert isinstance(response["result"], str)
assert len(response["result"]) > 0
def test_query_with_empty_chat_history(self):
"""Test query with empty chat history list."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test response"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
response = pipeline.query("What is AI?", chat_history=[])
assert "result" in response
assert isinstance(response["result"], str)
class TestGetAnswerWithChatHistory:
"""Test get_answer method with chat history."""
def test_get_answer_without_chat_history(self):
"""Test get_answer without chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test answer"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
answer = pipeline.get_answer("What is AI?")
assert isinstance(answer, str)
assert len(answer) > 0
def test_get_answer_with_chat_history(self):
"""Test get_answer with chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test answer"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"role": "user", "content": "Previous question"}]
answer = pipeline.get_answer("What is AI?", chat_history=history)
assert isinstance(answer, str)
assert len(answer) > 0
class TestGetAnswerWithSourcesChatHistory:
"""Test get_answer_with_sources method with chat history."""
def test_get_answer_with_sources_without_chat_history(self):
"""Test get_answer_with_sources without chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test answer"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
answer, sources = pipeline.get_answer_with_sources("What is AI?")
assert isinstance(answer, str)
assert isinstance(sources, list)
def test_get_answer_with_sources_with_chat_history(self):
"""Test get_answer_with_sources with chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test answer"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"role": "user", "content": "Previous question"}]
answer, sources = pipeline.get_answer_with_sources("What is AI?", chat_history=history)
assert isinstance(answer, str)
assert isinstance(sources, list)
class TestStreamQueryWithChatHistory:
"""Test stream_query method with chat history."""
def test_stream_query_without_chat_history(self):
"""Test stream_query without chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.stream.return_value = iter(["Test", " response", " chunks"])
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
chunks = list(pipeline.stream_query("What is AI?"))
assert len(chunks) > 0
assert all(isinstance(chunk, str) for chunk in chunks)
def test_stream_query_with_chat_history(self):
"""Test stream_query with chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.stream.return_value = iter(["Test", " response", " chunks"])
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"role": "user", "content": "Previous question"}]
chunks = list(pipeline.stream_query("What is AI?", chat_history=history))
assert len(chunks) > 0
assert all(isinstance(chunk, str) for chunk in chunks)
def test_stream_query_error_handling(self):
"""Test stream_query error handling."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.stream.side_effect = Exception("Stream error")
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
chunks = list(pipeline.stream_query("What is AI?"))
# Should yield error message
assert len(chunks) > 0
assert "technical issue" in chunks[0].lower()
class TestStreamAnswerWithChatHistory:
"""Test stream_answer method with chat history."""
def test_stream_answer_without_chat_history(self):
"""Test stream_answer without chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.stream.return_value = iter(["Answer", " chunks"])
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
chunks = list(pipeline.stream_answer("What is AI?"))
assert len(chunks) > 0
assert all(isinstance(chunk, str) for chunk in chunks)
def test_stream_answer_with_chat_history(self):
"""Test stream_answer with chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.stream.return_value = iter(["Answer", " chunks"])
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [{"role": "user", "content": "Previous question"}]
chunks = list(pipeline.stream_answer("What is AI?", chat_history=history))
assert len(chunks) > 0
assert all(isinstance(chunk, str) for chunk in chunks)
class TestChatHistoryInPrompt:
"""Test that chat history is properly included in prompts."""
def test_prompt_template_includes_chat_history_placeholder(self):
"""Test that prompt template includes chat_history placeholder."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
template_str = pipeline.prompt_template.messages[0].prompt.template
# Should contain chat_history placeholder
assert "{chat_history}" in template_str
def test_chat_history_position_in_template(self):
"""Test that chat history appears in correct position in template."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_handler.get_llm.return_value = MagicMock()
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
template_str = pipeline.prompt_template.messages[0].prompt.template
# Chat history should come after system prompt but before main doc and context
chat_history_pos = template_str.find("{chat_history}")
main_doc_pos = template_str.find("{main_document_section}")
context_pos = template_str.find("{context}")
question_pos = template_str.find("{question}")
# Verify order: system -> chat_history -> main_doc -> context -> question
assert chat_history_pos < main_doc_pos
assert chat_history_pos < context_pos
assert chat_history_pos < question_pos
class TestChatHistoryIntegration:
"""Integration tests for chat history feature."""
def test_multiple_queries_with_history(self):
"""Test multiple queries building up chat history."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test response"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
# First query - no history
response1 = pipeline.query("What is AI?")
assert "result" in response1
# Second query - with history from first query
history = [
{"role": "user", "content": "What is AI?"},
{"role": "assistant", "content": response1["result"]},
]
response2 = pipeline.query("Tell me more", chat_history=history)
assert "result" in response2
# Third query - with extended history
extended_history = history + [
{"role": "user", "content": "Tell me more"},
{"role": "assistant", "content": response2["result"]},
]
response3 = pipeline.query("What are the applications?", chat_history=extended_history)
assert "result" in response3
def test_chat_history_with_main_document(self):
"""Test chat history works together with main document."""
with patch("src.rag_pipeline.get_llm_handler") as mock_llm_handler:
mock_handler = MagicMock()
mock_llm = MagicMock()
mock_llm.invoke.return_value = "Test response"
mock_handler.get_llm.return_value = mock_llm
mock_handler.get_system_prompt.return_value = "System prompt"
mock_llm_handler.return_value = mock_handler
pipeline = RAGPipeline()
history = [
{"role": "user", "content": "What is your background?"},
{"role": "assistant", "content": "I have experience in AI."},
]
response = pipeline.query("Tell me more about your projects", chat_history=history)
assert "result" in response
assert isinstance(response["result"], str)
class TestAppChatHistoryHelpers:
"""Test chat history helper functions from app.py."""
def test_extract_chat_history_empty(self):
"""Test extract_chat_history with empty messages."""
try:
from app import extract_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
result = extract_chat_history([])
assert result == []
result = extract_chat_history(None)
assert result == []
def test_extract_chat_history_single_message(self):
"""Test extract_chat_history with single message."""
try:
from app import extract_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
messages = [{"role": "user", "content": "Hello"}]
result = extract_chat_history(messages, exclude_last=True)
assert result == []
result = extract_chat_history(messages, exclude_last=False)
assert result == [{"role": "user", "content": "Hello"}]
def test_extract_chat_history_multiple_messages(self):
"""Test extract_chat_history with multiple messages."""
try:
from app import extract_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
messages = [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi!"},
{"role": "user", "content": "How are you?"},
]
# Exclude last message
result = extract_chat_history(messages, exclude_last=True)
assert len(result) == 2
assert result[0]["role"] == "user"
assert result[1]["role"] == "assistant"
# Include last message
result = extract_chat_history(messages, exclude_last=False)
assert len(result) == 3
def test_extract_chat_history_filters_invalid_roles(self):
"""Test extract_chat_history filters out invalid roles."""
try:
from app import extract_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
messages = [
{"role": "user", "content": "Hello"},
{"role": "system", "content": "System message"},
{"role": "assistant", "content": "Hi!"},
]
result = extract_chat_history(messages, exclude_last=False)
assert len(result) == 2
assert result[0]["role"] == "user"
assert result[1]["role"] == "assistant"
def test_extract_chat_history_filters_empty_content(self):
"""Test extract_chat_history filters out empty content."""
try:
from app import extract_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
messages = [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": ""},
{"role": "user", "content": "How are you?"},
]
result = extract_chat_history(messages, exclude_last=False)
assert len(result) == 2
assert result[0]["role"] == "user"
assert result[1]["role"] == "user"
def test_truncate_chat_history_empty(self):
"""Test truncate_chat_history with empty history."""
try:
from app import truncate_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
result = truncate_chat_history([])
assert result == []
result = truncate_chat_history([], max_turns=5)
assert result == []
def test_truncate_chat_history_no_limits(self):
"""Test truncate_chat_history with no limits."""
try:
from app import truncate_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
history = [
{"role": "user", "content": "Question 1"},
{"role": "assistant", "content": "Answer 1"},
{"role": "user", "content": "Question 2"},
{"role": "assistant", "content": "Answer 2"},
]
result = truncate_chat_history(history)
assert len(result) == 4
assert result == history
def test_truncate_chat_history_by_turns(self):
"""Test truncate_chat_history by turn count."""
try:
from app import truncate_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
history = [
{"role": "user", "content": "Question 1"},
{"role": "assistant", "content": "Answer 1"},
{"role": "user", "content": "Question 2"},
{"role": "assistant", "content": "Answer 2"},
{"role": "user", "content": "Question 3"},
{"role": "assistant", "content": "Answer 3"},
]
# Keep only 2 most recent turns (4 messages)
result = truncate_chat_history(history, max_turns=2)
assert len(result) == 4
assert result[0]["content"] == "Question 2"
assert result[-1]["content"] == "Answer 3"
def test_truncate_chat_history_by_tokens(self):
"""Test truncate_chat_history by token limit."""
try:
from app import truncate_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
# Create history with varying message lengths
history = [
{"role": "user", "content": "Short question"},
{"role": "assistant", "content": "Short answer"},
{"role": "user", "content": "This is a much longer question that will use more tokens"},
{
"role": "assistant",
"content": "This is a much longer answer that will also use more tokens",
},
]
# Truncate to a small token limit (should keep only recent messages that fit)
result = truncate_chat_history(history, max_tokens=50)
assert len(result) <= len(history)
# Should keep most recent messages that fit within token limit
def test_truncate_chat_history_by_turns_and_tokens(self):
"""Test truncate_chat_history with both turn and token limits."""
try:
from app import truncate_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
history = [
{"role": "user", "content": "Question 1"},
{"role": "assistant", "content": "Answer 1"},
{"role": "user", "content": "Question 2"},
{"role": "assistant", "content": "Answer 2"},
]
# First apply turn limit, then token limit
result = truncate_chat_history(history, max_turns=1, max_tokens=1000)
assert len(result) <= 2 # 1 turn = 2 messages
def test_truncate_chat_history_zero_limits(self):
"""Test truncate_chat_history with zero limits."""
try:
from app import truncate_chat_history
except ImportError:
pytest.skip("Cannot import app module (streamlit may not be available)")
history = [
{"role": "user", "content": "Question 1"},
{"role": "assistant", "content": "Answer 1"},
]
# Zero limits don't truncate (function only truncates if > 0)
# So it returns the original history unchanged
result = truncate_chat_history(history, max_turns=0)
assert result == history # Returns original when limit is 0
result = truncate_chat_history(history, max_tokens=0)
assert result == history # Returns original when limit is 0