ai · 2026 · 技术研究笔记
把 DeepSeek Harness 放到局域网,并给它加上实时语音输入
能从手机打开只是第一步,麦克风、HTTPS 和实时音频链路才是难点
记录一次真实的 DeepSeek Harness 本地改造:监听 0.0.0.0、用 Caddy 补上局域网 HTTPS,并把 Sayi 实时语音识别接入对话输入框
研究版 · 7 个章节 · 约 13 分钟 · 更新于 2026-08-24
我平时在电脑上运行 DeepSeek Harness,但有些任务并不适合一直坐在桌前。比如让 Agent 跑一段耗时操作,或者临时补一句要求,如果手机能直接打开同一个页面,会方便很多。再往前走一步,手机上最自然的输入方式不是键盘,而是说话。
这次改造因此只有两个表面目标:让 Web 服务能从局域网访问,给对话输入框加一个麦克风按钮。真正动手后才发现,它们其实是一条连续的链路:服务要监听局域网地址,浏览器要信任 HTTPS 页面,页面要取得麦克风权限,原始音频还要转成识别服务接受的格式。
本文记录的是 DeepSeek Harness 0.1.1-rc.1 本地工作区中的实现。它不是上游项目的标准配置,接口和文件位置也可能随版本变化。要先了解 Harness 的 Host、Client 和 API 分层,可以看上一篇《DeepSeek Harness 架构全解》。
先纠正一个容易混淆的写法
服务监听地址应写成 0.0.0.0,不是 0.0.0/0。
0.0.0.0 用在服务器监听配置里,意思是接受本机所有 IPv4 网络接口上的连接。0.0.0.0/0 是 CIDR 网段写法,表示整个 IPv4 地址空间,常见于路由和防火墙规则。两者看起来接近,使用位置却不同。
DeepSeek Harness 默认监听 127.0.0.1:3080。回环地址 127.0.0.1 只能由当前电脑访问,手机即使和电脑连着同一个 Wi-Fi,也无法连接它。
我没有直接改默认配置,而是在仓库根目录增加一份部署覆盖层 lan.cordis.yml:
# Explicit LAN deployment overlay.
# WARNING: the Web surface currently has no TLS or login authentication.
- id: webserver
config:
host: 0.0.0.0
port: 3080
Cordis overlay 是叠加到原有插件树上的配置文件。这样做不会改变项目默认的本机安全边界,需要局域网访问时才显式启用:
pnpm run build
pnpm dsh web --patch ./lan.cordis.yml --no-open
启动成功后,Host 仍会在终端打印 Web 服务地址。此时同一网络中的设备可以通过电脑的局域网 IP 和 3080 端口建立连接。
这里必须加一句警告:监听 0.0.0.0 等于扩大了服务暴露面。当前 Web 页面没有为这次局域网部署额外增加登录认证,所以它只适合可信的家庭或办公网络。不要把 3080 端口映射到公网,也不要在公共 Wi-Fi 上使用这份配置。
页面能打开,麦克风却不一定能用
如果需求只到“手机能看页面”,监听地址改完就够了。语音输入把问题带到了浏览器安全模型。
页面通过 navigator.mediaDevices.getUserMedia() 请求麦克风。除 localhost 等少数例外,主流浏览器只允许安全上下文调用这项能力。安全上下文通常就是经过浏览器信任的 HTTPS 页面。手机访问 http://192.168.x.x:3080 时,即使页面完全正常,麦克风接口仍可能不存在或被拒绝。
因此本地部署又加了一层 Caddy。Caddy 是一个 Web 服务器,这里只做两件事:在 3443 端口终止 HTTPS,再把请求反向代理到 Harness 的 127.0.0.1:3080。
https://192.168.50.147:3443, https://localhost:3443, https://127.0.0.1:3443 {
tls .certs/lan.pem .certs/lan-key.pem
header Permissions-Policy "microphone=(self)"
handle {
reverse_proxy 127.0.0.1:3080
}
}
其中的 IP 要换成运行 Harness 那台电脑当前的局域网地址。证书由本地证书颁发机构签发,并把根证书安装到手机的信任列表。只在页面上点“仍然访问”通常不够:地址栏可能显示页面,但浏览器仍不会把它视为可使用麦克风的安全上下文。
启动代理的命令是:
caddy run --config Caddyfile.lan
正常情况下,手机最终访问的是 https://电脑局域网IP:3443,Caddy 在内部转发给 3080,Harness 本身不用处理 TLS。
语音按钮放在现有输入组件里
前端改动集中在 packages/client/ui-conversation/src/client/skeleton/InputBar.tsx。输入栏新增了麦克风按钮,以及四组状态:登录、连接中、录音中和停止。
第一次点击按钮时,页面会要求登录 Sayi。登录请求没有由浏览器直接跨域发送,而是沿用 Harness 的 /api 请求封装,调用新增加的 sayi.login 方法。Host 再向 Sayi 登录接口发出请求,只把访问令牌和 Pro 权限状态返回页面。
这条方法需要同时进入 Harness API 的几个位置:
api/sayi.ts定义 TypeScript 请求和响应类型;api/sayi.schema.ts使用 Zod 校验进入 Host 的数据;api/rpc-map.ts注册sayi.login和sayi.transcribe方法名;fetch/handler.ts把 HTTP 路由交给对应实现;api-proxy.ts执行真正的上游请求。
这比在 React 组件里随手写一个第三方请求多几层代码,但边界清楚:输入校验和上游错误由 Host 处理,Client 只关心登录结果和录音状态。
当前版本还定义了 sayi.transcribe 的整段音频接口,为非实时识别留了入口;输入框实际使用的是实时 WebSocket 链路。
浏览器里的声音如何变成文字
用户允许麦克风后,页面先创建 MediaStream,再连接 Sayi 的实时识别 WebSocket。WebSocket 是一条可双向持续传输数据的连接,比反复提交短 HTTP 请求更适合实时音频。
连接建立后,前端发送 run-task 消息,选择 paraformer-realtime-v2 模型。识别服务回复 task-started 后才开始推送音频,避免声音先到、任务还没准备好。
浏览器拿到的是浮点采样,不是接口要求的音频格式。pcm16At16k() 在发送前做了两步处理:
- 按浏览器的
AudioContext.sampleRate把采样率降到 16 kHz; - 把
Float32Array中-1到1的样本转换为 16 位有符号 PCM。
PCM 是脉冲编码调制,简单说就是未经 MP3、AAC 这类有损压缩的数字音频样本。当前实现每次处理 4096 个样本,并把得到的二进制块直接写入 WebSocket。
识别结果分为临时文本和句子结束后的最终文本。临时文本会不断变化,不能每次都直接追加,否则输入框会出现重复内容。实现中保留了三个值:开始录音前的草稿、已经确认的语音文本、当前尚未结束的句子。每次结果到达时,用这三部分重新计算输入框内容:
输入框内容 = 原草稿 + 必要的空格 + 已确认文本 + 当前临时文本
这样用户可以先打半句话,再用语音继续;实时结果更新时,也不会覆盖录音前已有的文字。
再次点击麦克风会发送 finish-task,随后断开音频处理节点、停止所有麦克风轨道、关闭 AudioContext 和 WebSocket。错误路径也调用同一套清理逻辑,避免页面看似停止,麦克风其实仍被占用。
局域网 HTTP 还暴露了一个兼容问题
改造过程中,页面在普通局域网 HTTP 地址下还遇到了 crypto.randomUUID() 不可用的问题。这个 API 同样受安全上下文限制,而 Harness 会用 UUID 生成消息 ID、附件 ID 和 RPC ID。
为此,本地代码增加了一个基于 crypto.getRandomValues() 的 UUID v4 生成函数,手动设置版本位和变体位,并替换了三个调用点。它让非安全页面上的基础消息流程可以工作,但不代表 HTTP 页面就能录音。语音输入仍要求 HTTPS。
这个细节也说明,网络可达和浏览器能力是两回事。0.0.0.0 解决操作系统监听范围;证书与 HTTPS 决定浏览器是否愿意开放麦克风等敏感能力。
目前实现中需要继续收紧的地方
这版功能已经能从手机打开页面并实时把语音写进输入框,但它仍是个人局域网改造,不是可以直接公开部署的成品。
Sayi 访问令牌目前保存在 localStorage。它避免每次录音都重新登录,但页面中发生的脚本注入也可能读取它。更稳妥的做法是让 Host 保存凭据,浏览器只拿短期、用途受限的会话票据。
实时连接还把令牌放在 WebSocket 查询参数中,查询参数可能进入代理日志。后续应优先使用服务端中转,或者使用上游支持的认证头和短期票据。
音频处理使用的 ScriptProcessorNode 已经是旧式 Web Audio 接口。它实现简单、兼容当前代码,但长期应换成 AudioWorklet,把音频处理移出主线程,减少页面忙碌时的丢帧风险。
更重要的是,0.0.0.0 前面仍缺少真正的用户认证。Caddy 目前解决的是加密和浏览器信任,不负责判断访问者是谁。只要服务准备离开完全可信的局域网,这一层就必须补上。
这次改造真正连通了什么
表面上,我们只是加了一个监听地址和一个麦克风图标。实际连通的是一条完整路径:手机通过局域网访问 Caddy,Caddy 用 HTTPS 承接浏览器请求并转发给 Harness Host;输入栏获得麦克风流,把声音转为 16 kHz PCM,通过 WebSocket 接收实时识别结果,再把文字写回原有草稿系统。
这条链路里没有哪个单点特别复杂,麻烦来自边界:操作系统的监听边界、浏览器的安全边界、Harness 的 Host/Client 边界,以及第三方语音服务的认证边界。把这些边界逐个处理清楚后,语音输入才不再是一个孤立按钮,而是能稳定融入现有对话流程的能力。
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:把 DeepSeek Harness 放到局域网,并给它加上实时语音输入
本文链接:https://www.sshipanoo.com/blog/ai/DeepSeek-Harness局域网与语音输入改造/
