Skip to main content
Ein Transkript sind keine Notizen. Es ist das Meeting noch einmal, nur dauert das Lesen länger, als es zu erleben gedauert hat. Was Menschen hinterher tatsächlich wollen, ist kurz: Was haben wir entschieden, wer hat sich zu was verpflichtet, und was ist noch offen. Dieses Tutorial baut genau das und verknüpft jeden Eintrag mit der Sekunde, in der er gesagt wurde, damit du dir den Teil anhören kannst, dem du nicht zustimmst:
Dabei werden wir:
  1. Eine Aufnahme mit /audio/transcriptions transkribieren
  2. Nach Zeitangaben fragen, die nicht jedes Modell liefert
  3. Entscheidungen und Action Items gegen ein Schema extrahieren
  4. Damit umgehen, dass das Transkript nie sagt, wer spricht
  5. Eine lange Aufnahme aufteilen, ohne die Uhr zu verlieren

Setup

Du brauchst Python 3.9 oder neuer, das requests-Paket und einen Venice-API-Schlüssel. Siehe API-Schlüssel erzeugen, falls du noch keinen hast. Bring irgendeine Aufnahme eines Gesprächs mit, in wav, mp3, m4a, flac, aac, mp4, ogg oder webm.

1. Die Aufnahme transkribieren

/audio/transcriptions ist OpenAI-kompatibel und nimmt einen Multipart-Upload entgegen. Die Datei muss ein echter File-Part sein, denn base64 wird an diesem Endpunkt nicht akzeptiert.
Die Transkription wird nach Länge des Audios abgerechnet, nicht danach, wie viel darin gesagt wurde, was die Kosten eines Meetings vor der Ausführung leicht abschätzbar macht: Ruf GET /models?type=asr für die aktuelle Liste auf, statt diese hier festzupinnen, denn der Katalog ändert sich.

2. Nach Zeitangaben fragen

Zeitstempel sind das, was Notizen überprüfbar macht, also ist das die Entscheidung, auf die es am meisten ankommt — und der Standard liefert sie dir nicht:
nvidia/parakeet-tdt-0.6b-v3 ist das Standardmodell, und es akzeptiert timestamps=true und ignoriert es dann. Es gibt keinen Fehler und keine Warnung, nur eine Antwort mit nichts als text darin. Wenn du Zeitangaben brauchst, frag ein Modell an, das sie zurückgibt, und prüf, dass der Schlüssel da ist.
Wenn ein Modell Zeitangaben zurückgibt, ist timestamps ein Objekt statt einer Liste, und der Schlüssel darin hängt vom Modell ab. Whisper gruppiert nach Phrasen, Scribe nach Wörtern:
Segmente auf Phrasenebene haben die richtige Größe für diese Aufgabe. Worttiming ist für Untertitel nützlich und zu fein, um daran eine Entscheidung aufzuhängen. Wir werden diese Segmente in Zeilen mit einer Zeit vor jeder abflachen, was alles ist, was das Modell braucht, um sie später zu zitieren:

3. Die Notizen extrahieren

Beschreib die Notizen, die du willst, als Schema, damit das Ergebnis ein Datensatz ist statt Prosa, die du parsen musst:
disable_thinking ist aus demselben Grund da, aus dem es in jeden Extraktionsschritt gehört. Das Schema entscheidet bereits die Form der Antwort, also bringt es nichts, ein Reasoning-Modell dafür zu bezahlen, darüber nachzudenken, und macht die Kosten jedes Laufs vom letzten verschieden. Strukturierte Daten aus Dokumenten extrahieren misst diesen Unterschied. Lass es auf einem 53-sekündigen Standup laufen, und die Notizen kommen mit angehängter Uhr zurück:
Jedes spoken_at ist echt. Spring zu 19,6 Sekunden und du hörst den Satz, der die Aufgabe erzeugt hat.
Gib dem Modell Zeilen ohne Zeiten, und jedes spoken_at kommt als 0 zurück. Das Feld ist erforderlich, das Modell hat nichts, was es hineinschreiben könnte, und ein erforderliches Feld ist eine Aufforderung, etwas zu produzieren, statt einer Einladung zu sagen, dass es nichts weiß. Das ist immer dann eine Erinnerung wert, wenn ein Schema zu funktionieren scheint: dass die Form stimmt, ist nicht dasselbe wie dass die Werte stimmen.

4. Niemand ist markiert

