1. 背景与核心概念
这两年 AI 大模型发展非常快,从最初的网页聊天工具,逐渐变成可以嵌入到各种设备中的“智能大脑”。我们习惯了在手机上打开 App 和 AI 对话,但在某些场景下,掏手机、解锁、打开应用、输入文字这一套流程还是太繁琐了。于是很多人开始琢磨:能不能做一个巴掌大小、带语音交互、能随身携带的 AI 助手设备?
我给这个项目起了个名字叫“口袋 AI 助手”。它的定位很简单:小体积、低功耗、能语音交互、能调用大模型 API、能离线也能联网,主打一个“小身体大智慧”。
这个项目之所以值得做,是因为它把以下几件事串在了一起:
- 嵌入式硬件开发,包括主控选型、外设连接、电源管理;
- 语音采集与播放,涉及麦克风阵列或单麦降噪、音频编解码;
- 大模型 API 接入,需要处理 HTTP 请求、JSON 解析、流式输出;
- 本地知识库与记忆机制,让助手能记住上下文,甚至结合个人知识库回答;
- 低功耗优化和移动供电,关系到设备能不能真的“随身带”。
从技术角度看,这个项目的难点不在于某一个环节有多复杂,而在于工程整合。硬件、软件、云端接口、用户体验,每个环节都有坑。本文会把整套方案的核心思路、关键代码、硬件选型和排查经验整理出来,如果你想自己复刻一个,可以参考这个路线逐步落地。
在开始之前,先明确几个容易混淆的概念:
- AI 助手 vs 智能音箱。智能音箱通常是固定电源、固定位置的设备,依赖云端技能生态。口袋 AI 助手是随身设备,更强调离线唤醒、低功耗、便携性和私有化数据。
- 在线大模型 vs 本地模型。在线大模型(如 GPT 系列、通义千问、文心一言等)能力强,但需要网络,且要考虑 API 密钥安全和调用成本。本地模型(如通过 llama.cpp、Ollama 部署的小参数模型)可以离线运行,但需要设备有足够的算力。
- 唤醒词 vs 按键触发。智能音箱常用唤醒词(如“小爱同学”),功耗高,需要持续监听。口袋设备为了省电,很多设计采用按键触发或低功耗唤醒芯片方案。
本文的实战案例会采用“按键触发 + 在线大模型 API + 本地记忆”的架构,兼顾功耗和智能化程度。最后的扩展部分再讨论如何换成离线模型方案。
2. 环境准备与版本说明
在动手之前,先把开发和运行环境梳理一下。
2.1 硬件清单
口袋 AI 助手属于嵌入式 IoT 设备,硬件选型成本大约在 100-200 元人民币范围。以下是参考清单:
| 部件 | 型号示例 | 作用 | 注意事项 |
|---|---|---|---|
| 主控开发板 | ESP32-S3-DevKitC | 运行主程序、联网、处理音频 | 选择带 8MB PSRAM 的版本,音频处理更从容 |
| 麦克风 | INMP441(I2S 接口) | 采集用户语音 | 单麦即可;追求降噪可上双麦阵列 |
| 音频功放 | MAX98357A(I2S 接口) | 播放 AI 回复语音 | 支持 3W 输出,小喇叭够用 |
| 小喇叭 | 8Ω 1W-3W 小喇叭 | 发声 | 尺寸要匹配外壳设计 |
| 电池 | 18650 锂电池或 3.7V 聚合物电池 | 移动供电 | 建议配充电保护板 |
| 充电模块 | TP4056 Type-C 模块 | 电池充电 | 支持过充过放保护 |
| 按键 | 轻触按键 | 触发语音输入 | 长按/短按可以设计不同功能 |
| 外壳 | 3D 打印外壳 | 保护与便携 | 可后面再设计 |
2.2 开发环境
本文代码以 Python(MicroPython 固件)为主,原因是开发效率高、迭代快,适合原型验证。如果你更熟悉 C/C++,也可以基于 ESP-IDF 实现,原理类似。
| 软件/工具 | 版本说明 | 用途 |
|---|---|---|
| MicroPython | 以 ESP32-S3 官方固件为准,建议使用 2023 年之后版本 | 在开发板上运行 Python 代码 |
| Thonny | 4.x 或以上 | 烧录固件、编写代码、串口调试 |
| Python | 3.9 或以上(PC 端) | 编写测试脚本、搭建模拟 API |
| 串口驱动 | CP210x 或 CH340 驱动 | 开发板通过 USB 连接电脑需要 |
| 大模型 API | OpenAI 兼容接口或国内大模型服务 | 提供对话能力 |
版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。
2.3 项目结构
项目代码建议按以下结构组织:
pocket-ai-assistant/ ├── boot.py # 开机启动,初始化网络 ├── main.py # 主程序循环 ├── config.py # 配置信息(WiFi、API Key) ├── wifi_helper.py # WiFi 连接模块 ├── audio_helper.py # I2S 音频采集与播放 ├── llm_helper.py # 大模型 API 调用模块 ├── memory.py # 本地记忆存储模块 ├── lib/ │ ├── inmp441.py # 麦克风驱动封装 │ ├── max98357a.py # 功放驱动封装 │ └── urequests.py # HTTP 请求库(MicroPython 可用) └── README.md这个结构把不同功能模块分开,后续如果要移植到其他硬件平台,只需要替换底层驱动模块,上层逻辑基本不用动。
3. 核心原理与模块拆解
3.1 为什么选择 ESP32-S3
ESP32-S3 是乐鑫推出的一款 AIoT 芯片,对比传统 ESP32,它的主要优势包括:
- 支持向量指令加速,对神经网络推理有一定帮助;
- 内置 USB OTG,方便调试和烧录;
- 内存更大,外接 PSRAM 后可以处理更长的音频缓冲区;
- 双核 Xtensa LX7 处理器,主频可达 240MHz;
- 支持 BLE 和 Wi-Fi,满足联网需求。
对于口袋 AI 助手来说,ESP32-S3 的 I2S 外设非常重要。麦克风和功放都通过 I2S 接口传输音频数据,ESP32-S3 可以同时配置多条 I2S 总线,因此能实现“录音同时播放”的功能。
3.2 音频链路设计
整个语音交互链路由两部分组成:
采集链路:
麦克风(INMP441) -> I2S 总线 -> ESP32-S3 -> 音频数据缓冲 -> 发送到大模型 API播放链路:
大模型 API 返回文本 -> ESP32-S3 调用 TTS 服务 -> 音频数据 -> I2S 总线 -> 功放 -> 喇叭这里有一个设计难点:大模型 API 返回的是文本,不是音频。因此需要选用支持 TTS(文本转语音)的服务,或者自己部署一个轻量 TTS 模型。为了方便演示,本文示例直接采用云端 TTS 服务,返回 MP3 或 WAV 音频数据。
如果不想依赖 TTS 服务,也可以让设备直接输出文字到一块小显示屏上,这样就省去了解码音频的步骤。但作为“语音助手”,带发声功能体验才完整,所以本文选择语音播放方案。
3.3 大模型 API 接入思路
大模型 API 的接入方式一般是 HTTP POST 请求,发送对话消息,接收回复。目前主流模型的接口都兼容 OpenAI 的/v1/chat/completions格式,所以代码可以通用。
一个完整的对话请求体如下:
{ "model": "gpt-4o-mini", "messages": [ {"role": "system", "content": "你是一个口袋 AI 助手,请用简短的中文回答问题。"}, {"role": "user", "content": "今天天气怎么样?"} ], "stream": false }在嵌入式设备上,由于内存有限,一般不建议开启流式输出(stream: true),而是等模型生成完整结果后一次性接收。如果你希望设备响应更快,可以后续优化为流式解析,但要注意缓冲区管理。
3.4 记忆机制
口袋 AI 助手的记忆机制分为两层:
- 短期记忆:在会话过程中,把用户消息和 AI 回复存放在内存列表中,随请求一起发送到大模型,形成多轮对话能力。
- 长期记忆:把重要信息(比如用户偏好、常用命令、日程)保存到本地 Flash 或 SD 卡,下次启动时加载,并在构造 prompt 时加入。
长期记忆的简单实现是使用 JSON 文件存储键值对,例如:
{ "username": "小明", "preference": "回答尽量简洁,不要超过50字", "reminder": "每周五下午3点提醒我开周会" }构造请求时,把这些内容转换为 system prompt:
你是我的口袋 AI 助手。 用户信息:姓名小明,偏好简洁回答。 请结合以上信息进行对话。这种实现不依赖数据库,在 MicroPython 环境中非常实用。
3.5 唤醒交互设计
考虑到功耗和实用性,口袋 AI 助手的交互逻辑可以这样设计:
- 短按按键:开始录音并发送请求,AI 回复后自动播放。
- 长按按键:进入设置模式,可以通过语音修改助手名称、提醒事项等。
- 双击按键:查看本地记忆内容。
这种交互比持续监听唤醒词要省电得多。如果后续想增加唤醒词功能,可以外接一个低功耗语音唤醒芯片(比如启英泰伦的 CI1122),让主控平时休眠,唤醒后再启动大模型处理。
4. 完整实战案例
接下来我们实现一个最小可用版本:按键触发语音输入,调用云端大模型 API 获取回答,然后用 TTS 播放出来。为了方便没有硬件的读者,我也会提供一个 Python 模拟版,你可以在电脑上先跑通逻辑。
4.1 创建项目结构
先在电脑上创建项目目录:
mkdir pocket-ai-assistant cd pocket-ai-assistant touch boot.py main.py config.py wifi_helper.py audio_helper.py llm_helper.py memory.py mkdir lib4.2 编写 config.py 配置模块
文件路径:pocket-ai-assistant/config.py
# 配置信息,请根据实际服务商修改 WIFI_SSID = "your_wifi_ssid" WIFI_PASSWORD = "your_wifi_password" # 大模型 API 配置 LLM_API_KEY = "sk-your-api-key" LLM_API_URL = "https://api.openai.com/v1/chat/completions" LLM_MODEL = "gpt-4o-mini" # TTS 服务配置 TTS_API_URL = "https://api.openai.com/v1/audio/speech" TTS_VOICE = "alloy" # 系统提示词 SYSTEM_PROMPT = "你是一个口袋 AI 助手,请用简短自然的中文回答问题。"这里有一点需要特别注意:API Key 是敏感信息,在真实项目中不要硬编码在代码里。ESP32 设备的固件很容易被读取,建议至少把 API Key 放在单独的配置区域,并设置编译时加密,或者使用后端代理转发请求。
4.3 编写 wifi_helper.py 网络模块
文件路径:pocket-ai-assistant/wifi_helper.py
import network import time def connect_wifi(ssid, password, timeout=15): """ 连接 Wi-Fi 网络 :param ssid: 无线网络名称 :param password: 无线网络密码 :param timeout: 超时时间(秒) :return: 是否连接成功 """ wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print("正在连接 Wi-Fi:", ssid) wlan.connect(ssid, password) start_time = time.time() while not wlan.isconnected(): if time.time() - start_time > timeout: print("Wi-Fi 连接超时") return False time.sleep(0.5) print("Wi-Fi 连接成功,IP 地址:", wlan.ifconfig()[0]) return True def is_connected(): """检查当前是否已连接 Wi-Fi""" wlan = network.WLAN(network.STA_IF) return wlan.isconnected()在口袋设备中,Wi-Fi 连接状态很关键。如果设备移动过程中网络断开,需要在主循环中定期检查并自动重连。
4.4 编写 llm_helper.py 大模型调用模块
文件路径:pocket-ai-assistant/llm_helper.py
import urequests import json import config def chat_with_llm(messages): """ 调用大模型 API 获取对话回复 :param messages: 对话消息列表 :return: AI 回复文本 """ headers = { "Content-Type": "application/json", "Authorization": "Bearer " + config.LLM_API_KEY } payload = { "model": config.LLM_MODEL, "messages": messages, "temperature": 0.7, "max_tokens": 300 } try: response = urequests.post( config.LLM_API_URL, headers=headers, data=json.dumps(payload).encode("utf-8"), timeout=30 ) print("HTTP 状态码:", response.status_code) if response.status_code == 200: data = response.json() reply = data["choices"][0]["message"]["content"] return reply else: print("API 返回错误:", response.text) return None except Exception as e: print("调用大模型 API 异常:", e) return None finally: if "response" in dir(): response.close()这里要注意的是,MicroPython 的urequests库对 HTTPS 请求支持已经比较完善,但如果你使用的是自签名证书或特殊端口,可能需要额外处理。另外,大模型 API 的响应可能很大,建议在服务端或代理层做大小限制,防止设备内存溢出。
4.5 编写 memory.py 本地记忆模块
文件路径:pocket-ai-assistant/memory.py
import json import os MEMORY_FILE = "memory.json" def load_memory(): """ 从 Flash 中加载长期记忆 :return: 字典形式的记忆内容 """ try: with open(MEMORY_FILE, "r") as f: return json.load(f) except (OSError, ValueError): return {} def save_memory(memory_dict): """ 保存长期记忆到 Flash :param memory_dict: 字典形式的记忆内容 """ try: with open(MEMORY_FILE, "w") as f: json.dump(memory_dict, f) print("记忆已保存") except Exception as e: print("保存记忆失败:", e) def add_memory(key, value): """ 添加一条记忆 :param key: 记忆键 :param value: 记忆值 """ memory = load_memory() memory[key] = value save_memory(memory) def build_system_prompt(): """ 根据记忆内容构造系统提示词 """ memory = load_memory() prompt = config.SYSTEM_PROMPT if memory: prompt += "\n以下是用户长期记忆信息:\n" for key, value in memory.items(): prompt += f"- {key}: {value}\n" return prompt这个模块在设备重启后仍然能保留用户偏好,是实现“越用越懂你”的关键。
4.6 编写 audio_helper.py 音频模块
文件路径:pocket-ai-assistant/audio_helper.py
由于 INMP441 和 MAX98357A 的底层寄存器配置比较复杂,这里给出一个简化封装思路。完整实现需要根据具体驱动库调整。
from machine import I2S, Pin import struct class AudioHelper: """ 音频采集与播放辅助类 使用 INMP441 麦克风 + MAX98357A 功放 """ def __init__(self): # 麦克风 I2S 配置 self.mic = I2S( 0, sck=Pin(4), # BCLK ws=Pin(5), # LRCLK sd=Pin(6), # DIN mode=I2S.RX, bits=16, format=I2S.MONO, rate=16000, ibuf=32000 ) # 功放 I2S 配置 self.speaker = I2S( 1, sck=Pin(15), # BCLK ws=Pin(16), # LRCLK sd=Pin(17), # DOUT mode=I2S.TX, bits=16, format=I2S.MONO, rate=16000, ibuf=32000 ) def record_audio(self, duration_ms=3000): """ 录制音频数据(PCM 格式) :param duration_ms: 录制时长(毫秒) :return: 字节数组 """ frame_count = int(16000 * duration_ms / 1000) audio_data = bytearray(frame_count * 2) # 实际使用时需要循环读取 I2S 数据 # 这里省略底层读取细节,记录逻辑根据实际驱动编写 return bytes(audio_data) def play_audio(self, pcm_data): """ 播放 PCM 音频数据 :param pcm_data: PCM 字节数据 """ self.speaker.write(pcm_data)在实际项目中,麦克风录到的是裸 PCM 数据,直接发送给大模型 API 是不行的,需要先经过语音识别(ASR)转成文本。有两种做法:
- 使用云端 ASR 服务(如 Whisper API、讯飞语音识别),将 PCM 编码为 WAV 或 MP3 后上传。
- 使用本地离线 ASR 模块,比如 ESP32-S3 上跑 WakeNet 或轻量语音识别模型。
考虑到 ESP32-S3 的算力有限,本文示例采用云端 ASR 方案,也就是先把录音文件传到 ASR 服务,拿到文本后再发到大模型。
4.7 编写主程序 main.py
文件路径:pocket-ai-assistant/main.py
import time from machine import Pin import config import wifi_helper from llm_helper import chat_with_llm from memory import load_memory, build_system_prompt, add_memory # 初始化按键(GPIO 0 常用于 BOOT 按键) button = Pin(0, Pin.IN, Pin.PULL_UP) def wait_for_button_press(timeout_ms=1000): """ 等待按键按下(低电平触发) """ start = time.ticks_ms() while time.ticks_ms() - start < timeout_ms: if button.value() == 0: # 消抖 time.sleep_ms(50) if button.value() == 0: return True time.sleep_ms(10) return False def main(): print("口袋 AI 助手启动中...") # 连接 Wi-Fi if not wifi_helper.connect_wifi(config.WIFI_SSID, config.WIFI_PASSWORD): print("网络连接失败,请检查配置") return # 加载记忆并构造系统提示词 system_prompt = build_system_prompt() messages = [ {"role": "system", "content": system_prompt} ] print("请按下按键开始语音对话") while True: if wait_for_button_press(timeout_ms=5000): print("按键已按下,开始录音...") # 这里演示为直接输入文本,实际场景需要接入 ASR 服务 user_input = input("请输入要问的内容(模拟语音识别结果): ") if user_input == "退出" or user_input == "quit": print("退出对话") break # 保存用户消息 messages.append({"role": "user", "content": user_input}) # 调用大模型 print("正在请求大模型...") reply = chat_with_llm(messages) if reply: print("AI 助手:", reply) # 保存助手回复 messages.append({"role": "assistant", "content": reply}) # 简单记忆示例:如果用户说“记住”,则存入记忆 if user_input.startswith("记住"): memory_key_value = user_input[2:].strip() if "是" in memory_key_value: parts = memory_key_value.split("是") add_memory(parts[0].strip(), parts[1].strip()) else: print("没有获取到 AI 回复") time.sleep_ms(100) if __name__ == "__main__": main()这个主程序把核心流程串起来了:等待按键、获取输入、调用大模型、保存上下文、处理记忆。
4.8 PC 端模拟版本
如果你暂时没有硬件,可以在电脑上跑一个模拟版本,验证对话逻辑是否正常。需要安装requests库:
pip install requests文件路径:pocket-ai-assistant/simulator.py
import requests import json import config def chat_with_llm_simulator(messages): """ PC 端模拟调用大模型 API """ headers = { "Content-Type": "application/json", "Authorization": "Bearer " + config.LLM_API_KEY } payload = { "model": config.LLM_MODEL, "messages": messages, "temperature": 0.7 } response = requests.post( config.LLM_API_URL, headers=headers, json=payload, timeout=30 ) if response.status_code == 200: data = response.json() return data["choices"][0]["message"]["content"] else: print("API 返回错误:", response.status_code, response.text) return None if __name__ == "__main__": messages = [ {"role": "system", "content": config.SYSTEM_PROMPT} ] print("口袋 AI 助手模拟器(输入 exit 退出)") while True: user_input = input("你: ") if user_input.lower() == "exit": break messages.append({"role": "user", "content": user_input}) reply = chat_with_llm_simulator(messages) if reply: print("AI:", reply) messages.append({"role": "assistant", "content": reply})运行模拟器:
cd pocket-ai-assistant python simulator.py预期输出:
口袋 AI 助手模拟器(输入 exit 退出) 你: 你好,请介绍一下你自己 AI: 你好!我是一个口袋 AI 助手,随时为你解答问题、记录信息、提供帮助。请问有什么可以帮你的吗?4.9 烧录到开发板
如果要在 ESP32-S3 上运行,需要先将 MicroPython 固件烧录到开发板:
- 下载对应芯片型号的 MicroPython 固件(
.bin文件)。 - 按住开发板上的 BOOT 键,用 USB 线连接电脑。
- 使用 esptool 烧录固件:
pip install esptool esptool.py --port COM3 erase_flash esptool.py --port COM3 write_flash -z 0x0 esp32s3-20231005-v1.21.0.bin注意:COM3要根据你的实际串口号修改,Windows 系统可以在设备管理器中查看。
烧录完成后,用 Thonny 连接开发板,将项目文件上传到开发板 Flash 根目录,然后运行main.py即可。
5. 常见问题与排查思路
在实际开发中,口袋 AI 助手最容易踩坑的地方集中在网络、音频和内存管理三个方向。下面整理了一张排查表。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Wi-Fi 连接失败 | 2.4G 频段未开启、密码错误、信号弱 | ESP32-S3 不支持 5G Wi-Fi,确认路由器开启 2.4G |
| HTTPS 请求报错 | 固件缺少 TLS 证书 | 升级 MicroPython 固件,或使用urequests时设置 verify 参数 |
| 录音声音很小 | INMP441 需要偏置电压,接线错误 | 检查麦克风的 L/R 引脚是否接地,确认供电 3.3V |
| I2S 播放有杂音 | 功放供电不足或接地不良 | 使用独立 3.3V/5V 供电,共地处理 |
| 设备运行一段时间后死机 | 内存泄漏 | 检查是否频繁创建大对象,及时关闭 HTTP 响应连接 |
| API 请求超时 | 网络不稳定或 API 服务响应慢 | 设置合理超时时间,增加重试机制 |
| 大模型返回内容超长 | 未设置 max_tokens | 在请求 payload 中明确限制 token 数量 |
| TTS 音频无法播放 | 音频格式不匹配(MP3 解码问题) | 使用支持 MP3 解码的音频模块,或转换为 WAV/PCM 格式 |
下面挑几个重点问题展开说。
5.1 HTTPS 请求证书问题
这是 MicroPython 开发者最常见的问题之一。较旧的固件版本可能不包含根证书,导致urequests请求 HTTPS 接口时报错:
OSError: [Errno 116] EINVAL或者:
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed解决方案有两种:
- 升级到较新的 MicroPython 固件版本,通常新版固件会带上更完整的 TLS 支持。
- 在请求时修改
ssl参数,使用ssl.MODE_NO_VERIFY模式。但这种方式会降低安全性,仅建议在局域网测试时使用。
import ssl import urequests # 不推荐在生产环境使用,仅用于测试 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.verify_mode = ssl.CERT_NONE response = urequests.post(url, headers=headers, data=data, timeout=30, ssl=context)5.2 INMP441 麦克风无声问题
INMP441 是一款很常见的 I2S 数字麦克风,但它有一些硬件细节需要注意:
- L/R 引脚接地时使用左声道,接 VDD 时使用右声道。通常我们需要将其接地。
- SD 引脚是数据输出,必须连接到 ESP32-S3 的 I2S DIN 引脚。
- 麦克风供电不能超过 3.3V,否则可能损坏。
- 如果录音数据全为 0 或全为噪声,优先检查接线和供电稳定性。
5.3 内存不足问题
ESP32-S3 虽然有 320KB 左右的 SRAM,但 MicroPython 运行时本身会占用不少内存。如果外接 PSRAM,需要确保固件支持esp32.SPIRAM。
在代码中可以通过如下方式查看内存:
import gc print("可用内存:", gc.mem_free())建议在每次大模型请求结束后调用gc.collect()手动回收内存。
5.4 音频数据格式不匹配问题
麦克风采集到的是 16-bit PCM 数据,采样率通常为 16kHz。但大模型的 ASR 接口可能需要 WAV 或 MP3 格式,因此需要做格式转换。
在 MicroPython 中生成 WAV 文件头可以这样实现:
import struct def create_wav_header(sample_rate=16000, bits_per_sample=16, channels=1, data_size=0): byte_rate = sample_rate * channels * bits_per_sample // 8 block_align = channels * bits_per_sample // 8 header = b"RIFF" header += struct.pack("<I", 36 + data_size) header += b"WAVE" header += b"fmt " header += struct.pack("<I", 16) header += struct.pack("<H", 1) header += struct.pack("<H", channels) header += struct.pack("<I", sample_rate) header += struct.pack("<I", byte_rate) header += struct.pack("<H", block_align) header += struct.pack("<H", bits_per_sample) header += b"data" header += struct.pack("<I", data_size) return header然后将头部和 PCM 数据拼接上传即可。
6. 最佳实践与工程建议
6.1 密钥安全与后端代理
前面提到,直接在设备固件中写 API Key 风险很高。任何拿到你设备的人都有可能通过串口读取固件,提取密钥,造成资损。更安全的做法是引入一个轻量后端代理:
ESP32-S3 -> 后端代理服务器 -> 大模型 API设备只保存后端服务器的临时 Token,真正的 API Key 存放在服务器环境变量中。后端代理还可以做限流、日志记录、内容过滤,方便后续扩展。
以 Python Flask 为例,代理服务核心代码如下:
import os import requests from flask import Flask, request, jsonify app = Flask(__name__) OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_API_URL = "https://api.openai.com/v1/chat/completions" @app.route("/chat", methods=["POST"]) def chat(): data = request.get_json() headers = { "Content-Type": "application/json", "Authorization": f"Bearer {OPENAI_API_KEY}" } response = requests.post(OPENAI_API_URL, json=data, headers=headers, timeout=30) return jsonify(response.json()) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)设备端只需要把请求发送到你的代理服务器地址即可,后端负责与真正的大模型服务通信。
6.2 请求重试与幂等设计
语音助手在弱网环境下很容易遇到请求超时。建议在设备端实现简单的重试逻辑:
MAX_RETRY = 3 def chat_with_retry(messages): for attempt in range(MAX_RETRY): reply = chat_with_llm(messages) if reply: return reply print(f"第 {attempt + 1} 次请求失败,准备重试...") time.sleep(2) return None需要注意的是,如果你的后端接口不是幂等的,重试可能导致重复扣费或生成重复内容。可以在请求中增加一个request_id字段,后端做去重处理。
6.3 低功耗优化策略
口袋设备如果要实现长时间待机,低功耗设计是必须考虑的。以下是一些实用策略:
- 深度睡眠模式。当设备空闲时,进入
machine.deepsleep(),按键通过外部中断唤醒。 - 关闭不需要的外设。在不录音、不播放时,释放 I2S 引脚和关闭功放电源。
- 限制 API 调用频率。避免用户误触导致频繁请求,每次请求前加入冷却时间。
- 选型低功耗器件。INMP441 和 MAX98357A 在待机时功耗都不高,但功放芯片在无信号输入时会存在静态电流,可以在功放供电端加入 MOS 管开关控制。
6.4 日志与调试
嵌入式设备出现问题时,日志是最好的排查手段。建议在项目早期就建立日志框架:
- 业务日志:记录每次请求的大模型、token 消耗、响应时间。
- 错误日志:记录网络异常、内存异常、API 错误码。
- 音频日志:记录录音时长、音频电平峰值,便于判断麦克风是否正常工作。
MicroPython 中可以使用以下方式打日志:
import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) logger.info("口袋 AI 助手启动成功")6.5 模型选型建议
不同大模型在中文理解、响应速度、API 费用上差异较大。口袋 AI 助手场景对延迟和成本比较敏感,建议遵循以下原则:
- 优先选择轻量模型。若后端用 OpenAI 兼容接口,可以考虑
gpt-4o-mini这类速度较快的小模型。 - 如果部署在境内,注意 API 服务的合规性和网络延迟。选择国内大模型服务商或自部署开源模型更稳妥。
- 对回答长度做限制。在 system prompt 中明确要求“回答不超过 100 字”,既降低 token 成本,也减少 TTS 播放时间。
6.6 代码工程化
不要把所有代码堆在main.py里。参考本文的项目结构,把功能模块拆分,每组函数职责单一。后续你可以在 PC 上先写单元测试,再用部署脚本同步到开发板。
这里推荐一个简单流程:
- PC 端用 Python 编写并测试核心逻辑。
- 功能验证通过后,移植到 MicroPython。
- 代码同步前用
mpremote或 Thonny 检查语法。 - 每次修改后先跑模拟器,再烧录真机。
7. 总结与学习路线
这篇文章从口袋 AI 助手的整体架构出发,完整拆解了硬件选型、音频链路、大模型 API 接入、本地记忆、按键交互、常见排错和工程化建议。核心收获可以归纳为三点:
第一,口袋 AI 助手是典型的 AIoT 整合型项目,单点技术难度不高,但把语音采集、网络通信、LLM 调用、电源管理串起来后,复杂度会明显上升。建议先跑通模拟器,再逐步切换真实硬件。
第二,音频链路是最大的坑。麦克风接线、I2S 配置、PCM 格式转换、TTS 音频解码,每一个环节都可能让你调试半天。建议分步验证:先把录音数据保存下来,在 PC 上确认音频质量,再去对接 ASR 和 TTS。
第三,密钥安全和管理不能偷懒。无论是 API Key 还是用户数据,都要遵循最小权限原则,能通过后端代理转发就绝不放设备端。
如果你把本文的基础版本跑通了,下一步可以按以下方向继续深入学习:
- 接入离线语音唤醒,让设备像智能音箱一样“随叫随到”;
- 集成本地向量数据库,实现基于个人文档的问答;
- 用 ESP-IDF C 语言重写底层驱动,降低延迟并减少内存占用;
- 设计 3D 打印外壳,加入屏幕显示交互状态;
- 引入 RTC 模块,实现定时提醒和日程管理功能。
AI 硬件项目最有意思的地方在于,你永远可以往里面加新能力。把最小闭环跑通,剩下的就交给持续迭代了。如果本文对你有帮助,可以收藏备用,动手做一个属于自己的口袋 AI 助手。