Skip to main content

HarmonyOS SDK for real-time multimodal interaction

Download and integrate the HarmonyOS SDK for real-time multimodal interaction in Alibaba Cloud Model Studio, and learn its key APIs through code examples.

The MultiModalDialog SDK from the Alibaba Cloud Qwen team supports end-to-end, real-time multimodal audio and video interaction. It connects to Qwen models and backend agents to provide voice conversations, weather queries, music, news, and other capabilities, and supports video and images in model conversations.

Service architecture

Real-time multimodal interaction architecture

Prerequisites

  • To use real-time multimodal interaction in Alibaba Cloud Model Studio:
    • Enable a real-time multimodal interaction application and obtain a workspace ID, app ID, and API key.
    • Download the HarmonyOS SDK and demo, and configure the required environment and dependencies.
  • Import the sample code and follow the API call sequence to integrate the SDK.
  • To test the SDK, run the HAP test application in the entry module of the release project (multimodal-dialog-harmony-V1.4.9-V1.0.0) and enter the three parameters above.

SDK integration

The SDK communicates with the server over WebSocket and supports AudioOnly and AudioAndVideo modes:
  • Audio interaction: Supports audio and text conversations only.
  • Audio and video interaction: A command switches the conversation to video call mode. The client continuously sends video frames to the server for end-to-end multimodal audio and video interaction.

Interaction modes

The SDK supports Push2Talk, Tap2Talk, and Duplex (full-duplex):
  • Push2Talk: Press and hold to speak, and release to finish.
  • Tap2Talk: Tap to start speaking. The end of speech is detected automatically.
  • Duplex: Start speaking at any time after connection, with support for voice interruptions.

API usage

SDK dependencies

The HarmonyOS SDK is integrated as a HAR (Harmony Archive). The release project has the following SDK dependencies:
// Release project: entry/oh-package.json5
"dependencies": {
  "@ali/convsdk": "file:./libs/convsdk.har",             // Voice conversation SDK
  "@ali/mmsdk": "file:./libs/multimodal_sdk.har"         // Multimodal conversation SDK
}
The HarmonyOS SDK supports two links:
  • WebSocket (ChainMode.WEBSOCKET): Supports AudioOnly and AudioAndVideo.
  • RTC (ChainMode.RTC): An audio and video link for LiveAI video calls.

Key parameters

Connection and authentication parameters: Pass url, chainMode, workspaceId, appId, and dialogMode to the MultiModalDialog constructor. Pass apiKey to start or startTwo. These are not parameters of MultiModalRequestParam.
ParameterSubparameterTypeRequiredDescription
urlstringYesServer URL. For WebSocket, use wss://dashscope.aliyuncs.com/api-ws/v1/inference. For RTC audio and video, pass the RTC URL.
apiKeystringYesModel Studio API key for server authentication.
workspaceIdstringYesWorkspace ID in the Model Studio console.
appIdstringYesID of the application created in the Model Studio console.
dialogModeDialogModeYesInteraction mode: PUSH2TALK, TAP2TALK, or DUPLEX.
chainModeChainModeYesLink mode: WEBSOCKET or RTC.

Demo overview

The release project (multimodal-dialog-harmony-V1.4.9-V1.0.0) includes these pages:
  • ConfigPage: The entry page for configuring url, api_key, workspace_id, app_id, and other settings.
  • MultimodalSessionPage: A multimodal session page for real-time multimodal interaction and VQA (image-to-text). It supports VQA requests such as "take a photo to identify xxx":
    • After taking a photo, send its URL or base64 data (image ≤ 180 KB) to trigger recognition and conversation for a single image.
    • In audio and video mode, the demo accepts externally captured image sequences to facilitate capture and video conversation integration for IoT devices such as smart glasses.

API design

MultiModalDialog entry class
MultiModalDialog Initialize the dialog class with the required global parameters.
/**
  * Initialize the dialog class
  * @param url: Server URL
  * @param chainMode: Link mode
  * @param workspaceId: Workspace ID
  * @param appId: Application ID
  * @param dialogMode: Interaction mode
  */
constructor(url: string, chainMode: ChainMode, workspaceId: string, appId: string, dialogMode: DialogMode)
  1. setContext
Set the application context. HarmonyOS SDK initialization requires UIAbilityContext. Call this method before createConversation.
/**
 * Set the application context for NativeConversation resource copying and other operations.
 * Call this method before createConversation.
 * @param context Application context
 */
setContext(context: common.UIAbilityContext): void
  1. createConversation
