| """CYPHER V12 M13 — French language boost. |
| |
| Detects French prompts and applies adjustments: |
| - Boost generation temperature slightly (FR vocab less covered) |
| - Inject French few-shot example (via M15 fewshot_prompter) |
| - Post-process: detect English code-switching and flag for retry |
| - Optional: simple FR↔EN dictionary for common terms |
| |
| Used by cypher_bridge_v12.py /chat pipeline. |
| """ |
| from __future__ import annotations |
|
|
| import logging |
| import re |
| from typing import Any |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| |
| _FR_ACCENT_RE = re.compile(r"[éèêëàâäîïôöùûüçÉÈÊËÀÂÄÎÏÔÖÙÛÜÇ]") |
| _FR_FUNCTION_WORDS = { |
| " est ", " sont ", " une ", " des ", " du ", " au ", " aux ", |
| " pour ", " avec ", " dans ", " sur ", " sans ", " entre ", |
| " qui ", " que ", " quoi ", " comment ", " pourquoi ", " quel ", |
| " quelle ", " quels ", " quelles ", " ceux ", " celle ", " ces ", |
| " mon ", " ma ", " mes ", " ton ", " ta ", " tes ", " son ", " sa ", |
| " ses ", " notre ", " votre ", " leur ", " leurs ", |
| } |
| _FR_QUESTION_STARTS = ( |
| "qui ", "que ", "quoi", "quel", "quelle", "comment", |
| "pourquoi", "où ", "ou ", "quand ", "explique", "décris", |
| "presente", "présente", "bonjour", "salut", "bonsoir", |
| "merci", "aide", "donne", |
| ) |
|
|
|
|
| def detect_french(text: str, threshold: float = 0.5) -> dict: |
| """Detect if input is French. Returns confidence + signals.""" |
| if not text: |
| return {"is_fr": False, "confidence": 0.0, "signals": []} |
| signals: list[str] = [] |
| score = 0.0 |
| |
| n_accents = len(_FR_ACCENT_RE.findall(text)) |
| if n_accents > 0: |
| score += min(0.4, n_accents * 0.1) |
| signals.append(f"accents:{n_accents}") |
| |
| text_padded = f" {text.lower()} " |
| fr_word_hits = sum(1 for w in _FR_FUNCTION_WORDS if w in text_padded) |
| if fr_word_hits > 0: |
| score += min(0.5, fr_word_hits * 0.08) |
| signals.append(f"fr_words:{fr_word_hits}") |
| |
| text_lower = text.lower().strip() |
| if any(text_lower.startswith(qs) for qs in _FR_QUESTION_STARTS): |
| score += 0.3 |
| signals.append("fr_question_start") |
| |
| try: |
| from langdetect import detect, DetectorFactory |
| DetectorFactory.seed = 0 |
| lang = detect(text) |
| if lang == "fr": |
| score += 0.3 |
| signals.append("langdetect_fr") |
| except Exception: |
| pass |
| is_fr = score >= threshold |
| return { |
| "is_fr": is_fr, |
| "confidence": min(1.0, score), |
| "signals": signals, |
| } |
|
|
|
|
| def detect_english_codeswitch(response: str, min_run: int = 5) -> bool: |
| """Detect if response contains a run of English words inside FR context.""" |
| |
| if not response: |
| return False |
| tokens = response.split() |
| if len(tokens) < min_run * 2: |
| return False |
| has_fr_overall = bool(_FR_ACCENT_RE.search(response)) or any( |
| w in f" {response.lower()} " for w in _FR_FUNCTION_WORDS |
| ) |
| if not has_fr_overall: |
| return False |
| |
| run = 0 |
| max_run = 0 |
| for tok in tokens: |
| tok_clean = tok.strip(".,;:!?\"'()[]") |
| if not tok_clean: |
| continue |
| is_en = (tok_clean.isascii() and len(tok_clean) >= 3 and |
| tok_clean.lower() not in |
| ("de", "la", "le", "les", "un", "une", "des", "du", "au", "et", "ou", |
| "ne", "pas", "ce", "ces", "se", "ses", "ma", "ta", "sa", "mon", "ton", "son")) |
| if is_en: |
| run += 1 |
| max_run = max(max_run, run) |
| else: |
| run = 0 |
| return max_run >= min_run |
|
|
|
|
| class FRLanguageBoost: |
| """Applies FR-aware inference adjustments at the bridge level.""" |
|
|
| def __init__( |
| self, |
| base_temperature: float = 0.35, |
| fr_temperature_bump: float = 0.05, |
| codeswitch_retry: bool = True, |
| ): |
| self.base_temperature = base_temperature |
| self.fr_temperature_bump = fr_temperature_bump |
| self.codeswitch_retry = codeswitch_retry |
|
|
| def get_temperature(self, prompt: str) -> float: |
| info = detect_french(prompt) |
| if info["is_fr"]: |
| return self.base_temperature + self.fr_temperature_bump |
| return self.base_temperature |
|
|
| def should_inject_fr_fewshot(self, prompt: str) -> bool: |
| return detect_french(prompt)["is_fr"] |
|
|
| def post_process(self, prompt: str, response: str) -> dict: |
| """Diagnose response quality vs FR expectation.""" |
| prompt_info = detect_french(prompt) |
| if not prompt_info["is_fr"]: |
| return {"ok": True, "issue": None} |
| response_info = detect_french(response) |
| if not response_info["is_fr"]: |
| return {"ok": False, "issue": "fr_prompt_en_response"} |
| if self.codeswitch_retry and detect_english_codeswitch(response): |
| return {"ok": False, "issue": "fr_response_en_codeswitch"} |
| return {"ok": True, "issue": None} |
|
|
|
|
| |
| FR_EN_GLOSSARY = { |
| "vulnérabilité": "vulnerability", |
| "menace": "threat", |
| "attaque": "attack", |
| "chiffrement": "encryption", |
| "déchiffrement": "decryption", |
| "pare-feu": "firewall", |
| "mot de passe": "password", |
| "hameçonnage": "phishing", |
| "rançongiciel": "ransomware", |
| "logiciel malveillant": "malware", |
| "renseignement sur les menaces": "threat intelligence", |
| "réponse à incident": "incident response", |
| "détection": "detection", |
| "atténuation": "mitigation", |
| "pile": "stack", |
| "tas": "heap", |
| "débordement": "overflow", |
| "ordre block": "order block", |
| "vide de juste valeur": "fair value gap", |
| "balayage de liquidité": "liquidity sweep", |
| "argent intelligent": "smart money", |
| "tendance": "trend", |
| "structure de marché": "market structure", |
| "premium": "premium", |
| "discount": "discount", |
| "stop suiveur": "trailing stop", |
| } |
|
|
|
|
| def glossary_hint(text: str) -> str: |
| """Returns a brief glossary hint if text contains any FR↔EN term.""" |
| text_lower = text.lower() |
| matches: list[str] = [] |
| for fr_term, en_term in FR_EN_GLOSSARY.items(): |
| if fr_term in text_lower or en_term in text_lower: |
| matches.append(f"{fr_term}↔{en_term}") |
| if not matches: |
| return "" |
| return f"[FR_GLOSSARY: {'; '.join(matches[:5])}]" |
|
|
|
|
| __all__ = [ |
| "detect_french", |
| "detect_english_codeswitch", |
| "FRLanguageBoost", |
| "FR_EN_GLOSSARY", |
| "glossary_hint", |
| ] |
|
|
|
|
| if __name__ == "__main__": |
| logging.basicConfig(level=logging.INFO) |
| print("=== M13 fr_language_boost SMOKE ===") |
|
|
| |
| tests = [ |
| ("Bonjour, comment puis-je analyser ce CVE?", True), |
| ("Hello, what is SQL injection?", False), |
| ("Qui es-tu?", True), |
| ("Tell me about Order Blocks", False), |
| ("Explique le concept de Smart Money", True), |
| ("Mix: explique CVE-2024-3400 mais en anglais please", True), |
| ] |
| for txt, expected in tests: |
| info = detect_french(txt) |
| mark = "✓" if info["is_fr"] == expected else "✗" |
| print(f" {mark} '{txt[:40]}' is_fr={info['is_fr']} conf={info['confidence']:.2f} signals={info['signals']}") |
|
|
| |
| fr_response_clean = "Je suis CYPHER, l'ASI cybersécurité défensive. Je travaille avec les CVE et MITRE ATT&CK." |
| fr_response_switched = "Je suis CYPHER. The defensive cybersec ASI of the family. We work with various CVE catalogs and MITRE ATT&CK techniques for threat hunting and detection engineering across enterprise." |
| print(f"\nCodeswitch clean: {detect_english_codeswitch(fr_response_clean)}") |
| print(f"Codeswitch switched: {detect_english_codeswitch(fr_response_switched)}") |
|
|
| |
| boost = FRLanguageBoost() |
| print(f"\nTemperature FR prompt: {boost.get_temperature('Qui es-tu?'):.2f}") |
| print(f"Temperature EN prompt: {boost.get_temperature('Who are you?'):.2f}") |
| pp = boost.post_process("Qui es-tu?", "I'm CYPHER the defensive ASI of the family.") |
| print(f"Post-process FR→EN response: {pp}") |
|
|
| |
| print(f"\nGlossary hint: {glossary_hint('Explique le rançongiciel et le phishing')}") |
| print("=== SMOKE PASS ===") |
|
|