被动交互示例 · iOS

被动交互示例 · iOS

源码位置:agentsdk_for_ios/Examples/

8 个被动示例按输入/输出复杂度分为三类。每个 Example/main.swift 都是自包含程序; 只有 register* 调用和 onRequest 签名不同。其余——SDK 创建、鉴权注册、共享基类回调、初始化——完全一致。

共享骨架(每个示例都用到)

swift
import Foundation
import LinksoulAgentSDK

let url = "wss://open.agibot.com/api/V1/open-portal/app/wss/agent-sdk"
let appId = "<灵心开放平台应用 appId>"
let appKey = "<灵心开放平台应用 appKey>"
let appSecret = "<灵心开放平台应用 appSecret>"

// Step 1: 创建 SDK 实例(每个 appId 单例)
let agentSdk = AgentSdk.create(url: url, appId: appId, appKey: appKey, appSecret: appSecret)

// Step 2: 注册鉴权回调
final class AuthCallback: AgentAuthCallback {
    func onAuthState(appId: String, code: Int, msg: String) {
        print("onAuthState => code=\(code), msg=\(msg)")
    }
}
agentSdk.registerAuth(AuthCallback())

// Step 3: 注册业务回调(见下方每个示例)
agentSdk.registerXxx(XxxCallback(agentSdk: agentSdk))

// Step 4: 初始化(建立 WebSocket,自动重连)
agentSdk.initialize()

RunLoop.main.run()

共享基类回调

每个 *Callback 继承 PassiveCallback,可重载以下方法(有安全的空实现默认值):

swift
override func onRobotOnline(agentId: String, agentMeta: AgentMeta) {
    // 机器人上线——初始化 per-robot 资源
}

override func onRobotOffline(agentId: String) {
    // 机器人下线——释放 per-robot 资源
}

override func onFaceInfo(agentId: String, eventId: String, param: AgentParam) {
    // 人脸/声纹 UID 检测
}

override func onVideoFrame(agentId: String, eventId: String, flag: Int, buf: Data?,
                           isKeyFrame: Bool, localTs: Int64, param: AgentParam) {
    // 视频透传:flag=2 H264,flag=3 图片
}

override func onGreetSignal(agentId: String, eventId: String, param: AgentParam, response: GreetResponse) {
    // 打招呼信令;通过 response.onGreetVlm*/onGreetTts* 应答
}

override func onState(agentId: String, eventId: String, stateName: String, stateValue: String) {
    // 机器人状态推送
}

1. 纯音频

Audio2LlmExample — 音频进 → ASR + LLM 文本出

swift
final class Audio2LlmCb: Audio2LlmCallback {
    /**
     * - flag: 0 VAD 开始,1 音频块,2 VAD 结束
     * - audio: 音频字节(flag == 1 时有效)
     */
    override func onRequest(agentId: String, eventId: String, flag: Int, audio: Data?,
                            param: AgentParam, response: Audio2LlmResponse) {

        let agentMeta = agentSdk.getAgentMeta(agentId: agentId)
        let wakeup = agentMeta?.wakeupWord

        if flag == 0 {
            // init ASR 客户端
        } else if flag == 1 {
            // feed 音频块给 ASR
        } else if flag == 2 {
            // VAD 结束
        }
        response.onAsrMiddle(eventId: eventId, middleAsr: "今天")
        response.onAsrFinal(eventId: eventId, finalAsr: "今天天气怎么样")

        // LLM 流式
        let itemId = "itemId_000001"
        response.onInterrupt(eventId: eventId, interruptType: "chat", interruptTips: nil)

        let skillParam = AgentParam.create().setInt("step", 3)
        response.onSkill(eventId: eventId, itemId: itemId,
                         skillType: "movement", skillName: "move_forward", skillParam: skillParam)

        response.onLlmItemDelta(eventId: eventId, itemId: itemId, textDelta: "the LLM streaming text")
        response.onLlmItemDone(eventId: eventId, itemId: itemId)
        response.onLlmDone(eventId: eventId)

        // response.onError(eventId: eventId, errorCode: 1010, errorMsg: "ASR call failed")
    }
}
agentSdk.registerAudio2Llm(Audio2LlmCb(agentSdk: agentSdk))

Audio2TtsExample — 音频进 → ASR + LLM + TTS 全链路

swift
final class Audio2TtsCb: Audio2TtsCallback {
    override func onRequest(agentId: String, eventId: String, itemId: String, flag: Int,
                            audio: Data?, param: AgentParam, response: Audio2TtsResponse) {

        // 1) ASR
        if flag == 0 {
            // init ASR
        } else if flag == 1 {
            // feed 音频
        } else if flag == 2 {
            // VAD 结束
        }
        response.onAsrMiddle(eventId: eventId, middleAsr: "今天")
        response.onAsrFinal(eventId: eventId, finalAsr: "今天天气怎么样")

        // 2) LLM 流式
        response.onLlmItemDelta(eventId: eventId, itemId: itemId, textDelta: "the LLM streaming text")
        response.onLlmItemDone(eventId: eventId, itemId: itemId)
        response.onLlmDone(eventId: eventId)

        // 3) 打断 + 技能
        response.onInterrupt(eventId: eventId, interruptType: "chat", interruptTips: nil)
        response.onSkill(eventId: eventId, itemId: itemId,
                         skillType: "movement", skillName: "move_forward",
                         skillParam: AgentParam.create().setInt("step", 3))

        // 4) TTS 流式
        let ttsAudio = Data([0x01, 0x02])   // 合成音频字节
        response.onTtsItemDelta(eventId: eventId, itemId: itemId, audio: ttsAudio)
        response.onTtsItemDone(eventId: eventId, itemId: itemId)
        response.onTtsDone(eventId: eventId)
    }
}
agentSdk.registerAudio2Tts(Audio2TtsCb(agentSdk: agentSdk))

2. 纯文本

Asr2LlmExample — ASR 文本进 → LLM 出

swift
final class Asr2LlmCb: Asr2LlmCallback {
    override func onRequest(agentId: String, eventId: String, text: String,
                            param: AgentParam, response: Asr2LlmResponse) {

        response.onInterrupt(eventId: eventId, interruptType: "chat", interruptTips: nil)

        let itemId = "itemId_000001"
        response.onSkill(eventId: eventId, itemId: itemId,
                         skillType: "movement", skillName: "move_forward",
                         skillParam: AgentParam.create().setInt("step", 3))

        response.onLlmItemDelta(eventId: eventId, itemId: itemId, textDelta: "the LLM streaming text")
        response.onLlmItemDone(eventId: eventId, itemId: itemId)
        response.onLlmDone(eventId: eventId)
    }
}
agentSdk.registerAsr2Llm(Asr2LlmCb(agentSdk: agentSdk))

Asr2TtsExample — ASR 文本进 → LLM + TTS 出

swift
final class Asr2TtsCb: Asr2TtsCallback {
    override func onRequest(agentId: String, eventId: String, text: String,
                            param: AgentParam, response: Asr2TtsResponse) {

        let itemId = "itemId_000001"

        response.onLlmItemDelta(eventId: eventId, itemId: itemId, textDelta: "the LLM streaming text")
        response.onLlmItemDone(eventId: eventId, itemId: itemId)
        response.onLlmDone(eventId: eventId)

        response.onInterrupt(eventId: eventId, interruptType: "chat", interruptTips: nil)
        response.onSkill(eventId: eventId, itemId: itemId,
                         skillType: "movement", skillName: "move_forward",
                         skillParam: AgentParam.create().setInt("step", 3))

        let ttsAudio = Data([0x01, 0x02])
        response.onTtsItemDelta(eventId: eventId, itemId: itemId, audio: ttsAudio)
        response.onTtsItemDone(eventId: eventId, itemId: itemId)
        response.onTtsDone(eventId: eventId)
    }
}
agentSdk.registerAsr2Tts(Asr2TtsCb(agentSdk: agentSdk))

3. 多模态(文本/音频 + 视频)

