ms-ai-architect/skills/ms-ai-engineering/references/multi-modal/speech-to-ai-pipelines.md
Kjell Tore Guttormsen ddce43d8b2 feat(ms-ai-architect): Spor 1 — Port-1-substrat migrert på 4 ikke-advisor-skills (243 Source + 327 Type + 325 TOC + stale-verified poison fjernet) [skip-docs]
Steg 9 (R4): unified migrate-corpus.mjs --write over engineering/governance/
infrastructure/security. 327 filer mutert, verified=null, prosa byte-identisk
(fra første ## seksjon), advisor urørt (0 endringer).

To applier-fixes oppdaget under kjøring (TDD, RED→GREEN):
- insertHeaderFields: anker faller nå tilbake når en meta-linje selv passerer
  500B (2 filer pakket et avsnitt i **Status:** → Type/Source landet utenfor
  scan-vinduet, applierens post-write-assertion fanget + restaurerte).
- normalizeStaleVerified: fjerner nå ALLE stale non-date **Verified:** i
  500B-vinduet, inkl. stray body-dup rett under --- (9 mlops-genaiops-filer var
  ellers falskt "verified"/fresh, droppet fra worklist). Operatør-godkjent
  utvidelse av carve-out; kun stray metadata-linjer, aldri prosa.

test-transform-criterion: precondition oppdatert til post-migrasjons-sannhet
(fila bærer nå Source). Suite 728/728 grønn.
2026-07-04 10:19:11 +02:00

531 lines
20 KiB
Markdown

# Speech-to-AI Integration Pipelines
**Last updated:** 2026-02
**Status:** GA
**Category:** Multi-Modal AI
**Type:** reference
---
## Innhold
- [Introduksjon](#introduksjon)
- [Speech Recognition og Language Detection](#speech-recognition-og-language-detection)
- [Audio Preprocessing og Quality Assessment](#audio-preprocessing-og-quality-assessment)
- [Low-Latency Streaming Architectures](#low-latency-streaming-architectures)
- [Error Handling og Confidence Scoring](#error-handling-og-confidence-scoring)
- [Pipeline-arkitekturar for norsk offentleg sektor](#pipeline-arkitekturar-for-norsk-offentleg-sektor)
- [For Cosmo](#for-cosmo)
## Introduksjon
Speech-to-AI-integrasjon handlar om å kople talegjenkjenning med downstream AI-tenester for å skape ende-til-ende-pipelines som konverterer tale til handlingsorienterte innsikter. Azure Speech Service dannar grunnlaget, med støtte for sanntids talegjenkjenning, batchtranskribering, språkdeteksjon, talardiarisering og tilpassa talemodular.
For norsk offentleg sektor er tale-til-AI-pipelines avgjerande for tilgjengelegheit (automatisk teksting), møtetranskribering (kommunestyre, utval), klageblandling per telefon, og innbyggardialog via talebaserte brukargrensesnitt. Azure Speech Service støttar norsk bokmål (nb-NO) og kan kombinerast med Azure OpenAI for intelligente samtaleagenter.
Denne referansefila dekkjer arkitekturmønster for sanntidsstreaming, batchprosessering, språkdeteksjon og feilhandtering — med fokus på robuste produksjonsklare implementeringar.
---
## Speech Recognition og Language Detection
### Azure Speech Service oversikt
| Funksjon | Tilgjengelegheit | Brukstilfelle |
|----------|-----------------|--------------|
| **Real-time STT** | GA | Samtaleagenter, live teksting |
| **Batch transcription** | GA | Arkivprosessering, møtereferat |
| **Fast transcription** | GA | Rask transkribering av filer |
| **Language identification** | GA | Fleirspråklege samtalar |
| **Speaker diarization** | GA | Møtetranskribering |
| **Custom speech** | GA | Bransjespecifikk terminologi |
| **Real-time TTS** | GA | Talebaserte assistentar |
| **Text streaming** | GA | Sanntids TTS frå GPT-output |
| **gpt-4o-realtime** | Preview | Direkte tale-til-tale |
### Språkgjenkjenning
Azure Speech Service tilbyr to typar språkdeteksjon:
**At-Start Language Identification:**
- Detekterer språk i starten av audiostraumen
- Støtta i C#, C++, Python, Java, JavaScript, Objective-C
- Best for scenario med eitt hovudspråk per sesjon
**Continuous Language Identification:**
- Detekterer språkbytter undervegs i samtalen
- Støtta i C#, C++, Java, JavaScript, Python
- Kritisk for fleirspråklege norske kommunar
```python
import azure.cognitiveservices.speech as speechsdk
speech_config = speechsdk.SpeechConfig(
subscription=os.environ["SPEECH_KEY"],
region=os.environ["SPEECH_REGION"]
)
# Konfigurer automatisk språkdeteksjon
auto_detect_config = speechsdk.languageconfig.AutoDetectSourceLanguageConfig(
languages=["nb-NO", "nn-NO", "en-US", "se-NO"] # Bokmål, nynorsk, engelsk, nordsamisk
)
speech_recognizer = speechsdk.SpeechRecognizer(
speech_config=speech_config,
auto_detect_source_language_config=auto_detect_config,
audio_config=audio_config
)
def recognized_handler(evt):
result = evt.result
auto_detect_result = speechsdk.AutoDetectSourceLanguageResult(result)
detected_language = auto_detect_result.language
print(f"[{detected_language}] {result.text}")
speech_recognizer.recognized.connect(recognized_handler)
speech_recognizer.start_continuous_recognition()
```
### Tilpassa talemodular (Custom Speech)
For norsk offentleg sektor med spesialisert terminologi:
| Tilpasning | Brukstilfelle | Eksempel |
|-----------|--------------|---------|
| **Phrase list** | Forbetra gjenkjenning av spesifikke ord | Stadnamn, fagtermar |
| **Custom model** | Trenar ny modell med eigne data | Vegsektoren, helsesektor |
| **Display format** | Tilpass visning av gjenkjend tekst | Tal-til-siffer, dato-format |
```python
# Phrase list for forbetra norsk gjenkjenning
phrase_list = speechsdk.PhraseListGrammar.from_recognizer(speech_recognizer)
phrase_list.addPhrase("Direktoratet for digital tjenesteutvikling")
phrase_list.addPhrase("E6 Megården-Mørsvikbotn")
phrase_list.addPhrase("Utredningsinstruksen")
phrase_list.addPhrase("Forvaltningsloven")
phrase_list.addPhrase("personvernforordningen")
```
---
## Audio Preprocessing og Quality Assessment
### Audioformat og kvalitetskrav
| Parameter | Tilrådde verdi | Minimum | Kommentar |
|-----------|---------------|---------|-----------|
| **Samplingsrate** | 16 kHz | 8 kHz | 16 kHz gir best nøyaktigheit |
| **Bit depth** | 16-bit | 8-bit | Mono PCM tilrådde |
| **Kanalar** | Mono | Mono | Stereo splittar automatisk |
| **Format** | WAV (PCM) | WAV, MP3, OGG | WAV gir minst komprimeringstap |
| **SNR** | >20 dB | >10 dB | Signal-to-noise ratio |
### Audio preprocessing pipeline
```python
import numpy as np
from scipy.io import wavfile
from scipy.signal import butter, lfilter
class AudioPreprocessor:
"""Preprosessering av audio for optimal talegjenkjenning."""
def __init__(self, target_sample_rate=16000):
self.target_sample_rate = target_sample_rate
def assess_quality(self, audio_data: np.ndarray, sample_rate: int) -> dict:
"""Vurder audiokvalitet før talegjenkjenning."""
# Berekn SNR
signal_power = np.mean(audio_data ** 2)
noise_floor = np.percentile(np.abs(audio_data), 5) ** 2
snr = 10 * np.log10(signal_power / max(noise_floor, 1e-10))
# Berekn varigheit
duration = len(audio_data) / sample_rate
# Detekter stille
silence_threshold = np.percentile(np.abs(audio_data), 10)
silence_ratio = np.sum(np.abs(audio_data) < silence_threshold) / len(audio_data)
# Berekn peak level
peak_level = 20 * np.log10(np.max(np.abs(audio_data)) / 32768)
return {
"snr_db": round(snr, 1),
"duration_seconds": round(duration, 1),
"silence_ratio": round(silence_ratio, 3),
"peak_level_db": round(peak_level, 1),
"sample_rate": sample_rate,
"quality_score": self._calculate_quality_score(snr, silence_ratio, peak_level),
"recommendation": self._get_recommendation(snr, silence_ratio)
}
def _calculate_quality_score(self, snr, silence_ratio, peak_level):
score = 0
if snr > 20: score += 40
elif snr > 15: score += 30
elif snr > 10: score += 20
else: score += 10
if silence_ratio < 0.3: score += 30
elif silence_ratio < 0.5: score += 20
else: score += 10
if -6 < peak_level < -1: score += 30
elif -12 < peak_level < 0: score += 20
else: score += 10
return min(score, 100)
def _get_recommendation(self, snr, silence_ratio):
if snr < 10:
return "Låg SNR — vurder støyreduksjon eller betre mikrofon"
if silence_ratio > 0.7:
return "Mykje stille — sjekk at audio faktisk inneheld tale"
return "Kvaliteten er akseptabel for talegjenkjenning"
def apply_noise_reduction(self, audio_data: np.ndarray, sample_rate: int) -> np.ndarray:
"""Enkel bandpassfiltrering for å redusere støy."""
low_freq = 80 # Hz — under menneskeleg tale
high_freq = 8000 # Hz — over dei fleste talefrekvensane
nyquist = sample_rate / 2
low = low_freq / nyquist
high = high_freq / nyquist
b, a = butter(4, [low, high], btype='band')
return lfilter(b, a, audio_data).astype(np.int16)
```
### Quality gates for produksjon
| Gate | Terskel | Handling |
|------|---------|---------|
| **SNR-sjekk** | < 10 dB | Avvis, be om ny innspeling |
| **Varighetssjekk** | < 1 sekund | Avvis, for kort |
| **Stillesjekk** | > 80% stille | Åtvar, be om verifikasjon |
| **Peak clipping** | > -0.5 dB | Åtvar, mogleg klipping |
| **Format-validering** | Støtta format | Konverter automatisk |
---
## Low-Latency Streaming Architectures
### Sanntids talegjenkjenning
```
Mikrofon → Audio stream → Azure Speech SDK
┌── Recognizing event (interim)
│ "Eg trur at veg..."
├── Recognized event (final)
│ "Eg trur at vegsektoren bør investere meir."
│ Offset: 1800000 ticks
│ Duration: 30500000 ticks
└── SessionStopped event
```
### Speech SDK streaming-arkitektur
```python
import azure.cognitiveservices.speech as speechsdk
import asyncio
import json
class StreamingSpeechPipeline:
"""Sanntids tale-til-AI pipeline med låg latency."""
def __init__(self, speech_key, speech_region, openai_client):
self.speech_config = speechsdk.SpeechConfig(
subscription=speech_key,
region=speech_region
)
self.speech_config.speech_recognition_language = "nb-NO"
self.speech_config.request_word_level_timestamps()
self.speech_config.set_property(
speechsdk.PropertyId.SpeechServiceResponse_StablePartialResultThreshold,
"3" # Reduser flimring i delresultat
)
self.openai_client = openai_client
self.transcript_buffer = []
async def start_streaming(self, audio_config=None):
"""Start sanntids talegjenkjenning med AI-prosessering."""
if audio_config is None:
audio_config = speechsdk.audio.AudioConfig(use_default_microphone=True)
recognizer = speechsdk.SpeechRecognizer(
speech_config=self.speech_config,
audio_config=audio_config
)
# Interim-resultat for live visning
recognizer.recognizing.connect(self._on_recognizing)
# Endelege resultat for AI-prosessering
recognizer.recognized.connect(self._on_recognized)
# Start gjenkjenning
recognizer.start_continuous_recognition()
return recognizer
def _on_recognizing(self, evt):
"""Handter interim-resultat (vis til brukar)."""
print(f"\r [interim] {evt.result.text}", end="", flush=True)
def _on_recognized(self, evt):
"""Handter endeleg resultat (send til AI)."""
if evt.result.reason == speechsdk.ResultReason.RecognizedSpeech:
text = evt.result.text
offset = evt.result.offset
duration = evt.result.duration
self.transcript_buffer.append({
"text": text,
"offset_ticks": offset,
"duration_ticks": duration,
"timestamp": offset / 10_000_000 # Konverter til sekund
})
print(f"\n[{offset/10_000_000:.1f}s] {text}")
```
### Speech-to-Speech med Azure OpenAI
For samtaleagenter med direkte tale-til-tale:
```
Brukar tale → Speech SDK (STT) → Tekst
Azure OpenAI (GPT-4o)
Svar-tekst
Speech SDK (TTS) med text streaming
Syntetisert tale → Brukar
```
```python
# Text streaming for låg-latency TTS
speech_config.set_speech_synthesis_output_format(
speechsdk.SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm
)
tts_endpoint = (
f"wss://{region}.tts.speech.microsoft.com"
f"/cognitiveservices/websocket/v2"
)
# Stream GPT-output direkte til TTS
async def stream_response_to_speech(gpt_stream, synthesizer):
"""Stream GPT-4o tokens direkte til TTS for minimal latency."""
request = speechsdk.SpeechSynthesisRequest(
input_type=speechsdk.SpeechSynthesisRequestInputType.TextStream
)
# Start TTS-syntese
result_future = synthesizer.speak_async(request)
# Stream tekst-chunks frå GPT
async for chunk in gpt_stream:
if chunk.choices[0].delta.content:
request.input_stream.write(chunk.choices[0].delta.content)
# Steng straumen
request.input_stream.close()
result = await result_future
```
### GPT-4o Realtime API for direkte tale-til-tale
```
Brukar tale → WebSocket → GPT-4o Realtime API → Syntetisert tale
(bidireksjonell audiostraum)
```
| Eigenskap | Verdi |
|-----------|-------|
| **Latency** | < 500ms |
| **Protokoll** | WebSocket |
| **Audio input** | PCM 24kHz 16-bit mono |
| **Audio output** | PCM 24kHz 16-bit mono |
| **Innhaldsmoderering** | Ja, innebygd |
| **Norsk støtte** | Ja (nb-NO) |
---
## Error Handling og Confidence Scoring
### Confidence scoring i talegjenkjenning
Azure Speech Service rapporterer confidence på fleire nivå:
| Nivå | Tilgjengeleg | Brukstilfelle |
|------|-------------|--------------|
| **Ytring-nivå** | Recognized event | Filtrering av låg-kvalitets resultat |
| **Ord-nivå** | Med word timestamps aktivert | Identifisere usikre ord |
```python
speech_config.request_word_level_timestamps()
speech_config.output_format = speechsdk.OutputFormat.Detailed
def handle_detailed_result(evt):
result = evt.result
detailed = json.loads(result.json)
# N-best alternatives
for nbest in detailed.get("NBest", []):
confidence = nbest.get("Confidence", 0)
text = nbest.get("Display", "")
if confidence < 0.6:
print(f" [LAV CONFIDENCE {confidence:.2f}] {text}")
else:
print(f" [OK {confidence:.2f}] {text}")
# Ord-nivå confidence
for word in nbest.get("Words", []):
word_confidence = word.get("Confidence", 0)
if word_confidence < 0.5:
print(f" Usikkert ord: '{word['Word']}' ({word_confidence:.2f})")
```
### Robuste feilhandteringsmønster
```python
class ResilientSpeechPipeline:
"""Robust tale-pipeline med retry og fallback."""
MAX_RETRIES = 3
RETRY_DELAY = 1.0 # sekund
def __init__(self, primary_config, fallback_config=None):
self.primary_config = primary_config
self.fallback_config = fallback_config
self.error_counts = {"no_match": 0, "canceled": 0, "timeout": 0}
async def recognize_with_retry(self, audio_data):
"""Gjenkjenn tale med retry-logikk."""
for attempt in range(self.MAX_RETRIES):
try:
result = await self._attempt_recognition(audio_data, self.primary_config)
if result.reason == speechsdk.ResultReason.RecognizedSpeech:
self.error_counts = {"no_match": 0, "canceled": 0, "timeout": 0}
return {"status": "success", "text": result.text, "confidence": self._get_confidence(result)}
elif result.reason == speechsdk.ResultReason.NoMatch:
self.error_counts["no_match"] += 1
details = result.no_match_details
return {
"status": "no_match",
"reason": str(details.reason),
"recommendation": self._no_match_recommendation(details)
}
elif result.reason == speechsdk.ResultReason.Canceled:
cancellation = result.cancellation_details
if cancellation.reason == speechsdk.CancellationReason.Error:
if "timeout" in str(cancellation.error_details).lower():
self.error_counts["timeout"] += 1
await asyncio.sleep(self.RETRY_DELAY * (attempt + 1))
continue
raise SpeechServiceError(cancellation.error_details)
except Exception as e:
if attempt == self.MAX_RETRIES - 1:
if self.fallback_config:
return await self._attempt_recognition(audio_data, self.fallback_config)
raise
await asyncio.sleep(self.RETRY_DELAY * (attempt + 1))
def _no_match_recommendation(self, details):
if details.reason == speechsdk.NoMatchReason.InitialSilenceTimeout:
return "Ingen tale detektert — sjekk mikrofon eller audiokjelde"
elif details.reason == speechsdk.NoMatchReason.InitialBabbleTimeout:
return "For mykje bakgrunnsstøy — forbetra audiokvalitet"
return "Ukjend årsak — prøv på nytt"
```
### Feilkategoriar og handtering
| Feiltype | Årsak | Handling | Retry? |
|---------|-------|---------|--------|
| **NoMatch - InitialSilenceTimeout** | Ingen tale i starten | Sjekk mikrofon, auk timeout | Nei |
| **NoMatch - InitialBabbleTimeout** | For mykje støy | Forbetra audiokvalitet | Nei |
| **Canceled - AuthenticationError** | Ugyldig nøkkel/token | Forny token | Ja (etter fornyelse) |
| **Canceled - ConnectionError** | Nettverksproblem | Retry med exponential backoff | Ja |
| **Canceled - ServiceTimeout** | Tenesta overbelasta | Retry med delay | Ja |
| **Canceled - RuntimeError** | Intern feil | Retry | Ja |
### Monitoring og observability
```python
from azure.monitor.opentelemetry import configure_azure_monitor
from opentelemetry import metrics
# Konfigurer Azure Monitor
configure_azure_monitor(connection_string=os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"])
meter = metrics.get_meter("speech-pipeline")
recognition_counter = meter.create_counter("speech.recognition.count")
confidence_histogram = meter.create_histogram("speech.recognition.confidence")
latency_histogram = meter.create_histogram("speech.recognition.latency_ms")
def track_recognition(result, latency_ms):
recognition_counter.add(1, {"status": result["status"], "language": "nb-NO"})
if result.get("confidence"):
confidence_histogram.record(result["confidence"])
latency_histogram.record(latency_ms)
```
---
## Pipeline-arkitekturar for norsk offentleg sektor
### Møtetranskribering med diarisering
```
Møtelyd → Audio preprocessing → Quality gate
Speaker diarization
"Talar 1: ..." | "Talar 2: ..."
Continuous recognition
(nb-NO med phrase list)
Strukturert transkripsjon
[tidskode, talar, tekst]
GPT-4o: Oppsummering + handlingspunkt
Dokument: Møtereferat + opptak
```
### Innbyggardialog via telefon
```
Innringar → Azure Communication Services
Real-time transcription
+ Language detection
GPT-4o: Klassifisering
+ Intentdeteksjon
Routing til rett etat/saksbehandlar
ELLER
Automatisk svar via TTS
```
---
## For Cosmo
- **Azure Speech Service støttar norsk bokmål (nb-NO) fullt ut** for STT og TTS. Nynorsk (nn-NO) og nordsamisk (se-NO) har meir avgrensa støtte. Bruk language identification for fleirspråklege scenario.
- **Word-level timestamps og confidence scoring** er avgjerande for produksjonsbruk — aktiver `request_word_level_timestamps()` og filtrer resultat med confidence under 0.6 for kvalitetssikring.
- **Text streaming for TTS** (websocket v2) reduserer opplevd latency dramatisk når du kombinerer GPT-4o med Speech Service. Stream GPT-tokens direkte til TTS i staden for å vente på fullstendig svar.
- **Custom Speech med phrase lists** er eit låg-innsats, høg-verdi tiltak for norske offentlege scenario. Legg til stadnamn, fagtermar og organisasjonsnamn for vesentleg forbetra gjenkjenning.
- **Implementer quality gates før talegjenkjenning** — sjekk SNR, varigheit og støynivå. Det reduserer feilrate og unødvendige API-kall mot Speech Service.