被动交互开发指南

被动交互开发指南

← 返回首页

被动交互是 SDK 最核心的功能:机器人发起请求(音频/视频/文本)→ SDK 回调处理 → 通过 Response 返回结果

8 种被动回调类型

根据输入输出组合选择合适的回调类型。每个 SDK 实例只能注册一种被动回调。

纯音频类

回调类输入输出典型场景
Audio2LlmCallback音频流ASR + LLM 文本语音对话,文本回复
Audio2TtsCallback音频流ASR + LLM + TTS 音频语音对话,语音回复

纯文本类

回调类输入输出典型场景
Asr2LlmCallbackASR 文本LLM 文本文本对话
Asr2TtsCallbackASR 文本LLM + TTS 音频文本转语音回复

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

回调类输入输出典型场景
AsrVideo2VlmCallbackASR 文本 + 视频VLM 文本看图理解
AsrVideo2TtsCallbackASR 文本 + 视频VLM + TTS 音频看图语音回复
AudioVideo2VlmCallback音频 + 视频ASR + VLM 文本全模态理解
AudioVideo2TtsCallback音频 + 视频ASR + VLM + TTS全模态语音回复

v1.4.0Audio2Asr / Audio2Audio / AudioVideo2Audio / Text2Tts 四种回调及其 Response 类已从 SDK 中删除。

Flag 标志位详解

flag 是区分同一回调中不同数据类型的关键参数。

音频类回调 (Audio2*, AudioVideo2*)

