|
8 | 8 | from pathlib import Path |
9 | 9 | from unittest.mock import MagicMock, patch |
10 | 10 |
|
| 11 | +from openlrc.agents import create_chatbot |
| 12 | +from openlrc.media_utils import get_similarity |
11 | 13 | from openlrc.prompter import LeanContextReviewPrompter, LeanTranslatePrompter |
12 | 14 | from openlrc.translate import LeanTranslator |
13 | 15 | from openlrc.validators import LeanTranslateValidator |
| 16 | +from tests.conftest import LIVE_API, TEST_LLM_API_KEY, TEST_MODELS |
14 | 17 |
|
15 | 18 |
|
16 | 19 | def _make_mock_chatbot(name: str = "gpt-4.1-nano") -> MagicMock: |
@@ -643,3 +646,88 @@ def mock_message(msgs, **kwargs): |
643 | 646 | # api_fee should include fees from index 3 onward (after the 3 pre-existing entries) |
644 | 647 | # The mock_message appends 0.05 each call; at least one call for translation |
645 | 648 | self.assertGreater(translator.api_fee, 0) |
| 649 | + |
| 650 | + |
| 651 | +@unittest.skipUnless(LIVE_API, "Requires OPENLRC_TEST_LIVE_API=1 and valid API keys") |
| 652 | +class TestLeanTranslatorLive(unittest.TestCase): |
| 653 | + """Live API tests for LeanTranslator. |
| 654 | +
|
| 655 | + These tests make real LLM API calls and require: |
| 656 | + - OPENLRC_TEST_LIVE_API=1 |
| 657 | + - OPENLRC_TEST_LLM_API_KEY set to a valid API key |
| 658 | + """ |
| 659 | + |
| 660 | + @classmethod |
| 661 | + def setUpClass(cls): |
| 662 | + if not TEST_LLM_API_KEY: |
| 663 | + raise unittest.SkipTest("OPENLRC_TEST_LLM_API_KEY is required for LLM integration tests.") |
| 664 | + |
| 665 | + def tearDown(self): |
| 666 | + compare_path = Path("translate_intermediate.json") |
| 667 | + compare_path.unlink(missing_ok=True) |
| 668 | + |
| 669 | + def test_single_chunk_no_cr(self): |
| 670 | + """Basic single-chunk translation without CR.""" |
| 671 | + chatbot = create_chatbot(TEST_MODELS["gemini"]) |
| 672 | + try: |
| 673 | + translator = LeanTranslator(chatbot=chatbot, enable_cr=False) |
| 674 | + translations = translator.translate("Hello, how are you?", "en", "es") |
| 675 | + finally: |
| 676 | + chatbot.close() |
| 677 | + |
| 678 | + self.assertEqual(len(translations), 1) |
| 679 | + self.assertTrue(translations[0].strip()) |
| 680 | + self.assertGreater(get_similarity(translations[0], "Hola, ¿cómo estás?"), 0.5) |
| 681 | + |
| 682 | + def test_multiple_chunk_no_cr(self): |
| 683 | + """Multiple chunks without CR, verifying all lines are translated.""" |
| 684 | + texts = ["Hello, how are you?", "I am fine, thank you.", "See you tomorrow.", "Good night."] |
| 685 | + chatbot = create_chatbot(TEST_MODELS["gemini"]) |
| 686 | + try: |
| 687 | + translator = LeanTranslator(chatbot=chatbot, enable_cr=False, chunk_size=2) |
| 688 | + translations = translator.translate(texts, "en", "es") |
| 689 | + finally: |
| 690 | + chatbot.close() |
| 691 | + |
| 692 | + self.assertEqual(len(translations), len(texts)) |
| 693 | + self.assertTrue(all(t.strip() for t in translations)) |
| 694 | + |
| 695 | + def test_with_cr(self): |
| 696 | + """Full pipeline: CR generates guideline, then translation uses it.""" |
| 697 | + texts = ["The suspect fled to the uptown area.", "Detective John began the investigation."] |
| 698 | + chatbot = create_chatbot(TEST_MODELS["gemini"]) |
| 699 | + try: |
| 700 | + translator = LeanTranslator(chatbot=chatbot, enable_cr=True) |
| 701 | + translations = translator.translate(texts, "en", "zh") |
| 702 | + finally: |
| 703 | + chatbot.close() |
| 704 | + |
| 705 | + self.assertEqual(len(translations), len(texts)) |
| 706 | + self.assertTrue(all(t.strip() for t in translations)) |
| 707 | + |
| 708 | + def test_cr_chatbot_separation(self): |
| 709 | + """Mixed-model: GPT does CR, Gemini does translation.""" |
| 710 | + texts = ["The suspect fled to the uptown area.", "Detective John began the investigation."] |
| 711 | + cr_bot = create_chatbot(TEST_MODELS["gpt"]) |
| 712 | + mt_bot = create_chatbot(TEST_MODELS["gemini"]) |
| 713 | + try: |
| 714 | + translator = LeanTranslator(chatbot=mt_bot, cr_chatbot=cr_bot, enable_cr=True) |
| 715 | + translations = translator.translate(texts, "en", "zh") |
| 716 | + finally: |
| 717 | + cr_bot.close() |
| 718 | + mt_bot.close() |
| 719 | + |
| 720 | + self.assertEqual(len(translations), len(texts)) |
| 721 | + self.assertTrue(all(t.strip() for t in translations)) |
| 722 | + self.assertGreater(translator.api_fee, 0) |
| 723 | + |
| 724 | + def test_empty_texts(self): |
| 725 | + """Empty input returns empty list without API calls.""" |
| 726 | + chatbot = create_chatbot(TEST_MODELS["gemini"]) |
| 727 | + try: |
| 728 | + translator = LeanTranslator(chatbot=chatbot, enable_cr=False) |
| 729 | + translations = translator.translate([], "en", "es") |
| 730 | + finally: |
| 731 | + chatbot.close() |
| 732 | + |
| 733 | + self.assertEqual(translations, []) |
0 commit comments