socialrobot 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- socialrobot-0.2.0/LICENSE +21 -0
- socialrobot-0.2.0/PKG-INFO +212 -0
- socialrobot-0.2.0/README.md +180 -0
- socialrobot-0.2.0/pyproject.toml +50 -0
- socialrobot-0.2.0/setup.cfg +4 -0
- socialrobot-0.2.0/socialrobot/__init__.py +67 -0
- socialrobot-0.2.0/socialrobot/builder.py +1124 -0
- socialrobot-0.2.0/socialrobot/client.py +322 -0
- socialrobot-0.2.0/socialrobot/mcp_server.py +100 -0
- socialrobot-0.2.0/socialrobot/py.typed +0 -0
- socialrobot-0.2.0/socialrobot/runtime.py +278 -0
- socialrobot-0.2.0/socialrobot.egg-info/PKG-INFO +212 -0
- socialrobot-0.2.0/socialrobot.egg-info/SOURCES.txt +19 -0
- socialrobot-0.2.0/socialrobot.egg-info/dependency_links.txt +1 -0
- socialrobot-0.2.0/socialrobot.egg-info/entry_points.txt +2 -0
- socialrobot-0.2.0/socialrobot.egg-info/requires.txt +9 -0
- socialrobot-0.2.0/socialrobot.egg-info/top_level.txt +1 -0
- socialrobot-0.2.0/tests/test_client_mfa_login.py +50 -0
- socialrobot-0.2.0/tests/test_client_upload_refresh.py +114 -0
- socialrobot-0.2.0/tests/test_nao_duplex_client.py +320 -0
- socialrobot-0.2.0/tests/test_nao_meme_bridge.py +186 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hung-Chun Chang (MEME — Social Robot Platform)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: socialrobot
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python runtime client for the Social Robot Platform — connect a robot body (NAO, Pi, any Python process) to a live session and drive it.
|
|
5
|
+
Author-email: Hung-Chun Chang <jonathanchang9@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://sociallab.duckdns.org/ntu_social_robot/
|
|
8
|
+
Project-URL: Repository, https://github.com/hungchunchang/srdp
|
|
9
|
+
Project-URL: Issues, https://github.com/hungchunchang/srdp/issues
|
|
10
|
+
Keywords: social robot,robotics,wizard-of-oz,woz,kebbi,nao,hri,sdk
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: httpx>=0.24
|
|
25
|
+
Requires-Dist: websockets>=11
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
28
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
29
|
+
Provides-Extra: mcp
|
|
30
|
+
Requires-Dist: mcp<2,>=1.0; extra == "mcp"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# socialrobot — Social Robot Platform Python SDK
|
|
34
|
+
|
|
35
|
+
把一個**機器人身體**(NAO、Raspberry Pi、任何 Python 程式)接上平台的 live session:
|
|
36
|
+
meme 是大腦,你的程式是身體。負責驅動、監看、以及把外部系統接成中斷條件。
|
|
37
|
+
|
|
38
|
+
> **角色設計不在這裡做**——那是網頁編輯器 canvas 的工作。詳見下方
|
|
39
|
+
> 「角色設計請用網頁編輯器」。
|
|
40
|
+
|
|
41
|
+
> 這個目錄同步鏡像到公開 repo [github.com/hungchunchang/srdp](https://github.com/hungchunchang/srdp)(`main` 分支),供 PyPI 套件頁與外部使用者參考;開發仍在這個 monorepo 的 `sdk/` 進行,鏡像不接受直接 PR。
|
|
42
|
+
|
|
43
|
+
## 在哪裡用?怎麼用?
|
|
44
|
+
|
|
45
|
+
**在你自己的電腦(或任何伺服器/CI)跑,不是在網頁裡跑。** 流程:
|
|
46
|
+
|
|
47
|
+
1. **安裝**(任何 Python ≥3.10 環境):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install socialrobot # PyPI
|
|
51
|
+
pip install -e sdk/ # 或從 repo checkout 安裝
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
> 安裝名稱和 import 名稱都是 `socialrobot`(下面所有範例都是)。
|
|
55
|
+
>
|
|
56
|
+
> ⚠️ **不要 `pip install srdp`。** PyPI 上的 `srdp` 是一個無關的專案(Single Repo Data Platform),不是這個 SDK,照打會裝到別人的程式。
|
|
57
|
+
|
|
58
|
+
2. **取得憑證**——擇一:
|
|
59
|
+
- **API key(建議)**:登入網頁 → 右上角使用者設定 → API Key → 產生。
|
|
60
|
+
`srk_` 開頭、只顯示一次,只能存取**你自己**帳號的資源。
|
|
61
|
+
- 或直接用 email + password(SDK 會替你登入換 JWT)。該 JWT 預設只有 60 分鐘壽命,
|
|
62
|
+
但 `SocialRobotClient` 會在收到 401 時,用登入時一併拿到的 refresh cookie 靜默換發
|
|
63
|
+
新 token 再重送原請求,所以長時間執行的 `RobotSession` 不必自行處理過期。
|
|
64
|
+
**只在台大網段(或 VPN)內有效**:密碼登入拿到的是網頁 token,校外的請求與換發
|
|
65
|
+
都會被拒(`ntu_network_only`,#830);開了兩步驟驗證的帳號還要附 30 秒內的驗證碼。
|
|
66
|
+
在校外、或寫成長期跑的程式,請用 API key。
|
|
67
|
+
|
|
68
|
+
3. **指向平台**——`SocialRobotClient(base_url=…)` **必填指向你自己的部署**:
|
|
69
|
+
- 部署環境:`https://sociallab.duckdns.org/ntu_social_robot/api/v1`
|
|
70
|
+
- 本機開發:`http://localhost:6868/api/v1`
|
|
71
|
+
- ⚠️ 不傳 `base_url` 時預設是 `http://localhost:6868/api/v1`(只適合本機);
|
|
72
|
+
用 `pip install` 在別台機器上跑時,務必傳入你部署的 URL。
|
|
73
|
+
|
|
74
|
+
4. **在網頁編輯器把角色設計好**,然後用這個 SDK 把機器人身體接上去:
|
|
75
|
+
`RobotSession` 連進該角色的 live session,逐則 `state_update` render 到你的
|
|
76
|
+
硬體上(TTS、`motor_keyframes`、表情),並把感測器/ASR 事件送回平台。
|
|
77
|
+
完整範例見 `examples/nao_meme_bridge.py`。
|
|
78
|
+
|
|
79
|
+
## 角色設計請用網頁編輯器(Builder DSL 已退出公開 API)
|
|
80
|
+
|
|
81
|
+
**這個 SDK 不再提供「用程式碼建角色」的公開介面。** 角色設計請在網頁編輯器
|
|
82
|
+
的 canvas 上進行。原因很單純:Builder 腳本寫完仍然要 `deploy()` 把結果上傳
|
|
83
|
+
到後端,跟在 canvas 上編輯的是同一份資料、同一套 REST API,所以它給設計師的
|
|
84
|
+
好處等於零,而真正持續維護、持續長功能的介面是 canvas。
|
|
85
|
+
|
|
86
|
+
`socialrobot.builder` 這個模組仍然留在 repo 裡、也仍然 import 得到,但它現在
|
|
87
|
+
的定位是**平台自己的測試/佈建基礎設施**:`server/scripts/seed_*.py`
|
|
88
|
+
(Museum、Origami、搶答主持人、聽力認知搶答主持人)和約 15 支
|
|
89
|
+
`server/scripts/e2e_*.py` 都用它產生 fixture 角色。它不在 `__all__` 裡、不在
|
|
90
|
+
本文件裡、也不在套件描述裡,對外**沒有相容性承諾**——請不要在它上面開發新東西。
|
|
91
|
+
|
|
92
|
+
需要用程式產生角色的話,直接打 REST API(就是 canvas 用的那套)。
|
|
93
|
+
|
|
94
|
+
## Runtime client — 驅動正在跑的機器人(這才是這個 SDK 的用途)
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
import asyncio
|
|
98
|
+
from socialrobot import RobotSession
|
|
99
|
+
|
|
100
|
+
async def main():
|
|
101
|
+
async with await RobotSession.start(client, "我的導覽員") as s:
|
|
102
|
+
await s.send_face(embedding=[0.1] * 128) # 模擬鏡頭
|
|
103
|
+
ev = await s.expect(state="UI", timeout=10) # 等 ask_name UI
|
|
104
|
+
await s.send_event("UI_INPUT", {"variable": "name", "value": "小明"})
|
|
105
|
+
await s.expect(state="SPEAKING", text="小明", timeout=20)
|
|
106
|
+
|
|
107
|
+
asyncio.run(main())
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 外部系統整合 — 把 API 結果變成中斷條件
|
|
111
|
+
|
|
112
|
+
**Inbound**(你的程式推進來)——不需要 robot WS,純 HTTP:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
for sess in client.list_sessions(): # 只看得到自己的機器人
|
|
116
|
+
client.inject(sess["session_id"],
|
|
117
|
+
event="order_ready", # 觸發 OnSensor("order_ready") hook
|
|
118
|
+
variables={"yolo_result": "1"}) # 餵 Switch / {{var}} 模板
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Outbound — 後端替你輪詢/訂閱**(掛在節點上的 hook):
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
waiting = Script("等待製作", loop=True, hooks=[
|
|
125
|
+
# HTTP 輪詢
|
|
126
|
+
OnApiPoll("https://your-service/jobs/123",
|
|
127
|
+
path="data.status", equals="done", every=2.0,
|
|
128
|
+
write_to="job_status", goto="完成播報"),
|
|
129
|
+
# 或 WebSocket 串流(後端持續訂閱外部 feed)
|
|
130
|
+
OnWebSocket("wss://your-service/jobs/123/stream",
|
|
131
|
+
path="status", equals="done",
|
|
132
|
+
subscribe={"action": "subscribe"}, # 連上後送一次(可省)
|
|
133
|
+
write_to="job_status", goto="完成播報"),
|
|
134
|
+
])
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Inbound 串流**——高頻推送時用持久 WS(取代一次一個 `inject`):
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
async with await client.open_stream(session_id) as stream:
|
|
141
|
+
await stream.send(variables={"price": 42}) # → VARIABLE switch
|
|
142
|
+
await stream.send(event="order_ready") # → OnSensor hook
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
注意:有台詞的 loop 在播台詞時一直處於 SPEAKING,`OnSensor` / `OnFace` 會被延後,
|
|
146
|
+
補送時機是**這一輪台詞播完回捲的那一刻**(2026-08 起;在那之前是永遠不觸發),所以
|
|
147
|
+
最慢會晚一輪才離開節點。要求「按下去就立刻打斷」的等待節點(例如搶答)仍請用**無台詞
|
|
148
|
+
的 loop**。`OnWs`(控制面板/外部程式的推送)以及後端自己輪詢的 `OnApiPoll` /
|
|
149
|
+
`OnWebSocket` / `OnTimeout` / `OnVision` / `OnSchedule` 不受此限,隨時都會立刻打斷。
|
|
150
|
+
|
|
151
|
+
## 自訂視覺模型
|
|
152
|
+
|
|
153
|
+
上傳自己的分類/偵測模型(`.onnx` / `.pt` / `.tflite` …),掛成 `OnVision`
|
|
154
|
+
中斷條件,讓機器人「看到某個標籤 → 換節點」:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
model = client.upload_vision_model("origami_step.onnx", name="摺紙判斷",
|
|
158
|
+
model_type="onnx", labels=["step1", "step2"])
|
|
159
|
+
step2 = Script("步驟2", loop=True, lines=[Say("把紙對折")], hooks=[
|
|
160
|
+
OnVision(model["id"], fire_on_label="step2", threshold=0.7, goto=correct_node),
|
|
161
|
+
])
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
節點開著時後端持續對機器人鏡頭的畫面跑模型,標籤信心值過 threshold 就跳。
|
|
165
|
+
Teachable Machine 匯出的 `.tflite` 可直接上傳。
|
|
166
|
+
|
|
167
|
+
同一個模型猜每一步時,相鄰步驟長得像,前幾步的機率會一直搶走答案。`OnVision`
|
|
168
|
+
的 `allowed_labels` 讓這一步只在指定標籤裡挑最高分並重新算比例(step1–3 的機率被忽略):
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
step4 = Script("步驟4", loop=True, lines=[Say("把角往內折")], hooks=[
|
|
172
|
+
OnVision(model["id"], fire_on_label="step4", threshold=0.7, goto=step5,
|
|
173
|
+
allowed_labels=["step3", "step4"]), # 觸發標籤一定在名單內
|
|
174
|
+
])
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
名單裡至少要留一個不會觸發的標籤(這裡是 `step3`)——只剩觸發標籤的名單不管鏡頭看到什麼都會立刻觸發,後端會拒收並改回全部允許。同一步的所有 `OnVision` 共用一份名單(取聯集);不給就是全部允許。
|
|
178
|
+
|
|
179
|
+
## 已驗證的環境
|
|
180
|
+
|
|
181
|
+
從一般使用者環境(容器外的 venv/conda)對 localhost 與公開 prod URL 全程
|
|
182
|
+
驗證過:登入、DSL deploy、WS runtime、HTTP/WS inject。`make e2e-sdk` 是
|
|
183
|
+
完整迴歸(HTTP poll、WS subscribe、HTTP inject、WS stream、vision 全涵蓋)。
|
|
184
|
+
|
|
185
|
+
## MCP server (drive the platform from a terminal agent)
|
|
186
|
+
|
|
187
|
+
`socialrobot.mcp_server` exposes the SDK as [MCP](https://modelcontextprotocol.io)
|
|
188
|
+
tools, so an agent (Claude Code, etc.) can run the platform conversationally:
|
|
189
|
+
|
|
190
|
+
| tool | does |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `list_characters` | your characters (id/name/public) |
|
|
193
|
+
| `list_sessions` | running robot sessions to inject into |
|
|
194
|
+
| `inject_event` | push an event/variables to a live session — `buzz` `{winner}`, `judged` `{result}`, `next_question` `{answer, clip_url}` |
|
|
195
|
+
| `upload_media` | upload an audio/video clip → `{url, signed_url}` |
|
|
196
|
+
| `get_scripts` | read a workflow's WoZ lines (incl. `audio_clip_url`) |
|
|
197
|
+
|
|
198
|
+
Install + register with Claude Code:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pip install "socialrobot[mcp]" # or from a checkout: pip install -e "sdk/[mcp]"
|
|
202
|
+
# NOT `pip install srdp` — that PyPI name is an unrelated project
|
|
203
|
+
claude mcp add socialrobot \
|
|
204
|
+
-e SOCIALROBOT_API_KEY=srk_… \
|
|
205
|
+
-e SOCIALROBOT_BASE_URL=https://sociallab.duckdns.org/ntu_social_robot/api/v1 \
|
|
206
|
+
-- socialrobot-mcp
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Auth via `SOCIALROBOT_API_KEY` (an `srk_` developer key — mint in 個人設定 → API key)
|
|
210
|
+
or `SOCIALROBOT_EMAIL` + `SOCIALROBOT_PASSWORD` (the latter only from the NTU network:
|
|
211
|
+
a password login is a web session, refused elsewhere with `ntu_network_only`). The key lives only in the MCP
|
|
212
|
+
process's env, never in tool arguments.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# socialrobot — Social Robot Platform Python SDK
|
|
2
|
+
|
|
3
|
+
把一個**機器人身體**(NAO、Raspberry Pi、任何 Python 程式)接上平台的 live session:
|
|
4
|
+
meme 是大腦,你的程式是身體。負責驅動、監看、以及把外部系統接成中斷條件。
|
|
5
|
+
|
|
6
|
+
> **角色設計不在這裡做**——那是網頁編輯器 canvas 的工作。詳見下方
|
|
7
|
+
> 「角色設計請用網頁編輯器」。
|
|
8
|
+
|
|
9
|
+
> 這個目錄同步鏡像到公開 repo [github.com/hungchunchang/srdp](https://github.com/hungchunchang/srdp)(`main` 分支),供 PyPI 套件頁與外部使用者參考;開發仍在這個 monorepo 的 `sdk/` 進行,鏡像不接受直接 PR。
|
|
10
|
+
|
|
11
|
+
## 在哪裡用?怎麼用?
|
|
12
|
+
|
|
13
|
+
**在你自己的電腦(或任何伺服器/CI)跑,不是在網頁裡跑。** 流程:
|
|
14
|
+
|
|
15
|
+
1. **安裝**(任何 Python ≥3.10 環境):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install socialrobot # PyPI
|
|
19
|
+
pip install -e sdk/ # 或從 repo checkout 安裝
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> 安裝名稱和 import 名稱都是 `socialrobot`(下面所有範例都是)。
|
|
23
|
+
>
|
|
24
|
+
> ⚠️ **不要 `pip install srdp`。** PyPI 上的 `srdp` 是一個無關的專案(Single Repo Data Platform),不是這個 SDK,照打會裝到別人的程式。
|
|
25
|
+
|
|
26
|
+
2. **取得憑證**——擇一:
|
|
27
|
+
- **API key(建議)**:登入網頁 → 右上角使用者設定 → API Key → 產生。
|
|
28
|
+
`srk_` 開頭、只顯示一次,只能存取**你自己**帳號的資源。
|
|
29
|
+
- 或直接用 email + password(SDK 會替你登入換 JWT)。該 JWT 預設只有 60 分鐘壽命,
|
|
30
|
+
但 `SocialRobotClient` 會在收到 401 時,用登入時一併拿到的 refresh cookie 靜默換發
|
|
31
|
+
新 token 再重送原請求,所以長時間執行的 `RobotSession` 不必自行處理過期。
|
|
32
|
+
**只在台大網段(或 VPN)內有效**:密碼登入拿到的是網頁 token,校外的請求與換發
|
|
33
|
+
都會被拒(`ntu_network_only`,#830);開了兩步驟驗證的帳號還要附 30 秒內的驗證碼。
|
|
34
|
+
在校外、或寫成長期跑的程式,請用 API key。
|
|
35
|
+
|
|
36
|
+
3. **指向平台**——`SocialRobotClient(base_url=…)` **必填指向你自己的部署**:
|
|
37
|
+
- 部署環境:`https://sociallab.duckdns.org/ntu_social_robot/api/v1`
|
|
38
|
+
- 本機開發:`http://localhost:6868/api/v1`
|
|
39
|
+
- ⚠️ 不傳 `base_url` 時預設是 `http://localhost:6868/api/v1`(只適合本機);
|
|
40
|
+
用 `pip install` 在別台機器上跑時,務必傳入你部署的 URL。
|
|
41
|
+
|
|
42
|
+
4. **在網頁編輯器把角色設計好**,然後用這個 SDK 把機器人身體接上去:
|
|
43
|
+
`RobotSession` 連進該角色的 live session,逐則 `state_update` render 到你的
|
|
44
|
+
硬體上(TTS、`motor_keyframes`、表情),並把感測器/ASR 事件送回平台。
|
|
45
|
+
完整範例見 `examples/nao_meme_bridge.py`。
|
|
46
|
+
|
|
47
|
+
## 角色設計請用網頁編輯器(Builder DSL 已退出公開 API)
|
|
48
|
+
|
|
49
|
+
**這個 SDK 不再提供「用程式碼建角色」的公開介面。** 角色設計請在網頁編輯器
|
|
50
|
+
的 canvas 上進行。原因很單純:Builder 腳本寫完仍然要 `deploy()` 把結果上傳
|
|
51
|
+
到後端,跟在 canvas 上編輯的是同一份資料、同一套 REST API,所以它給設計師的
|
|
52
|
+
好處等於零,而真正持續維護、持續長功能的介面是 canvas。
|
|
53
|
+
|
|
54
|
+
`socialrobot.builder` 這個模組仍然留在 repo 裡、也仍然 import 得到,但它現在
|
|
55
|
+
的定位是**平台自己的測試/佈建基礎設施**:`server/scripts/seed_*.py`
|
|
56
|
+
(Museum、Origami、搶答主持人、聽力認知搶答主持人)和約 15 支
|
|
57
|
+
`server/scripts/e2e_*.py` 都用它產生 fixture 角色。它不在 `__all__` 裡、不在
|
|
58
|
+
本文件裡、也不在套件描述裡,對外**沒有相容性承諾**——請不要在它上面開發新東西。
|
|
59
|
+
|
|
60
|
+
需要用程式產生角色的話,直接打 REST API(就是 canvas 用的那套)。
|
|
61
|
+
|
|
62
|
+
## Runtime client — 驅動正在跑的機器人(這才是這個 SDK 的用途)
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import asyncio
|
|
66
|
+
from socialrobot import RobotSession
|
|
67
|
+
|
|
68
|
+
async def main():
|
|
69
|
+
async with await RobotSession.start(client, "我的導覽員") as s:
|
|
70
|
+
await s.send_face(embedding=[0.1] * 128) # 模擬鏡頭
|
|
71
|
+
ev = await s.expect(state="UI", timeout=10) # 等 ask_name UI
|
|
72
|
+
await s.send_event("UI_INPUT", {"variable": "name", "value": "小明"})
|
|
73
|
+
await s.expect(state="SPEAKING", text="小明", timeout=20)
|
|
74
|
+
|
|
75
|
+
asyncio.run(main())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 外部系統整合 — 把 API 結果變成中斷條件
|
|
79
|
+
|
|
80
|
+
**Inbound**(你的程式推進來)——不需要 robot WS,純 HTTP:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
for sess in client.list_sessions(): # 只看得到自己的機器人
|
|
84
|
+
client.inject(sess["session_id"],
|
|
85
|
+
event="order_ready", # 觸發 OnSensor("order_ready") hook
|
|
86
|
+
variables={"yolo_result": "1"}) # 餵 Switch / {{var}} 模板
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Outbound — 後端替你輪詢/訂閱**(掛在節點上的 hook):
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
waiting = Script("等待製作", loop=True, hooks=[
|
|
93
|
+
# HTTP 輪詢
|
|
94
|
+
OnApiPoll("https://your-service/jobs/123",
|
|
95
|
+
path="data.status", equals="done", every=2.0,
|
|
96
|
+
write_to="job_status", goto="完成播報"),
|
|
97
|
+
# 或 WebSocket 串流(後端持續訂閱外部 feed)
|
|
98
|
+
OnWebSocket("wss://your-service/jobs/123/stream",
|
|
99
|
+
path="status", equals="done",
|
|
100
|
+
subscribe={"action": "subscribe"}, # 連上後送一次(可省)
|
|
101
|
+
write_to="job_status", goto="完成播報"),
|
|
102
|
+
])
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Inbound 串流**——高頻推送時用持久 WS(取代一次一個 `inject`):
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
async with await client.open_stream(session_id) as stream:
|
|
109
|
+
await stream.send(variables={"price": 42}) # → VARIABLE switch
|
|
110
|
+
await stream.send(event="order_ready") # → OnSensor hook
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
注意:有台詞的 loop 在播台詞時一直處於 SPEAKING,`OnSensor` / `OnFace` 會被延後,
|
|
114
|
+
補送時機是**這一輪台詞播完回捲的那一刻**(2026-08 起;在那之前是永遠不觸發),所以
|
|
115
|
+
最慢會晚一輪才離開節點。要求「按下去就立刻打斷」的等待節點(例如搶答)仍請用**無台詞
|
|
116
|
+
的 loop**。`OnWs`(控制面板/外部程式的推送)以及後端自己輪詢的 `OnApiPoll` /
|
|
117
|
+
`OnWebSocket` / `OnTimeout` / `OnVision` / `OnSchedule` 不受此限,隨時都會立刻打斷。
|
|
118
|
+
|
|
119
|
+
## 自訂視覺模型
|
|
120
|
+
|
|
121
|
+
上傳自己的分類/偵測模型(`.onnx` / `.pt` / `.tflite` …),掛成 `OnVision`
|
|
122
|
+
中斷條件,讓機器人「看到某個標籤 → 換節點」:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
model = client.upload_vision_model("origami_step.onnx", name="摺紙判斷",
|
|
126
|
+
model_type="onnx", labels=["step1", "step2"])
|
|
127
|
+
step2 = Script("步驟2", loop=True, lines=[Say("把紙對折")], hooks=[
|
|
128
|
+
OnVision(model["id"], fire_on_label="step2", threshold=0.7, goto=correct_node),
|
|
129
|
+
])
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
節點開著時後端持續對機器人鏡頭的畫面跑模型,標籤信心值過 threshold 就跳。
|
|
133
|
+
Teachable Machine 匯出的 `.tflite` 可直接上傳。
|
|
134
|
+
|
|
135
|
+
同一個模型猜每一步時,相鄰步驟長得像,前幾步的機率會一直搶走答案。`OnVision`
|
|
136
|
+
的 `allowed_labels` 讓這一步只在指定標籤裡挑最高分並重新算比例(step1–3 的機率被忽略):
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
step4 = Script("步驟4", loop=True, lines=[Say("把角往內折")], hooks=[
|
|
140
|
+
OnVision(model["id"], fire_on_label="step4", threshold=0.7, goto=step5,
|
|
141
|
+
allowed_labels=["step3", "step4"]), # 觸發標籤一定在名單內
|
|
142
|
+
])
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
名單裡至少要留一個不會觸發的標籤(這裡是 `step3`)——只剩觸發標籤的名單不管鏡頭看到什麼都會立刻觸發,後端會拒收並改回全部允許。同一步的所有 `OnVision` 共用一份名單(取聯集);不給就是全部允許。
|
|
146
|
+
|
|
147
|
+
## 已驗證的環境
|
|
148
|
+
|
|
149
|
+
從一般使用者環境(容器外的 venv/conda)對 localhost 與公開 prod URL 全程
|
|
150
|
+
驗證過:登入、DSL deploy、WS runtime、HTTP/WS inject。`make e2e-sdk` 是
|
|
151
|
+
完整迴歸(HTTP poll、WS subscribe、HTTP inject、WS stream、vision 全涵蓋)。
|
|
152
|
+
|
|
153
|
+
## MCP server (drive the platform from a terminal agent)
|
|
154
|
+
|
|
155
|
+
`socialrobot.mcp_server` exposes the SDK as [MCP](https://modelcontextprotocol.io)
|
|
156
|
+
tools, so an agent (Claude Code, etc.) can run the platform conversationally:
|
|
157
|
+
|
|
158
|
+
| tool | does |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `list_characters` | your characters (id/name/public) |
|
|
161
|
+
| `list_sessions` | running robot sessions to inject into |
|
|
162
|
+
| `inject_event` | push an event/variables to a live session — `buzz` `{winner}`, `judged` `{result}`, `next_question` `{answer, clip_url}` |
|
|
163
|
+
| `upload_media` | upload an audio/video clip → `{url, signed_url}` |
|
|
164
|
+
| `get_scripts` | read a workflow's WoZ lines (incl. `audio_clip_url`) |
|
|
165
|
+
|
|
166
|
+
Install + register with Claude Code:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pip install "socialrobot[mcp]" # or from a checkout: pip install -e "sdk/[mcp]"
|
|
170
|
+
# NOT `pip install srdp` — that PyPI name is an unrelated project
|
|
171
|
+
claude mcp add socialrobot \
|
|
172
|
+
-e SOCIALROBOT_API_KEY=srk_… \
|
|
173
|
+
-e SOCIALROBOT_BASE_URL=https://sociallab.duckdns.org/ntu_social_robot/api/v1 \
|
|
174
|
+
-- socialrobot-mcp
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Auth via `SOCIALROBOT_API_KEY` (an `srk_` developer key — mint in 個人設定 → API key)
|
|
178
|
+
or `SOCIALROBOT_EMAIL` + `SOCIALROBOT_PASSWORD` (the latter only from the NTU network:
|
|
179
|
+
a password login is a web session, refused elsewhere with `ntu_network_only`). The key lives only in the MCP
|
|
180
|
+
process's env, never in tool arguments.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "socialrobot"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Python runtime client for the Social Robot Platform — connect a robot body (NAO, Pi, any Python process) to a live session and drive it."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [{ name = "Hung-Chun Chang", email = "jonathanchang9@gmail.com" }]
|
|
10
|
+
keywords = ["social robot", "robotics", "wizard-of-oz", "woz", "kebbi", "nao", "hri", "sdk"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Intended Audience :: Science/Research",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Human Machine Interfaces",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.24",
|
|
25
|
+
"websockets>=11",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = ["build>=1.0", "twine>=5.0"]
|
|
30
|
+
# MCP server (socialrobot.mcp_server) — drive the platform from a terminal agent.
|
|
31
|
+
# <2: mcp 2.x renamed FastMCP to MCPServer; mcp_server.py is written against 1.x.
|
|
32
|
+
mcp = ["mcp>=1.0,<2"]
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
socialrobot-mcp = "socialrobot.mcp_server:main"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://sociallab.duckdns.org/ntu_social_robot/"
|
|
39
|
+
Repository = "https://github.com/hungchunchang/srdp"
|
|
40
|
+
Issues = "https://github.com/hungchunchang/srdp/issues"
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["setuptools>=77"]
|
|
44
|
+
build-backend = "setuptools.build_meta"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
include = ["socialrobot*"]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.package-data]
|
|
50
|
+
socialrobot = ["py.typed"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Social Robot Platform SDK — a RUNTIME client, not an authoring tool.
|
|
2
|
+
|
|
3
|
+
The supported public API is the runtime half: connect a non-Android robot
|
|
4
|
+
body (or any program) to a live platform session, and drive/observe it.
|
|
5
|
+
|
|
6
|
+
from socialrobot import SocialRobotClient, RobotSession
|
|
7
|
+
|
|
8
|
+
`RobotSession` is how a NAO, a Raspberry Pi rig, or any Python-driven body
|
|
9
|
+
participates as a robot — meme is the brain, your process is the body (see
|
|
10
|
+
`examples/nao_meme_bridge.py`). `SocialRobotClient` is the authenticated REST
|
|
11
|
+
client underneath it, plus the asset/session endpoints those bodies need.
|
|
12
|
+
|
|
13
|
+
INTERNAL, NOT PUBLIC API — the Builder DSL (`Character`, `Script`, `Say`,
|
|
14
|
+
`Agent`, `Screen`, `Identify`, hooks, …). Authoring a character is the
|
|
15
|
+
WEB EDITOR's job: a Builder script still has to `deploy()` its result up to
|
|
16
|
+
the backend, so it buys nothing a designer can't do on the canvas, and the
|
|
17
|
+
canvas is the surface that actually gets maintained. The module stays in the
|
|
18
|
+
repo because the platform's own seed scripts and ~15 e2e drivers provision
|
|
19
|
+
their fixture characters with it (`server/scripts/seed_*.py`,
|
|
20
|
+
`server/scripts/e2e_*.py`) — it is test/provisioning infrastructure now.
|
|
21
|
+
|
|
22
|
+
Those names are still importable so that in-repo tooling keeps working, but
|
|
23
|
+
they are deliberately absent from `__all__`, excluded from the documented
|
|
24
|
+
surface, and carry no compatibility promise for external callers. Do not
|
|
25
|
+
build anything new on them; use the web editor, or the REST API directly.
|
|
26
|
+
"""
|
|
27
|
+
from .builder import (
|
|
28
|
+
Agent,
|
|
29
|
+
Button,
|
|
30
|
+
Call,
|
|
31
|
+
Character,
|
|
32
|
+
Data,
|
|
33
|
+
DataSource,
|
|
34
|
+
DeployedCharacter,
|
|
35
|
+
Hook,
|
|
36
|
+
Identify,
|
|
37
|
+
Judgment,
|
|
38
|
+
Mirror,
|
|
39
|
+
Node,
|
|
40
|
+
OnApiPoll,
|
|
41
|
+
OnFace,
|
|
42
|
+
OnPlaybackDone,
|
|
43
|
+
OnSchedule,
|
|
44
|
+
OnSensor,
|
|
45
|
+
OnTimeout,
|
|
46
|
+
OnUtterance,
|
|
47
|
+
OnVision,
|
|
48
|
+
OnWebSocket,
|
|
49
|
+
OnWs,
|
|
50
|
+
Return,
|
|
51
|
+
Say,
|
|
52
|
+
Screen,
|
|
53
|
+
Script,
|
|
54
|
+
subflow,
|
|
55
|
+
)
|
|
56
|
+
from .client import MfaRequiredError, SocialRobotClient
|
|
57
|
+
from .runtime import ExternalStream, RobotSession, SubscribeStream
|
|
58
|
+
|
|
59
|
+
__version__ = "0.2.0"
|
|
60
|
+
|
|
61
|
+
# The PUBLIC surface: the runtime client only. Builder DSL names are
|
|
62
|
+
# imported above for in-repo tooling (seeds + e2e drivers) but are
|
|
63
|
+
# deliberately NOT listed here — `__all__` is what this package promises.
|
|
64
|
+
__all__ = [
|
|
65
|
+
"__version__",
|
|
66
|
+
"ExternalStream", "MfaRequiredError", "RobotSession", "SocialRobotClient", "SubscribeStream",
|
|
67
|
+
]
|