前 8 篇把 Agent 的全部核心机制 + 周边横切能力讲了。一个完整的 Agent 已经能跑起来、能接聊天平台、能调工具、能压缩记忆、能定时触发。
但有个问题——用户怎么用?
总不能要求所有用户都"在终端里输 python code.py 跟 Agent 聊"。大部分人想:
打开浏览器有个聊天界面能用 在 Cursor / VSCode 里用 OpenAI 协议接 Agent 当 AI 助手 在自定义应用里通过 HTTP API 调 Agent
这一篇讲 nanobot 怎么用"channel 的思路"统一这些外部接入。所有客户端都是 channel 的实例——业务层零改动。
对应教程的 s13 章节——WebSocket 桥 / HTTP API / WebUI 三大入口。
1. 怎么给 Agent 加个 Web 界面
直觉的写法——给 AgentLoop 加一个"WebUI 特殊模式":
python
class AgentLoop:
async def process(self, msg, source="cli"):
if source == "cli":
... # 终端交互
elif source == "web":
... # WebUI 推送消息
elif source == "http":
... # HTTP API 调用
elif source == "telegram":
... # Telegram 消息
# ... 加一个客户端加一个 elif
还是老问题,加到第 4 个客户端,主循环已经很难维护了。
但等等——s08 章节不是讲过 BaseChannel 抽象吗?再加一个客户端不就是"写一个新的 Channel 子类"吗?
是的。nanobot 正是这么做的——WebUI / HTTP API / 浏览器原生,所有外部接入都被当成"特殊 channel"。
关键观察:所有"桥"都是 channel 中的一个,业务层零感知。
2. 三种特殊 Channel
| 客户端 | 协议 | 实现位置 |
|---|---|---|
| 浏览器 WebUI | WebSocket | nanobot/bridge/websocket.py 的 WebSocketBridge |
| Cursor / aider / Continue | HTTP + OpenAI 兼容 | nanobot/bridge/http.py 的 /v1/chat/completions |
| curl / 自定义脚本 | HTTP + JSON | 同上 |
| Telegram | Telegram Bot SDK | TelegramChannel(s09 讲过) |
后两个(Cursor / curl)共享同一个 HTTP API 实现。关键是 OpenAI 兼容 schema——只要暴露 /v1/chat/completions + /v1/models 两个端点,整个 OpenAI 生态的客户端零配置可用。
这就是为什么 nanobot 选择实现 OpenAI 兼容协议——它是一个行业事实标准。
3. WebSocket Bridge:给浏览器用
WebSocketBridge 就是一个用 WebSocket 协议的 Channel:
python
class WebSocketBridge(BaseChannel):
"""WebUI 入口:浏览器通过 WebSocket 收发消息。"""
async def start(self) -> None:
self._server = await websockets.serve(
self._handle_connection,
"0.0.0.0",
8765,
)
async def _handle_connection(self, ws):
async for raw in ws:
msg = json.loads(raw)
# 转成 InboundMessage 推 bus
await self.inbound.put(InboundMessage(
channel="webui",
chat_id=ws.id,
user_id=msg.get("user_id", "anonymous"),
text=msg["text"],
))
async def send(self, msg: OutboundMessage) -> None:
# 通过 WebSocket 发回浏览器
await self._connections[msg.chat_id].send(json.dumps({
"text": msg.text,
"channel": msg.channel,
}))
业务代码(AgentLoop)完全不知道前端是 WebUI。它只看到"有 InboundMessage 进来"和"有 OutboundMessage 出去"。
Vite+React 前端(nanobot/webui/)通过 WebSocket 跟 Python 后端通信,典型的"前端 SPA + Python 后端"结构。
4. HTTP API:OpenAI 兼容协议
/v1/chat/completions 是 OpenAI 定义的端点。任何支持 OpenAI 协议的客户端(Cursor / Continue / aider / 自定义 HTTP 客户端)都能用。
s13 的 code.py 给了一个最简 stdlib 实现——用 http.server 起服务:
python
class OpenAICompatHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path == "/v1/chat/completions":
body = json.loads(self.rfile.read(...))
messages = body["messages"]
# 把 messages 转成 InboundMessage
user_text = messages[-1]["content"]
asyncio.run(self._handle(user_text))
def _handle(self, user_text):
# 调 LLM
response = complete(user_text)
# 包成 OpenAI schema 返回
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({
"choices": [{
"message": {"role": "assistant", "content": response},
}],
}).encode())
真实 nanobot 是这套的"功能超集"——SSE 流式响应、多 session 路由、API key 认证、模型列表端点全有。但核心逻辑就这几行。
跑一下:
bash
python s13_bridge_webui/code.py
# 另一个终端:
curl -X POST http://127.0.0.1:8765/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"Reply with one word: pong"}]}'
返回:
json
{"choices": [{"message": {"role": "assistant", "content": "pong"}}]}
任何 OpenAI 客户端都能这么用——不用改 nanobot 一行代码。
5. 一个误区:每种客户端写一套业务代码
如果Agent 项目这样写:
python
# 业务层
async def process(msg, source):
if source == "cli":
return handle_cli(msg)
elif source == "webui":
return handle_webui(msg)
elif source == "telegram":
return handle_telegram(msg)
elif source == "http":
return handle_http(msg)
加一个客户端就要改业务层。几个问题:
业务代码被各种协议细节污染 LLM 推理逻辑在 4 个分支里重复 测试要写 4 套(每个 source 一套) 加新协议 = 改业务层
正确做法:用 s08 章节讲的 BaseChannel 抽象。
python
# 业务层
class AgentLoop:
async def run(self):
while True:
msg = await self.bus.inbound.get()
response = await self._process(msg) # 不知道消息来源
await self.bus.outbound.put(response)
业务层只关心"有消息来"和"有消息出"。新加客户端 = 写一个 BaseChannel 子类,零侵入。
Cursor 集成 / Telegram bot / WebUI 全部走这个套路——你不需要懂 nanobot 业务层,只懂 BaseChannel 契约就行。
6. WebUI 前端:Vite + React SPA
nanobot 真实代码里的 nanobot/webui/ 是一个完整的 Vite+React 前端项目:
bash
nanobot/webui/
├── src/
│ ├── components/
│ │ ├── ChatView.tsx # 聊天界面
│ │ ├── MessageList.tsx # 消息列表
│ │ ├── InputBox.tsx # 输入框
│ │ └── ...
│ ├── hooks/
│ │ ├── useWebSocket.ts # WebSocket 客户端 hook
│ │ └── ...
│ └── App.tsx
├── index.html
├── package.json
└── vite.config.ts
通过 WebSocket 跟 Python 后端通信:
typescript
// 前端 useWebSocket hook
function useWebSocket(url: string) {
const [messages, setMessages] = useState<Message[]>([])
useEffect(() => {
const ws = new WebSocket(url)
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
setMessages(prev => [...prev, msg])
}
return () => ws.close()
}, [url])
const send = (text: string) => ws.send(JSON.stringify({ text }))
return { messages, send }
}
前后端通过 WebSocket 协议通信——前端只关心消息文本,后端只关心消息文本。协议层透明。
7. 怎么集成到自己的应用
/v1/chat/completions + WebSocket 这两个接口让 nanobot 嵌到任何应用里都简单:
方式 1:把 nanobot 当 OpenAI 代理
python
import openai
client = openai.OpenAI(
base_url="http://localhost:8765/v1", # 指向 nanobot
api_key="anything", # nanobot 不校验 key
)
response = client.chat.completions.create(
model="nanobot", # 任意模型名
messages=[{"role": "user", "content": "你好"}],
)
方式 2:把 nanobot 当 HTTP 后端
javascript
// 前端 fetch
const response = await fetch("http://localhost:8765/v1/chat/completions", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
messages: [{role: "user", content: "你好"}],
}),
})
const data = await response.json()
console.log(data.choices[0].message.content)
方式 3:把 nanobot 当 WebSocket 服务
javascript
// 浏览器
const ws = new WebSocket("ws://localhost:8765")
ws.onopen = () => ws.send(JSON.stringify({text: "你好"}))
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
console.log("Agent:", msg.text)
}
三种集成方式,零侵入业务层。
8. 常见疑问:为什么不直接用 WebSocket,不用 HTTP
s13 章节讲了两套协议——WebSocket 和 HTTP API。它们各有适用场景:
| 场景 | 适合协议 |
|---|---|
| 实时双向(聊天界面) | WebSocket |
| 服务端推送流式响应 | SSE(Server-Sent Events)/ WebSocket |
| 标准客户端集成(Cursor / aider) | HTTP(OpenAI 协议) |
| 简单脚本 | HTTP curl |
两个协议都支持同一个 nanobot 实例——业务层零感知。
9. 真实效果
接入新客户端的代码量:
| 客户端 | 实现量 | 业务代码改动 |
|---|---|---|
| WebUI(浏览器) | ~200 行(Vite+React) | 0 行 |
| HTTP API(OpenAI 兼容) | ~100 行(FastAPI) | 0 行 |
| Telegram | ~200 行(Bot SDK) | 0 行 |
| curl 调用 | 1 行 | 0 行 |
接入 4 个客户端,AgentLoop 业务代码改动 0 行——这就是 channel 抽象的力量。
10. 下一篇
下一篇是大结局——s14 贡献附录,讲怎么给 nanobot 加新能力(Tool / Provider / Channel 三种扩展点的具体流程)、怎么参与开源贡献(PR 流程 / 测试约定 / 文档规范)。整个系列到这里收尾。
11. 参考资料
nanobot 源码:https://github.com/HKUDS/nanobot nanobot-tutorial(14 章配套教程):https://github.com/yaoweizhang/nanobot_tutorial nanobot 官方 Roadmap:https://github.com/HKUDS/nanobot/discussions/431
跟读建议:跑 python s13_bridge_webui/code.py 起一个最小 HTTP server,用 curl 调 /v1/chat/completions。再翻 nanobot/webui/ 看 Vite+React 前端项目结构。
夜雨聆风