Create a conversation.
/**
  * Create a conversation
  * @param requestParam Request parameters
  * @param chatCallback Conversation callback
  */
async createConversation(params: MultiModalRequestParam, chatCallback: IDialogCallback): Promise<void>
  1. start
Start a conversation. WebSocket connects directly. RTC requests authentication first and then joins the RTC channel.
/**
 * Connect and start the conversation
 * @param apiKey   API Key
 * @param dialogId Dialog ID; pass an empty string for a new conversation
 * @param taskId   Task ID
 */
start(apiKey: string, dialogId: string, taskId: string): void

// Convenience method without taskId (equivalent to start(apiKey, dialogId, ""))
startTwo(apiKey: string, dialogId: string): void
  1. stop
End the conversation.
/**
  * Disconnect and end the conversation
  */
stop(): void
  1. destroy
Destroy the dialog instance.
/**
  * Destroy the instance
  */
destroy(): void
  1. setDialogTimeout
Set the conversation timeout.
/**
  * Set the timeout; a timeout event is reported if no speech is detected within this period
  * @param timeout Timeout in milliseconds
  */
setDialogTimeout(timeout: number): void
  1. interrupt
Interrupt the AI's speech.
/**
  * Interrupt the AI's speech
  */
interrupt(): void
  1. startSpeech / stopSpeech / cancelSpeech
Notify the server to start, stop, or cancel audio uploads. Call these methods only in Push2Talk mode.
/**
 * Notify the server to start audio uploads. Call only in the Listening state.
 * Call only in Push2Talk mode.
 */
startSpeech(): void

/**
 * Notify the server to stop audio uploads.Call only in Push2Talk mode.
 * The user finishes speaking in Push2Talk mode.
 */
stopSpeech(): void

/**
 * The user cancels speech in Push2Talk mode
 */
cancelSpeech(): void
  1. sendAudioData
Upload audio.
/**
  * Upload audio.
  * @param audioData Audio frame data
  */
sendAudioData(audioData: Uint8Array | null): void
  1. sendRefData
Send reference audio for acoustic echo cancellation (AEC). Send the downstream TTS playback data as the AEC reference, which is required to cancel speaker echoes in full-duplex mode.
/**
 * Send reference audio for acoustic echo cancellation (AEC)
 * @param audioData Reference audio data (must match playback data)
 */
sendRefData(audioData: Uint8Array | null): void
  1. requestToRespond
Ask the server to answer a specified question or synthesize speech using TTS.
/**
 * Ask the server to answer a question or synthesize speech
 * @param type   transcript converts text to speech; prompt sends text to the model for an answer;
 *               visual_qa recognizes an image
 * @param text   Text; for visual_qa, an image URL or base64 data (image <= 180 KB)
 * @param params Additional parameters
 */
requestToRespond(type: string, text: string, params: Record<string, Object>): void
  1. sendVideoFrame
Push a video frame for a LiveAI video call.
/**
 * Push a video frame
 * @param videoFrame Video frame data
 * @param callback   Frame push result callback
 */
sendVideoFrame(videoFrame: TYVideoFrame, callback: IVideoChatPushFrameCallback): void
  1. sendResponseStarted / sendResponseEnded
Notify the start or end of a spoken response.
/**
 * Notify the start of a spoken response
 */
sendResponseStarted(): void

/**
 * Notify the end of a spoken response
 */
sendResponseEnded(): void
  1. Other APIs
/**
 * Whether the current configuration uses duplex mode
 */
isDuplexMode(): boolean

/**
 * Whether the current mode is Push2Talk (true only in Push2Talk)
 */
isPush2TalkMode(): boolean

/**
 * Whether the current mode is Tap2Talk (true only in Tap2Talk)
 */
isTap2TalkMode(): boolean

/**
 * Get the current conversation state
 */
getDialogState(): DialogState

/**
 * Get the current network quality
 */
getNetworkQuality(): number

/**
 * Resume interaction
 */
beginInteraction(): void

/**
 * Update conversation information
 * @param paramValue Updated parameters
 */
updateInfo(paramValue: Record<string, Object>): void

/**
 * Send a heartbeat
 */
sendHearBeat(): void
  1. Static configuration APIs (RTC audio and video capture switches)
/**
 * Whether to use RTC internal audio capture (default: true)
 */
static isRtcUseInternalAudio(): boolean
static setRtcUseInternalAudio(useInternal: boolean): void

/**
 * Whether to use RTC internal video capture (default: true)
 */
