1. 前言做完一个机器人项目往往会留下几份开发笔记接入方式记在一处运行顺序放在另一处不同版本的说明也常常同时保存。再查“这个版本要不要唤醒”“录制前需要准备什么”仍得重新翻文件。如果能直接问身边的 T1并在电脑上看到它查到的原文查资料会方便一些。这次做的就是一个资料问答 Skill。这里的 Skill 是运行在机器人上的小程序负责收音、提示音和播报电脑端负责把语音识别成文字从自己的笔记里查找内容再请 DeepSeek 组织回答。项目在已有语音能力上增加资料导入、版本查询和原文查看真机交互以语音为主不需要机器人移动。下面使用已经完成真机问答的同一组资料从实际效果讲起再展开环境准备、资料查询和原生 Skill 的实现。最后给出完整运行顺序并补充网页操作与真机问答演示。2. 先看资料问答怎样使用2.1 给 T1 准备什么资料演示资料来自前一个技术面试官项目包括两份开发笔记和一份录制检查表全部随源码保存在 sample_docs 目录。先看两份笔记里与本次提问直接有关的原句资料笔记中的内容技术面试官开发笔记 V2机器人完整播完问题并响两声提示音后使用者直接回答不需要再次叫唤醒名字。技术面试官开发笔记 V1使用者需要先唤醒机器人再说出回答。V1、V2 是这两份笔记的版本分别描述技术面试官项目早期和后来的接法。它们不是 T1 固件版本。选择 V2就是让程序按 V2 笔记回答问旧版则应重新查 V1不能把两份笔记的句子混在一起。第三份 recording-guide-v2.txt 记录录制准备和使用顺序归在 V2 下。它和 V2 开发笔记可以共同参与一次查询。这样既能问“要不要唤醒”这种具体问题也能查询同版本资料中其他已记下的步骤。2.2 问完整问题再接着追问真机运行时先选择 V2 并建立会话。听到两声提示音后说第二版需要唤醒机器人吗T1 当时的实际回答是“不需要。机器人完整播完问题并响两声提示音后直接回答即可不用再叫唤醒名字。”电脑页面同时保留 V2 标签和所引用的原句。点击“查看原文”可以展开相应笔记直接看到回答依据。等它答完并再次响提示音再问“那旧版呢”。这一轮回答变成“需要。资料提到早期方案中使用者要先唤醒机器人再说出回答。”程序沿用上一句“需要唤醒机器人吗”这个问题改查 V1使用者不用把整句话再说一遍。这两轮来自同一次真机连续问答原始识别文字与采用的问题一致没有人工改字现场也确认了完整播报和没有原来的闲聊插话。后面还会单独演示“机器人官方售价是多少”示例资料没写价格程序就说明资料不足不补一个数值。这个问题在另一场 V2 会话中完成不与前两轮拼成一次历史运行。2.3 一句话经过哪些步骤以“第二版需要唤醒机器人吗”为例声音先从 T1 麦克风进入程序在电脑上识别成文字。程序把“第二版”对应到 V2再从 V2 笔记中找与唤醒有关的段落。DeepSeek 根据这些段落生成回答和引用最后由 T1 读出回答网页展示原文。开发时可以把它分成两部分。先让电脑能够根据资料回答文字问题再把真机收音和播报接进来。两边共用同一个问答核心网页输入和麦克风识别出的文字最后都会进入 Knowledge.ask()。这样排查时也有顺序资料查错了就先看查询结果声音没收完整则检查语音采集不必同时改动所有环节。这里没有把模型和资料库都塞进机器人。T1 保留现场交互开发电脑运行识别模型、文本检索和网页当前问题、检索到的片段及同版本少量问答历史会发送到 DeepSeek。自己的资料是否适合发给文本服务需要在导入之前确定。3. 准备开发环境与项目资料3.1 先分清使用哪一种开发方式启元提供的资料里有 ROS2 SDK也有原生 Skill 的开发与调试方式。两者的运行入口不同环境不能混着装。T1 ROS2 SDK 快速入门推荐 Ubuntu 22.04 与 ROS2 Humble本文使用的是机器人机内已有的 Lumina 运行时让它执行 Skill 包中的 Python 程序电脑另外运行资料服务。位置本文使用的环境与职责开发电脑Windows、Python 3.12.14运行资料服务、中文识别、DeepSeek 请求与网页T1 机器人机内 Python 3.10.12、已有 Lumina 服务执行原生 Skill 并访问机器人音频能力连接电脑与 T1 局域网互通通过已核验主机指纹的 SSH 建立开发连接PrimeHub Dev Kit 手册介绍了原生 Skill 结构与 robot 对象官方 PrimeConsole 下载页可获取调试工作台及资料包。本文按已经使用的源码调试入口安装并启动任务。机器人语音接管部分还适配了本台设备的机内对象和消息类型换固件时需先核对这一部分不能把它当作所有 T1 都相同的公开方法。3.2 安装电脑端依赖取得项目源码后进入 projects/02-private-knowledge。下面的命令在 Windows CMD 中执行先建立独立环境再安装项目依赖暂时无法在飞书文档外展示此内容激活后命令提示符前会出现 (.venv)随后使用的 python 就是这个环境里的解释器。--quiet 只是减少安装日志最后的 pip check 用于检查依赖关系。本次安装后显示 No broken requirements found同时核对了 Paramiko、Vosk、FunASR ONNX 和 ONNX Runtime 的版本。电脑还需要准备三份本地模型。Paraformer 用来把中文录音转为文字FSMN VAD 判断一句话是否已经说完Vosk 保留为同段录音的识别对照。按 README 的文件清单下载后放成下面的目录或在启动命令里换成自己的路径暂时无法在飞书文档外展示此内容Paraformer 和 FSMN 目录需要相应的 model_quant.onnx、配置及辅助文件不能只有一个空文件夹。具体文件名及模型链接见 README 的运行前准备。DeepSeek Key 可保存在项目的 private/deepseek.key也可以使用当前终端的 DEEPSEEK_API_KEY 环境变量它由电脑读取不写进机器人技能包。3.3 启动资料控制台仍在这个 CMD 窗口执行暂时无法在飞书文档外展示此内容18882 是本机网页端口。vad 表示说完并停顿后结束收音20 是一次收音窗口的上限review-seconds 0 表示默认直接采用识别文字。命令中的 ^ 是 CMD 的续行符不要把它换成 PowerShell 的写法。终端出现本地地址后浏览器打开 http://127.0.0.1:18882/。先确认页面能够加载、识别模型就绪、资料列表里有三份示例文本再进行下一步。此时打开的是开发电脑上的页面127.0.0.1 不是 T1 的设备地址。浏览器能显示控制台说明电脑服务可以访问后面部署命令的 --host 才填写机器人在局域网中的地址。把两个地址分清可以避免拿网页端口去测试机器人是否在线。控制台的版本选择决定本轮从哪组资料查找内容“新建问答”建立一次会话问题与回答显示在当前会话中。真机尚未启动时也可以先用文字输入核对资料查询。文字输入和麦克风输入会分别记录方便后面确认一个答案究竟来自哪次操作。3.4 导入自己的 Markdown 或 TXT示例文本由 sample_docs/index.json 自动载入复现本文时不必重新添加。换成自己的笔记时展开“添加自己的资料”填写资料 ID、标题、版本再选择文件或粘贴正文。资料 ID 用来关联同一份笔记例如两版开发笔记都叫 interview-guide标题用于在回答旁边辨认出处版本用于限定查询范围。初次导入前把这三个字段定好后面引用就能找到确定的一份正文。版本标签也需要在同一批资料里保持一致。例如开发笔记和录制检查表都属于当前方案就一起归到 V2。程序不会按文件修改日期自动推断最新版也不会把只有 V1 的段落补进 V2。整理资料时先定版本再提问结果才容易核对。目前支持 UTF-8 Markdown 和 TXT单份正文不超过 64 KiB。同一资料、同一版本和相同正文重复导入会保持原记录内容变了则应使用新版本。这样昨天回答所引用的那句话今天还能查回原来的内容。PDF 或网页资料需要先整理成文本本项目没有把文档提取一并实现。4. 实现“按资料回答”的核心逻辑4.1 先把笔记整理成可检索的段落阅读源码时先找到这几个文件不必从语音底层一路追起文件本例中负责的工作knowledge_core.py导入资料、查询版本、检索原文与生成回答server.py给网页和原生 Skill 提供同一套问答接口buffered_asr.py在电脑上完成整段中文识别与句尾检测skill/src/main.py串起提示音、收音、请求答案和播报run_robot.py打包、建立开发连接、安装及启动原生任务先读资料核心再看原生入口如何调用它能够把问题分开即使暂时没有机器人仍可检查资料是否正确导入、同一问题在两版笔记里找到什么接上 T1 后再检查声音和播报是否完整。资料由 knowledge_core.py 中的 Knowledge.import_document() 接收。导入时先检查文件和正文再调用 split_chunks()按照 Markdown 标题与段落拆分内容。每段同时保存标题路径、原文行号和版本后面既能检索也能定位出处。这里没有逐句随意切开正文。例如 V2 笔记中的“提示音后直接回答”带有使用条件应该与同一段的上下文一起交给模型。保留段落可以减少只拿到“不需要”三个字、却不知道在什么条件下不需要的问题。较长段落会继续按长度拆分完整文档仍单独保存方便页面展开。资料并不是发一次问题就重新从文件读一遍。电脑将规范化文本和段落存进本地 SQLite 数据库后续查询在这份已导入内容上进行。新增版本时保留旧文本因此一次问答始终能对应到当时使用的资料。4.2 在选定版本里找相关内容Knowledge.retrieve() 接收问题和资料版本先限定 V2再给 V2 下的段落计算相关程度。本项目资料量小采用中文相邻双字片段与英文标识符的词法匹配没有额外部署向量数据库。返回的是最多四段值得进一步查看的原文不是最终答案。可以先运行下面的独立示例看看“需要唤醒机器人吗”会找到哪些内容。它使用临时数据库只查本地文本不请求模型也不连接机器人暂时无法在飞书文档外展示此内容输出里应同时看文件名、版本和正文。即使结果里出现两段不同资料只要它们都属于 V2也可能共同说明同一个操作但某段只提到“机器人”却没有回答唤醒问题就还不能据此下结论。若没有找到预期原句先检查导入正文和所选版本再调整问法。检索的作用是缩小阅读范围。如果问题与“技术面试官”有关就先找到这个项目的相关段落段落里是否真的回答了问题还要继续判断。因此代码不会把得分第一的原文直接当作答案读出。具体特征、分数和阈值可以在调试时查看正常使用先关注找到的原文是否相关。4.3 把问题和原文交给 DeepSeek网页和语音入口最后都调用 Knowledge.ask()。它先检查会话是否仍在进行再进入 _ask_snapshot()识别问题中的版本补全短追问调用检索最后请求模型。第一次说“第二版需要唤醒机器人吗”程序得到 V2 和“需要唤醒机器人吗”。下一次只说“那旧版呢”就沿用刚才的实质问题改用 V1 重新检索。这个补全来自会话保存的上一条问题不是把上一句答案里的“不需要”改成“需要”。找到段落后发送给 DeepSeek 的数据包含当前问题、所选版本、相关原文以及同版本最近少量已回答内容。提示要求只依据这些资料回答片段没有提供被问事实时就说明资料不足。模型负责把原文组织成可播报的回答不负责替程序选一个未经查询的资料版本。历史问答也要跟随当前资料版本。上一轮问 V2、下一轮改查 V1 时不能把 V2 的答案继续当作 V1 的事实。这里保留的是“上一轮问了什么”让短句能够补全用于回答的原文仍要从本轮版本重新取得。这个区别很小却决定了连续问答是在继续查资料还是只在改写上一句话。项目通过 DeepSeek JSON Output接收结构化结果。其中 answer 是要读出来的话citations 保存答案使用的原句和片段编号status 区分正常回答与资料不足。先把这几项分开网页就能同时显示回答和出处机器人只播报适合听的正文。4.4 给回答留下可以打开的出处模型返回内容后Knowledge.validate_answer() 做三项检查引用的片段确实来自本次检索引用版本与当前选择一致引用文字逐字存在于原文。有答案却没有引用或者引用了一段不存在的文字都不能直接保存为正常回答。通过检查后程序从原始候选中补上资料标题、文件名和行号。模型只负责指出用了哪一段、哪一句不自由填写一个看似可信的来源。网页点击“查看原文”时再通过 get_document() 取回同一版本的完整正文并高亮对应文字。回答旁边的信息读者用它核对什么资料标题与版本回答使用的是哪份笔记原文引句结论直接依据哪一句话行号与完整正文引句前后有没有适用条件这些检查能防止部分错引文仍需要人阅读原文判断结论有没有概括过头。查询重要内容时应核对原句讨论的是不是同一个对象而不是只看回答说得顺不顺。更细的引用测试与检索分析放在 FAQ不影响这里的使用流程。模型服务出错时程序也要与“资料没写”分开处理。前者需要检查接口或重新尝试后者需要补充资料如果都回复“资料不足”使用者就分不清是笔记缺内容还是服务根本没有正常工作。本项目在生成请求失败时保留错误不把这一轮保存为正常回答。5. 将问答程序接入 T1 Skill5.1 技能包里装什么原生 Skill 的最小结构是 cfg/pkg.yaml 和 src/main.py。Lumina 启动入口脚本时会注入全局对象 robot脚本通过它使用播报和音频能力不在入口里自行 import robot。这个约定可对照 Dev Kit 运行时契约。本项目把语音流程、采集器和模式适配分别维护在 skill/src。run_robot.py 构建时将这些模块合并成入口连同包配置写入 RPK。机内程序只使用标准库和现有运行时Paraformer、检索数据库及 DeepSeek Key 留在电脑。暂时无法在飞书文档外展示此内容先在第二个 CMD 窗口进入同一项目并激活环境检查仓库附带的包和部署参数暂时无法在飞书文档外展示此内容仓库附带的包在 artifacts截图检查的是本机构建目录 dist 下的同一份包。压缩包检查与 --help 都只在电脑执行。真正安装、启动任务和收音播报还要在连接 T1 后分别完成。RPK 结构与打包方式可参考工具包的 pack.py。5.2 把文字回答变成机器人播报问答服务返回后原生入口从结果中取出 answer把这段文字交给播报函数。机器人发声用到的是 Lumina 注入的 robot 对象关键调用都在 skill/src/main.py 中实际调用在本例中的作用robot.play_tts(text)提交要说的文字检查请求是否被接受robot.get_audio_play_state()读取播放状态与所属技能确认本轮确实开始播放并进入稳定空闲robot.play_audio_file(path, pkg_namePACKAGE, priority6)播放两声本地 WAV 提示音告诉使用者可以提问speak() 先提交文字再在循环里读取音频状态。本项目使用的运行时中状态值 1 表示正在播放2 表示空闲程序先观察本技能的播放再等待连续空闲。这样才能判断这一句已经播完随后安排提示音和收音。若播报被其他语音抢占或者没有观察到完成程序会记录原因并停止本轮。长答案由 speak_answer() 按句子边界分成不超过 100 字的片段再依次交给 speak()。这个长度是本项目的分段策略。完整逻辑可直接查看原生入口源码资料服务、播报函数和入口代码分别负责查询、发声和流程控制。下面这段实际入口把两端接在一起向电脑的 /api/ask 请求资料答案取回回答与引用最后调用 speak_answer(result[answer])。前面第 4 章的资料查询到这里才变成 T1 扬声器里的声音。两声提示音同样需要检查播放状态。完整播报、提示音、收音依次进行使用者听到提示后再说一句。音频能力可对照官方 SDK 的接口说明和 Dev Kit 运行时契约本文执行的具体方法名来自后者的 robot 对象。5.3 麦克风音频怎样送到电脑采集部分参考官方 SDK 的 record_audio.py调用机内已有的 parec 工具从机器人本机 PulseAudio Unix socket 读取内置麦克风。本次设备是八通道、48 kHz代码混合第 5、6 通道并转换为 16 kHz 单声道再通过开发连接发送给电脑识别。SdkMicrophone 管理这一段采集电脑端 BufferedAsr 判断句尾并识别完整录音。识别完成后原生入口将文字交给 /api/ask电脑返回答案入口再调用上一节的播报函数。底层音频与资料查询因此可以分开检查前者要拿到完整问题后者要找对版本和原文。为了避免原来的聊天助手同时抢答程序在交互阶段管理临时语音接管并在采集收尾时恢复、读回状态。当前 only_voice 适配写在 voice_mode.py依赖本台机内服务它与资料检索逻辑分开。语音底座的前期接入过程可参照技术面试官项目本篇不再重复整套收音调试。6. 在真机上运行同一组问答6.1 建立连接并启动原生任务先确认 T1 已开机联网电脑能够通过局域网访问它。手机 App 显示蓝牙连接不能代替这里的开发连接。先取得本台 T1 可用的 SSH 登录资料并核验主机指纹再运行部署器暂时无法在飞书文档外展示此内容大写参数换成自己的设备信息。--known-hosts 指向已核验的主机指纹文件用于确认连接对象密码由终端交互读取使用 SSH 私钥时增加 --key-file。部署器通过 SSH 访问机器人本机的 Lumina 服务向 /packages/script 装入主 Skill 与恢复 Skill并建立回环转发让机器人能访问电脑的 18882 服务。它采用的源码调试方式可对照 deploy_test.py。--prepare-only 会安装技能但暂不开始问答。看到 ready: true 后先在网页选 V2、点击“新建问答”再回到部署终端输入 run。这时程序向 /tasks 发起任务机器人开始执行原生入口。后面要分别看是否听到开场、提示音、回答及结束提示而不只看安装返回值。本文完成的是这条开发入口的安装和真机运行没有把 PrimeConsole 图形导入或 PrimeHub 技能市场上架算作已经完成。机器人脚本里的 127.0.0.1 和浏览器里的本地地址原本指向不同设备。本项目通过 SSH 回环转发把机器人发到 18882 的请求送回电脑端服务所以原生入口可以调用同一套问答接口。连接中断后这条转发也随之停止运行期间保留控制台和部署终端等任务实际结束再关闭连接。6.2 完成两轮连续提问并查看原文第一段按以下顺序操作保留机器人声音、使用者提问和电脑页面之间的对应关系顺序实际操作需要看到或听到的结果1在 V2 新会话中启动原生任务开场完整随后响两声提示音2提示音后问“第二版需要唤醒机器人吗”回答不需要页面显示 V2 和原文引用3等回答结束及下一次提示音后问“那旧版呢”回答早期方案需要唤醒本轮资料变为 V14第二答播完后在页面点击“结束问答”等任务结束、robot.active_session 为空并确认语音模式恢复5展开这两轮的“查看原文”分别看到 V2 第 9 行和 V1 第 7 行对应的句子6在部署终端输入 close关闭本次开发连接保留技能和已保存的问答每次原生任务最多使用四个收音窗口。没有选版本时程序可能先问要查哪一版这段澄清也会占一个窗口复现上述两问时先选 V2流程更直接。每个窗口只说一句等下一次提示音再继续。第二答后先结束问答再查原文可以避免翻阅时第三个收音窗口等待超时。结束会话会保留当前页面记录查完后再新建下一场会话。回答措辞可能随模型返回变化核对重点是版本、核心事实和引文不要求逐字重演历史播报。6.3 另开一轮查询资料没有提供的内容结束前一段后重新建立开发连接在页面选择 V2 并新建问答再启动一次原生任务。提示音后问“机器人官方售价是多少”。这组笔记只记录项目接入没有售价保存的真机结果为资料不足引用列表为空。这个例子检查的是资料范围。给机器人导入自己的笔记并不意味着它就有了与项目有关的全部事实。想让它回答价格应先加入确实包含价格及适用条件的资料再进行新的验证。已有问答仍保留当时使用的版本和原文不随新增资料改写。6.4 网页操作与真机演示这次选择“技术面试官更新后的开发笔记”把问题上限设为 1。开始真机问答后直接说出“模型密钥应该保存到哪里”。T1 根据笔记回答密钥留在电脑本地不放进机器人 Skill也不放进公开仓库网页同步保存答案、原文引用和本次知识整理。网页操作与结果资料专家_网页操作与结果真机语音演示资料专家_真机语音演示7. 联调中遇到的问题7.1 原来的聊天助手也在回答怎么办早期直接使用原生聊天转写时原有助手也会处理同一份语音出现插话或抢占播放。当前方案将原生 Skill 的采集与播报作为一个受控交互过程临时管理语音接管采集结束后检查恢复状态。资料问答只接受这一轮送来的文字不另拼原聊天结果。排查时先看问题有没有完整收到再看问答结果最后确认播放有没有被其他语音打断。不能因为电脑已经生成答案就忽略现场只听到半句的情况。第一篇与本篇也要串行使用不能同时让两个 Skill 占用相同的语音状态。7.2 说完一句为什么还要等很久最初的固定录音时间加上文字确认会让短问题也等待较长时间。现在默认采用句尾检测说完并停顿约 800 毫秒后结束本轮上限 20 秒识别完成后直接采用文字。需要人工核对技术词时再打开页面的十秒确认选项。提示音前的等待则通过提前准备麦克风、确认上一句播完后直接播放提示音来减少。收音等待、识别耗时和模型请求是不同环节调试时分别看具体测量与对照保存在等待时间记录不把少数轮次当作普遍时延保证。7.3 当前录制前还需要核对什么完整问题、旧版追问和资料不足已有真机记录。短结束口令曾经漏收当前代码增加了录音流低延迟请求但这项修改后的完整“结束问答”尚未完成真机复测。录制当前案例可以先用页面按钮结束并等待任务结束和语音恢复不能把安装成功当作短口令已经修好。引用检查、版本处理和页面接口另有本地受控测试详细范围见真机验证记录与 FAQ。遇到问题时按实际发生的环节查看不需要先读完全部日志和测试脚本才能使用这个 Skill。8. 总结这次给 T1 增加的能力是把自己已有的项目笔记接进语音问答。电脑保存资料T1 接收问题并播报回答问到另一版就重新查对应版本想确认答案则在页面打开原文。资料查询、现场交互和结果核对由此能连成一次实际操作。工程目前适合小型 Markdown、TXT 资料集运行时需要电脑参与识别和文本问答。后续替换成自己的笔记可以先用文字问题检查内容再用同样的问题接真机验证。完整源码、示例资料、技能包和运行说明均保存在本项目目录前面的操作顺序也可用于替换自己的资料后复现问答。