本文最初发布于Boston.AI官网,经授权转载。
在"让AI按你的方式写代码"系列的第一篇中,我们介绍了如何通过项目级SKILL.md文件解决"生成代码与我们风格不符"的问题。两个技能文件、几条TRIGGER子句,再加上CLAUDE.md底部的一张简单对应表,就足以让Claude在制定计划的过程中自动调用正确的技能。
本篇将把同样的技能文件、编写规范和使用预期,迁移到另一个智能体上:在本地推理硬件上运行Qwen3-Coder的Qwen Code CLI。
技能格式本身是事实上的通用标准,相同的技能文件理应在各类编程智能体上通用。然而,Qwen Code CLI的表现并不一致。为了让Qwen3-Coder-80B-A3B能主动调用我们的技能文件,我们额外花费了不少精力。
为什么在本地部署中选择Qwen3-Coder
我们经常使用Anthropic的前沿模型Sonnet和Opus,但某些项目要求本地部署。以下是我们最常选择Qwen3-Coder作为本地模型的原因:
部分工作无法离开我们的内网。对于不能发送给第三方的代码,本地模型是默认选择。
前沿模型的溢价在面对复杂问题时才物有所值。而对于占编码工作量约60%的常规任务,在我们已有硬件上运行本地模型要便宜得多。
Qwen3-Coder在我们的RTX 6000 GPU上运行速度极快,准确率也相当不错。但抽象意义上的快速和准确,并不等同于在一个采用激进依赖注入、符合SOLID原则架构的Qt/QML代码库上同样出色。
要让Qwen3-Coder在我们的项目中发挥生产力,必须同时满足两个条件:技能文件要为其量身定制,技能文件要能被真正调用。
为Qwen3-Coder定制技能文件
Claude Sonnet或Opus通常能从一两个现有类中推断出我们的代码规范,但Qwen3-Coder做不到。我们必须把规范写得清清楚楚。正因如此,面向Qwen的技能文件明显比面向Claude的原版更长,包含更多实际示例、更多"错误写法vs正确写法"对照表,以及更多边界情况的说明。
有句话说得好:如果你的时间值钱,免费就不是真的免费。我们不得不投入时间来改进和验证技能文件,并管理模型的上下文。从长远来看,这笔投入是值得的,因为这些技能和方法可以在所有Qt项目上复用。以下几种模式对我们帮助很大:
RAG辅助Qt文档查询
我们搭建了一套内部RAG服务器,对Qt和QML文档进行索引。Qwen3-Coder通过MCP调用该服务器来解答所有Qt API问题,而不是凭记忆作答。没有这套系统时,Qwen3-Coder在Qt函数签名和枚举值上的幻觉频率高到难以接受。有了它之后,Qt API的准确率大幅提升。
更详尽的技能示例
凡是Claude能"参照现有模式"推断的地方,面向Qwen的版本都需要提供具体示例。技能文件的篇幅增加了,而生成代码的一致性也随之提升。事实证明,改进后的技能文件与Claude也完全兼容,可谓一举两得。
这里有个重要启示:本地模型不能直接替代前沿模型,它是一种具有不同失效模式的不同工具。我们发现,技能文件是弥合差距的有力手段,但前提是我们愿意比对待前沿模型时投入更多。
真正的瓶颈:技能文件根本不被调用
差点让我们放弃Qwen3-Coder部署的,不是代码质量问题——RAG和精心设计的技能内容已经解决了大部分Qt/QML代码生成的问题。真正的问题在于:Qwen3-Coder几乎从不主动加载这些技能文件。
在同一个项目、相同的技能文件、以及同样的CLAUDE.md风格快速参考文件(我们将其命名为QWEN.md)的条件下,Qwen Code CLI在判断"这个提示词匹配某个技能,我应该加载它"方面明显不如Claude Code。
通过/skill命令显式调用可以奏效,直接提示有时也管用,但计划执行几乎从不奏效。一个写着"添加一个新的QML组件并测试"的计划步骤,会直接开始写代码,根本不加载技能文件。
这个问题也许在后续版本中会得到改善,因为Qwen3-Coder的迭代速度很快。但我们需要一个当下就能用的解决方案。
解决方案:UserPromptSubmit钩子
Qwen3-Coder最近新增了一套与Claude Code非常相似的钩子系统。钩子是一个辅助工具(在我们的场景中是一条Shell命令),智能体在特定会话生命周期节点执行它:提交提示词之前、工具调用之后、会话启动时,等等。钩子可以将Shell命令的标准输出内容注入上下文,智能体会将该输出纳入下一轮对话。这是一个干净的"逃生通道",用于"在每次触发时,在这个位置注入一段文本"。
钩子配置
我们挂载了UserPromptSubmit事件。每次提交提示词时,都会注入一个简短的skill_reminder.json文件的内容,告知Qwen3-Coder当前项目有哪些技能文件以及各自的调用时机。这本质上和QWEN.md中的对应表是同一个思路,区别在于:我们不再依赖QWEN.md在漫长会话中保持显著位置,而是强制将提醒信息注入每一轮用户提示词的顶部。
在~/.qwen/settings.json(或项目级别的对应文件)中配置如下:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "cat ./.qwen/skill_reminder.json"
}
]
}
]
}
}
命令需要以特定JSON格式将提醒文本发送到标准输入:
{
"continue": true,
"hookSpecificOutput": {
"additionalContext": "提醒文本内容"
}
}
skill_reminder.json文件内容如下:
{
"continue": true,
"hookSpecificOutput": {
"additionalContext": "项目技能提醒。本项目包含编码了我们架构和测试规范的Qt/QML技能文件。在写代码之前,请调用匹配的技能。技能:creating-qt-classes。调用时机:新增C++类、接口(I<Name>.h)、Mock(Mock<Name>.h)、服务,或将C++类型暴露给QML时。对已有类的修改或仅涉及构建的变更,无需调用。技能:writing-qml-and-tests。调用时机:新增QML组件、tst_*.qml测试、C++单例的QML Mock,或需要设置objectName以支持测试发现时。规则:所有Qt/QML/C++相关工作,先检查上方技能列表。如有匹配技能,在写代码或调用工具之前先调用它。Qt API问题请使用MCP Qt文档工具,不要凭记忆作答。如果文档无法确认某API存在,请如实说明,不要猜测。"
}
}
有两点值得特别说明。第一,这与QWEN.md中的对应表内容相同,我们并没有告诉Qwen任何新信息。第二,该文件刻意保持简短。若每次都向上下文注入大量提醒内容,不仅会消耗大量Token,还会挤占用户的有效内容。钩子的意义就在于让这段提醒保持简短且可靠。
效果对比
Qwen随即开始在直接提示词场景下自主调用技能文件,更关键的是,在计划执行阶段也开始这样做了。对应表的内容并不是新信息,只是这些信息现在紧靠在提示词旁边,而不是埋在几千个Token之前的上下文里了。我们注意到两个明显变化:
此前凭记忆直接生成代码的计划步骤,现在会先加载匹配的技能文件,再生成符合我们架构的代码。
直接提示词在第一轮就能命中正确的技能,而不再需要额外的提醒。在部署了钩子的项目中,我们需要按Ctrl-C并提醒Qwen3-Coder"请使用技能文件"的次数,几乎降到了零。
更广泛的启示
这个问题并非Qwen独有。核心启示在于:当指令存放在距离当前活跃轮次较远的位置时,智能体究竟能多可靠地遵循它。前沿模型能够容忍指令与其生效时刻之间较大的距离,而更小、更快、更便宜的模型则容忍度低得多。钩子让你用极少的上下文窗口空间换取有保障的时序邻近性,对于重要的工作流规则,我们发现这笔交换几乎总是值得的。
如果你正在将任何小型或自托管模型部署在智能体框架中,你应该审视每一条"模型应该记住做X"的规则,思考将其作为UserPromptSubmit注入,是否比写在智能体配置文件的某个段落里更可靠。
根据我们的经验,答案通常是肯定的。
总结
Qwen3-Coder是我们自有硬件上的一头真正的"工作骡子"。一旦满足两个条件,它就能在Qt/QML项目上生成高质量代码:技能文件写得足够详尽,以及技能文件能被真正调用。项目专属技能文件(已在第一篇中介绍)解决了前者,注入小型skill_reminder.json文件的UserPromptSubmit钩子解决了后者。
两者结合,使Qwen3-Coder成为我们智能体体系中可靠的第二梯队,能够承担约60%的常规编码工作,让我们得以将前沿模型留给最难啃的问题。第三篇将详细探讨如何判断该使用哪个模型。
免责声明:本文描述的行为均在我们自有的Qt/QML评估环境中观测得出,该环境使用Qwen Code CLI对接自托管的Qwen3-Coder。在不同技术栈上的实际表现可能有所差异。
作者:Andrey Pozdnyakov,ICS Qt软件开发工程师;Justin Noel,ICS高级软件架构师
Q&A
Q1:Qwen3-Coder为什么不能直接替代Claude等前沿模型?
A:Qwen3-Coder是一种具有不同失效模式的不同工具。它无法像Claude Sonnet或Opus那样从少量示例中推断代码规范,需要更详尽的技能文件和更多具体示例才能生成符合项目架构的代码。此外,Qwen3-Coder在主动调用技能文件方面也明显弱于Claude,需要额外配置才能可靠运行。
Q2:UserPromptSubmit钩子是怎么工作的?为什么能解决技能文件不被调用的问题?
A:UserPromptSubmit钩子会在每次用户提交提示词时,自动将skill_reminder.json的内容注入上下文顶部。由于提醒信息紧靠在提示词旁边,模型能够直接感知并遵循,不再依赖埋在几千个Token之前的配置文件。这种方式以极少的Token消耗,换取了技能调用的高度可靠性,让Qwen3-Coder在直接提示和计划执行阶段都能主动加载正确的技能文件。
Q3:RAG服务器对Qwen3-Coder的Qt/QML开发有多重要?
A:非常关键。没有RAG服务器时,Qwen3-Coder在Qt函数签名和枚举值上的幻觉频率极高,严重影响代码质量。该团队搭建了一套内部RAG服务器,对Qt和QML文档进行索引,Qwen3-Coder通过MCP调用它来解答所有Qt API问题,而不是凭记忆作答。接入RAG后,Qt API的准确率大幅提升,成为保障代码质量的重要基础设施。
