超视距遥操(Teleop)指南

超视距遥操(Teleop)指南

⚠️ 尚未上线:本能力为 v1.5.0-SNAPSHOT 开发中特性,接口与协议可能调整,正式发布前请勿用于生产环境。

v1.5.0 新增能力。Teleop = 超视距遥操(远端操作员在线接管机器人的语音交互,不受本地视距限制)。它是继「被动交互 / 主动交互」之后的第三条交互路径,走独立的事件族与独立的策略枚举 AgentPolicy.TELEOP。Python 端接口与 Java 版同版本 API 完全等价,仅遵循 PEP8 的 snake_case 命名。

使用场景

  • 机器人在现场无人守卫或光线 / 网络不佳的位置,需要远端操作员对特定请求进行接管应答;
  • 二开应用需要旁路 LLM,直接由人工语音接管一段窗口(例如安抚客诉、精确核对下单信息);
  • 操作员的音频通过 SDK 上行到网关,网关按 agent_id 下发到目标机器人执行。

调用流程

严格顺序:enter → 任意次 start_audio / append_audio_delta / done_audio → exit。 遥操存续期间可任意频次调用 keepalive 探活(推荐每 10 秒一次,见下文 机器人在线检测(keepalive))。

text
              enter                        exit
   ┌───────────────────────┐       ┌────────────────────┐
   │  TeleopRequest.enter  │       │  TeleopRequest.exit │
   │      (on_enter_ack)   │       │      (on_exit_ack)   │
   └───────────┬───────────┘       └────────────┬────────┘
               │  任意次 (fire-and-forget)         │
               ▼                                 │
   ┌───────────────────────┐                     │
   │  start_audio(param)   │                     │
   │  append_audio_delta(*)│─────────────────────┘
   │  done_audio           │
   └───────────────────────┘

   ┌─────────────────────────────┐
   │ TeleopRequest.keepalive     │ ← 建议每 10 秒一次;code != 0 视为机器人离线
   │   (on_keepalive_ack)        │
   └─────────────────────────────┘
  • enter / keepalive / exit 都有 ACK 回调;相应 on_*_ack 收到 code == 0 视为成功;
  • start_audio / append_audio_delta / done_audio 是 fire-and-forget,本地不排队等 ACK;
  • 若发送瞬间 SDK 连接已关闭,enter / keepalive / exit 回调会在原线程同步以 code == 1000 + msg == "Agentsdk connection is closed" 触发一次失败回执。

事件族与协议

Python 端事件类型枚举位于 linksoul_agentsdk.enums.AgentEventType:

枚举成员事件类型字符串说明
AGENTSDK_TELEOP_ENTERagentsdk.teleop.enter进入遥操
AGENTSDK_TELEOP_ENTER_ACKagentsdk.teleop.enter_ack进入回执
AGENTSDK_TELEOP_AUDIO_STARTagentsdk.teleop_audio.start音频段开始(可携带 param)
AGENTSDK_TELEOP_AUDIO_DELTAagentsdk.teleop_audio.delta音频增量(audio 字段为 base64 字符串,可选 gain 为音频增益倍数,默认 1.0)
AGENTSDK_TELEOP_AUDIO_DONEagentsdk.teleop_audio.done音频段结束
AGENTSDK_TELEOP_KEEPALIVEagentsdk.teleop.keepalive机器人在线探活(SDK → 网关)
AGENTSDK_TELEOP_KEEPALIVE_ACKagentsdk.teleop.keepalive_ack探活回执(网关 → SDK)
AGENTSDK_TELEOP_EXITagentsdk.teleop.exit退出遥操
AGENTSDK_TELEOP_EXIT_ACKagentsdk.teleop.exit_ack退出回执

所有 Teleop 消息 wire 结构:

json
{
  "type": "<event type>",
  "agentId": "<目标机器人 agentId>",
  "teleopId": "teleop_xxxxxxxxxxxxxxxxxxxxx",
  "eventId": "event_xxxxxxxxxxxxxxxxxxxxx",
  "agentMode": "teleop"
}

teleopId 由 IdGenerator.generate_teleop_id() 生成;agentMode 恒为 "teleop"(对应 AgentPolicy.TELEOP)。

start_audio 参数

start_audio(param=None) 支持在每一段音频开始时携带一个 AgentParam,主要用于告诉服务端本段音频的音色 / VAD 灵敏度:

键类型取值示例说明
rolestr"male" / "female"遥操音色选择
thresholdint-14 ~ 14VAD 灵敏度阈值,数值越大越不易被截断

Python 端构造示例:

python
start_param = (
    AgentParam.create()
    .set_string("role", "male")
    .set_integer("threshold", 0)
)
event_id = teleop.start_audio(start_param)

wire 上会以 param.extParam.role / param.extParam.threshold 的形式下发(与 Java 版一致)。

机器人在线检测(keepalive)

