Initial commit: Voice Assistant Gateway mit Konfig-/Routing-Fundament
- FastAPI-Gateway mit REST-Endpunkten (chat/speak/transcribe/devices/sessions/config) - Geschichtete Konfiguration mit Profilen (local-dev/hybrid/cloud) via TOML + ENV - Registry-Pattern + einheitliche Route-Aufloesung (Default->Profil->ENV->Session->Request) - Device Router (strikt, Singleton) und Output-Lifecycle im Orchestrator - OpenRouter-Adapter (STT multipart/LLM/TTS) + lokale Provider-Stubs - Regelbasierte Pipeline (Cleaner/Spoken-Adapter/TTS-Normalizer) - 22 automatisierte Tests; Doku: README, BEDIENUNGSANLEITUNG, Architektur - Secrets ausschliesslich ueber Umgebung; .env und lokale config gitignored Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
293ed257db
72 changed files with 2612 additions and 0 deletions
0
app/pipeline/__init__.py
Normal file
0
app/pipeline/__init__.py
Normal file
4
app/pipeline/input_cleaner.py
Normal file
4
app/pipeline/input_cleaner.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
class InputCleaner:
|
||||
async def run(self, text: str) -> str:
|
||||
cleaned = " ".join(text.strip().split())
|
||||
return cleaned.replace(" äh ", " ").replace(" hm ", " ")
|
||||
37
app/pipeline/spoken_response_adapter.py
Normal file
37
app/pipeline/spoken_response_adapter.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import re
|
||||
|
||||
|
||||
class SpokenResponseAdapter:
|
||||
async def run(self, text: str, language: str = "de") -> str:
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
text = text.strip()
|
||||
|
||||
# Markdown / Formatierung entfernen
|
||||
text = re.sub(r"```[\s\S]*?```", " ", text) # code blocks
|
||||
text = re.sub(r"`([^`]*)`", r"\1", text) # inline code
|
||||
text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text) # markdown links
|
||||
text = re.sub(r"[*_~#>]+", " ", text) # markdown symbols
|
||||
|
||||
# Listen entschärfen
|
||||
text = re.sub(r"(?m)^\s*[-•]\s+", "", text)
|
||||
text = re.sub(r"(?m)^\s*\d+\.\s+", "", text)
|
||||
|
||||
# Mehrfache Leerzeichen / Zeilenumbrüche glätten
|
||||
text = re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
# Für Voice natürlicher machen: Doppelpunkte/Semikolons etwas beruhigen,
|
||||
# aber Uhrzeiten/Verhältnisse (10:30) nicht zerstören -> nur am Wortende ersetzen.
|
||||
text = re.sub(r"[:;](?=\s|$)", ",", text)
|
||||
|
||||
# Klammern meist nicht gut für TTS
|
||||
text = text.replace("(", ", ")
|
||||
text = text.replace(")", " ")
|
||||
|
||||
# Abschlusspunktion sicherstellen
|
||||
if text and not text.endswith((".", "!", "?")):
|
||||
text += "."
|
||||
|
||||
return text
|
||||
|
||||
59
app/pipeline/tts_normalizer.py
Normal file
59
app/pipeline/tts_normalizer.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import re
|
||||
|
||||
|
||||
class TTSNormalizer:
|
||||
async def run(self, text: str, language: str = "de") -> str:
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
normalized = text
|
||||
|
||||
if language == "de":
|
||||
replacements = {
|
||||
"24/7": "vierundzwanzig sieben",
|
||||
"&": " und ",
|
||||
"%": " Prozent",
|
||||
"€": " Euro",
|
||||
"$": " Dollar",
|
||||
"km/h": " Kilometer pro Stunde",
|
||||
"z.B.": "zum Beispiel",
|
||||
"bzw.": "beziehungsweise",
|
||||
"u.a.": "unter anderem",
|
||||
"ca.": "circa",
|
||||
}
|
||||
else:
|
||||
replacements = {
|
||||
"24/7": "twenty four seven",
|
||||
"&": " and ",
|
||||
"%": " percent",
|
||||
"€": " euros",
|
||||
"$": " dollars",
|
||||
"km/h": " kilometers per hour",
|
||||
"e.g.": "for example",
|
||||
"i.e.": "that is",
|
||||
}
|
||||
|
||||
for old, new in replacements.items():
|
||||
normalized = normalized.replace(old, new)
|
||||
|
||||
# Slashes zwischen Wörtern/Zahlen sprachfreundlicher machen
|
||||
normalized = re.sub(r"(\w)/(\w)", r"\1 oder \2", normalized)
|
||||
|
||||
# Datums-/Versions-/Bereichsstriche etwas entschärfen
|
||||
normalized = normalized.replace("–", " bis ")
|
||||
normalized = normalized.replace("—", ", ")
|
||||
normalized = normalized.replace(" - ", ", ")
|
||||
|
||||
# URLs und E-Mails nicht roh vorlesen
|
||||
normalized = re.sub(r"https?://\S+", "Link", normalized)
|
||||
normalized = re.sub(r"\b[\w\.-]+@[\w\.-]+\.\w+\b", "E-Mail-Adresse", normalized)
|
||||
|
||||
# Mehrfache Leerzeichen glätten
|
||||
normalized = re.sub(r"\s+", " ", normalized).strip()
|
||||
|
||||
if normalized and not normalized.endswith((".", "!", "?")):
|
||||
normalized += "."
|
||||
|
||||
return normalized
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue