PawAI / Go2
我用 Unitree Go2、Jetson 與 ROS2 打造的多模態具身互動系統:從感知、語音與決策,到安全閘與可觀測工具。
我用 Unitree Go2、Jetson 與 ROS2 打造的多模態具身互動系統:從感知、語音與決策,到安全閘與可觀測工具。
PawAI 是我以 Unitree Go2 Pro 為載體,整合 NVIDIA Jetson、Intel RealSense D435、ROS2 與多模態 AI 所打造的具身互動機器狗。
我想做的不是把聊天機器人裝進機器狗,也不是把人臉、語音、手勢與物體辨識各自展示一次;我真正想解的問題是:機器人能不能把看見、聽見、理解、決策與行動串成一條可解釋、可降級,而且知道何時不該動的互動迴路?

這張成品照背後,是八個多月的硬體改裝、模型選型、真機事故與系統重構。
PawAI 的核心可以濃縮成四步:
PawAI 的設計底線:LLM 只能提案;production sport-motion 主線只能由 Executive 放行。音訊與 hard-stop 保留獨立路徑,而 hard-stop 仍需獨立審計與驗證。
這段影片會依序展示即時感知、語音互動、安全拒絕,以及 PawAI Studio 如何留下決策 Trace。它是 2026 年 6 月的系統快照,不代表下方列出的實驗能力都已達到產品等級。
完整 Demo:PawAI—基於多模態感知融合之自主尋物與具身互動。
一開始,我接觸的是 Go2 的 ROS2 與 WebRTC 驅動。讓機器狗走動、取得感測資料或呼叫一個動作並不算最難;真正困難的是,當人臉、物體、姿勢、語音、導航與 LLM 同時加入後,到底由誰決定現在該做什麼?如果兩個模組同時要求機器狗動作,誰有最後控制權?雲端服務中斷、感知誤判或 LLM 說錯話時,系統又該怎麼退回安全狀態?
這些問題讓 PawAI 從一組功能原型,逐步變成一個完整系統工程專案。開發重心也從「再加一個模型」,轉向三件更重要的事:
PawAI 啟動時,我仍是一個以 Web 與軟體開發為主、幾乎沒有機器人硬體整合經驗的學生。我們第一次挑戰機器人,就選了開發限制比 Go2 EDU 更多、主要依賴 WebRTC 與第三方 SDK 的 Go2 Pro。
我粗估自己在這個專案投入超過 500 小時。只要晚上、週末或課業空檔能用,我幾乎都在接線、重跑、看 log、錄 bag、改參數,或把前一天看似可行的方案整個推翻。
從 2025 年 11 月到公開版前,repository 橫跨 138 個有提交紀錄的日子,留下超過 1,300 次由我提交的 commit;後來的 lessons-learned 又整理出 70 多個具名 failure modes。這些數字不能換算成工時或品質,卻留下了密集迭代的痕跡。
2025 年 11 月的一個週日,我從午餐後一路做到凌晨一點。日記裡寫著:「做到凌晨一點,卻覺得工程師可能真的是我的天職。」
PawAI 是一個由十個主要 ROS2 套件組成的 workspace。感知、決策、行動與共享契約彼此分層,依賴方向朝向共用介面,而不是讓每個功能直接互相呼叫。

這張圖可以從下往上理解:Go2 Driver 與硬體負責「真的做事」;Executive 負責「允不允許做」;Brain 負責「現在最適合做什麼」;各個感知節點只負責把世界轉成事件。中間的 go2_interfaces 與 pawai_contracts 則像共同語言,規定事件、技能提案與執行結果應該長什麼樣子。
這個分層刻意避免兩種常見問題:第一,感知模組不能因為看到某個東西就直接命令機器狗;第二,新增一個模型時,不需要同時修改 Brain、Studio 與 Go2 Driver。每個 ROS2 node 可以獨立啟動、停止、測試與替換,整體系統仍透過固定契約協作。
PawAI 不是只寫「使用 AI」;每一條感知與語音路徑都有明確模型、執行位置、資源預算與 fallback。下面是 2026 年 6 月 Demo/公開版所採用的主線。
| 能力 | 實際模型與管線 | 執行位置 | 為什麼這樣選 | 目前邊界 |
|---|---|---|---|---|
| 人臉 | YuNet 2023mar 偵測 + SFace 2021dec embedding + IOU tracking/identity hysteresis | Jetson CPU | YuNet 單模型 benchmark 71.3 FPS,不搶 YOLO 的 GPU | 低光、距離與 tracking 抖動仍會影響身分判斷 |
| 手勢 | MediaPipe Gesture Recognizer + 自製幾何/時序規則 | Jetson CPU | 實測約 7.2 FPS、GPU 0%,能和其他模型共存 | 靜態手勢較可靠;動態 wave 尚未通過 baseline |
| 姿勢 | MediaPipe Pose + COCO 17 點 adapter + 幾何分類器 | Jetson CPU | 實測約 18.5 FPS,不需把 GPU 塞滿 | awkward viewpoint 可能產生假 landmark;跌倒不作可靠照護宣稱 |
| 物體與顏色 | YOLO26n ONNX + onnxruntime-gpu TensorRT EP FP16 + OpenCV HSV 12 色 | Jetson GPU + CPU | 約 15 FPS;YOLO 負責類別,HSV 負責輕量顏色語意 | 遠距小物與 cup/bottle 類別混淆仍是限制 |
| ASR | GPU Server SenseVoice → Jetson SenseVoice Local → Whisper Local | 遠端 GPU/Jetson | 雲端主線追求辨識品質,本地路徑保留降級能力 | 麥克風、VAD、回音與網路仍會影響端到端表現 |
| LLM | OpenRouter openai/gpt-5.4-mini → 條件式 google/gemini-3-flash-preview → RuleBrain | Cloud/Jetson rules | 主線負責自然語言與 skill proposal;規則層保底 | LLM 永遠沒有直接 actuation 權限 |
| TTS | 長句/情緒:google/gemini-3.1-flash-tts-preview → edge-tts → Piper;短句/安全:edge-tts → Piper | Cloud/Jetson CPU | 依品質、速度與離線可用性分流 | provider、音訊格式與 Go2 播放路徑都可能各自失敗 |
| 決策編排 | LangGraph Conversation Engine + Skill Registry + Executive | Jetson | 把 world state、persona、記憶、模型決策與 trace 拆成可觀測節點 | LangGraph 只產生候選;production motion 由 Executive 放行 |
這張表看起來像最後答案,但模型選型曾經三次翻案。手勢/姿勢從 RTMPose 的 GPU 滿載,走到 DWPose 的 TensorRT 精度偏移與 JetPack 6 部署困境,最後才用實測推翻「MediaPipe 在 Jetson 不可行」的早期判斷。
真正的選型條件從來不只是單模型準確率,而是:在 8GB unified memory 上,當人臉、姿勢、手勢、物體與語音一起跑時,它還能不能活著?
以使用者對 PawAI 說「介紹你自己」為例,整條路徑不是一次 LLM call,而是六個可觀測步驟:
/event/speech_intent_recognized 或 /brain/text_input。SkillPlan。SAY、MOTION 或 NAV step。因此,就算自然語言回覆可以播放,動作提案仍可能被拒絕。「有回答」與「有權移動機器狗」在 PawAI 裡是兩件分開的事。
在 PawAI 裡,Brain 不是「想做什麼就做什麼」的 Agent。它產生的是一份提案,真正執行前還要經過三層處理:

這個設計不是為了讓架構看起來比較複雜,而是因為機器人會真的移動。當模型產生錯誤內容、感知資料不足,或系統同時收到多個事件時,預設不動、拒絕越權,以及保留人工介入,比讓 Demo 看起來更聰明重要。
目前能公開確認的是:deterministic Safety Gate 與技能 allowlist 的機制存在,並有單元測試保護;這不等於 PawAI 已經不會幻覺,也不代表所有安全情境都完成了實機端到端驗證。
PawAI 使用 LangGraph 編排 Conversation Engine,但它負責的不是馬達控制,而是把原本塞在單一 LLM bridge 裡的責任拆成可觀測的決策節點。實作上共有 12 個 graph stages;下方把它們合併成九個較容易閱讀的概念階段。
早期版本的 llm_bridge_node 同時負責 ROS adapter、Persona、模型 fallback、對話歷史、環境資訊、JSON 解析與 TTS 輸出。程式一路成長到很難定位錯誤;有一次中文回答總是在逗號附近被截斷,最後才發現不是模型問題,而是 Jetson 的 stale install/ 還留著舊版截斷邏輯。這讓我決定把 conversation orchestration 獨立成 pawai_brain package。
LangGraph 把一輪對話拆成類似以下的 pipeline:
reply、可選的 skill 與參數。Conversation Engine 的輸出契約刻意很小:
{ "reply": "我是 PawAI,一隻正在學習理解環境的機器狗。", "skill": "self_introduce", "args": {}}reply 可以成為語音;skill 只是提案。像 show_status 這類低風險技能可以經 gate 後執行,而較長的動作序列可以只留下 trace,改由 Studio 按鈕在有人控制的情況下觸發。導航與高風險動作不會因為 LLM 提議就自動開放。
LangGraph 不是一次取代舊系統。開發期間,我用 feature flag 讓 legacy 與 LangGraph 分別擔任 primary 或 shadow:primary 是唯一能發布 /brain/chat_candidate 的 engine;shadow 收到相同輸入,但只輸出 trace 供比較。
這種遷移方式讓我可以先比較兩個 engine 的回覆、結構與錯誤,再決定是否切換主線。新架構失敗時,舊路徑仍能工作;而且 Brain 只依賴固定的 chat_candidate 契約,不需要知道候選結果來自哪一個 engine。
PawAI 並不是把全部工作都丟到雲端,也不是要求 Jetson 獨自完成所有推論。部署被拆成四個角色:
| 位置 | 主要工作 | 斷線時的行為 |
|---|---|---|
| Jetson Orin Nano | ROS2 runtime、感知、Brain、Executive、部分本地語音 | 保留本地感知、規則與安全路徑 |
| 開發機/操作筆電 | PawAI Studio、投影與人工操作 | Studio 中斷不會繞過或改變 Executive |
| 雲端模型服務 | 高品質 ASR、LLM 與 TTS | 依序降級到替代模型、本地模型或 RuleBrain |
| Unitree Go2 | Sport API、Megaphone 與實體動作 | production sport 主線走 Executive;音訊與 hard-stop 有獨立路徑 |
ASR、LLM 與 TTS 都設計了多層 fallback。Jetson 麥克風路徑可從 SenseVoice cloud 退到 local/Whisper;LLM 可退到替代模型與 RuleBrain;TTS 可退到 edge-tts/Piper。安全層、allowlist 與 Executive 則留在本地 runtime。
這不等於完整斷網語音閉環已被驗證。Studio 的筆電收音路徑目前仍只走 cloud SenseVoice,離線時會成為單點故障;因此 offline-first 是架構方向,不是目前已通過 baseline 的公開能力。
Repository 仍保留 legacy/demo 直發 publisher;production flags 必須將它們關閉。這是尚未完全消失的技術債,不能把「設計約束」誤寫成整個 repo 已無例外。
在 production flags 關閉 legacy/demo publisher 時,Brain 停止便不會產生新 proposal;Executive 停止則會關閉一般 sport-motion 主線。音訊與 hard-stop 仍刻意獨立,後者尚需完成獨立審計與驗證。
整合多模態系統後,最常遇到的問題不是「完全沒資料」,而是資料到了、某個模組也判斷了,但最後沒有動作。只看終端輸出,很難知道事件在哪一層被忽略、降級或擋下。
因此我做了 PawAI Studio:一個結合對話主控台、感知面板與決策 Trace 的操作介面。它讓操作者能同時看到感知事件、Brain 提案、Safety Gate 結果與 Executive 的最終決定。