AsrVideo2VlmExample — ASR 文本 + 视频 → VLM 出

swift
final class AsrVideo2VlmCb: AsrVideo2VlmCallback {
    /**
     * - flag: 3 ASR 文本,4 H264,5 图片
     * - text: flag == 3 时有效
     * - buf:  flag == 4 或 5 时有效
     */
    override func onRequest(agentId: String, eventId: String, flag: Int, text: String?,
                            buf: Data?, param: AgentParam, response: AsrVideo2VlmResponse) {

        let itemId = "itemId_000001"
        response.onInterrupt(eventId: eventId, interruptType: "chat", interruptTips: nil)
        response.onSkill(eventId: eventId, itemId: itemId,
                         skillType: "movement", skillName: "move_forward",
                         skillParam: AgentParam.create().setInt("step", 3))

        response.onVlmItemDelta(eventId: eventId, itemId: itemId, textDelta: "the VLM streaming text")
        response.onVlmItemDone(eventId: eventId, itemId: itemId)
        response.onVlmDone(eventId: eventId)
    }
}
agentSdk.registerAsrVideo2Vlm(AsrVideo2VlmCb(agentSdk: agentSdk))

AsrVideo2TtsExample — ASR 文本 + 视频 → VLM + TTS 出

swift
final class AsrVideo2TtsCb: AsrVideo2TtsCallback {
    override func onRequest(agentId: String, eventId: String, flag: Int, text: String?,
                            buf: Data?, param: AgentParam, response: AsrVideo2TtsResponse) {

        let itemId = "itemId_000001"

        // 1) VLM
        response.onVlmItemDelta(eventId: eventId, itemId: itemId, textDelta: "the VLM streaming text")
        response.onVlmItemDone(eventId: eventId, itemId: itemId)
        response.onVlmDone(eventId: eventId)

        // 2) 打断 + 技能
        response.onInterrupt(eventId: eventId, interruptType: "chat", interruptTips: nil)
        response.onSkill(eventId: eventId, itemId: itemId,
                         skillType: "movement", skillName: "move_forward",
                         skillParam: AgentParam.create().setInt("step", 3))

        // 3) TTS
        response.onTtsItemDelta(eventId: eventId, itemId: itemId, audio: Data([0x01, 0x02]))
        response.onTtsItemDone(eventId: eventId, itemId: itemId)
        response.onTtsDone(eventId: eventId)
    }
}
agentSdk.registerAsrVideo2Tts(AsrVideo2TtsCb(agentSdk: agentSdk))

AudioVideo2VlmExample — 音频 + 视频 → ASR + VLM 出

swift
final class AudioVideo2VlmCb: AudioVideo2VlmCallback {
    /**
     * - flag: 0 VAD 开始,1 音频块,2 VAD 结束,4 H264 视频帧,5 图片
     * - buf:  音频字节(flag=1)或 H264 字节(flag=4)
     */
    override func onRequest(agentId: String, eventId: String, flag: Int, buf: Data?,
                            param: AgentParam, response: AudioVideo2VlmResponse) {

        if flag == 0 {
            // init ASR
        } else if flag == 1 {
            // feed 音频
        } else if flag == 2 {
            // VAD 结束
        }
        response.onAsrMiddle(eventId: eventId, middleAsr: "今天")
        response.onAsrFinal(eventId: eventId, finalAsr: "今天天气怎么样")

        let itemId = "itemId_000001"
        response.onInterrupt(eventId: eventId, interruptType: "chat", interruptTips: nil)
        response.onSkill(eventId: eventId, itemId: itemId,
                         skillType: "movement", skillName: "move_forward",
                         skillParam: AgentParam.create().setInt("step", 3))

        response.onVlmItemDelta(eventId: eventId, itemId: itemId, textDelta: "the VLM streaming text")
        response.onVlmItemDone(eventId: eventId, itemId: itemId)
        response.onVlmDone(eventId: eventId)
    }
}
agentSdk.registerAudioVideo2Vlm(AudioVideo2VlmCb(agentSdk: agentSdk))

