flexigo-support-bot / tests /test_lang.py
victor34593993's picture
fix(lang): pin the detected customer language explicitly (Spanish-heavy context was overriding plain mirror); None->mirror fallback
1316064 verified
Raw
History Blame
9.94 kB
from __future__ import annotations
import pytest
from app.lang import detect_language
@pytest.mark.parametrize(
"text",
[
# The live regression: langdetect scores this pt at 99.9%.
"hola, ¿hacéis envíos a Portugal?",
"que productos vendeis y de que material son",
"quiero devolver mi pedido",
# langdetect scores this one as Catalan.
"envianme a Lisboa el paquete por favor",
"¿cuánto tarda el envío a Canarias?",
# live bug: this all-caps Spanish was answered in HUNGARIAN (langdetect
# misfired 'hu'); the expanded markers ("soy","mandais") now win.
"SOY MELILLA, MANDAIS A MELILLA",
"soy de melilla, mandais cosas a melilla?",
],
)
def test_spanish_detected(text):
assert detect_language(text) == "español"
@pytest.mark.parametrize(
"text",
[
"bom dia, que produtos tendes?",
"qual é o prazo de entrega para Lisboa?",
"obrigado, quero devolver o meu pedido",
"não recebi a minha encomenda",
],
)
def test_portuguese_detected(text):
assert detect_language(text) == "portugués de Portugal (pt-PT)"
def test_catalan_detected_not_portuguese():
# regression: "bon dia, què veneu?" was answered in Portuguese
assert detect_language("bon dia, què veneu?") == "català"
assert detect_language("bona tarda, voleu ajudar-me amb això?") == "català"
@pytest.mark.parametrize(
"text,expected",
[
("مرحبا، ماذا تبيعون في متجركم؟", "árabe (Arabic)"),
("你们卖什么产品?我想了解一下", "chino simplificado (Chinese)"),
("こんにちは、どんな商品がありますか?", "japonés (Japanese)"),
("안녕하세요, 어떤 제품을 판매하나요?", "coreano (Korean)"),
("Здравствуйте, какие товары у вас есть?", "ruso (Russian)"),
("नमस्ते, आप क्या बेचते हैं?", "hindi (Hindi)"),
("Merhaba, hangi ürünleri satıyorsunuz?", "turco (Turkish)"),
("Hallo, welke producten verkoopt u?", "neerlandés (Dutch)"),
# genuine Hungarian must STILL detect (the Melilla fix must not over-suppress hu)
("Szeretném tudni a szállítási költségeket Budapestre kérem", "húngaro (Hungarian)"),
],
)
def test_world_languages_detected(text, expected):
assert detect_language(text) == expected
def test_english_detected():
assert detect_language("where is my order, can you check the tracking?") == "English"
def test_english_with_stray_spanish_word_stays_english():
# regression: "mi" must NOT flip a clearly-English sentence to Spanish
assert detect_language("where is mi product please") == "English"
assert detect_language("hello, can you help me find a product") == "English"
@pytest.mark.parametrize(
"text",
[
# the real production bug: plain Spanish WITHOUT ¿¡ñ accents was pinned
# English because the Spanish preposition "a" tripped _looks_english.
"Hola, voy a comprar la Toorx msx70 y quiero saber los plazos de entrega "
"para envios a la peninsula.",
# same defect without a leading greeting (exercises the score path)
"quiero comprar la bici y saber los plazos de envios a la peninsula",
"necesito ayuda para elegir y comprar productos para mi pedido",
],
)
def test_plain_spanish_without_accents_is_spanish(text):
# regression: a strong Spanish word-score must beat the "a"/"to" English bias
assert detect_language(text) == "español"
@pytest.mark.parametrize(
"text",
[
"i want to talk to a human", # was detected Swahili
"hello can you help me", # was detected Welsh
"i need help with my order please",
"can you tell me where my product is",
],
)
def test_short_english_not_misdetected(text):
# regression: short English must NOT be pinned to an exotic language
assert detect_language(text) == "English"
@pytest.mark.parametrize(
"text",
[
# regression: langdetect guessed Iberian, no markers → was pinned español
"do you have garden hoses?",
"do you sell garden products?",
],
)
def test_english_misclassified_as_iberian_stays_english(text):
assert detect_language(text) == "English"
@pytest.mark.parametrize(
"text,expected",
[
# regression: dense non-Latin under 12 chars returned None (no pin → drift)
("你们卖什么?", "chino simplificado (Chinese)"),
("你们有花园用的水管吗?", "chino simplificado (Chinese)"),
("정원용 호스 있나요?", "coreano (Korean)"),
("ガーデンホース", "japonés (Japanese)"),
("لديكم خراطيم؟", "árabe (Arabic)"),
],
)
def test_short_nonlatin_detected_by_script(text, expected):
assert detect_language(text) == expected
def test_short_cyrillic_pins_a_cyrillic_language():
# exact Slavic language varies, but it must pin a Cyrillic language (not None)
cyrillic = {"ruso (Russian)", "ucraniano (Ukrainian)", "búlgaro (Bulgarian)",
"macedonio (Macedonian)"}
assert detect_language("у вас есть садовые шланги?") in cyrillic
def test_unsure_latin_text_not_forced():
# gibberish/ambiguous Latin → don't force a wrong language (let LLM decide)
assert detect_language("zxcv qwer asdf zxcv") is None
def test_short_greetings_pinned_by_lexicon():
# regression: "buenas" was answered in Portuguese
assert detect_language("buenas") == "español"
assert detect_language("hola") == "español"
assert detect_language("bonjour") == "français"
assert detect_language("hello") == "English"
assert detect_language("olá") == "portugués de Portugal (pt-PT)"
def test_unknown_short_text_not_pinned():
assert detect_language("xy") is None
assert detect_language("") is None
def test_language_guard_flags_drift():
from app.lang import needs_language_repair
ko = "coreano (Korean)"
zh = "chino simplificado (Chinese)"
# drift: Korean pinned but answer is Spanish → must repair
assert needs_language_repair("Sí, disponemos de mangueras para jardín.", ko) is True
assert needs_language_repair("Sí, tenemos varias opciones.", zh) is True
# correct: answer in the pinned script → no repair (Latin product names allowed)
assert needs_language_repair("네, 정원용 호스가 있습니다. Gobeflat Medium 모델입니다.", ko) is False
assert needs_language_repair("我们有花园用的水管。PVC 平面水管。", zh) is False
# Latin-script languages are NOT force-repaired by design: the model mirrors the
# customer's language (system prompt), and a heuristic Latin pin can be wrong —
# repairing toward a wrong pin is exactly what caused the Hungarian/Catalan
# misfire. So Latin always returns False (trust the model), only non-Latin repairs.
assert needs_language_repair("Sí, tenemos varias mangueras de jardín disponibles para ti.", "français") is False
assert needs_language_repair("Bonjour, nous avons plusieurs tuyaux d'arrosage disponibles.", "français") is False
assert needs_language_repair("anything", None) is False
def test_strip_emojis_removes_pictographs_keeps_text():
from app.lang import strip_emojis
assert strip_emojis("¡Hola! 😊 ¿En qué te ayudo?") == "¡Hola! ¿En qué te ayudo?"
assert strip_emojis("Tenemos mangueras 🌿✅ disponibles") == "Tenemos mangueras disponibles"
# non-Latin text must survive untouched (emoji ranges don't overlap scripts)
assert strip_emojis("정원용 호스 😀 있습니다") == "정원용 호스 있습니다"
assert strip_emojis("我们有水管 🚀") == "我们有水管"
assert strip_emojis("normal text") == "normal text"
def test_strip_links_removes_fabricated_links():
from app.lang import strip_links
# markdown link → plain text (the inline form is the real CTA, no 404 link)
out = strip_links("Rellena el [Formulario de contacto](https://flexigobe.com/contacto) abajo.")
assert "https://" not in out and "](" not in out
assert "Formulario de contacto" in out # keep the words, drop the link
# bare URL removed
assert "http" not in strip_links("Visita https://flexigobe.com/contacto para más.")
# clean text untouched
assert strip_links("Rellena el formulario que aparece abajo.") == \
"Rellena el formulario que aparece abajo."
def test_prompt_forbids_emojis():
from app.prompts import build_system_prompt
assert "emoji" in build_system_prompt("X").lower()
def test_prompt_pins_detected_language_else_mirrors():
# The store context is heavily Spanish and drags the model into Spanish even for
# an English customer (observed live), so a detected language is pinned EXPLICITLY;
# when detection is unsure (None) we mirror. The Hungarian misfire is fixed at the
# DETECTOR (see test_spanish_detected), not by refusing to pin.
from app.prompts import build_system_prompt
pinned = build_system_prompt("Tienda", "English").lower()
assert "english" in pinned and "entire reply" in pinned
unsure = build_system_prompt("Tienda", None).lower()
assert "same language" in unsure and "mirror" in unsure
def test_prompt_forbids_redirecting_customer_to_third_parties():
"""Regression: the bot once told a Spanish customer to 'visit Toorx's website
/ contact their customer service' instead of helping. The prompt must forbid
sending the customer to a manufacturer/third party and keep them with the
store's own team."""
from app.prompts import build_system_prompt
p = build_system_prompt("Tienda X").lower()
assert "third party" in p and "manufacturer" in p
assert "escalate_to_human" in p