from pathlib import Path from typing import Any, Callable from jinja2 import Environment, StrictUndefined, Template TEMPLATE_PATH = Path(__file__).parent / "chat_template.jinja" def raise_exception(message: str) -> None: """ Jinja2 şablonunun içinden Python hatası oluşturur. Hugging Face chat template ortamındaki raise_exception fonksiyonunu taklit eder. """ raise ValueError(message) def load_template() -> Template: """chat_template.jinja dosyasını okuyup çalıştırılabilir hâle getirir.""" if not TEMPLATE_PATH.exists(): raise FileNotFoundError( f"Template dosyası bulunamadı: {TEMPLATE_PATH.resolve()}" ) template_text = TEMPLATE_PATH.read_text(encoding="utf-8") environment = Environment( undefined=StrictUndefined, autoescape=False, trim_blocks=True, lstrip_blocks=True, ) environment.globals["raise_exception"] = raise_exception return environment.from_string(template_text) def render_template( template: Template, messages: list[dict[str, Any]], *, add_generation_prompt: bool = False, tools: list[dict[str, Any]] | None = None, bos_token: str | None = None, eos_token: str | None = None, ) -> str: """Verilen mesajları chat template ile metne dönüştürür.""" parameters: dict[str, Any] = { "messages": messages, "add_generation_prompt": add_generation_prompt, "bos_token": bos_token, "eos_token": eos_token, } if tools is not None: parameters["tools"] = tools return template.render(**parameters) def assert_contains(output: str, expected_values: list[str]) -> None: """Beklenen bütün ifadelerin çıktıda bulunduğunu kontrol eder.""" for value in expected_values: if value not in output: raise AssertionError( f"Beklenen ifade çıktıda bulunamadı: {value!r}" ) def run_success_test( test_name: str, test_function: Callable[[], str], expected_values: list[str], ) -> bool: """Başarılı olması beklenen bir testi çalıştırır.""" print("\n" + "=" * 70) print(f"TEST: {test_name}") print("=" * 70) try: output = test_function() assert_contains(output, expected_values) print(output.strip()) print(f"\n✅ BAŞARILI: {test_name}") return True except Exception as error: print(f"❌ BAŞARISIZ: {test_name}") print(f"Hata türü: {type(error).__name__}") print(f"Hata mesajı: {error}") return False def run_error_test( test_name: str, test_function: Callable[[], str], expected_error_text: str, ) -> bool: """Hata vermesi beklenen bir testi çalıştırır.""" print("\n" + "=" * 70) print(f"TEST: {test_name}") print("=" * 70) try: output = test_function() print(output.strip()) print(f"\n❌ BAŞARISIZ: {test_name}") print("Bu testin hata vermesi gerekiyordu.") return False except Exception as error: if expected_error_text not in str(error): print(f"❌ BAŞARISIZ: {test_name}") print(f"Beklenmeyen hata mesajı: {error}") return False print(f"Beklenen hata yakalandı: {error}") print(f"✅ BAŞARILI: {test_name}") return True def test_normal_conversation(template: Template) -> str: """System, user ve assistant rollerini test eder.""" messages = [ { "role": "system", "content": "Sen Türkçe cevap veren yardımcı bir asistansın.", }, { "role": "user", "content": "Türkiye'nin başkenti neresidir?", }, { "role": "assistant", "content": "Türkiye'nin başkenti Ankara'dır.", }, { "role": "user", "content": "Peki hangi bölgede bulunur?", }, ] return render_template( template, messages, add_generation_prompt=True, ) def test_developer_message(template: Template) -> str: """Developer rolünü test eder.""" messages = [ { "role": "system", "content": "Sen güvenilir bir yapay zekâ asistanısın.", }, { "role": "developer", "content": "Cevaplarını kısa ve Türkçe olarak oluştur.", }, { "role": "user", "content": "Merhaba!", }, ] return render_template( template, messages, add_generation_prompt=True, ) def test_tool_calling(template: Template) -> str: """Tool tanımı, tool çağrısı ve tool sonucunu test eder.""" tools = [ { "type": "function", "function": { "name": "multiply", "description": "İki sayıyı çarpar.", "parameters": { "type": "object", "properties": { "a": { "type": "number", "description": "Birinci sayı", }, "b": { "type": "number", "description": "İkinci sayı", }, }, "required": ["a", "b"], }, }, } ] messages = [ { "role": "system", "content": "Gerektiğinde sana verilen araçları kullan.", }, { "role": "user", "content": "12 ile 8'i çarp.", }, { "role": "assistant", "content": None, "tool_calls": [ { "id": "call_001", "type": "function", "function": { "name": "multiply", "arguments": { "a": 12, "b": 8, }, }, } ], }, { "role": "tool", "tool_call_id": "call_001", "name": "multiply", "content": "96", }, ] return render_template( template, messages, tools=tools, add_generation_prompt=True, ) def test_multimodal_content(template: Template) -> str: """Metin, görsel, ses ve video içeriklerini test eder.""" messages = [ { "role": "system", "content": "Çok modlu içerikleri analiz edebilirsin.", }, { "role": "user", "content": [ { "type": "text", "text": "Bu içerikleri incele:", }, { "type": "image", }, { "type": "audio", }, { "type": "video", }, { "type": "text", "text": "Aralarındaki ilişkiyi açıkla.", }, ], }, ] return render_template( template, messages, add_generation_prompt=True, ) def test_custom_tokens(template: Template) -> str: """Tokenizer tarafından verilen BOS ve EOS tokenlarını test eder.""" messages = [ { "role": "user", "content": "Özel token testi yap.", } ] return render_template( template, messages, add_generation_prompt=True, bos_token="", eos_token="", ) def test_consecutive_users(template: Template) -> str: """Art arda iki user mesajının reddedilmesini test eder.""" messages = [ { "role": "user", "content": "Birinci kullanıcı mesajı.", }, { "role": "user", "content": "İkinci kullanıcı mesajı.", }, ] return render_template(template, messages) def test_unsupported_role(template: Template) -> str: """Desteklenmeyen bir rolün reddedilmesini test eder.""" messages = [ { "role": "user", "content": "Merhaba.", }, { "role": "moderator", "content": "Bu rol desteklenmemelidir.", }, ] return render_template(template, messages) def test_late_system_message(template: Template) -> str: """Konuşma başladıktan sonra system mesajını reddeder.""" messages = [ { "role": "user", "content": "Konuşmayı başlatıyorum.", }, { "role": "assistant", "content": "Konuşma başladı.", }, { "role": "system", "content": "Bu mesaj çok geç geldi.", }, ] return render_template(template, messages) def main() -> None: template = load_template() results = [ run_success_test( "Normal sohbet", lambda: test_normal_conversation(template), [ "<|begin_of_chat|>", "<|system|>", "<|user|>", "<|assistant|>", "<|end_message|>", ], ), run_success_test( "Developer mesajı", lambda: test_developer_message(template), [ "<|system|>", "<|developer|>", "<|user|>", "<|assistant|>", ], ), run_success_test( "Tool calling", lambda: test_tool_calling(template), [ "<|available_tools|>", "<|tool_call|>", "<|end_tool_call|>", "<|tool_result|>", "<|end_tool_result|>", ], ), run_success_test( "Çok modlu içerik", lambda: test_multimodal_content(template), [ "<|image|>", "<|audio|>", "<|video|>", ], ), run_success_test( "Özel BOS ve EOS tokenları", lambda: test_custom_tokens(template), [ "", "", "<|assistant|>", ], ), run_error_test( "Art arda iki user mesajı", lambda: test_consecutive_users(template), "İki user mesajı art arda gelemez.", ), run_error_test( "Desteklenmeyen rol", lambda: test_unsupported_role(template), "Desteklenmeyen rol: moderator", ), run_error_test( "Geç gelen system mesajı", lambda: test_late_system_message(template), "system mesajları yalnızca konuşmanın başında bulunabilir.", ), ] successful_tests = sum(results) total_tests = len(results) print("\n" + "#" * 70) print("TEST ÖZETİ") print("#" * 70) print(f"Başarılı test: {successful_tests}/{total_tests}") if successful_tests == total_tests: print("🎉 Bütün testler başarıyla tamamlandı.") else: failed_tests = total_tests - successful_tests print(f"⚠️ Başarısız test sayısı: {failed_tests}") raise SystemExit(1) if __name__ == "__main__": main()