refactor: standardize developer text and identifiers in English

This commit is contained in:
2026-10-05 10:13:25 -03:00
parent 6b8c27fd76
commit dfc1f663c7
20 changed files with 933 additions and 1142 deletions
@@ -196,7 +196,7 @@ class ChatCompletionsApiModelHandler(BaseOpenAICompatibleHandler):
if self.stream:
create_kwargs["stream_options"] = {"include_usage": True}
# Svuota i parametri dei tool per evitare il blocco 400 di NVIDIA NIM
# Remove tool parameters to avoid NVIDIA NIM's HTTP 400 response.
create_kwargs.pop("tools", None)
create_kwargs.pop("tool_choice", None)
+15 -16
View File
@@ -4,7 +4,7 @@ LLM Output Processor
Intercepts LLM output to:
1. Extract tool calls and send them via text_output_queue
2. Forward clean text to TTS pipeline
3. 2026-09-25: SANITIZE APIPILOT internal markers before clients / TTS (anti-leak tecnico)
3. Sanitize ApiPilot internal markers before sending output to clients or TTS
"""
from __future__ import annotations
@@ -17,16 +17,15 @@ from queue import Queue
from speech_to_speech.baseHandler import BaseHandler
from speech_to_speech.pipeline.events import AssistantTextEvent, ResponseFailedEvent, TokenUsageEvent
# 2026-09-25 SANITIZE APIPILOT CALL/RESULT markers in LLM output BEFORE:
# (a) AssistantTextEvent viene mandato ai client WS (e loggato come <<< TEXT:/ASSISTANT:)
# (b) TTSInput viene mandato a Qwen3-TTS (che lo pronuncerebbe ad alta voce).
# Formati osservati reali oggi (upstream Vocero start_pipeline_rag):
# 1) <<<APIPILOT_CALL:NAME=tool,ARGUMENTS=json>>> standard 3x
# Sanitize ApiPilot CALL/RESULT markers before emitting AssistantTextEvent
# to WebSocket clients or TTSInput to Qwen3-TTS, which would speak the markers.
# Observed formats from upstream Vocero start_pipeline_rag:
# 1) <<<APIPILOT_CALL:NAME=tool,ARGUMENTS=json>>> standard triple brackets
# 2) <APIPILOT_CALL:NAME=get_user_briefing,ARGUMENTS={"format":"voice"}
# singolo '<' '>' + args inventati da AI (format=voice)
# 3) <<<APIPILOT_CALL:...}> \n>> spezzato multi-riga
# 4) >>>APIPILOT_CALL:...<<< invertito (errore sintassi AI)
# 5) <<<APIPILOT_RESULT:PASS2>>> + blocco result.
# single brackets and model-generated arguments
# 3) <<<APIPILOT_CALL:...}> \n>> split across lines
# 4) >>>APIPILOT_CALL:...<<< inverted brackets
# 5) <<<APIPILOT_RESULT:PASS2>>> followed by a result block
_APIPILOT_CALL_NORM = re.compile(
r"[<]{1,3}\s*APIPILOT_CALL:NAME=([A-Za-z0-9_.-]+),ARGUMENTS=(.*?)[>]{1,3}",
re.IGNORECASE | re.DOTALL,
@@ -64,7 +63,7 @@ def _sanitize_apipilot_before_client_and_tts(text: str) -> tuple[str, bool, str]
if _APIPILOT_RESULT.search(new_text):
new_text = _APIPILOT_RESULT.sub(" ", new_text)
found_result = True
# safety catchall frammenti marker senza wrapper (se marker troncato mid-stream)
# Catch marker fragments without a wrapper, including mid-stream truncation.
low = new_text.lower()
if "apipilot" in low and ("call" in low or "result" in low or "name=" in low or "arguments=" in low):
new_text = re.sub(r"(?i)\bAPIPILOT(?:_CALL|_RESULT)?\b", " ", new_text)
@@ -184,8 +183,8 @@ class LMOutputProcessor(BaseHandler[LLMOut, TTSIn]):
logger.debug(f"LM processor: text='{lm_output.text}', tools={lm_output.tools}")
# 2026-09-25 SANITIZE SINGOLO PUNTO CENTRALE: PRIMA di AssistantTextEvent (client/log)
# E PRIMA di TTSInput (Qwen3-TTS che pronuncerebbe marker APIPILOT CALL).
# Sanitize centrally before AssistantTextEvent and TTSInput so neither
# clients nor Qwen3-TTS receive internal ApiPilot markers.
clean_text = lm_output.text
any_sanitized = False
sanitize_tag = ""
@@ -193,14 +192,14 @@ class LMOutputProcessor(BaseHandler[LLMOut, TTSIn]):
clean_text, any_sanitized, sanitize_tag = _sanitize_apipilot_before_client_and_tts(clean_text)
if any_sanitized:
logger.warning(
"[LM-OUTPUT-APIPILOT-SANITIZE] %s — tool=%s rawLen=%d cleanLen=%d (rimosso / sostituito con placeholder spagnolo PRIMA di client + TTS).",
"[LM-OUTPUT-APIPILOT-SANITIZE] %s — tool=%s rawLen=%d cleanLen=%d (removed or replaced with the Spanish placeholder before client/TTS output).",
sanitize_tag or "-",
(sanitize_tag if sanitize_tag else "marker"),
len(lm_output.text or ""),
len(clean_text or ""),
)
# evita che lo stesso chunk (marker solo) arrivi a TTS come "" → Hello.
# Se dopo sanitize testo vuoto MA avevamo trovato marker → placeholder.
# Use the placeholder when sanitization removes the entire chunk,
# avoiding empty text that would trigger the TTS greeting fallback.
if not clean_text.strip():
clean_text = _APIPILOT_SPANISH_PLACEHOLDER
+16 -19
View File
@@ -13,16 +13,15 @@ import re
import tempfile
import unicodedata
# 2026-09-25 SANITIZE APIPILOT MARKERS PRIMA DI TTS (anti-leak pronunciato da Qwen3).
# Lato server S2S Python abbiamo gia' gate + sanitize ma come DOPPIA RETE
# di sicurezza sanitizziamo DIRETTAMENTE il testo che entra in Qwen3-TTS.
# Formati osservati reali oggi:
# (a) <<<APIPILOT_CALL:NAME=tool,ARGUMENTS=json>>> standard 3 parentesi
# Sanitize ApiPilot markers directly before TTS as an additional safeguard
# alongside the S2S relay's audio gating and sanitization.
# Observed formats:
# (a) <<<APIPILOT_CALL:NAME=tool,ARGUMENTS=json>>> standard triple brackets
# (b) <APIPILOT_CALL:NAME=tool,ARGUMENTS={"format":"voice"}
# singolo < > (caso user oggi)
# (c) <<<APIPILOT_CALL:...}> \n>> spezzato piu' righe
# (d) >>>APIPILOT_CALL:...<<< invertito
# Regola: CALL -> placeholder spagnolo (frase naturale utente), RESULT -> strip.
# single brackets
# (c) <<<APIPILOT_CALL:...}> \n>> split across lines
# (d) >>>APIPILOT_CALL:...<<< inverted brackets
# Replace CALL with a natural Spanish utterance; strip RESULT markers.
_APIPILOT_CALL_NORMAL_RE = re.compile(
r"[<]{1,3}\s*APIPILOT_CALL:NAME=([A-Za-z0-9_.-]+),ARGUMENTS=(.*?)[>]{1,3}",
re.IGNORECASE | re.DOTALL,
@@ -39,7 +38,7 @@ _APIPILOT_SPANISH_PLACEHOLDER = "Voy a consultar tus datos, un momento por favor
def _tts_sanitize_apipilot_markers(text: str) -> tuple[str, bool, str]:
"""Rimuove marker APIPILOT dal testo TTS.
"""Remove ApiPilot markers from TTS text.
Returns (sanitized_text, found_any, log_tag).
"""
@@ -48,7 +47,7 @@ def _tts_sanitize_apipilot_markers(text: str) -> tuple[str, bool, str]:
found_call = False
found_result = False
tool_name = ""
# 1) marker CALL standard + invertito -> placeholder spagnolo
# 1) Replace standard/inverted CALL markers with the Spanish placeholder.
def _repl_call(m: re.Match) -> str:
nonlocal found_call, tool_name
found_call = True
@@ -62,11 +61,11 @@ def _tts_sanitize_apipilot_markers(text: str) -> tuple[str, bool, str]:
text2 = _APIPILOT_CALL_NORMAL_RE.sub(_repl_call, text)
if not found_call:
text2 = _APIPILOT_CALL_INVERTED_RE.sub(_repl_call, text2)
# 2) marker RESULT -> strip silenzioso (non sono pronunciabili)
# 2) Strip RESULT markers silently; they must not be spoken.
if _APIPILOT_RESULT_RE.search(text2):
found_result = True
text2 = _APIPILOT_RESULT_RE.sub(" ", text2)
# 3) safety: FRAMMENTI marker singoli che non hanno wrapper completo
# 3) Catch marker fragments without a complete wrapper.
low = text2.lower()
if "apipilot" in low and ("call" in low or "result" in low):
import re as _re_sf
@@ -782,18 +781,16 @@ class Qwen3TTSHandler(BaseHandler[TTSIn, TTSOut]):
coalesced_text, _language_code, _saw_end_of_response = self._coalesce_pending_tts_input(tts_input)
raw_text = coalesced_text or "Hello."
# 2026-09-25 SANITIZE APIPILOT PRIMA DI TTS (doppia rete sicurezza):
# Anche se lato server S2s Python abbiamo gia' gate audio drop + sanitize,
# Qwen3-TTS puo' ricevere marker per race / nuovi formati. Quindi QUI
# eliminiamo / sostituiamo con placeholder PRIMA di chiamare generate().
# A race or a new marker format can bypass relay sanitization. Strip or
# replace markers again before passing text to the TTS model.
text, found_marker, sanitize_tag = _tts_sanitize_apipilot_markers(raw_text)
if found_marker:
logger.warning(
"[TTS-APIPILOT-SANITIZE] marker=%s rawLen=%d sanitizedLen=%d → sostituito con placeholder spagnolo / strip.",
"[TTS-APIPILOT-SANITIZE] marker=%s rawLen=%d sanitizedLen=%d → replaced with the Spanish placeholder or stripped.",
sanitize_tag, len(raw_text or ""), len(text or ""),
)
if not text.strip():
# Safety se il marker era l'unica cosa: ripetiamo placeholder invece di Hello.
# Use the Spanish placeholder when the marker was the entire utterance.
text = _APIPILOT_SPANISH_PLACEHOLDER
model_type = self._model_type()
@@ -30,7 +30,7 @@ class SessionHandler(RealtimeBaseHandler):
"""
s = event.session
# Forza il bypass della voce inviata dal browser
# Ignore the voice supplied by the browser.
if hasattr(s, "voice") and s.voice is not None:
s.voice = None
elif isinstance(s, dict) and "voice" in s:
@@ -277,11 +277,10 @@ class RealtimeService:
try:
return model_cls.model_validate(raw) # type: ignore[return-value]
except ValidationError as e:
# ── Retrocompatibilità session.update ─────────────────────────────────
# Il formato standard OpenAI Realtime GA annida instructions/tools/tool_choice
# DENTRO "session": { ... }. Per tollerare anche client che li mandano
# alla RADICE (es. bug prima della Fase 2 MCP), ricostruiamo qui il payload
# corretto e riproviamo la validazione. Funziona SOLO per session.update.
# Backward-compatible session.update parsing.
# OpenAI Realtime GA nests instructions/tools/tool_choice in "session".
# Accept older clients that send them at the root by rebuilding the
# nested payload and retrying validation, only for session.update.
if event_type == "session.update":
try:
raw_dict = dict(raw) if not isinstance(raw, dict) else raw
@@ -290,7 +289,7 @@ class RealtimeService:
normalized_inner = dict(session_inner)
else:
normalized_inner = {}
# Promuovi campi noti dalla root → inner se mancanti (fallback flat)
# Promote known root fields into the inner session when missing.
SESSION_KNOWN_TOP_LEVEL = (
"instructions",
"tools",
@@ -14,6 +14,6 @@ class ChatTTSHandlerArguments:
chat_tts_chunk_size: int = field(
default=512,
metadata={
"help": "Sets the size of the audio data chunk processed per cycle, balancing playback latency and CPU load.. Default is 512。."
"help": "Sets the size of the audio data chunk processed per cycle, balancing playback latency and CPU load. Default is 512."
},
)