static isRtcUseInternalVideo(): boolean
static setRtcUseInternalVideo(useInternal: boolean): void
Key enumerations
ParameterValueDescription
ChainModeWEBSOCKETWebSocket link supporting AudioOnly and AudioAndVideo
RTCRTC audio and video link
DialogModePUSH2TALKStart and stop manually
TAP2TALKStart manually and detect the end of speech automatically
DUPLEXFull-duplex interaction
Request protocol fields: The following table describes the requests sent by the SDK to the server, not the methods of MultiModalRequestParam.builder(). Configure upstream, downstream, client information, and business parameters through upStream, downStream, clientInfo, and bizParams, respectively. Upstream and downstream protocol parameters without dedicated setters can be passed through passThroughParams on the corresponding object.
Field pathTypeRequiredDescription
header.actionstringYesRequest action, fixed to "run-task"
header.task_idstringYesTask ID in UUID format
header.streamingstringYesStreaming mode, fixed to duplex
payload.task_groupstringYesTask group, fixed to "aigc"
payload.taskstringYesTask name, fixed to "multimodal-generation"
payload.functionstringYesFunction, fixed to "generation"
payload.modelstringYesModel name, fixed to "multimodal-dialog"
payload.input.directivestringYesRequest directive
payload.input.workspace_idstringYesWorkspace ID
payload.input.app_idstringYesApplication ID
payload.input.dialog_idstringNoDialog ID
payload.parameters.upstreamobjectYesUpstream parameters
payload.parameters.downstreamobjectNoDownstream parameters
payload.parameters.client_infoobjectYesClient information
payload.parameters.biz_paramsobjectNoBusiness parameters
parameters.upstream:
ParameterTypeRequiredDescription
typestringYesUpstream type: AudioOnly for audio calls only, or AudioAndVideo for audio and video
modestringNoClient mode: push2talk, tap2talk, or duplex. Default: tap2talk.
audio_formatstringNoAudio format: pcm or raw-opus. Default: pcm.
sample_rateintNoASR sample rate: 8000, 16000, 24000, or 48000. Default: 16000.
vocabulary_idstringNoHotword vocabulary ID. Overrides the hotword configuration in the console.
languagestringNoASR language. Defaults to the console setting.
parameters.downstream:
ParameterTypeRequiredDescription
voicestringNoTTS voice. Available voices depend on the TTS model selected in the console.
sample_rateintNoTTS sample rate: 8000, 16000, 24000, or 48000. The SDK's DownStream default is 24000. Qwen TTS models support only 24000.
audio_formatstringNoAudio format: pcm, opus, or raw-opus. Default: pcm. Qwen TTS models support only pcm.
frame_sizeintNoTTS audio frame size: 10, 20, 40, 60, 100, or 120. Default: 60 ms. Applies only to opus or raw-opus.
volumeintNoTTS volume, from 0 to 100. Default: 50.
speech_rateintNoTTS speech rate, from 50 to 200. Default: 100.
pitch_rateintNoTTS pitch, from 50 to 200. Default: 100.
bit_rateintNoTTS bitrate, from 6 to 510 kbps. Default: 32. Applies only to mp3, opus, or raw-opus.
intermediate_textstringNoIntermediate text to return: transcript for the user's ASR result, or dialog for intermediate conversation responses. Separate multiple values with commas. Default: transcript.
word_timestamp_enabledbooleanNoWhether to return timestamps for synthesized speech. Default: false. Returned in RespondingContent.extra_info.
transmit_rate_limitintNoDownstream audio transmission rate limit, in bytes per second.
incremental_responsebooleanNoWhether to return incremental model output. Default: false, which returns the full output.
instructionstringNoInstructions to control synthesis effects such as dialect and emotion.
languagestringNoTTS language. Defaults to the console setting.
parameters.client_info:
ParameterSubparameterTypeRequiredDescription
user_idstringYesEnd-user ID generated according to your business rules. Up to 36 characters.
deviceuuidstringNoGlobally unique client ID, up to 40 characters. A user can have multiple devices with different uuid values and the same user_id.
networkipstringNoCaller's public IP address.
locationlatitudestringNoCaller's latitude.
longitudestringNoCaller's longitude.
city_namestringNoCaller's city.
parameters.biz_params:
ParameterSubparameterTypeRequiredDescription
user_defined_paramsjson objectNoParameters passed through to agent and MCP services. In the extra_config child node, set enable_web_search to control web search, and agent_timeout to set the agent connection timeout (10–120 seconds, default: 10).
user_prompt_paramsjson objectNoCustom prompt variables, with JSON keys and values defined by you.
user_query_paramsjson objectNoCustom conversation variables, with JSON keys and values defined by you.

