Skip to main content

Industrial instruction transcription interaction protocol (WebSocket)

This topic describes how to transcribe industrial production instructions using WebSocket.

This topic describes the real-time industrial instruction transcription WebSocket API. The WebSocket protocol is the preferred connection method because it offers low latency and is resource-efficient. WebSocket is a network protocol that supports full-duplex communication. The client and server establish a persistent connection through a single handshake. Both parties can actively push data to each other. This provides significant advantages in real-time performance and efficiency. Before you begin, make sure that you understand the basic principles and technical details of WebSocket.

Prerequisites

Activate the service and obtain and configure an API key. To prevent security risks from code leaks, configure the API key as an environment variable instead of hard coding it in your code.
To grant temporary access permissions to third-party applications or users, or want to strictly control risky operations such as accessing or deleting sensitive data, you can use a temporary authentication token.Compared to a long-term API key, a temporary authentication token is valid for only a short period (60 seconds) and offers higher security. It is suitable for temporary call scenarios and can effectively reduce the risk of API key leaks.Usage: In your code, replace the API key used for authentication with the obtained temporary authentication token.

Sequence diagram

image
You must follow the sequence shown in the diagram when you make calls. Otherwise, the operation may fail.

Establish a WebSocketconnection

This corresponds to process 1 in the sequence diagram. For common programming languages, many ready-to-use WebSocket libraries and examples are available, such as:
  • Go: gorilla/websocket
  • PHP: Ratchet
  • Node.js: ws

Connection request headers

Authorization: Bearer {api-key} // Replace {api-key} with your actual API key.
Upgrade: websocket
Connection: Upgrade

WebSocketaccess URL

wss://dashscope.aliyuncs.com/api-ws/v1/inference

Send instructions to the server

You can send instructions to the server to start and stop transcription. There are two types of instructions: run-task and finish-task. Both must be sent as JSON data in a text frame. The protocol is as follows:

run-task instruction

This corresponds to process 2 in the sequence diagram. It notifies the server to start a transcription task. The protocol fields are as follows:

Field

Type

Description

header

Object

header.action

String

Set to run-task.

header.task_id

String

A custom 16-character random string used for troubleshooting. The subsequent finish-task must use the same task_id.

header.streaming

String

Set to duplex.

payload

Object

payload.model

String

Set to tingwu-industrial-instruction.

payload.task_group

String

Set to aigc.

payload.task

String

Set to multimodal-generation.

payload.function

String

Set to generation.

payload.input

Object

payload.input.appId

String

The application ID from the industrial instruction transcription console. You can obtain it from the console.

payload.input.directive

String

Set to start.

payload.parameters

Object

payload.parameters.sampleRate

Integer

The audio sample rate. Only 16000 is supported.

payload.parameters.format

String

The format of the audio to be recognized. Supported formats: pcm, wav, mp3, opus, speex, aac, and amr. For opus and speex formats, Ogg encapsulation is required. For the wav format, PCM encoding is required.

payload.parameters.maxEndSilence

Integer

The maximum duration of silence in milliseconds (ms). If silence longer than this duration is detected after text is generated, the sentence is considered complete. The value must be in the range of [0, 6000].

This parameter is optional. The default value is 1500.

payload.parameters.terminology

String

The ID of the transcription instruction set. You can obtain it from the console.

The following is an example of a run-task instruction:
{
  "header": {
    "action": "run-task",
    "task_id": "f2E3zvK*******wp",
    "streaming": "duplex"
  },
  "payload": {
    "model": "tingwu-industrial-instruction",
    "task_group": "aigc",
    "task": "multimodal-generation",
    "function": "generation",
    "input": {
      "appId": "tw_YrN*******Cw",
      "directive": "start"
    },
    "parameters": {
      "sampleRate": 16000,
      "format": "pcm",
      "maxEndSilence": 3000,
      "terminology": "Ddc*******Cf"
    }
  }
}

finish-task

This corresponds to process 5 in the sequence diagram. It notifies the server that all audio has been sent and the recording has ended. The protocol fields are as follows:

Field

Type

Description

header

Object

header.action

String

Set to finish-task.

header.task_id

String

Enter the task_id specified in the run-task command.

header.streaming

String

Set to duplex.

payload

Object

payload.model

String

Set to tingwu-industrial-instruction.

