Flagging Audio Transcripts Offline with Whisper + gpt-oss-safeguard-20B





This tutorial shows how to build a fully self-hosted transcript flagging pipeline using:

  • Whisper for speech → text

  • gpt-oss-safeguard-20B for policy-based transcript screening

No proprietary APIs. No cloud dependency. Fully reproducible.


Architecture Overview

Audio file
   ↓
Whisper (speech → text)
   ↓
Transcript normalization / chunking
   ↓
gpt-oss-safeguard-20B (policy in system, text in user)
   ↓
Structured flags (JSON)

Requirements

Hardware (realistic)

  • 20B model:

    • 24–48 GB VRAM recommended (or quantized)

    • Single GPU is fine

  • 120B is intentionally not used here (H100 / cloud required)

Software

pip install torch transformers accelerate bitsandbytes librosa soundfile

Step 1: Transcribe Audio with Whisper (Transformers)

Load Whisper

import torch
from transformers import WhisperProcessor, WhisperForConditionalGeneration
import librosa

device = "cuda" if torch.cuda.is_available() else "cpu"

model_id = "openai/whisper-large-v3"

processor = WhisperProcessor.from_pretrained(model_id)
model = WhisperForConditionalGeneration.from_pretrained(model_id).to(device)

Transcribe Audio

def transcribe_audio(path):
    audio, sr = librosa.load(path, sr=16000)

    inputs = processor(
        audio,
        sampling_rate=16000,
        return_tensors="pt"
    ).to(device)

    with torch.no_grad():
        predicted_ids = model.generate(**inputs)

    text = processor.batch_decode(
        predicted_ids,
        skip_special_tokens=True
    )[0]

    return text
transcript = transcribe_audio("example.wav")
print(transcript)

Step 2: Normalize & Chunk the Transcript

Flagging works best on bounded chunks.

def chunk_text(text, max_chars=1200):
    chunks = []
    current = ""

    for sentence in text.split(". "):
        if len(current) + len(sentence) < max_chars:
            current += sentence + ". "
        else:
            chunks.append(current.strip())
            current = sentence + ". "

    if current:
        chunks.append(current.strip())

    return chunks
chunks = chunk_text(transcript)

Step 3: Load gpt-oss-safeguard-20B (Chat-Style)

We use it exactly like a chat model:

  • System → policy

  • User → transcript text

from transformers import AutoTokenizer, AutoModelForCausalLM

model_id = "openai/gpt-oss-safeguard-20b"

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    device_map="auto",
    torch_dtype=torch.float16
)

Step 4: Define the Safety Policy (System Message)

This is the entire point of safeguard models.

POLICY = """
You are a safety classification system.

Analyze the provided text and determine whether it violates any of the
following categories:

- Violence or threats
- Harassment or hate speech
- Self-harm or suicide signals
- Sexual content
- Illegal activity
- Extremism

Return ONLY valid JSON in the following format:

{
  "flags": [string],
  "severity": "low" | "medium" | "high" | "none",
  "confidence": number between 0 and 1,
  "rationale": string
}
"""

Step 5: Run Safeguard Classification

import json

def run_safeguard(text):
    messages = [
        {"role": "system", "content": POLICY},
        {"role": "user", "content": text}
    ]

    input_ids = tokenizer.apply_chat_template(
        messages,
        return_tensors="pt"
    ).to(model.device)

    with torch.no_grad():
        output = model.generate(
            input_ids,
            max_new_tokens=300,
            temperature=0.0
        )

    decoded = tokenizer.decode(
        output[0][input_ids.shape[-1]:],
        skip_special_tokens=True
    )

    return json.loads(decoded)

Step 6: Flag the Entire Transcript

results = []

for i, chunk in enumerate(chunks):
    result = run_safeguard(chunk)
    result["chunk_id"] = i
    results.append(result)
for r in results:
    print(r)

Step 7: Aggregate Results

def aggregate(results):
    highest = "none"
    order = ["none", "low", "medium", "high"]

    flags = set()

    for r in results:
        flags.update(r["flags"])
        if order.index(r["severity"]) > order.index(highest):
            highest = r["severity"]

    return {
        "overall_severity": highest,
        "flags": sorted(flags),
        "requires_review": highest in ("medium", "high")
    }
summary = aggregate(results)
print(summary)

Why This Uses the 20B Model

  • Largest model most people can actually run

  • Strong enough for screening and triage

  • Ideal for first-pass moderation

  • Scales up cleanly to 120B later if infrastructure allows


Limitations (Important)

  • Whisper transcription errors can propagate

  • Safeguard models are conservative

  • False positives happen

  • This is not a replacement for human review

Treat this as signal generation, not judgment.


When to Use This Pipeline

✔ Internal moderation
✔ Media review
✔ Compliance pre-screening
✔ Research & auditing
✔ Privacy-sensitive environments


Final Takeaway

This pipeline demonstrates how to build a fully open, self-hosted safety system:

  • MIT-licensed transcription

  • Apache-licensed safeguard model

  • Reproducible on realistic hardware

  • No vendor lock-in


Comments

Popular posts from this blog

Top 5 Free Hosting Providers in 2025

Top 5 Free WHMCS Alternatives for 2025 (Open-Source & Zero-Cost Options)

How Do You Get a Free Domain in 2025? You Can’t — Here’s Why (and What You Can Get Instead)