指南

泰语实时语音转文字:一轮发言在哪里结束

泰语不写句号,实时转写仍要判断说话人何时说完,由此引出轮次的定义、中间结果的用途和实时会话的计费方式。

· 阅读约 1 分钟

泰语的句子末尾没有标点。书面泰语只在短语和分句之间留空格,其余部分连写,说出口的话更是没有任何标点。处理录制完成的文件时,转写系统可以先读完整个文件,再做任何判断。实时转写则必须在对方还在说话时就下结论。

Paxa STT Lite Realtime 的做法是按轮次工作。WebSocket 连接持续接收音频,判断一段语音在哪里结束,然后为这一段发送一份完整的转写。下面依次说明轮次本身、泰语为何让这条边界难以判断、边界之前的中间结果有什么用,以及一次会话的费用。

什么是轮次#

一个轮次是同一位说话人连续的一段音频,起点是开口的那一刻,终点是停下的那一刻。连接会通知这两个时刻。在两者之间,连接发送中间结果,每一份都是同一轮次更完整的草稿。轮次结束后,连接发送一份最终结果,程序应当依据的就是这份最终结果。

调用方当然也可以逐词接收识别结果,自己判断一句话说到哪里算完。这个判断是实时流程的难点,在泰语里,它比识别本身更难。

turn 2
speech_started  turn 2
transcript      turn 2  interim  ผม
transcript      turn 2  interim  ผมโอนไปแล้ว
transcript      turn 2  interim  ผมโอนไปแล้วเมื่อเช้า
speech_ended    turn 2
transcript      turn 2  final    ผมโอนไปแล้วเมื่อเช้านี้ครับ

这一轮最后到达的词是 ครับ,它不包含任何关于这笔付款的信息,只表示说话人已经说完。如果智能体在第三份中间结果上就采取行动,它回应的是一句还没说完的话。

泰语为何让这条边界难以判断#

泰语口语有三个特点,与为英语编写的端点判断规则相冲突。

  • 没有句号。泰语句子说完了就是结束,书面上至多留一个空格。等待句末标点的规则会一直等下去。
  • 礼貌的话语以一个词收尾。ครับ 和 ค่ะ 会结束一轮发言,นะคะ 和 แล้วครับ 也一样。这些词出现在内容之后,也就是说,句子中承载信息的部分在说话人说完之前就已经完整了。
  • 停顿落在短语之间。泰语在短语之间留空格,说话人在句子中间也会在同样的位置停下。这个间隙离这句话真正的结尾还很远。

只靠静音判断边界的规则,正是被第三点打破的。切分会落在名词和它的量词之间,或者落在动词和它所支配的数量之间。切开后的每一半读起来都是残句,意思比整句少,下游模型回应的也只是它拿到的残句。

语码转换让同一个问题更加突出。泰语使用者会在一个分句内部切换到英语。英语词前后出现的任何间隙,都位于一句尚未说完的话中间。转写结果必须把两种文字保留在同一行,并把这个间隙算作该轮次的一部分。

中间结果有什么用#

中间结果是草稿。每一份都会替换前一份,随着更多音频到达,任何一份都可能改变。可以把它们显示在屏幕上,供观看字幕滚动的人阅读。任何会执行操作的环节都不应使用它们:依据中间结果完成的预订,依据的只是半句话。

最终结果是定稿。它包含轮次编号、文本,以及该轮次在音频中的位置。语音智能体在收到最终结果后作答。主管设置的关键词提醒也在最终结果到达时触发。实时字幕先显示中间结果,最后落定为最终结果。

说明

轮次边界也是停止说话的信号。连接一报告 speech_started,语音智能体就取消正在播放的回复。来电者正是这样打断助手,并让自己的话被听到。

实时会话如何计费#

计费依据连接收到的音频,静音也计入,每分钟 12.5 积分。连接在每个轮次处切分为计费时段,边进行边扣费,因此长时间的会话可以随时报告累计金额。模型未能转写的轮次不收费,报告的累计金额已经考虑了这一点。

在请求日志中,无论包含多少轮次,整个连接都只占一行。计费时段只是计算费用的方式;调用方请求的是会话本身。

已经录好的音频应当交给整段转写接口。Paxa STT Lite 接受最长 60 分钟的文件,一次返回完整转写,每分钟费用也更低,因为它不必跟上说话人的节奏。

打开 WebSocket 连接#

一个 start 帧指定模型和音频编码。此后,客户端上传二进制音频,服务端下发事件,连接在整个对话期间保持打开。

import { open } from "node:fs/promises";

// Runs on Bun as-is; on Node, `import WebSocket from "ws"` accepts
// the same constructor options. Browsers cannot send this header, and
// API keys never ship to browsers anyway: relay the audio through your
// server.
const socket = new WebSocket("wss://api.paxalabs.com/v1/stt/live", {
  headers: { Authorization: `Bearer ${process.env.PAXA_API_KEY}` },
});

socket.onopen = async () => {
  socket.send(JSON.stringify({
    type: "start",
    model: "paxa-stt-lite-realtime-v1-preview",
    audio: { encoding: "pcm_s16le", sample_rate: 16000 },
    language: "th",
  }));
  // 16 kHz signed 16-bit mono is 32,000 bytes a second. A microphone
  // delivers frames at this pace on its own; a file has to be paced.
  const file = await open("caller.pcm");
  const frame = new Uint8Array(640); // 20 ms
  for (;;) {
    const { bytesRead } = await file.read(frame, 0, frame.length);
    if (bytesRead === 0) break;
    socket.send(frame.subarray(0, bytesRead));
    await new Promise((resolve) => setTimeout(resolve, 20));
  }
  socket.send(JSON.stringify({ type: "end" }));
};

socket.onmessage = (event) => {
  const message = JSON.parse(String(event.data));
  switch (message.type) {
    case "speech_started":
      // The user is talking: stop the agent's audio, cancel its reply.
      break;
    case "transcript":
      if (message.is_final) console.log(`turn ${message.turn}: ${message.text}`);
      break;
    case "done":
      socket.close();
  }
};
Server events
{"type":"started","connection":"7d9c2b7e-4a41-4c8e-9a35-1f26d8c04b7a","model":"paxa-stt-lite-realtime-v1-preview"}
{"type":"speech_started","turn":1,"time":0.3}
{"type":"speech_ended","turn":1,"time":12.252}
{"type":"charged","seq":1,"seconds":12.252,"credits":2.56,"total_credits":2.56}
{"type":"transcript","turn":1,"is_final":true,"text":"สวัสดีค่ะคุณสมชายยอดชำระของท่านจำนวน พันสองร้อยห้าสิบ บาทครบกำหนดวันที่ สิบห้า สิงหาคมนี้ชำระผ่านแอปได้ตลอด ยี่สิบสี่ ชั่วโมงค่ะ","start":0.3,"end":12.252}
{"type":"done","total_seconds":12.252,"turns":1,"total_credits":2.56}

下一步#

  • 阅读实时接口参考,了解帧、事件和关闭码。
  • 阅读转写指南,了解 start 帧可以携带的选项。
  • 阅读实时转写应用场景,了解呼叫中心规模的业务量下一次会话的费用。
  • 打开转写工具并切换到实时模式。浏览器会转发麦克风的音频,每个轮次一结束,结果就会出现。

继续阅读

更多指南