payload.task_group

String

Set to aigc.

payload.task

String

Set to multimodal-generation.

payload.function

String

Set to generation.

payload.input

Object

payload.input.directive

String

Set to stop.

The following is an example of a finish-task instruction:
{
  "header": {
    "action": "finish-task",
    "task_id": "f2E3zvK*******wp",
    "streaming": "duplex"
  },
  "payload": {
    "model": "tingwu-industrial-instruction",
    "task_group": "aigc",
    "task": "multimodal-generation",
    "function": "generation",
    "input": {
      "directive": "stop"
    }
  }
}

Send audio to the server

Convert the raw audio directly into a binary stream without extra processing. Note the following:
  • The sample rate of the uploaded audio for speech recognition must be 16000 Hz.
  • The audio coding format must be the same as the one specified in the run-task instruction.
  • Supported audio formats: pcm, wav, mp3, opus, speex, aac, and amr. For opus and speex formats, Ogg encapsulation is required. For the wav format, PCM encoding is required.

Receive events from the server

After you send an instruction or audio, the server sends different types of events. Each event represents a different processing stage. You must strictly follow the sequence diagram to handle these events. There are four types of events: speech-listen, recognize-result, ai-result, and speech-end.

speech-listen event

This corresponds to process 3 in the sequence diagram. The speech-listen event is returned after the run-task instruction. It indicates that the server has received your transcription instruction and completed the initialization. You can now start sending audio. The protocol fields are as follows:

Field

Type

Description

header

Object

header.event

String

Always result-generated.

header.task_id

String

The task_id that you specified in the run-task instruction.

payload

Object

payload.output

Object

payload.output.action

String

Always speech-listen.

payload.output.dataId

String

The ID of the current transcription task. You can use this ID to submit feedback. You can also use this ID to view the billing items for the task in your bill.

The following is an example of a speech-listen event:
{
  "header": {
    "event": "result-generated",
    "task_id": "f2E3zvK*******wp"
  },
  "payload": {
    "output": {
      "action": "speech-listen",
      "dataId": "Adb*******uY"
    }
  }
}

recognize-result event

This corresponds to process 4 in the sequence diagram. The recognize-result event is returned after you send audio. It may also be returned after you send the finish-task instruction. This event contains the text and translation results that the server has recognized so far. The protocol fields are as follows:

Field

Type

Description

header

Object

header.event

String

Always result-generated.

header.task_id

String

The task_id that you specified in the run-task instruction.

payload

Object

payload.output

Object

payload.output.action

String

Always recognize-result.

payload.output.transcription

Object

The original transcription result.

payload.output.transcription.sentenceId

Integer

The sentence sequence number.

payload.output.transcription.beginTime

Integer

The start time in the audio for the first character of the currently recognized part of the sentence, in ms.

payload.output.transcription.endTime

Integer

The end time in the audio for the last character of the currently recognized part of the sentence, in ms.

payload.output.transcription.sentenceEnd

Boolean

Indicates whether the current sentence is complete.

payload.output.transcription.text

String

The content of the currently recognized part of the sentence.

payload.output.transcription.words

List[Word]

Word segmentation information for the sentence.

payload.output.translations

Object

The translation result of the transcription.

payload.output.translations.sentenceEnd

Boolean

Indicates whether the current sentence is complete.

payload.output.translations.translations

Object

The result set for the target translation languages.

payload.output.translations.translations.zh

Object

Currently, only translation to Chinese is supported, so there will only be one zh object.

payload.output.translations.translations.zh.lang

String

Set the value to zh.

payload.output.translations.translations.zh.sentenceId

Integer

The sentence sequence number.

payload.output.translations.translations.zh.beginTime

Integer

The start time in the audio for the first character of the currently translated part of the sentence, in ms.

payload.output.translations.translations.zh.endTime

Integer

The end time in the audio for the last character of the currently translated part of the sentence, in ms.

payload.output.translations.translations.zh.sentenceEnd

Boolean

Indicates whether the current sentence is complete.

payload.output.translations.translations.zh.text

String

The content of the currently translated part of the sentence.

payload.output.translations.translations.zh.words

List[Word]

Word segmentation information for the translated sentence.

The Word type protocol is as follows:

Field

Type

Description

beginTime

Integer

The start time of the current word in the audio.

endTime

Integer