Studio 的價值不是用漂亮 UI 證明能力成功,而是保留一條可追查的證據鏈:機器狗為什麼有反應、為什麼沒有反應,以及是哪一層阻止了它。
我不想用「支援很多功能」取代真正的驗證,所以 PawAI 把程式能力、開發觀察與可信的實測證據分開記錄。
| 能力 | 目前狀態 | 可以怎麼理解 |
|---|---|---|
| 已註冊使用者辨識 | 受控條件通過 | 單一已註冊使用者、有限距離與光線;不是門禁或通用身分驗證 |
| 近距離杯子辨識 | 受控條件通過 | 約 1 公尺、cup-only 場景;不宣稱 80 類物體都能穩定辨識 |
| 固定語音指令 | 受控條件通過 | 能把限定指令分類成系統意圖;不等於自由語音控制已可靠 |
| Brain 安全閘 | 機制與單元測試通過 | 能擋未授權技能;尚不能宣稱 Brain 不會幻覺 |
| 動態揮手、語音停止 | 尚未通過 | 保留失敗紀錄,不作為目前成果展示 |
| 姿勢/跌倒情境 | 證據不足 | 可在 Studio 觀察開發訊號,但不作照護或安全宣稱 |
| 自主導航與動態避障 | 實驗中 | 只在有人監督、具備急停流程的環境測試,不宣稱完整自主導航 |
這張表不是缺點清單。對我來說,它記錄了 PawAI 最重要的一次轉變:從展示功能,走向只對證據允許的範圍負責。
如果只看現在的架構圖,很容易誤以為我們一開始就知道 Brain、Executive、LangGraph 與 Safety Gate 應該放在哪裡。實際上,很多邊界都是在硬體真的失控、模型真的誤判、Demo 真的卡住之後才寫進系統。
aiortc 1.14 與 STUN 的相容問題。降回 1.9、移除 STUN 後,DataChannel 才從超時變成約 0.5 秒連上。install/。Move{x},沿用上一個命令滑行。事故迫使我們改用 StopMove(1003)、節流、heartbeat,並把 production sport 主線收斂到單一 arbiter。這些事故不是勳章。每一次碰撞與誤判,都代表當時的安全設計還不夠完整。真正值得留下的是:我們沒有把失敗剪掉,而是把它轉成契約、測試、仲裁規則與更窄的公開宣稱。
Go2 Pro 沒有 Go2 EDU 那條完整的官方 unitree_sdk2/unitree_ros2 開發路徑。我們最後選擇在社群的 WebRTC bridge 上擴充,但當時連錯誤操作是否會傷到機器人,都只能非常保守地摸索。

2025 年 10 月:這不是成品照,而是一群機器人新手第一次面對 Go2 Pro。
我從 abizovnuralem/go2_ros2_sdk、WebRTC DataChannel 與 Sport Mode API 出發。11 月 8 日,Windows 開發環境終於讓 Go2 第一次站起、坐下;十天後,又因 aiortc/STUN 相容問題幾乎完全失聯。
接下來是 Windows、Mac VM、Ubuntu、ROS2、RViz2、Foxglove、雙網卡與 DDS 的連環坑。壞掉的 ROS2 安裝包、VM crash、CycloneDDS 舊語法與 SCTP timeout 疊在一起,光網路拓撲就花了好幾天。
這時的流程還很直接:拍照、交給模型理解,再產生移動指令。12 月 9 日,我為了取得一張影像,花了三小時才發現是 ros2 daemon 快取讓 DDS discovery 卡住。
12 月中旬,純視覺避障只能做到約 50–60% 的開發穩定度。我們嘗試 Depth Anything、校正 Go2 廣角鏡頭距離,也把 Mac VM、Windows 與遠端 GPU 之間的 tunnel 串起來。
2025-12-15|視覺導航 v1。這是客廳探索階段的歷史快照,不代表可靠、通用的自主導航。