IDialogCallback interface

The HarmonyOS conversation callback interface is IDialogCallback. Pass it to createConversation to receive conversation states and events.
/**
 * Conversation callback interface
 */
export interface IDialogCallback {
  /**
   * Conversation start result
   */
  onStartResult(isSuccess: boolean, errorInfo: TYError | null): void;

  /**
   * Interruption result
   */
  onInterruptResult(isSuccess: boolean, errorInfo: TYError | null): void;

  /**
   * Conversation ready after clients join the channel; business APIs may then be called
   * @param dialogId Dialog ID
   */
  onReadyToSpeech(dialogId: string): void;

  /**
   * State change callback: DIALOG_IDLE / DIALOG_LISTENING / DIALOG_THINKING / DIALOG_RESPONDING
   */
  onConvStateChangedCallback(state: DialogState): void;

  /**
   * Volume level callback, 0-100
   * @param audioLevel Volume level, 0-100
   * @param audioType  Volume source: MIC for local capture / PLAYER for remote TTS
   */
  onConvSoundLevelCallback(audioLevel: number, audioType: TYVolumeSourceType): void;

  /**
   * Conversation timeout callback
   * @param timeout Timeout in milliseconds
   */
  onSpeechTimeout(timeout: number): void;

  /**
   * Playback audio data callback
   */
  onPlaybackAudioData(bytes: Uint8Array): void;

  /**
   * Errors during the conversation
   */
  onErrorReceived(errorInfo: TYError): void;

  /**
   * Conversation event callback
   */
  onConvEventCallback(var1: ConvEvent | null): void;

  /**
   * Debug information callback
   */
  onDebugInfoTrack(level: number, type: TYDebugInfoType, debugInfo: string): void;

  /**
   * Downstream synthesized speech callback
   */
  onSynthesizedSpeech(bytes: Uint8Array): void;

  /**
   * Wake word detection callback
   */
  onKeyWordSpotted(word: string, type: KeyWordsType): void;
}
onConvEventCallback response details
  • EVENT_HUMAN_SPEAKING_DETAIL
The user's speech recognition result. Example:
{
  "header": { "request_id": "9B32878****************3D053", "status_code": 200 },
  "payload": {
    "output": {
      "event": "SpeechContent",
      "dialog_id": "b39398c9dd8147***35cdea81f7",
      "text": "一二三",
      "finished": false
    }
  }
}
  • EVENT_RESPONDING_DETAIL
Model response details. The AI response text is returned in payload.output.text. Example:
{
  "header": { "request_id": "9B32878****************3D053", "status_code": 200 },
  "payload": {
    "output": {
      "event": "AIResponse",
      "dialog_id": "b39398c9dd8147***35cdea81f7",
      "text": "好的,这是一个测试。",
      "finished": true
    }
  }
}

Error handling

onErrorReceived - response
Conversation errors are reported through the onErrorReceived callback:
/**
  * Conversation error
  * @param errorInfo: Error information
  */
onErrorReceived(errorInfo: TYError): void;

Common error codes

For troubleshooting HarmonyOS SDK errors, see Multimodal real-time interaction error codes.

Call sequences

Full-duplex interaction
HarmonyOS SDK full-duplex interaction sequence
Full-duplex (Duplex): Over WebSocket, the application captures microphone audio and continuously sends it through sendAudioData. Downstream synthesized audio is passed to the application for playback through onSynthesizedSpeech. Keep sending recorded audio during playback to support voice interruptions at any time.
Half-duplex interaction
HarmonyOS SDK half-duplex interaction sequence
Half-duplex (Push2Talk / Tap2Talk): In Push2Talk, press and hold to speak, and release to finish. In Tap2Talk, tap to start, and the server automatically detects the end of speech.

Additional SDK API usage

Duplex interaction

The HarmonyOS SDK supports Duplex full-duplex interaction. In WebSocket duplex mode, the application continuously captures and sends recorded audio while playing the AI's spoken response, allowing voice interruptions at any time.
  • For audio interaction, pass DialogMode.DUPLEX to the MultiModalDialog constructor to enable full-duplex mode.
  • Duplex interaction requires acoustic echo cancellation (AEC). The HarmonyOS SDK integrates internal AEC, which is disabled by default. Explicitly enable it by setting MultiModalDialog.wsUseInternalAEC = true. When internal AEC is disabled, external AEC is used. The application must send downstream TTS playback data through sendRefData as the echo reference.