The end time of the current word in the audio.

text

String

The content of the current word.

The following is an example of a recognize-result event:
{
  "header": {
    "event":"result-generated",
    "task_id": "f2E3zvK*******wp"
  },
  "payload": {
    "output": {
      "action": "recognize-result",
      "transcription": {
        "sentenceId": 0,
        "beginTime": 100,
        "endTime": 2720,
        "sentenceEnd": true,
        "text": "This is a sentence for testing.",
        "words": [
          {
            "beginTime": 100,
            "endTime": 427,
            "text": "This"
          },
          {
            "beginTime": 427,
            "endTime": 755,
            "text": "is a"
          },
          {
            "beginTime": 755,
            "endTime": 1082,
            "text": "sentence"
          },
          {
            "beginTime": 1082,
            "endTime": 1410,
            "text": "used for"
          },
          {
            "beginTime": 1410,
            "endTime": 1737,
            "text": "testing"
          },
          {
            "beginTime": 1737,
            "endTime": 2065,
            "text": "of"
          },
          {
            "beginTime": 2065,
            "endTime": 2392,
            "text": "text"
          },
          {
            "beginTime": 2392,
            "endTime": 2720,
            "text": "."
          }
        ]
      },
      "translations": {
        "sentenceEnd": true,
        "translations": {
          "zh": {
            "sentenceId": 0,
            "beginTime": 100,
            "endTime": 2720,
            "text": "This is a sentence for testing.",
            "lang": "zh",
            "words": [
              {
                "beginTime": 100,
                "endTime": 427,
                "text": "This"
              },
              {
                "beginTime": 427,
                "endTime": 755,
                "text": "is a"
              },
              {
                "beginTime": 755,
                "endTime": 1082,
                "text": "sentence"
              },
              {
                "beginTime": 1082,
                "endTime": 1410,
                "text": "used for"
              },
              {
                "beginTime": 1410,
                "endTime": 1737,
                "text": "testing"
              },
              {
                "beginTime": 1737,
                "endTime": 2065,
                "text": "of"
              },
              {
                "beginTime": 2065,
                "endTime": 2392,
                "text": "text"
              },
              {
                "beginTime": 2392,
                "endTime": 2720,
                "text": "."
              }
            ],
            "sentenceEnd": true
          }
        }
      }
    }
  }
}

ai-result event

This corresponds to process 6 in the sequence diagram. The ai-result event is returned after the last recognize-result event. It represents the final transcription result, which has been corrected using the specified instruction set. The protocol fields are as follows:

Field

Type

Description

header

Object

header.event

String

Always result-generated.

header.task_id

String

The task_id that you specified in the run-task instruction.

payload

Object

payload.output

Object

payload.output.action

String

Always ai-result.

payload.output.aiResult

Object

payload.output.aiResult.correction

String

The final result of the industrial instruction transcription.

The following is an example of an ai-result event:
{
  "header": {
    "event": "result-generated",
    "task_id": "f2E3zvK*******wp"
  },
  "payload": {
    "output": {
      "action": "ai-result",
      "aiResult": {
        "correction": "Right fender paint slag SQE."
      }
    }
  }
}

speech-end event

This corresponds to process 7 in the sequence diagram. The speech-end event is sent after the ai-result event. It indicates that the industrial instruction transcription is complete. You can then close the WebSocket connection. The protocol fields are as follows:

Field

Type

Description

header

Object

header.event

String

Always result-generated.

header.task_id

String

The task_id that you specified in the run-task instruction.

payload

Object

payload.output

Object

payload.output.action

String

This is fixed to the end of the speech.

The following is an example of a speech-end event:
{
  "header": {
    "event": "result-generated",
    "task_id": "f2E3zvK*******wp"
  },
  "payload": {
    "output": {
      "action": "speech-end"
    }
  }
}

task-failed event

If a task fails during processing because of incorrect parameters from the client or an internal server error, the server returns a task-failed event and then closes the WebSocket connection. The protocol fields are as follows:

Field

Type

Description

header

Object

header.event

String

Always result-generated.

header.task_id

String

The task_id that you specified in the run-task instruction.

payload

Object

payload.output

Object

payload.output.action

String

The value is always set to task-failed.

payload.output.errorCode

String

Error code

payload.output.errorMessage

String

Error message

