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()