// Enable duplex mode
let dialog = new MultiModalDialog(url, ChainMode.WEBSOCKET, workspaceId, appId, DialogMode.DUPLEX)
dialog.setContext(context)
MultiModalDialog.wsUseInternalAEC = true
let requestParam = MultiModalRequestParam.builder().build()
await dialog.createConversation(requestParam, this)
In duplex mode:
// Send microphone audio (16 kHz PCM for server recognition)
dialog.sendAudioData(audioData)

// In onSynthesizedSpeech, send downstream TTS data as the AEC reference
// bytes is the callback data; also pass it to the application player
dialog.sendRefData(bytes)

// The application player must use the downstream TTS sample rate

VQA interaction

VQA enables multimodal image-and-voice interaction by sending images during a conversation. A spoken request to take a photo triggers a "visual_qa" command. When the client receives this command through onConvEventCallback, it sends an image URL or base64 data (images smaller than 180 KB) to enable image understanding in the conversation.
// On receiving the "visual_qa" command
private handleVisualQaCommand() {
  // 1. Take a photo or select an image from the gallery
  let imageUrl = uploadImageToOSS()      // Generate a public image URL
  // 2. Send the image URL for image recognition
  dialog.requestToRespond("visual_qa", imageUrl, {})

  // 3. Alternatively, send base64 image data (image <= 180 KB)
  let imageBase64 = getLocalImageBase64()
  dialog.requestToRespond("visual_qa", imageBase64, {})
}

Call LiveAI over WebSocket

LiveAI (video calling) is the official agent for Model Studio multimodal real-time interaction. When calling LiveAI over WebSocket, the SDK continuously sends video frames using the AudioAndVideo upstream type.
// 1. Set the interaction type to AudioAndVideo
let dialog = new MultiModalDialog(url, ChainMode.WEBSOCKET, workspaceId, appId, DialogMode.DUPLEX);
// Set UpStream.type to AudioAndVideo
// UpStream defaults to AudioOnly; explicitly set AudioAndVideo for LiveAI video calls
let requestParam = MultiModalRequestParam.builder()
  .upStream(UpStream.builder().type('AudioAndVideo').build())
  .build()

// 2. After the conversation starts, send a video frame every 500 ms
// sendVideoFrame requires a frame push result callback (IVideoChatPushFrameCallback)
class VideoFramePushCb implements IVideoChatPushFrameCallback {
  onFinishPushFrame(isSuccess: boolean, errorMsg: string): void {
    //Handle the result, for example by logging and retrying on failure
  }
}
setInterval(() => {
  dialog.sendVideoFrame(getNextVideoFrame(), new VideoFramePushCb())   // LiveAI video frame
}, 500)
Note: In the current version, the external video frame push interfaces (sendVideoFrame / PushExternalVideoFrame) support only base64-encoded images for image interaction over WebSocket. Each image must be smaller than 180 KB. For real-time video calls, use the RTC link (ChainMode.RTC), where the SDK captures camera video and streams it using its built-in camera capture functionality.

Text-to-speech synthesis

TTS converts text into speech. The HarmonyOS SDK uses requestToRespond to request synthesized audio from the server.
// Request TTS synthesis in the Listening state
dialog.requestToRespond("transcript", "幸福是一种技能,是你摒弃了外在多余欲望后的内心平和。", {})

Custom prompt variables and values

  • Configure custom prompt variables in the project's Prompt settings in the console.
For example, define user_name for a user's nickname and insert it into the prompt as ${user_name}.
  • Set the values in code:
// Set custom prompt variables
let requestParam = MultiModalRequestParam.builder()
  .bizParams(BizParams.builder()
    .userPromptParams({ user_name: "大米" })
    .build())
  .build()

Real-time ASR correction

ASR results may contain errors or unexpected text during a conversation. In addition to configuring hotwords, you can upload a correction vocabulary through the real-time correction API. Parameters:
ParameterLevel 1Level 2TypeDescription
asrPostProcessingReplaceWord[]ASR correction vocabulary in the SDK upstream parameters
ReplaceWordObjectEach ReplaceWord defines a text replacement rule
sourceStringText to replace
targetStringReplacement text
matchModeStringMatching mode: exact for an exact sentence match, or partial for a partial match. Default: exact.
// Set the ASR correction vocabulary
let requestParam = MultiModalRequestParam.builder()
  .upStream(UpStream.builder()
    .asrPostProcessing([
      ReplaceWord.builder()
        .source("1加1")
        .target("一加一")
        .matchMode("partial")
        .build()
    ])
    .build())
  .build()
Agent Development
Agent Context
Agent Governance & Evolution
Store
Application Marketplace