AudioVideo2TtsExample — 音频 + 视频 → ASR + VLM + TTS 出

swift
final class AudioVideo2TtsCb: AudioVideo2TtsCallback {
    override func onRequest(agentId: String, eventId: String, flag: Int, buf: Data?,
                            param: AgentParam, response: AudioVideo2TtsResponse) {

        if flag == 0 {
            // init ASR
        } else if flag == 1 {
            // feed 音频
        } else if flag == 2 {
            // VAD 结束
        }
        response.onAsrMiddle(eventId: eventId, middleAsr: "今天")
        response.onAsrFinal(eventId: eventId, finalAsr: "今天天气怎么样")

        let itemId = "itemId_000001"
        response.onInterrupt(eventId: eventId, interruptType: "chat", interruptTips: nil)
        response.onSkill(eventId: eventId, itemId: itemId,
                         skillType: "movement", skillName: "move_forward",
                         skillParam: AgentParam.create().setInt("step", 3))

        response.onVlmItemDelta(eventId: eventId, itemId: itemId, textDelta: "the VLM streaming text")
        response.onVlmItemDone(eventId: eventId, itemId: itemId)
        response.onVlmDone(eventId: eventId)

        response.onTtsItemDelta(eventId: eventId, itemId: itemId, audio: Data([0x01, 0x02]))
        response.onTtsItemDone(eventId: eventId, itemId: itemId)
        response.onTtsDone(eventId: eventId)
    }
}
agentSdk.registerAudioVideo2Tts(AudioVideo2TtsCb(agentSdk: agentSdk))

跨切面关注点

技能下发

swift
let skillParam = AgentParam.create()
    .setInt("step", 3)
    .setDouble("speed", 0.5)
    .setString("target", "kitchen")

response.onSkill(eventId: eventId, itemId: itemId,
                 skillType: "movement", skillName: "walk_forward", skillParam: skillParam)

错误处理

swift
do {
    // 业务逻辑...
} catch {
    response.onError(eventId: eventId, errorCode: 5999, errorMsg: "unknown error")
}

Function Call 分发与 Arbiter 仲裁(v1.5.0 新增)

v1.5.0 在被动链路上开放 Function Call 分发与 Arbiter 仲裁。8 种被动回调均可重载 onFunctionCall。

swift
override func onFunctionCall(agentId: String, eventId: String,
                             functionCall: FunctionCallInfo, response: Audio2TtsResponse) {
    if functionCall.policy == "body" {
        // 本体技能 → 委派默认云管线
        response.onArbiter(eventId: eventId, decision: ArbiterDecision.delegate.decision, arbParam: nil)
    } else if functionCall.policy == "cloud" {
        // 云侧技能 → 二开应用覆盖接管
        response.onArbiter(eventId: eventId, decision: ArbiterDecision.override.decision, arbParam: nil)
    }
}

FunctionCallInfo 字段:source / policy / type / value / param。决策枚举 ArbiterDecision:.delegate / .override / .illegal。详见 被动指南 · Function Call 开放与 Arbiter 仲裁。

对话历史(onHistories,v1.5.0 新增)

swift
override func onHistories(agentId: String, eventId: String, histories: [HistoryInfo]) {
    let sorted = histories.sorted { $0.timestamp < $1.timestamp }
    for h in sorted {
        print("history => ts \(h.timestamp), query \(h.query ?? ""), answer \(h.answer ?? ""), complete \(h.complete)")
    }
}

HistoryInfo 字段:timestamp / query / answer / complete / eventId / skill(HistoryInfo.SkillInfo,含 type / value)。

选哪个示例?

输入 → 输出推荐示例
音频 → 文本Audio2LlmExample
音频 → 语音Audio2TtsExample
文本 → 文本Asr2LlmExample
文本 → 语音Asr2TtsExample
文本 + 视频 → 文本AsrVideo2VlmExample
文本 + 视频 → 语音AsrVideo2TtsExample
音频 + 视频 → 文本AudioVideo2VlmExample
音频 + 视频 → 语音AudioVideo2TtsExample