遥操会话通常持续较长,期间机器人可能因网络中断 / 电量耗尽 / 现场断电等原因掉线。SDK 提供 TeleopRequest.keepalive(...) 一次性探活方法,业务侧应定时调用(建议 每 10 秒一次)判断机器人是否仍在线。

接口与回调

python
class TeleopKeepaliveCallback:
    """遥操机器人在线探活回调"""
    def on_keepalive_ack(self, teleop_id: str, event_id: str, code: int, msg: str) -> None:
        raise NotImplementedError

# TeleopRequest 方法
def keepalive(self, keepalive_callback: TeleopKeepaliveCallback, timeout: int) -> str: ...
  • timeout:本地占位超时(< 50 clamp 到 50,语义同 enter/exit);
  • 返回本次消息的 event_id;
  • 每次调用发送一条 agentsdk.teleop.keepalive,网关按 teleop_id 转发到目标机器人;机器人上线则回 success == true。

code 含义(与 enter / exit 一致)

code含义
0机器人在线且响应正常
-1服务端返回失败,msg 携带 errorMsg——表示机器人已离线,业务侧必须处理该异常
1000SDK 端 WebSocket 已关闭,本次未真正下发

定时探活 + 掉线自动退出

任何一次 code != 0,都应视为机器人已离线——立即停止定时任务、exit 退出遥操、unregister_teleop 释放资源,避免后续 append_audio_delta 继续下发无效音频。

python
import threading
from linksoul_agentsdk import TeleopKeepaliveCallback, TeleopExitCallback

def start_keepalive(teleop_request, agent_sdk, interval_sec: int = 10):
    """enter 成功后调用;每 interval_sec 秒探活一次"""
    state = {"online": True}

    def tick():
        if not state["online"]:
            return

        class _Cb(TeleopKeepaliveCallback):
            def on_keepalive_ack(self, teleop_id, event_id, code, msg):
                if code != 0 and state["online"]:
                    state["online"] = False
                    print(f"robot offline => code={code}, msg={msg}")

                    class _ExitCb(TeleopExitCallback):
                        def on_exit_ack(self, teleop_id, event_id, code, msg):
                            agent_sdk.unregister_teleop(teleop_id)
                    teleop_request.exit(_ExitCb(), 2000)

        teleop_request.keepalive(_Cb(), 2000)
        if state["online"]:
            threading.Timer(interval_sec, tick).start()

    threading.Timer(interval_sec, tick).start()
    return state  # 业务侧可通过 state["online"] = False 主动停止

调用 exit 后应主动设置 state["online"] = False,避免继续发送探活消息。

API 速览

  • 请求类:linksoul_agentsdk.TeleopRequest
  • 回调基类:linksoul_agentsdk.TeleopEnterCallback / TeleopKeepaliveCallback / TeleopExitCallback
  • 注册:AgentSdk.register_teleop(TeleopRequest) / AgentSdk.unregister_teleop(teleop_id)
  • ID:IdGenerator.generate_teleop_id()

完整签名见 Teleop API。可运行示例见 Teleop 示例。

生命周期与清理

  • AgentSdkTeleopMgr 按 teleop_id 维护活跃 request;调用 enter 前必须先 register_teleop(...);
  • 每次 request 方法调用会刷新内部 update_ts;超过 7200s 未活动的 request 会被后台清理线程回收;
  • 调用完 exit 或不再需要遥操时,建议调用 agent_sdk.unregister_teleop(teleop_id) 显式回收;
  • AgentSdk.release() 会连带清理该实例名下的所有 Teleop request(按 _linksky_client 引用批量移除)。

与 Java SDK 对齐

Python 版接口命名遵循 PEP8(snake_case),语义完全对齐 Java:

JavaPython
TeleopRequest.enter(param, cb, timeout)TeleopRequest.enter(param, cb, timeout)
TeleopRequest.startAudio(param)TeleopRequest.start_audio(param=None)
TeleopRequest.appendAudioDelta(eventId, base64)TeleopRequest.append_audio_delta(event_id, audio_delta)
TeleopRequest.doneAudio(eventId)TeleopRequest.done_audio(event_id)
TeleopRequest.keepalive(cb, timeout)TeleopRequest.keepalive(cb, timeout)
TeleopRequest.exit(cb, timeout)TeleopRequest.exit(cb, timeout)
TeleopEnterCallback.onEnterAck(...)TeleopEnterCallback.on_enter_ack(...)
TeleopKeepaliveCallback.onKeepaliveAck(...)TeleopKeepaliveCallback.on_keepalive_ack(...)
TeleopExitCallback.onExitAck(...)TeleopExitCallback.on_exit_ack(...)
AgentSdk.registerTeleop(...)AgentSdk.register_teleop(...)
AgentSdk.unregisterTeleop(...)AgentSdk.unregister_teleop(...)
IdGenerator.generateTeleopId()IdGenerator.generate_teleop_id()