在第二篇中,我们通过模型量化、硬件预处理和合理的delegate配置,榨干了单路推理的性能,摸清了单路管线的算力上限。
然而,工业现场从来不会只有一路摄像头。当我们面对十字路口的4路监控、或者工厂产线的8路质检时,管线该如何搭建?是简单复制8条管线,还是用tee和queue做灵活的分流?如何避免一个慢模型拖垮整个采集端?
本文定位:本篇将把qimsdk-agentic-skills拆开看,它怎么把 SDK 能力封装成技能、Claude Code 接进去之后能生成什么、会在哪里翻车、怎样写约束把可用率提上去。
1. AI写音视频处理代码经常遇到的问题
先讲三个我反复见到的翻车现场。它们有个共同点:代码看起来完全正确,甚至编译能过,但一跑就出问题。
问题一:属性名是编的
你对 agent 说:“用 qtiqmmfsrc 采集 1080p30,然后送到检测。”
它给你一段很漂亮的代码:
qtiqmmfsrc camera-id=0 ! video/x-raw,width=1920,height=1080,framerate=30/1 ! ...问题出在camera-id。真实的属性可能叫camera,也可能叫device,还可能是camera-index,agent 不知道,于是它编了一个看起来很合理的名字。
一跑,报错:
WARNING: erroneous pipeline: no property "camera-id" in element "qtiqmmfsrc"它没有恶意的"幻觉",它只是把"通常应该叫什么"当成了"实际叫什么"。GStreamer 的插件属性名极其琐碎,没有任何通用规律能推出来——qtiqmmfsrc用camera不代表qtimltflite也用camera。这类知识必须从实物(gst-inspect-1.0)来,不能从模型权重里来。
问题二:tee后面忘了queue,直接死锁
第二个翻车更隐蔽。你让它做"检测 + 原图直显"双路输出,它给出:
qtiqmmfsrc ! tee name=t t. ! qtimlvconverter ! qtimltflite ! qtimlpostprocess ! qtivoverlay ! waylandsink t. ! waylandsink语法完全正确,管道结构也对。但它在两个tee分支后面都没加queue。
跑起来:卡死,一帧都不出。
这就是第一篇讲过的坑——tee分发的是同一块 buffer 的引用,两个分支在同一个线程里串行执行,快的分支(直显)被慢的分支(推理)拖住,或者反过来,最后互相等待,死锁。
为什么 agent 会漏?因为在绝大多数流媒体示例(尤其是 web / RTSP 相关的)里,tee后面加queue并不是必须的——那里通常已经没有"同一块硬件 buffer 被多个消费者占着"的问题。agent 把通用领域的习惯,套到了硬件零拷贝管线上。这是典型的"通用知识正确,特定场景致命"。
问题三:CMake 链接顺序错了,编译过、运行崩
第三个翻车最折磨人。你让它把 Python builder 的管线翻译成 C++ 工程。它给出的CMakeLists.txt:
target_link_libraries(my_app qimsdk gstreamer-1.0)编译过了。但一运行就崩,报 undefined symbol 或者初始化失败。
问题在链接顺序和依赖关系。GStreamer 的库有复杂的传递依赖,qimsdk内部依赖 GStreamer 的一些库,链接器(尤其是--as-needed模式下)对库的顺序很敏感。正确的写法往往要先链接底层的 GStreamer,再链接上层封装,或者显式声明全部依赖。
为什么编译能过?因为符号解析在编译期不检查(只检查声明),到链接期才真正解析,而静态库的顺序问题在某些链接器配置下不会立刻报错,直到运行时加载动态库才炸。agent 写出了"能编译"的代码,但没写出"能链接、能运行"的代码。而它的训练语料里,大量是find_package+target_link_libraries的简单用法,缺少"这种二次封装库该怎么链接"的实战样本。
先说失败,再讲怎么修。因为 AI 写嵌入式代码的真实水位就是这样——它能把 80% 的结构骨架写得很漂亮,剩下 20% 的"硬件/版本/链接细节"会以极其自信的方式写错。认识到这一点,你才知道该在哪里加护栏。这篇文章剩下的部分,就是讲怎么加这些护栏。
2. QIMSDK 的"文档即代码"到底改变了什么
翻车归翻车,但如果因此说"AI 写不了嵌入式代码",那是另一种偷懒。关键在于你给它什么上下文。
一个根本问题:模型权重的知识盲区
QIMSDK 是相对新的 SDK(2.0 是 2026 年的版本),它的插件名、属性名、API 约定,大概率不在任何主流模型的训练语料里。你直接问一个大模型"QIMSDK 的 qtiqmmfsrc 有哪些属性",它只能靠猜——而且会猜得很有自信。
这就是翻车一的根源。
"文档即代码"改变了注入方式
QIMSDK 2.0 强调的一个设计是:文档与代码仓库直接关联,示例、API 指南、管线说明与实现资产保持同步。配合qimsdk-agentic-skills仓库,这意味着:
训练语料里没有的知识,可以在运行时通过"仓库内文档 + skill"现场注入给 agent。
对比一下两种问法:
| 方式 | 输入给模型的东西 | 结果 |
|---|---|---|
| 直接问 | “QIMSDK 怎么跑 YOLOv8” + 模型权重 | 插件名/属性靠猜,大概率编造 |
| 加载 skill 后问 | 同上 + SKILL.md 里的能力描述、API 约定、示例片段、常见错误 | 有据可依,属性名从 skill 里抄,正确率质变 |
这不是"模型变聪明了",是把不确定性从模型权重里,转移到了可校验的文本里。SKILL.md 是文本,能 diff、能 review、能跟着 SDK 版本更新——而模型权重不能。
一个直观的类比
把 agent 想象成一个极聪明但刚入职的工程师:
- 不给他文档:他会凭"一般公司都这么做"来写代码,写得头头是道,然后到处踩你们公司特有的坑。
- 给他一份写得很好的 onboarding 文档:他还是会犯错,但犯的是"需要动手才能发现的错",而不是"翻一下文档就能避免的错"。
qimsdk-agentic-skills就是那份 onboarding 文档。而写这份文档的质量,直接决定了 AI 的可用率——这也是第 6 节提示词工程要说的事。
3. 拆解 qimsdk-agentic-skills
仓库结构:三套 builder
qimsdk-agentic-skills仓库提供三套技能,对应三种产物:
为什么分三套而不是一套:三种产物的抽象层次、校验方式、运行开销完全不同
(图1:同一段自然语言需求,进三套不同的 builder,出三种不同层次的产物。注意中间那层,skill 不是一个"黑盒函数",而是一份写满能力描述、API 约定、示例和常见错误的说明书。)
- Python app builder:产出高层语义装配的 Python 管线,可读性最好,适合验证阶段。
- C++ app builder:产出需要 CMake、链接、部署的 C++ 工程,适合量产。
- gst-launch builder:产出一条命令,适合快速排障。
每套 skill 里通常包含什么
拆开一个 SKILL.md,你会看到五类内容,缺一不可:
- 能力描述:这个 skill 能做什么、不能做什么。边界写清楚,agent 才不会越界发挥。
- API 约定:函数签名、参数含义、必填项。这是防止"编造属性"的第一道墙。
- 示例片段:能直接抄的代码骨架。agent 最擅长的就是"照着样子改",示例的质量直接决定产出质量。
- 常见错误:已知的坑(比如"
tee后必须加queue")。把翻车经验写进去,下一轮就不会再犯。 - 部署步骤:编译、运行、上板的顺序。防止"写对了代码但跑不起来"。
安装方式
按官方说明,通常是克隆仓库,把对应的 skill 目录放进你的 skills 目录(比如~/.claude/skills/),之后在会话里它就能被自动加载。
注意:具体的目录名、加载机制随 Claude Code 版本变化,以你所用版本的官方文档为准。这里不写死路径。
为什么要分三套而不是一套
这是图1 最想表达的一点:三种产物的抽象层次不同,正确性判据也就不同。
| 产物 | 抽象层次 | "对不对"怎么判 | 主要失败模式 |
|---|---|---|---|
| Python 管线 | 高(语义级) | 逻辑通不通、能不能跑出结果 | 属性名错、参数错 |
| C++ 工程 | 中(工程级) | 能不能编译、链接、部署 | CMake 依赖、链接顺序 |
| gst-launch | 低(链路级) | 这条命令通不通 | caps 不匹配、缺 queue |
一套 SKILL.md 无法同时校验这三种正确性。Python builder 不需要告诉 agent 怎么链接库;C++ builder 不需要教 agent 怎么快速改一条命令;gst-launch builder 更不需要 CMake。混在一起,agent 反而会在错误的层次上"发挥"。
4. 同一需求,三种产物
用一个统一需求做实验,看看三套 builder 各自会产出什么。需求是:
“USB 摄像头 1080p30 → 目标检测 → 叠加框 → 显示,模型用指定的 YOLO。”
Python app builder 的产物
大致是这种风格(结构示意):
fromqimsdkimportPipeline,Element pipe=Pipeline()pipe.add(Element("qtiqmmfsrc",camera=0,caps="video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1"))pipe.add(Element("tee",name="t"))pipe.add(Element("queue",name="q_det"))# ← 如果 skill 里写了这条,它会加上pipe.add(Element("qtimlvconverter"))pipe.add(Element("qtimltflite",model="/opt/models/yolov8.tflite"))pipe.add(Element("qtimlpostprocess",module="yolov8"))pipe.add(Element("qtimetamux",name="mux"))pipe.add(Element("qtivoverlay"))pipe.add(Element("waylandsink"))pipe.link("qtiqmmfsrc","t")pipe.link("t.q_det","q_det","qtimlvconverter")# 分支pipe.link("qtivoverlay","waylandsink")pipe.run()优点:可读性最好。管线结构一目了然,改参数、加分支都方便。适合验证"这个思路对不对"。
缺点:Python 的解释执行有一定开销,量产时一般不直接用。
C++ app builder 的产物
同样是这条管线,但 C++ builder 会额外产出:
- 一份
main.cpp:显式管理元素生命周期、GstBus消息循环、错误处理。 - 一份
CMakeLists.txt:find_package、pkg_check_modules、target_link_libraries——这里就是翻车三的高发区。 - 一份部署脚本:把二进制、模型、配置推上板子。
优点:性能好、可量产、能精细控制资源。
缺点:代码量翻几倍,构建和部署复杂度陡增。agent 在这里的错误率通常明显更高——不是因为它不会写 C++,而是因为"这条管线的 C++ 工程该怎么组织"这件事,比 Python 多了太多约束。
gst-launch builder 的产物
一条命令,完事(结构示意):
gst-launch-1.0-e\qtiqmmfsrccamera=0!\video/x-raw,format=NV12,width=1920,height=1080,framerate=30/1!\teename=t\t.!queue!qtimlvconverter!qtimltflitemodel=/opt/models/yolov8.tflite!\qtimlpostprocessmodule=yolov8!qtimetamuxname=mux\t.!queue!mux.\qtivoverlay!waylandsink优点:秒级迭代。改个参数、加个GST_DEBUG、换个queue深度,改完直接跑,不用编译。
缺点:不能带业务逻辑,只能验证链路。但它的价值被严重低估——它是最快的"链路是否通"验证器。
三者的对比
| Python | C++ | gst-launch | |
|---|---|---|---|
| 代码量 | 中 | 大 | 极小 |
| 可读性 | 高 | 中 | 高(对懂的人) |
| 可维护性 | 高 | 中 | 低(不适合复杂化) |
| 运行开销 | 中 | 低 | 低 |
| 构建部署成本 | 低 | 高 | 零 |
| agent 生成难度 | 低 | 高 | 低 |
| agent 错误率(体感) | 低 | 高 | 中 |
一个被验证过很多次的建议:先让 agent 产出
gst-launch,用它把链路跑通,再让 agent 翻译成 Python / C++。因为 gst-launch 迭代成本最低,能在几秒内验证"插件名对不对、caps 通不通、缺不缺 queue"。等链路验证过了,翻译工作就变成了"把已验证的结构改写成代码",难度骤降。先验证、后翻译——而不是一上来就要一个完整可编译的 C++ 工程。这个顺序,能省掉大量返工。
同一需求,三种产物:变的是什么,不变的是什么
把上面的对比再抽象一层。同一句需求,“USB 摄像头 1080p30 → 检测 → 叠加 → 显示”,三种产物里不变的内核和变的外壳是这样的:
| 层次 | 不变(三种产物共有) | 变(各产物独有) |
|---|---|---|
| 数据流拓扑 | 采集→转换→推理→后处理→合并→叠加→显示 | — |
| 节点语义 | 每一跳做什么 | 用哪个 API 表达这个语义 |
| 参数值 | 分辨率、模型路径、阈值 | 参数怎么写(配置项 / 命令行 / 构造参数) |
| 错误处理 | 需要处理什么错误 | 用 try/catch、GstBus、还是退出码 |
| 生命周期 | 要创建、运行、销毁 | 用手动管理还是 RAII / with 语句 |
这张表的洞察:三种产物的数据流拓扑是一模一样的。变的都是"外壳",怎么表达、怎么管理、怎么部署。这解释了为什么"先 gst-launch 后翻译"可行:拓扑验证过了,翻译只是换外壳,不会改变拓扑。
它也解释了为什么 agent 在 C++ 上错误率更高:Python 和 gst-launch 的"外壳"很薄(一个函数调用、一条命令),而 C++ 的外壳很厚(构建系统 + 链接 + 资源管理 + 部署)。外壳越厚,agent 需要"猜"的工程细节越多,翻车三正是厚外壳的代价。
一个推论:如果你要的是"快速验证想法",选外壳最薄的产物(gst-launch);如果你要的是"交付一个能长期维护的工程",那外壳的复杂度是省不掉的,必须人工 review 那层。别指望 agent 帮你把厚外壳一次写对,那是它最不擅长的部分。
5. 怎么设计你的实测
上面说的都是"体感"。如果要在团队里说服人,你需要真数据。这一节给你一个可执行的实测设计。
任务设计:难度递增的 10 条提示词
| 任务 | 难度 | 考察点 | |
|---|---|---|---|
| 1 | 摄像头直显 | ★ | 基础采集 + 显示 |
| 2 | 加缩放 | ★ | caps 与 converter |
| 3 | 单模型检测 | ★★ | 推理链路 |
| 4 | 检测 + 叠加 | ★★ | meta 与 overlay |
| 5 | 双路同时输出 | ★★★ | tee + queue |
| 6 | 双模型串联 | ★★★★ | 多级管线 |
| 7 | 双模型并行 | ★★★★ | 资源争用 |
| 8 | RTSP 推流 | ★★★★ | 编码 + 网络 |
| 9 | 动态增删分支 | ★★★★★ | 动态 pad |
| 10 | C++ 工程 + CMake | ★★★★★ | 构建与链接 |
关于难度曲线的一个预判
虽然没有跑分表,但从上面六类失败类型的分布,可以给出一个合理的预判,供你设计实测时参考:
| 任务难度 | 预计主要卡在哪一档 | 预计主要失败类型 |
|---|---|---|
| ★ ~ ★★(1-2) | 多半能到 L4 | 偶发 ① 属性名 |
| ★★★(3-5) | 主要卡 L3 | ③ 缺 queue(多分支)、② caps |
| ★★★★(6-8) | 卡 L3~L4 | ② caps、④ 动态 pad、⑤ 路径/预处理 |
| ★★★★★(9-10) | 卡 L2~L3 | ④ 动态 pad、⑥ CMake 链接 |
这个预判的用途:它告诉你实测时该重点盯哪儿。比如跑任务 5(双路输出)时,重点看 agent 有没有在两个分支后都加queue——这是 L3 死锁的命门。跑任务 10 时,重点看 CMakeLists 的链接顺序和依赖声明。
再次强调:上表是基于失败类型的推断,不是实测结果。真实跑出来可能出入很大——这正是你自己要跑一遍的理由。别人(包括我)的预判,永远替代不了你在自己环境里的第一手数据。
判定标准:四个层次
不要只判"能不能跑"。分成四档,失败在哪一档非常有信息量:
| 档次 | 判据 | 说明 |
|---|---|---|
| L1 生成 | 一次生成,无需人工修改 | 最高标准 |
| L2 编译 | 生成的代码能编译/能执行(gst-launch 能解析) | 语法正确 |
| L3 运行 | 启动不崩、不死锁 | 结构正确 |
| L4 正确 | 输出结果符合预期(框的位置对、帧率达标) | 语义正确 |
为什么分四档?因为 L1 通过但 L4 失败,说明是"业务细节错"(比如后处理参数);L2 就失败,说明是"语法/属性错";L3 失败,多半是"结构错"(死锁、关联不上)。不同档的失败,对应的补救措施完全不同。
记录什么
至少记这些,才能复现:
- 测试日期、SDK 版本、模型与硬件型号(缺一个,结论就不可复现)
- 每条提示词的原始输入
- 通过到哪一档(L1~L4)
- 失败类型(用下面这六类归类)
失败类型归类
这个归类比通过率更有价值——它直接告诉你该往 SKILL.md 里加什么:
| 类型 | 表现 | 主要出现在 |
|---|---|---|
| ① 属性臆造 | 插件名/属性名不存在 | 全部,尤其 C++ |
| ② caps 不匹配 | 协商失败、花屏 | Python / gst-launch |
| ③ 缺 queue 死锁 | 一帧不出 | 多分支场景 |
| ④ 动态 pad 不会接 | 运行时断言失败 | 动态增删分支 |
| ⑤ 路径/模型配置错 | 找不到模型 | 全部 |
| ⑥ CMake 依赖/链接错 | 运行期 undefined symbol | C++ |
用法:跑完一轮,数一下六类各占多少。如果 ① 和 ② 占了大头,说明你的 SKILL.md 里"属性白名单"和"caps 示例"不够,去补文档;如果 ⑥ 占大头,说明 C++ 模板缺了一份"可信的 CMakeLists.txt"。每补一次文档,重跑一次,看通过率有没有变化。这才是"调优 skill",而不是调优提示词。
失败归类的分诊流程
拿到一次失败,先别急着改提示词。按下面的顺序分诊,能直接告诉你该补文档、还是该改提示词、还是该人来接:
我们需要把 “失败"变成"路由信号” :L2 失败 → 补白名单/模板;L3 失败 → 补结构约束;L4 花屏 → 补 caps 示例;L4 精度低 →换人来走精度对齐流程(第 2 篇那套)。
最后一条尤其重要:精度问题不是一个"生成"问题。你反复改提示词,让 agent 输出不同的后处理,大概率是在浪费 token。该做的是回到第 2 篇的逐层 cosine,人来找根因。
6. 提示词工程:把"运气"变成"流程"
前面讲的都是"怎么评估"。这一节讲"怎么提高"。
核心模型:这是个闭环,不是一次性生成
(图2:生成 → 静态校验 → 运行验证 → 回灌修正,四步闭环外层还有一个"约束层"不断变厚。注意那个绿色回流箭头和灰色虚线——约束层是每一轮失败沉淀下来的,不是一开始就写全的。这才是"把运气变成流程"的实质。)
把「运气」变成「流程」——每一轮失败都要变成下一轮的约束
项目级 CLAUDE.md 应该写什么
这是"约束层"的核心。写法上,每一条都应该是"可判定的",而不是"请注意":
# 本项目 QIMSDK 编码约定 ## 环境 - 目标平台:Qualcomm Dragonwing(版本:<填>) - QIMSDK 版本:<填> - 交叉编译工具链:<填> ## 插件白名单(只允许使用以下插件) qtiqmmfsrc, qtimlvconverter, qtimltflite, qtimlpostprocess, qtimetamux, qtivoverlay, waylandsink, tee, queue > 使用白名单外的插件前,必须先运行 gst-inspect-1.0 确认存在。 ## 硬约束(违反即视为错误) 1. 任何 tee 的每个分支后必须紧跟 queue,无一例外。 2. 禁止在推理前使用非 qtimlvconverter 的变换元件做 resize。 3. 所有模型路径必须放在 /opt/models/ 下,不得硬编码绝对路径。 4. C++ 工程必须使用项目模板 CMakeLists.txt,不得自创链接顺序。 ## 验收标准 - 生成的 gst-launch 必须能被 `gst-launch-1.0` 解析(无 erroneous pipeline) - 生成的 C++ 必须能在交叉编译工具链下 `cmake --build` 成功 - 端到端必须能跑满 30fps(1080p) ## 禁止事项 - 禁止编造插件属性名;不确定就写 TODO 并说明需要 gst-inspect 确认 - 禁止省略错误处理(GstBus error/message)关键在设计哲学:禁止编造属性名;不确定就写 TODO——这条极其有效。它给了 agent 一个**“安全出口”**:与其自信地编一个错的,不如老实标注"这里需要确认"。你在 review 时一眼就能看到所有不确定点。
为什么"可判定"比"写得多"重要
很多人的 CLAUDE.md 写得很长,但全是这类句子:
“请注意性能”、“尽量使用 queue”、“确保 caps 正确”、“小心链接顺序”。
这些句子对 agent 几乎无效。因为它无法判断"什么时候算注意到了"“怎样算尽量”“什么算正确”。模型面对模糊指令的默认行为,是按概率最高的方式发挥——而概率最高的方式,往往是通用场景的写法,不是你这个特定管线的写法。
对比一下,什么叫"可判定":
| 模糊写法(无效) | 可判定写法(有效) |
|---|---|
| 尽量使用 queue | 每个 tee 分支后必须紧跟 queue |
| 注意 caps | 推理前必须用 qtimlvconverter,输入必须是 NV12 |
| 小心链接顺序 | 必须使用模板 CMakeLists.txt,不得自增 target_link_libraries 顺序 |
| 属性名要正确 | 只允许白名单内插件;白名单外必须先跑 gst-inspect |
一个实操检验:写完一条约束,问自己"如果 agent 违反了它,能不能用一条命令机器判定出来?"能,就是好约束;不能,就是装饰性的话,删掉或者改写。
比如"每个 tee 分支后必须紧跟 queue"——你可以写个脚本解析管线描述,检查tee的每个分支下一个元素是不是queue。这就是可机检。而"尽量用 queue"没法机检。能进 CI 的约束,才是真约束。
任务提示词模板
项目级的 CLAUDE.md 管"通用约束",具体任务还需要一个模板。这七个要素,缺哪个就在哪里翻车:
【目标】<一句话说清要做什么> 【输入源】<摄像头 / 文件 / RTSP,含分辨率帧率格式> 【模型与前后处理】 - 模型:<路径>,输入 <shape>,前后处理 <参数> - 预处理:<resize 方式、填充值、通道顺序、归一化> 【输出方式】<显示 / 推流 / 存文件 / 事件> 【性能约束】<目标 FPS、最大延迟、内存上限> 【必须使用的元素】<强制要求出现的插件/结构> 【验收标准】<怎么算通过,必须可判定>举个填好的例子:
【目标】USB 摄像头实时检测并显示,叠加检测框 【输入源】USB 摄像头 /dev/video0,1080p30,NV12 【模型】/opt/models/yolov8n.tflite,输入 1x3x640x640,letterbox 填充值 114,RGB 【输出】HDMI 显示,全屏 【性能】≥25fps,端到端 ≤150ms 【必须】tee 双分支(检测 + 直显)、每个分支后必带 queue、 使用 qtimlvconverter 做预处理 【验收】gst-launch 能解析;运行 60s 不掉帧;框位置与实际目标重合注意"验收标准"那一行,它把"好"变成了可判定的条件。没有这一行,agent 会给你一个"看起来完成了"的东西,而你无法证明它是不是真的完成。
三个提效技巧
技巧一:先 gst-launch,再翻译
前面说过,再强调一次。这个顺序的收益是数量级的——gst-launch 一次迭代几秒,C++ 一次迭代几分钟。先在便宜的载体上把错犯完。
技巧二:要求它先列gst-inspect检查清单,再写代码
在提示词里加一句:
“在写任何代码之前,先列出你需要用
gst-inspect-1.0确认的插件和属性清单。”
它会输出类似:
需要确认: 1. qtiqmmfsrc 的摄像头选择属性名(camera? device?) 2. qtimltflite 的 delegate 属性可用值 3. qtimlvconverter 是否接受 NV12 输入你(或它自己)跑一遍gst-inspect-1.0,把结果喂回去,再让它写代码。这一步把"猜测"变成了"查证",能消掉很大一部分翻车一的概率。
技巧三:把运行日志回灌给 agent
跑失败时,别自己分析。把完整报错贴回去:
“运行报错如下,请定位根因并给出最小修复:
<完整日志>”
agent 在"给定明确错误信息"时的定位能力,远强于"从零生成"。它需要的不是更多创造力,是更多约束信号。GStreamer 的报错信息往往很精确(比如no property "camera-id"),这类信息对 agent 是极好的输入。
给 skill 加"硬约束"的三个抓手
如果你的团队维护自己的 skill,这三个约束收益最高:
- 插件名白名单,直接消灭翻车一的一大部分。
- 强制
tee+queue配对,直接消灭翻车二(死锁是最贵的 bug,因为它不报错)。 - 提供一份"可信的 CMakeLists.txt"模板,直接消灭翻车三。
一个判断标准:如果你的 skill 里写的还是"建议使用 queue"、“注意链接顺序”,那它起不到护栏作用。必须写成命令式的、可判定的规则(“每个 tee 分支后必须紧跟 queue”),agent 才会当硬约束执行。
7. 边界:哪些事仍然不要交给 AI
说了这么多"怎么用好",也得说清楚"什么时候别用"。四类事,仍然要人来:
| 类别 | 为什么 | 谁来 |
|---|---|---|
| 硬件时序 / 时钟同步 | 依赖具体硬件行为和实测,模型没有这类信号 | 人 |
| 零拷贝路径的性能关键改动 | 影响面大、验证成本高,错一个 caps 就断链 | 人 |
| 安全与权限配置 | 错误代价高,且无法用"能不能跑"来验证 | 人 |
| 最终架构决策 | 需要权衡团队、交付、长期维护,不是代码问题 | 人 |
结论一句话:AI 适合生成 80% 的骨架,剩下 20% 必须人来审。
而那 20% 具体是什么?翻车三就是最好的注脚,CMake 链接顺序这种"编译能过、运行才炸"的问题,就是典型的 20%。它不体现在代码语义里,体现在构建系统的隐性依赖里。这类问题,目前的 agent 很难自己发现,因为它看不到运行时的失败(除非你把日志喂给它)。
8. 收尾:把 Agent 接进团队流程
单个开发者用好 agent 是一回事,团队规模化用起来是另一回事。三个落地点:
① 生成代码进 CI
别让人去 review 每一行生成的代码——让机器先筛一遍:
# CI 流程示意steps:-生成/提交代码-编译(cmake--build 或 gst-launch--version 校验)-静态校验(gst-inspect 检查插件与属性是否存在)-冒烟运行(跑 10 秒,确认无死锁、无崩溃)-性能门槛(帧率 ≥ 阈值)任何一步失败,就别进下一环。这一步把"AI 生成"从"个人技巧"变成了"可管理的工程流程"。
② 文档变更时同步更新 skill
这是闭环能持续运转的关键。SKILL.md 不是写完就完了——每次 SDK 升级、每次踩到新坑,都要回流到 SKILL.md。
否则会出现最糟的情况:skill 里的属性名过时了,agent 照着旧文档生成,错误率反而比不用 skill 还高(因为它"有据可依地错了")。
③ 建立失败案例库
把第 5 节的六类失败,逐条沉淀成"反例 + 正例 + 检测方法"。这是团队最值钱的资产,它比任何提示词模板都更能提高可用率,因为它是从真实翻车中长出来的。
写在最后
回到开头那三个问题。它们不是"AI 不行"的证据,而是"AI 的边界在哪"的说明:
- 属性名是编的 → 边界在**“知识必须来自实物”**;
- 忘了
queue→ 边界在**“通用习惯不等于特定约束”**; - CMake 链接错 → 边界在**“能编译不等于能运行”**。
这三个边界,都可以通过更好的约束层(SKILL.md + CLAUDE.md + 提示词模板)来收窄。但收窄不等于消除,最后那 20%,仍然需要人。
所以正确的态度不是"让 AI 全自动写",而是:让 AI 承担那些"结构清晰、可以校验"的部分,把人的精力留给"边界模糊、验证困难"的部分。这才是 Agentic Coding 在嵌入式场景下能真正落地的姿势。
【下篇预告】
至此,我们已经拥有了一个高性能、能处理多路复杂业务的本体程序。但它在客户眼里,依然只是一个“跑在板子上的黑盒Demo”。客户会问:断网了数据会丢吗?怎么远程升级模型?出了事故怎么回溯?
在最后一篇第四篇中,我们将完成“从Demo到产品”的终极跨越,微服务化与事件流实践,教你如何把这套能力打包成经得起生产环境考验的容器化服务。
系列索引:(一)GStreamer数据通路与多流调度 · (二)QNN/ONNX Runtime/TFLite三大推理后端选型 · (三)Agentic Skills实测与提示词工程· (四)微服务化与事件流实践