Zwei Dinge in dieser Ausgabe sind falsch, und beide kommen vom selben Ort. Die Verantwortliche für den Rollout heißt May. Ihr Name ist Mei. Spracherkennung ist bei Eigennamen am unzuverlässigsten, und Namen sind genau das, was für die Zuordnung gebraucht wird, also ist das der Fehler, den du erwarten solltest, nicht der unglückliche. Der letzte Eintrag ist unassigned, obwohl ihn jemand offenkundig übernommen hat. Der Satz war „I’ll ask legal today and report back tomorrow”, und das Transkript hält die Wörter fest, ohne festzuhalten, wer sie gesagt hat. Das Zweite ist kein Bug, den du beheben kannst. Kein Venice-Transkriptionsmodell führt Diarisierung durch, es gibt also kein speaker-Feld, nach dem du bei irgendeinem greifen könntest. Das Transkript ist ein stimmenloser Textstrom, und Aufgaben können nur zugeordnet werden, wenn ein Name laut gesprochen wird, wie in „Tomas, can you put the deprecation notice in the changelog”. Das Erste kannst du beheben, indem du dem Modell sagst, wer im Raum war:
May wird zu Mei Lin aufgelöst, weil das Modell jetzt eine kurze Liste hat, gegen die es abgleichen kann, und die Verantwortlichen tragen vollständige Namen, die dein Task-Tracker nachschlagen kann. Der dritte Eintrag bleibt korrekterweise unassigniert. Eine Teilnehmerliste behebt Verhören, und nichts stellt Information wieder her, die die Aufnahme nie getragen hat.
Wenn du echte Sprecherzuordnung brauchst, erfasse sie stromaufwärts, statt sie stromabwärts abzuleiten. Konferenz-Tools können eine Spur pro Teilnehmer aufzeichnen, und wenn du jede Spur separat transkribierst, bekommst du Sprecher gratis — zum Preis einer Anfrage pro Person.

5. Länger als eine Anfrage

Uploads sind auf 25 MB begrenzt, was bei unkomprimiertem Audio schneller erreicht ist, als man denkt, und ein langes Meeting ist es ohnehin wert, aufgeteilt zu werden, damit ein Fehlschlag dich nicht die ganze Transkription kostet. Für WAV-Dateien reicht die Standardbibliothek aus, ohne ffmpeg:
Der Offset ist der ganze Sinn. Jeder Chunk wird transkribiert, als würde er bei null beginnen, also müssen seine Zeitangaben zurück in die Zeitlinie der Originalaufnahme verschoben werden, bevor das Modell sie sieht. Genau dafür ist das Argument offset in timed_lines da:
Teil dasselbe Standup in Zwanzig-Sekunden-Stücke, und die Uhr bleibt über die Nahtstellen hinweg ehrlich. Die Wörter tun das nicht:
Ein Satz wurde dort gesprochen: „Tomas, can you put the deprecation notice in the changelog by Friday?” Der Schnitt landete mittendrin, sodass der Name in die eine Anfrage kam und die Aufforderung in die andere. Whisper hörte den verwaisten Namen als Frage, erfand ein awesome., um die Lücke am Ende des Chunks zu füllen, und machte aus einer Zeile drei. Die Zeiten sind noch richtig, und der Notizschritt findet die Aufgabe weiterhin. Was verloren geht, ist der Name — und von dem hängt die Zuordnung ab.
Aufteilen nach fester Dauer schneidet an jeder Grenze jemanden mitten im Satz ab. Zwanzig Sekunden sind kurz genug, um fast jedes Mal einen Satz zu treffen; zehn Minuten machen es selten, aber nicht unmöglich, und irgendwann landet es auf genau dem einen Satz, der die Arbeit zuweist. Aufteilen bei Stille umgeht das Problem sauber und braucht ein Tool, das die Lücken finden kann, etwa ffmpeg oder pydub. Teile nur auf, wenn die Datei es tatsächlich verlangt.
Komprimierte Formate lassen sich so nicht zerschneiden, denn ein MP3 kannst du mit der Standardbibliothek nicht an einer Frame-Grenze schneiden. Nutz dafür ffmpeg:

Alles zusammen

Nächste Schritte

  • Poste die Action Items in deinem Tracker, mit den Namen der Verantwortlichen, die die Teilnehmerliste aufgelöst hat.
  • Lies die Zusammenfassung mit Text-to-Speech vor, für Leute, die den Call verpasst haben.
  • Such über vergangene Meetings hinweg, indem du Transkripte mit Embeddings speicherst.
  • Lass einen Agenten entscheiden, wann er transkribiert und wann er aus Notizen antwortet, die er schon hat, mit Einen tool-nutzenden Agenten mit Function Calling bauen.

Speech-to-Text

Referenz für den Transkriptions-Endpunkt.

Strukturierte Daten aus Dokumenten extrahieren

Dieselbe schemazentrierte Extraktion, angewandt auf Dateien.

Strukturierte Antworten

Wie json_schema eine Completion einschränkt.

Voice Cloning

Gib der Zusammenfassung eine eigene Stimme.