Spaces:
Runtime error
Runtime error
| """Tests for multi-turn conversations with chat history.""" | |
| from unittest.mock import MagicMock, Mock, patch | |
| import pytest | |
| from qa_chain import QAChainWrapper | |
| from langchain_core.prompts import ChatPromptTemplate | |
| from langchain_core.documents import Document | |
| def qa_chain_wrapper(mock_vectorstore): | |
| """Create a QAChainWrapper instance.""" | |
| prompt = ChatPromptTemplate.from_template("Test: {question}\nHistory: {chat_history}") | |
| return QAChainWrapper(mock_vectorstore, prompt) | |
| def test_rag_with_chat_history_context(mock_format_history, mock_create_llm, qa_chain_wrapper): | |
| """Test RAG query with previous conversation context.""" | |
| # First turn: User asks initial question | |
| chat_history_turn1 = [] | |
| # Second turn: User asks follow-up question | |
| chat_history_turn2 = [ | |
| ("What is RAG?", "RAG stands for Retrieval-Augmented Generation."), | |
| ] | |
| mock_format_history.return_value = "Human: What is RAG?\nAssistant: RAG stands for Retrieval-Augmented Generation." | |
| mock_llm = MagicMock() | |
| mock_chunk = MagicMock() | |
| mock_chunk.content = "Based on the previous context, RAG combines retrieval and generation." | |
| mock_llm.stream.return_value = [mock_chunk] | |
| mock_create_llm.return_value = mock_llm | |
| mock_retriever = MagicMock() | |
| mock_doc = Document( | |
| page_content="RAG combines retrieval and generation", | |
| metadata={"source": "test.pdf", "page": 1} | |
| ) | |
| mock_retriever.invoke.return_value = [mock_doc] | |
| qa_chain_wrapper._retriever = mock_retriever | |
| mock_chain = MagicMock() | |
| mock_chain.stream.return_value = [mock_chunk] | |
| qa_chain_wrapper._prompt.__or__ = MagicMock(return_value=mock_chain) | |
| # Test follow-up question with history | |
| inputs = { | |
| "question": "How does it work?", | |
| "chat_history": chat_history_turn2, | |
| } | |
| results = list(qa_chain_wrapper.stream(inputs)) | |
| assert len(results) > 0 | |
| # Verify chat history was formatted and included | |
| mock_format_history.assert_called() | |
| # Verify results contain expected data | |
| assert "chunk" in results[0] | |
| def test_chat_history_limit_enforcement(mock_format_history, mock_create_llm, qa_chain_wrapper): | |
| """Test that chat history limit (CHAT_HISTORY_LIMIT) is enforced.""" | |
| from config import CHAT_HISTORY_LIMIT | |
| # Create history longer than limit | |
| long_history = [ | |
| (f"Question {i}", f"Answer {i}") for i in range(CHAT_HISTORY_LIMIT + 5) | |
| ] | |
| mock_format_history.return_value = "Formatted history" | |
| mock_llm = MagicMock() | |
| mock_chunk = MagicMock() | |
| mock_chunk.content = "Response" | |
| mock_llm.stream.return_value = [mock_chunk] | |
| mock_create_llm.return_value = mock_llm | |
| mock_retriever = MagicMock() | |
| mock_retriever.invoke.return_value = [Document(page_content="Test", metadata={})] | |
| qa_chain_wrapper._retriever = mock_retriever | |
| mock_chain = MagicMock() | |
| mock_chain.stream.return_value = [mock_chunk] | |
| qa_chain_wrapper._prompt.__or__ = MagicMock(return_value=mock_chain) | |
| inputs = { | |
| "question": "New question", | |
| "chat_history": long_history, | |
| } | |
| list(qa_chain_wrapper.stream(inputs)) | |
| # Verify format_chat_history was called with the long history | |
| # (it should handle limiting internally) | |
| mock_format_history.assert_called() | |
| # The format_chat_history function should limit to CHAT_HISTORY_LIMIT | |
| call_args = mock_format_history.call_args[0][0] | |
| # Should only include recent messages (last CHAT_HISTORY_LIMIT) | |
| assert len(call_args) <= CHAT_HISTORY_LIMIT + 5 # format_chat_history handles limiting | |
| def test_follow_up_question_references_previous_answer(mock_format_history, mock_create_llm, qa_chain_wrapper): | |
| """Test follow-up question that references previous answer.""" | |
| # First question about a topic | |
| chat_history = [ | |
| ("What is machine learning?", "Machine learning is a subset of AI."), | |
| ] | |
| mock_format_history.return_value = "Human: What is machine learning?\nAssistant: Machine learning is a subset of AI." | |
| mock_llm = MagicMock() | |
| mock_chunk = MagicMock() | |
| mock_chunk.content = "Deep learning is a subset of machine learning." | |
| mock_llm.stream.return_value = [mock_chunk] | |
| mock_create_llm.return_value = mock_llm | |
| mock_retriever = MagicMock() | |
| mock_doc = Document( | |
| page_content="Deep learning is a subset of machine learning", | |
| metadata={"source": "test.pdf", "page": 2} | |
| ) | |
| mock_retriever.invoke.return_value = [mock_doc] | |
| qa_chain_wrapper._retriever = mock_retriever | |
| mock_chain = MagicMock() | |
| mock_chain.stream.return_value = [mock_chunk] | |
| qa_chain_wrapper._prompt.__or__ = MagicMock(return_value=mock_chain) | |
| # Follow-up question that references "it" (machine learning) | |
| inputs = { | |
| "question": "What are its main types?", | |
| "chat_history": chat_history, | |
| } | |
| results = list(qa_chain_wrapper.stream(inputs)) | |
| assert len(results) > 0 | |
| # Verify history context was included | |
| mock_format_history.assert_called() | |
| # Verify results contain expected data | |
| assert "chunk" in results[0] | |
| def test_handlers_multi_turn_conversation(mock_create_llm, mock_vectorstore): | |
| """Test handlers with multi-turn conversation.""" | |
| from ui.handlers import create_stream_chat_response, create_respond_handler | |
| mock_qa_chain = MagicMock() | |
| mock_qa_chain.stream.return_value = [ | |
| { | |
| "chunk": "Answer to follow-up", | |
| "source_documents": [Mock(metadata={"source": "test.pdf", "page": 1})], | |
| "docs_with_scores": [(Mock(), 0.9)], | |
| "rewritten_query": None, | |
| "hybrid_scores": None, | |
| } | |
| ] | |
| stream_fn = create_stream_chat_response(mock_qa_chain) | |
| respond_fn = create_respond_handler(stream_fn) | |
| # First turn | |
| history_turn1 = [] | |
| results_turn1 = list( | |
| respond_fn( | |
| "What is RAG?", | |
| history_turn1, | |
| True, # RAG enabled | |
| "mmr", | |
| "All Documents", | |
| False, | |
| False, | |
| 70, | |
| ) | |
| ) | |
| # Second turn with history | |
| history_turn2 = [ | |
| {"role": "user", "content": "What is RAG?"}, | |
| {"role": "assistant", "content": "RAG stands for Retrieval-Augmented Generation."}, | |
| ] | |
| results_turn2 = list( | |
| respond_fn( | |
| "How does it work?", | |
| history_turn2, | |
| True, # RAG enabled | |
| "mmr", | |
| "All Documents", | |
| False, | |
| False, | |
| 70, | |
| ) | |
| ) | |
| assert len(results_turn2) > 0 | |
| # Verify chat history was passed to stream function | |
| assert mock_qa_chain.stream.called | |
| call_args = mock_qa_chain.stream.call_args[0][0] | |
| # Should have chat_history in the call | |
| assert "chat_history" in call_args or "question" in call_args | |