主题
技术调研:为什么选这些技术?
读完这篇文档,你会理解 miniOpenClaw 用到的每一项技术选型背后的原因,以及它们的通俗含义。
一、编程语言:为什么用 Python 3.11+?
不是因为"大家都用",而是因为真的合适
| 选型理由 | 说明 |
|---|---|
| AI 生态最完善 | OpenAI SDK、tiktoken、主流 AI 库都是 Python 优先 |
| 异步编程成熟 | Python 3.11 的 asyncio 性能大幅提升,TaskGroup 等新特性简化并发代码 |
| 学习门槛低 | 语法简洁,适合教学项目 |
| 类型标注完善 | 3.11+ 的类型系统让代码更易读、IDE 提示更好 |
Python 3.11+ 的关键新特性(白话版)
| 特性 | 白话解释 | 在本项目的应用 |
|---|---|---|
StrEnum | 让枚举值直接就是字符串,不用每次转换 | MessageType.TEXT 直接就是 "text",JSON 序列化零成本 |
asyncio.TaskGroup | 一群异步任务,一个出错全部取消,更安全 | EventBus 并发执行多个事件处理器 |
asyncio.timeout | 给一段代码设置"最多等多久" | Agent 推理循环的超时保护 |
异常组 ExceptionGroup | 多个错误可以同时汇报 | 并发任务的错误处理 |
二、异步编程(asyncio):为什么不用"普通"代码?
同步 vs 异步——用餐厅服务员来理解
同步编程就像一个笨拙的服务员:
服务员走到 1 号桌 → 点菜 → 等厨房做好 → 上菜 → 才去 2 号桌如果 1 号桌点了一道复杂的菜要等 10 分钟,2 号桌就得干等 10 分钟。
异步编程就像一个机灵的服务员:
服务员走到 1 号桌 → 点菜 → 告诉厨房 → 不等了,去 2 号桌
等厨房喊"1 号桌的菜好了" → 再回来上菜等待的时间不浪费,去服务别的客人。
为什么 AI Agent 需要异步?
AI Agent 有大量的"等待时间":
| 操作 | 等待时间 | 如果不异步 |
|---|---|---|
| 调用 LLM(大模型) | 1-10 秒 | 所有用户排队干等 |
| 执行 HTTP 工具(网络请求) | 0.5-5 秒 | 一个工具调用卡住整个系统 |
| 读写文件存储 | 几十毫秒 | 频繁 IO 累积起来很可观 |
| WebSocket 收发消息 | 不确定 | 一个连接卡住影响所有连接 |
用异步编程,一个进程就能同时服务几十上百个用户,不需要启动很多进程或线程。
代码长什么样?
python
# 同步写法(一个一个等)
def handle_users():
result1 = call_llm(user1_message) # 等 3 秒
result2 = call_llm(user2_message) # 再等 3 秒
# 总共 6 秒
# 异步写法(同时等)
async def handle_users():
task1 = call_llm(user1_message) # 发出请求,不等
task2 = call_llm(user2_message) # 发出请求,不等
result1, result2 = await asyncio.gather(task1, task2)
# 总共约 3 秒(两个请求同时在等)三、通信协议:WebSocket vs HTTP
打电话 vs 发短信
| 对比 | WebSocket(打电话) | HTTP(发短信) |
|---|---|---|
| 连接方式 | 一直保持连接,双方随时说话 | 发一条回一条,每次重新连接 |
| 实时性 | 高,服务端可以随时推送 | 低,客户端必须主动问 |
| 适合场景 | 聊天、实时通知、流式输出 | 简单查询、表单提交 |
| 资源消耗 | 长连接占用资源 | 用完即释放 |
miniOpenClaw 的选择
- Gateway 用 WebSocket:Agent 需要"实时对话"能力,服务端可能要主动推送消息(比如流式输出)
- WebChat Channel 同时支持两种:HTTP POST 方便测试和脚本调用,WebSocket 支持实时聊天
- Webhook Channel 用 HTTP:第三方平台回调天然就是 HTTP
这样设计兼顾了"实时性"和"便利性"。
四、数据校验:Pydantic 是干什么的?
类比:快递单格式校验
想象你寄快递,快递单上要填:收件人、电话、地址、重量。如果你漏填了电话,或者把重量写成了"很重"(应该写数字),快递员会当场拒收。
Pydantic 就是程序里的"快递单格式检查员":
python
from pydantic import BaseModel
class GatewayMessage(BaseModel):
msg_id: str # 必须是字符串
msg_type: MessageType # 必须是规定的几种类型之一
source: str # 必须是字符串
payload: Any # 什么都行
timestamp: datetime # 必须是时间格式如果有人传了一个 msg_type = "hahaha"(不在规定类型里),Pydantic 会立刻报错,而不是让这个"格式不对的快递"一路流转到系统深处才出问题。
为什么不用普通的 dict?
| 对比 | 普通 dict | Pydantic Model |
|---|---|---|
| 格式检查 | 无,随便塞什么都行 | 自动检查类型、必填项、取值范围 |
| 序列化 | 手写 json.dumps | 内置 .model_dump_json() |
| IDE 提示 | 无,msg["typ"] 写错了也不知道 | 有,msg.msg_type 拼错会报红 |
| 文档 | 要另外写 | 代码本身就是文档 |
五、各依赖库的选择理由
5.1 websockets —— WebSocket 服务端
| 为什么选它 | 说明 |
|---|---|
| 官方推荐 | Python 生态中最成熟的 WebSocket 库 |
| 纯异步 | 天然支持 asyncio,不需要额外的适配层 |
| 轻量 | 不像 aiohttp 那样是个全功能 Web 框架,只做 WebSocket 一件事 |
类比:如果 aiohttp 是一把瑞士军刀,websockets 就是一把专业螺丝刀——做一件事,做到最好。
5.2 aiohttp —— HTTP 服务端
| 为什么选它 | 说明 |
|---|---|
| 异步原生 | 从设计之初就是异步的,不是后来改的 |
| 功能完整 | 路由、中间件、WebSocket(服务端)、静态文件——Web 服务该有的都有 |
| 与 asyncio 一致 | 和项目的异步风格完全匹配 |
用在哪:WebChat Channel(HTTP /chat 接口)、Webhook Channel(接收外部平台回调)。
为什么不用 Flask/FastAPI?
- Flask 是同步的,和我们的异步设计冲突
- FastAPI 功能更强但更重(依赖 Starlette + Uvicorn),对教学项目来说太厚
5.3 httpx —— HTTP 客户端
| 为什么选它 | 说明 |
|---|---|
| 同时支持同步和异步 | httpx.AsyncClient 完美配合 asyncio |
| 现代 API | 比 requests 更现代,类型标注完善 |
| HTTP/2 支持 | 面向未来 |
用在哪:内置的 http_request 工具——让 Agent 能发 HTTP 请求获取信息。
类比:如果说 requests 是功能手机(经典好用),httpx 就是智能手机(经典功能都有,还多了很多新功能)。
5.4 openai —— LLM 调用客户端
| 为什么选它 | 说明 |
|---|---|
| 官方 SDK | 与 OpenAI API 100% 对齐,类型完善 |
| 兼容性好 | 大量第三方 LLM 服务(Azure、国内模型等)都兼容 OpenAI API 格式 |
| 异步支持 | AsyncOpenAI 完美配合我们的异步设计 |
| Function Calling | 内置支持 tools/function_calling,和我们的工具系统无缝对接 |
5.5 tiktoken —— Token 计数
什么是 Token?
LLM 不是按"字"来处理文本的,而是按 "Token" 来的。Token 是 LLM 的"最小处理单位":
| 文本 | 大约 Token 数 |
|---|---|
| "你好" | 约 2 个 |
| "Hello" | 1 个 |
| "Hello, World!" | 约 4 个 |
为什么要计数? 因为 LLM 有上下文长度限制(比如最多 128K token)。如果对话太长,需要截断旧消息。tiktoken 帮我们精确计算消息占了多少 Token。
类比:就像打电话有通话时长限制,tiktoken 帮你看"还剩多少分钟"。
5.6 python-dotenv —— 环境变量管理
解决什么问题? API Key 这种敏感信息不能写在代码里(万一提交到 GitHub 就暴露了)。python-dotenv 让你把敏感信息写在 .env 文件里,代码自动读取。
bash
# .env 文件(不提交到 Git)
OPENAI_API_KEY=sk-你的密钥python
# 代码里自动读取
import os
api_key = os.getenv("OPENAI_API_KEY")类比:就像密码不写在便利贴上,而是放在保险箱里——需要的时候去取。
六、为什么不用 LangChain?—— 详细对比
LangChain 是目前最流行的 AI Agent 框架,很多人的第一反应是"直接用 LangChain 不就好了"。我们来做一个诚实的对比:
LangChain 帮你做了什么
| 功能 | LangChain 做法 | miniOpenClaw 做法 |
|---|---|---|
| LLM 调用 | 封装好了,一行代码 | 自己用 OpenAI SDK,理解每个参数 |
| 工具调用 | 大量内置工具,直接用 | 自己写 Tool 基类和 @tool 装饰器 |
| 记忆管理 | 多种 Memory 类型开箱即用 | 自己实现短期/长期记忆 |
| 链式调用 | Chain/LCEL 声明式编排 | 自己写 ReAct 循环 |
| 向量检索 | 集成了几十种向量库 | 简单关键词匹配(教学足够) |
但 LangChain 也有代价
| 问题 | 说明 |
|---|---|
| 抽象层太多 | 一个简单的 LLM 调用要经过 5-6 层封装,出错了很难定位 |
| 版本变动大 | API 经常大改,今天写的代码明天可能就跑不了 |
| 学会了"用框架"而不是"理解原理" | 离开 LangChain 就不会写了 |
| 依赖树庞大 | 装一个 LangChain 带进来几百个依赖 |
总结一句话
LangChain 适合"快速做产品",miniOpenClaw 适合"深入学原理"。 先理解了原理,再用框架才能用得明明白白。
七、测试工具
| 工具 | 用途 | 白话解释 |
|---|---|---|
| pytest | 运行测试 | 自动检查"代码是否正确"的工具 |
| pytest-asyncio | 测试异步代码 | 让 pytest 能测 async def 函数 |
| pytest-cov | 覆盖率统计 | 告诉你"有多少代码被测试覆盖了" |
| ruff | 代码检查 | 自动检查代码风格问题(缩进、命名、导入顺序等) |
接下来读什么?
你已经了解了整个项目的需求、架构和技术选型。现在可以开始动手了:
- Day 1: Gateway 与消息协议 —— 写第一行代码,搭建消息基础设施