被动交互示例 · 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 |