每加一個感測器,就要重新面對重量、供電、走線、散熱與維修方式。
1 月 7 日的報告反而成為轉折。教授指出「拍照 → LLM → 移動」太慢,也不夠安全。我們因此把重心移向 Jetson、D435、ROS2 即時感知,以及更底層的動作安全。
Face、Speech、Gesture、Pose 與 Object 不是一次加入。3 月 19 日,我用一晚建立第一版 benchmark framework、adapter、reporter、gate 與測試;接著實測 16 個模型,才抓出套件、精度與資源競爭問題。
我們踩過 onnxruntime-cpu 覆蓋 GPU、NumPy 2.x 衝突、舊 YuNet 在新版 OpenCV crash,以及 RTMPose 把手部關鍵點標到臉上的問題。最後才收斂成 YuNet/SFace、MediaPipe、YOLO26n 與 SenseVoice。
語音也不是接上麥克風就完成。VAD、回音、Whisper 幻覺、Go2 喇叭與 5.3–14.5 秒延遲,逼我們把外接麥克風、USB 喇叭、ASR、TTS 與 Echo Gate 分開驗證。
2026-03-18|語音鏈早期測試。當時能發聲,距離穩定互動仍很遠。
Jetson、D435、RPLIDAR、麥克風與喇叭不會自己長到 Go2 背上。我們處理電壓、瞬間電流、USB 接觸、散熱、配重、視野與雷達方向,並由我自行 3D 列印背蓋與支架,才讓軟體有地方可以運行。
2026-04-25|裝上自製 3D 列印背蓋後,Jetson、D435 與 RPLIDAR 終於能以完整堆疊站上 Go2。
這一個月也很殘酷:一次 pip install ultralytics 讓 Torch/NumPy 與所有模型一起壞掉;RPLIDAR 到貨後又遇到 ARM64 crash、Cartographer、雷達方向相反、支架搖晃與四張作廢地圖。
早期 llm_bridge_node.py 長到超過 1,100 行,同時處理 ROS、Persona、fallback、history、JSON 與 TTS。當錯誤開始無法定位,我才把 Conversation Engine 抽成 LangGraph,並讓新路徑先跑 shadow。
5 月 2 日的一次救援操作誤發 Damp(1001),Go2 馬達卸力摔倒。之後的短距導航又暴露 Move{x:0} 不停車、感測器方向與安全距離計算錯誤,迫使 StopMove、Topic 與 WebRTC control contract 成為硬規則。
驗收時也沒有奇蹟:人臉與語音相對穩定,物體很差,手勢會誤觸,姿勢定義不清。這些結果讓我們停止用「功能有寫」代表「能力可靠」。
最後一個月,Conversation Engine、LangGraph、TTS、Go2 音訊、LiDAR 與五段 Demo 全部同時進場。Face、cup、pose、gesture 會爭奪 TTS、狀態與身體控制;功能不是相加,而是互相放大失敗面。
我們修過 10 秒抓不到 pose+cup 的融合窗、phase gate 靜默擋事件、重複問候、TTS cooldown、多個 /webrtc_req publisher,以及 LiDAR 啟動腳本漏掉 --ros-args、被誤判成硬體死亡。
6 月 4 日的 trusted baseline 讓樂觀敘事回到現實:15 項能力裡,只有人臉、固定語音指令與近距杯子通過窄版條件;wave 與 voice stop 失敗,其餘多項仍是 insufficient data,整體 readiness 是 not_ready。
6 月 17 日,正式發表前一天的彩排仍然問題百出;6 月 18 日,現場卻順利完成 18 分鐘展示。我的日記最後只留下一句:
過程全錯,結果全對。
6 月 23 日,我們整理十個核心 ROS2 package、雙語文件結構與新手 bring-up 路徑,公開第一版 repository。它仍是多模態具身互動系統的雛形,不是完成的自主機器人產品。
PawAI 後期形成了一套比較保守,但很適合實體機器人的開發方式:
這套流程是在多次整合失敗後才逐漸形成。它讓 PawAI 從「功能很多的學生專題」,走向一個知道自己目前能證明什麼、也知道何時必須停下來的工程系統。
PawAI 是團隊專題;這一頁聚焦我親自負責或深度參與的系統整合、Brain、語音、硬體 bring-up、測試與公開化工作。
我主要投入在把原本分散的硬體、感知模型與 AI 功能,整合成一條能被操作、觀測與驗證的系統:
單獨的人臉、語音或物體辨識可能都能運作;放進同一台會移動的機器人後,它們開始共享 GPU、相機、音訊、狀態與控制出口。真正困難的是協調,而不是模型數量。
有 Navigation Action、有 Safety Gate 或有 Studio 畫面,只能證明系統具備這條路徑;它不能自動證明真實環境下的成功率、可靠度或安全性。
語音停止失敗、動態揮手未通過、導航測試走歪,這些都比一段剪輯過的成功影片更能說明我如何理解機器人系統。PawAI 最後留下的不只是功能,也包括停止宣稱、降級與重新驗證的方法。
PawAI 還會繼續改善,但我會維持同一個原則:先建立可重現的證據,再擴張公開宣稱。
下一階段包含:
專案原始碼與公開文件:GitHub — roy4222/PawAI