The following is an example of a task-failed event:
{
  "header": {
    "event": "result-generated",
    "task_id": "f2E3zvK*******wp"
  },
  "payload": {
    "output": {
      "action": "task-failed",
      "errorCode": "Error code",
      "errorMessage": "Error message"
    }
  }
}
For more information about specific error codes and their meanings, see Error codes.

WebSocketconnection reuse

This feature does not support connection reuse. You must create a new WebSocket connection for each task. The connection is automatically closed if no text or binary messages are sent for 15 s.

Code examples

package nls.alibaba;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Hex;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

import static java.lang.Thread.sleep;

public class TingwuIndustrialInstructionUsage extends WebSocketClient {

    private final byte[] audioData;

    private final String taskId;

    private final ObjectMapper objectMapper = new ObjectMapper();

    private boolean audioSent = false;

    private static final int CHUNK_SIZE = 3200;

    private static final long SEND_INTERVAL_MS = 100; // Simulate streaming send interval

    private static final String apiKey = "Replace with your API key";

    public TingwuIndustrialInstructionUsage(URI serverUri, String audioFilePath) throws IOException {
        super(serverUri, createHeaders());
        this.audioData = Files.readAllBytes(new File(audioFilePath).toPath());

        // Generate a random 16-character string
        SecureRandom random = new SecureRandom();
        byte[] taskIdBytes = new byte[8];
        random.nextBytes(taskIdBytes);
        this.taskId = Hex.encodeHexString(taskIdBytes);
    }