flag含义buf 内容触发时机
0VAD 开始None用户开始说话
1音频帧PCM 音频数据(bytes持续传输中
2VAD 结束None用户停止说话
3H264 视频帧H264 字节(AudioVideo2* 才出现)视频流传输

ASR+视频类回调 (AsrVideo2*)

flag含义有效参数触发时机
3ASR 文本text 有效,buf=NoneASR 识别完成
4H264 视频帧buf 有效,text=None视频流传输
5图片帧buf 有效,text=None图片传输

关键理解AsrVideo2* 类的 on_request 会被多次调用(分别传入文本和视频),需要根据 flag 判断本次传入的是什么数据。

开发模板

python
from linksoul_agentsdk import AgentMeta, AgentParam, AgentSdk
from linksoul_agentsdk.passive import Audio2TtsCallback, Audio2TtsResponse


class MyCallback(Audio2TtsCallback):

    def on_robot_online(self, agent_id: str, agent_meta: AgentMeta) -> None:
        # 机器人上线,初始化该机器人的资源
        print(f"on_robot_online => {agent_id}, wakeup={agent_meta.get_wakeup_word()}")

    def on_robot_offline(self, agent_id: str) -> None:
        # 机器人下线,清理该机器人相关的资源
        print(f"on_robot_offline => {agent_id}")

    def on_request(
        self,
        agent_id: str,
        event_id: str,
        item_id: str,
        flag: int,
        buf: bytes | None,
        param: AgentParam,
        response: Audio2TtsResponse,
    ) -> None:
        if flag == 0:
            # VAD 开始:可以初始化 ASR 会话
            init_asr_session(agent_id, event_id)
        elif flag == 1:
            # 音频帧:发送到 ASR 服务进行流式识别
            feed_asr(agent_id, event_id, buf)
        elif flag == 2:
            # VAD 结束:完成 ASR,调用 LLM,合成 TTS
            asr_text = finalize_asr(agent_id, event_id)
            response.on_asr_final(event_id, asr_text)

            # 有效指令必须先打断
            response.on_interrupt(event_id, "chat", None)

            # 调用 LLM 获取回复
            llm_reply = call_llm(asr_text)
            response.on_llm_item_delta(event_id, item_id, llm_reply)
            response.on_llm_item_done(event_id, item_id)
            response.on_llm_done(event_id)

            # 合成 TTS 音频
            tts_audio = synthesize_tts(llm_reply)
            response.on_tts_item_delta(event_id, item_id, tts_audio)
            response.on_tts_item_done(event_id, item_id)
            response.on_tts_done(event_id)


agent_sdk = AgentSdk.create(url, app_id, app_key, app_secret)
agent_sdk.register_audio2_tts(MyCallback(agent_sdk))
agent_sdk.initialize()

Response 方法一览

所有 Response 类都继承自 AgentSdkResponse,根据回调类型不同,可用的方法组合不同:

方法说明参数
on_asr_middle(event_id, text)ASR 中间结果(流式)text: 当前已识别文本
on_asr_final(event_id, text)ASR 最终结果text: 完整识别文本
on_llm_item_delta(event_id, item_id, text)LLM 流式输出text: 本次增量文本
on_llm_item_done(event_id, item_id)LLM 单段完成
on_llm_done(event_id)LLM 全部完成
on_vlm_item_delta(event_id, item_id, text)VLM 流式输出text: 本次增量文本
on_vlm_item_done(event_id, item_id)VLM 单段完成
on_vlm_done(event_id)VLM 全部完成
on_tts_item_delta(event_id, item_id, audio)TTS 音频片段audio: bytes
on_tts_item_done(event_id, item_id)TTS 单段完成
on_tts_done(event_id)TTS 全部完成
on_skill(event_id, item_id, skill_type, skill_name, param)下发技能指令param: AgentParam,可下发的技能全集见 语义技能参考
on_interrupt(event_id, type, tips)打断当前对话type 取值:risk_control / action / chat / realtimetipsrisk_control 必填,详见 打断
on_error(event_id, error_code, error_msg)返回错误所有 Response 均支持

各 Response 可用方法矩阵

Response 类ASRLLMVLMTTSSkillInterrupt
Audio2LlmResponse
Audio2TtsResponse
Asr2LlmResponse
Asr2TtsResponse
AsrVideo2VlmResponse
AsrVideo2TtsResponse
AudioVideo2VlmResponse
AudioVideo2TtsResponse

标准响应流程

text
on_request(flag=0) → 初始化
on_request(flag=1) → 累积音频 → ASR 流式识别
                                └→ on_asr_middle()(可选,中间结果)
on_request(flag=2) → ASR 完成 → on_asr_final()
                              → 语义理解分类
                                ├─ 拒识 → return(不调用任何 response.on_*)
                                └─ 有效指令
                                     ① on_interrupt(...)(必须先于 ②)
                                     ② 按需 on_skill / on_llm_* / on_vlm_* / on_tts_*

技能返回时机

触发场景: 拿到一段 ASR 文本后,业务侧做语义理解将其分类, 如果分类结果属于"技能"(如动作、移动、表情等),就通过 response.on_skill(...) 把技能下发给机器人执行。 否则只走普通的 LLM/VLM 文本回复链路,不调用 on_skill

ASR 文本的来源分两种:

回调类型文本来源何时分类
Asr2Llm / Asr2Tts / AsrVideo2Vlm / AsrVideo2Tts上游(机器人侧)已完成 ASR,直接通过 on_request(... text ...) 把 final 文本送进来收到 text 即可分类
Audio2Llm / Audio2Tts / AudioVideo2Vlm / AudioVideo2TtsSDK 侧从 flag=1 的音频帧累积,在 flag=2(VAD 结束)时收尾出 final 文本拿到 final 文本(通常在 flag=2 分支调用 response.on_asr_final 之后)再分类

典型流程(以 Asr2Llm 为例):

text
on_request(text="今天天气怎么样")
      │
      ▼
语义理解分类
      ├─ 拒识(无效查询 / 误唤醒 / 噪音等)
      │    └→ 直接 return,不调用任何 response.on_* 方法
      │
      └─ 有效指令
           ① 必须先 response.on_interrupt(event_id, type, tips)
           │    (type 见下文「打断」小节:风控 / 技能 / 有效闲聊 / realtime)
           │
           ② 再按命中结果回填:
           ├─ 命中技能 → response.on_skill(event_id, item_id, ...)
           ├─ 命中有效闲聊 → response.on_llm_item_delta / on_llm_item_done / on_llm_done
           ├─ 命中多模态 → response.on_vlm_item_delta / on_vlm_item_done / on_vlm_done
           └─ TTS 应答(如适用) → response.on_tts_item_delta / on_tts_item_done / on_tts_done

⚠️ 顺序强约束:一旦判定为有效指令必须先调用 on_interrupt, 之后才能调用 on_skill / on_llm_* / on_vlm_* / on_tts_*。 调换顺序会导致机器人不能正确进入新一轮应答状态。 唯一例外是拒识——拒识时一个都不调用,让机器人继续保持当前状态。

示例

python
def on_request(self, agent_id, event_id, text, param, response):
    nlu = classify(text)

    # ① 拒识:直接 return
    if nlu.is_rejected():
        return

    item_id = IdGenerator.generate_item_id()

    # ② 有效指令必须先打断
    if nlu.is_risk_control():
        # 风控:interrupt_tips 必填
        response.on_interrupt(
            event_id, "risk_control",
            "你谈到敏感话题了,咱们换个话题聊吧",
        )
        return  # 风控场景下后续通常不再返回 LLM/技能
    elif nlu.is_skill():
        response.on_interrupt(event_id, "action", None)   # 技能打断
    else:
        response.on_interrupt(event_id, "chat", None)     # 有效闲聊打断
        # 若是 realtime 对话:response.on_interrupt(event_id, "realtime", None)

    # ③ 命中技能:下发 skill_param(key/value 见《语义技能参考》)
    if nlu.is_skill():
        skill_param = (
            AgentParam.create()
            .set_string("movement", "move_forward")
            .set_integer("meter", 10)
        )
        response.on_skill(event_id, item_id, "movement", "move_forward", skill_param)

    # ④ LLM 流式回复
    response.on_llm_item_delta(event_id, item_id, "好的,我开始向前走。")
    response.on_llm_item_done(event_id, item_id)
    response.on_llm_done(event_id)

skill_type / skill_nameskill_param 的取值见 语义技能参考。 同一 event_id 下可多次调用 on_skill(多意图场景),每个意图建议用独立 item_id 标识。

打断 (on_interrupt)

response.on_interrupt(event_id, interrupt_type, interrupt_tips) 用于在新的应答开始前 打断机器人当前正在进行的播报、动作或对话——再开始新的 TTS / 技能 / 对话内容。

interrupt_type 取值

取值语义场景interrupt_tips
risk_control风控打断内容违规、敏感话题等,必须立即终止当前对话必填,例如 "你谈到敏感话题了,咱们换个话题聊吧"
action技能打断语义理解命中技能 / 动作,准备执行新动作前打断当前动作或播报None
chat有效闲聊打断语义理解命中普通对话(有效闲聊),开始新一轮 LLM/VLM 文本回复时打断旧回复None
realtimerealtime 打断实时(边听边说)对话场景下,新一段实时回复打断旧的None

interrupt_tips 是给用户的文案提示。仅 risk_control 必须填写——风控打断时机器人会用这句提示反馈给用户; 其他三种类型按约定固定传 None

调用顺序(必须遵守)

⚠️ 任何"有效指令"的应答都必须按下面的顺序进行,否则机器人无法正确进入新一轮应答:

  1. response.on_interrupt(event_id, type, tips)
  2. response.on_skill(...) 与 / 或 response.on_llm_item_* / on_vlm_item_* / on_tts_item_*

按语义理解分类结果选择 interrupt_type

语义理解结果第一步调用之后允许的应答
命中风控on_interrupt(..., "risk_control", "<给用户的话术>")一般不再返回 LLM/VLM/技能(按业务定)
命中技能on_interrupt(..., "action", None)on_skill(...)(可并行 on_llm_* / on_vlm_* / on_tts_*
命中有效闲聊on_interrupt(..., "chat", None)on_llm_item_* / on_vlm_item_* / on_tts_item_*
realtime 对话on_interrupt(..., "realtime", None)realtime 应答内容
拒识不调用 on_interrupt一个都不调用——保持机器人当前状态

示例

python
# 普通对话回复
response.on_interrupt(event_id, "chat", None)
response.on_llm_item_delta(event_id, item_id, "好的,我来回答你的问题。")

# 命中动作技能
response.on_interrupt(event_id, "action", None)
skill_param = AgentParam.create().set_string("action", "wave_hands")
response.on_skill(event_id, item_id, "action", "wave_hands", skill_param)

# 实时对话边听边说
response.on_interrupt(event_id, "realtime", None)
response.on_llm_item_delta(event_id, item_id, "嗯嗯,我听到了……")

# 风控命中(interrupt_tips 必填,作为给用户的话术)
response.on_interrupt(event_id, "risk_control", "你谈到敏感话题了,咱们换个话题聊吧")

基类共享回调(v1.4.0+)

PassiveCallback 基类定义了一组与具体语义模式无关的"通用回调",所有 8 种回调类(Audio2TtsCallbackAsr2LlmCallback 等)均自动继承,按需重载即可:

方法触发时机说明
on_robot_online(agent_id, agent_meta)智能体上线agent_id 是灵心平台配置的智能体 ID;agent_meta 含端侧设备元数据(唤醒词、城市等)
on_robot_offline(agent_id)智能体下线清理该 agent_id 关联的资源
on_face_info(agent_id, event_id, param)识别到人脸 / 声纹 UIDparam 内含人脸或声纹特征字段
on_video_frame(agent_id, event_id, flag, buf, is_key_frame, local_ts, param)透传视频流flag=2 H264,flag=3 图片
on_greet_signal(agent_id, event_id, param, response)打招呼信令通过 GreetResponse 应答 VLM 或 TTS
on_state(agent_id, event_id, state_name, state_value)机器人状态变化state_value 可能为 JSON 字符串;所有 state_name 模块定义见 机器人端侧状态参考

打招呼应答 (GreetResponse)

on_greet_signal 收到信令后,可在 GreetResponse 上以两种模式应答(不互斥,按需选择):

python
def on_greet_signal(self, agent_id, event_id, param, response):
    # 模式一:VLM 文本流式应答
    response.on_greet_vlm_delta(event_id, "你好")
    response.on_greet_vlm_delta(event_id, ",今天天气真不错")
    response.on_greet_vlm_done(event_id)

    # 模式二:TTS 音频流式应答(参数是合成好的音频字节经 base64 编码后的字符串片段)
    import base64
    audio_chunk1 = synthesize_tts("你好")              # PCM / Opus 等 bytes
    audio_chunk2 = synthesize_tts(",欢迎回家")
    response.on_greet_tts_delta(event_id, base64.b64encode(audio_chunk1).decode())
    response.on_greet_tts_delta(event_id, base64.b64encode(audio_chunk2).decode())
    response.on_greet_tts_done(event_id)
方法说明
on_greet_vlm_delta(event_id, text)VLM 流式片段
on_greet_vlm_done(event_id)VLM 流式输出结束
on_greet_tts_delta(event_id, audio_base64)TTS 音频流式片段——audio_base64 是合成音频字节经 base64 编码后的字符串
on_greet_tts_done(event_id)TTS 音频输出结束
on_error(event_id, code, msg)应答异常

重要注意事项

  1. 回调是单例:所有上线的智能体共享同一回调实例,用 agent_id(灵心平台配置的智能体 ID)区分
  2. 线程模型:每个 agent_id 绑定一个独立 worker,同一智能体的请求按顺序执行
  3. event_id 隔离:不同对话轮次通过 event_id 隔离,非线程安全资源应基于 event_id 管理
  4. Response 线程安全response.on_*() 方法可在任意线程调用(内部 threading.Lock 串行化)
  5. Response 超时清理:Response 对象在 120 秒无更新后由 AgentSdkResponseMgr 自动清理
  6. 先注册后初始化:必须先 register_*()initialize(),顺序不能反
  7. 基类回调按需重载on_greet_signal / on_state / on_face_info / on_video_framePassiveCallback 中已有默认日志实现,不重载也能正常运行