    private static Map<String, String> createHeaders() {
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "Bearer " + apiKey);
        headers.put("Upgrade", "websocket");
        headers.put("Connection", "Upgrade");
        return headers;
    }

    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        System.out.println("WebSocket connected.");
        System.out.println("Task ID: " + taskId);

        // Construct the run-task message
        String runTaskMessage = "{\n" +
                "  \"header\": {\n" +
                "    \"action\": \"run-task\",\n" +
                "    \"task_id\": \"" + taskId + "\",\n" +
                "    \"streaming\": \"duplex\"\n" +
                "  },\n" +
                "  \"payload\": {\n" +
                "    \"model\": \"tingwu-industrial-instruction\",\n" +
                "    \"task_group\": \"aigc\",\n" +
                "    \"task\": \"multimodal-generation\",\n" +
                "    \"function\": \"generation\",\n" +
                "    \"input\": {\n" +
                "      \"appId\": \"Replace with your application ID\",\n" +
                "      \"directive\": \"start\"\n" +
                "    },\n" +
                "    \"parameters\": {\n" +
                "      \"sampleRate\": 16000,\n" +
                "      \"format\": \"pcm\",\n" +
                "      \"maxEndSilence\": 1500,\n" +
                "      \"terminology\": \"Replace with your instruction set ID\"\n" +
                "    }\n" +
                "  }\n" +
                "}";

        System.out.println("Sending run-task: " + runTaskMessage);
        send(runTaskMessage);
    }

    @Override
    public void onMessage(String message) {
        System.out.println("Received text message: " + message);

        try {
            JsonNode root = objectMapper.readTree(message);

            JsonNode header = root.get("header");
            JsonNode payload = root.get("payload");

            if (header != null) {
                String event = header.path("event").asText("");
                // This message can be ignored
                if ("task-started".equals(event)) {
                    return;
                }

                String action = payload.path("output").path("action").asText("");
                switch (action) {
                    case "speech-listen":
                        System.out.println("Received speech-listen event, sending full audio...");
                        sendAudioFile();
                        break;
                    case "task-failed":
                        String errorCode = payload.path("output").path("errorCode").asText("N/A");
                        String errorMsg = payload.path("output").path("errorMessage").asText("N/A");
                        System.out.println("Task failed: " + errorCode + " - " + errorMsg);
                        close();
                        break;
                    case "recognize-result":
                        JsonNode output = payload.get("output");
                        System.out.println("Recognition result: " + (output != null ? output.toString() : "N/A"));
                        break;
                    case "ai-result":
                        JsonNode correction = payload.path("output").path("aiResult").path("correction");
                        System.out.println("AI correction result: " + correction.asText("N/A"));
                        break;
                    case "speech-end":
                        System.out.println("Speech ended. Closing...");
                        close();
                        break;
                    default:
                        System.out.println("Unknown message received: " + message);
                        break;
                }
            } else {
                System.out.println("Unknown message format (no header): " + message);
            }

        } catch (JsonProcessingException e) {
            System.err.println("Invalid JSON received: " + e.getMessage());
            System.out.println("Raw message: " + message);
        }
    }

    @Override
    public void onClose(int code, String reason, boolean b) {
        System.out.println("WebSocket closed. Code: " + code + ", Reason: " + reason);
    }

    @Override
    public void onError(Exception ex) {
        System.err.println("WebSocket error: " + ex.getMessage());
        ex.printStackTrace();
    }

    private void sendAudioFile() {
        if (audioSent) {
            System.out.println("Audio already sent, skipping...");
            return;
        }
        audioSent = true;

        int offset = 0;
        while (offset < audioData.length) {
            int len = Math.min(CHUNK_SIZE, audioData.length - offset);
            byte[] chunk = new byte[len];
            System.arraycopy(audioData, offset, chunk, 0, len);
            try {
                send(chunk);
                offset += len;
                System.out.println("Sent chunk: " + offset + "/" + audioData.length);
                sleep(100);
            } catch (Exception e) {
                System.err.println("Send failed at offset " + offset);
                e.printStackTrace();
                close();
                return;
            }
        }
        System.out.println("Audio sent completely.");
        sendFinishTask();

    }

    // Send the finish-task message
    private void sendFinishTask() {
        String finishTaskMessage = "{\n" +
                "  \"header\": {\n" +
                "    \"action\": \"finish-task\",\n" +
                "    \"task_id\": \"" + taskId + "\",\n" +
                "    \"streaming\": \"duplex\"\n" +
                "  },\n" +
                "  \"payload\": {\n" +
                "    \"model\": \"tingwu-industrial-instruction\",\n" +
                "    \"task_group\": \"aigc\",\n" +
                "    \"task\": \"multimodal-generation\",\n" +
                "    \"function\": \"generation\",\n" +
                "    \"input\": {\n" +
                "      \"directive\": \"stop\"\n" +
                "    }\n" +
                "  }\n" +
                "}";

        System.out.println("Sending finish-task: " + finishTaskMessage);
        send(finishTaskMessage);
    }

    public static void main(String[] args) {
        try {
            URI wsUri = new URI("wss://dashscope.aliyuncs.com/api-ws/v1/inference");
            String audioPath = "Replace with your audio file path";
            TingwuIndustrialInstructionUsage client = new TingwuIndustrialInstructionUsage(wsUri, audioPath);
            client.connect();
        } catch (Exception e) {
            System.err.println("Initialization error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Error codes

Error code

Error message

Description

InvalidParameter

Invalid parameter. For more information, see the official documentation.

The parameter is invalid. Check the parameters that you passed.

InvalidParameter

The value of maxEndSilence is invalid. It must be between 0 and 6000.

The value of the maxEndSilence parameter is invalid.

InvalidParameter

The term is not found.

The specified instruction set does not exist.

InvalidParameter

The sample rate is invalid.

The value of the sample rate parameter is invalid.

InvalidParameter

The audio format is invalid.

The value of the audio coding format parameter is invalid.

InvalidParameter

The term is invalid.

The specified instruction set ID is invalid.

Agent.FrameSequenceIllegal

The WebSocket frame sequence is invalid.

The instruction call sequence is invalid.

Agent.InputActionIllegal

The input action is invalid.

The value of the action field in the instruction is invalid.

Agent.InputAppIdIllegal

The input application ID is invalid.

The value of the application ID field is invalid.

Agent.AppNotPublished

The application has not been published.

The specified application has not been published.

Agent.CustomTaskIdInvalid

The custom task ID must be 16 characters long.

The length of the task ID is invalid.

BIL.ServiceNotActivate

The user has not activated the service.

You have not activated the Tingwu Agent service.

BIL.UserArrears

The user's account has an overdue payment.

Your account has an overdue payment.

Agent.AppInfoNotExist

The application information does not exist.

The specified application ID information does not exist. You must first save and publish the application configuration in the console.

ServerError

A server error occurred.

Internal server error.

If you encounter an error code that is not in this list, see Error messages for troubleshooting information.
Agent Development
Agent Context
Agent Governance & Evolution
Store
Application Marketplace