换个国家名就想混过去?8 词 shingle 重叠率不答应
维护一个 agent 提示词库久了,最难受的不是有人提交了写得差的 agent——写得差一眼就能看出来,打回去就是了。难受的是有人提交了一个看上去完全合规的 agent:frontmatter 三个必填字段齐全,章节结构标准,正文篇幅也不短,语气也对,合并进去毫无摩擦。等你半年后翻库,才发现它和另一个已有 agent 有八成句子是同一批,区别只是把某个国家名、某个平台名做了一次查找替换。
agency-agents 这个仓库(github.com/msitarzewski/agency-agents,MIT,2026-08-09 快照下 star 140729)把这件事当成一个需要脚本来守的具体问题,写了 scripts/check-agent-originality.sh。脚本自述的动机就是这句话的意思:新 agent 应该是真的新东西;把已有 agent 做一次换皮,在评审里很难被发现——它能合并、格式也规范——但会用重复内容把库撑肿。
这篇拆的就是这套检查的机制,重点不在「有这么个脚本」,而在你看到一个分数之后该怎么判断。
为什么先中性化,再算 shingle
脚本的算法可以拆成两步。
第一步是实体中性化。 把一批专有名词先替换成中性占位,再进入比对。源码列出的被中性化词包括 vietnam / vietnamese / china / chinese / douyin / tiktok / korea / korean / japan / japanese 等,源码注释还写明这份清单会「随着新市场出现继续扩充」。
这一步是整套设计里最关键的一手。因为换皮抄袭最常见的形态就是把地域和平台名换掉——一个「面向某市场的增长专家」改成「面向另一市场的增长专家」,正文里的方法论一个字没动。如果直接按原文比对,这两份文件的字面重叠率会被这几十处专名的差异稀释掉,看起来「不太像」。先把专名抹平,抄袭就藏不住了。
顺带一提,这条清单不是凭空定的。翻一遍仓库导出的 255 个 agent 花名册,带地域和平台属性的 agent 本来就不少,china、tiktok、douyin、korea 这些词在描述里都能检索到命中——换句话说,这正是这个库里最容易被换皮复制的一类 agent,清单是冲着自己库的实际形态去的。
第二步是 8 词 shingle 重叠率。 shingle 是把文本切成连续 N 个词的滑动窗口,N=8 就是每 8 个连续词构成一个片段,整篇文章变成一大堆片段的集合;两份文档的相似度就是这两个集合的重叠比例。
需要说明的是,仓库源码里并没有解释为什么选 8 而不是 5 或 12,下面这段是按 shingle 这种通用做法的常识来理解,不是该项目文档的说法:窗口越短越容易误伤——像 “you are an expert specializing in” 这种模板句在任何一个提示词库里都会大面积撞车,N 取 3、4 会把正常的行文习惯全算成抄袭;窗口越长越容易漏——抄的人只要每隔十几个词改一个用词,长窗口就全部对不上了。8 词大致落在「一个完整从句」的量级上,既能容忍个别同义词替换,又不会把公共套话当成证据。
两步合起来的效果是:它抓的是「结构和句子成段照搬」,不是「主题相近」。两个都在讲 CI/CD 的 agent,只要是各写各的,重叠率不会高。
40 和 20 怎么读:先看那两个校准值
脚本给了两个环境变量,都是可覆盖的默认值:
| 变量 | 默认值 | 含义 |
|---|---|---|
ORIGINALITY_FAIL | 40 | 达到或超过该百分比视为重复,退出码 1 |
ORIGINALITY_WARN | 20 | 达到或超过该百分比出警告,不判失败 |
只看这两个数字,很容易以为「40% 重复才算抄,这门槛也太松了」。真正的判断依据不在阈值上,在源码注释给出的校准数据:在现有的 agent 库里,两两之间最差的相似度约 1.5%,中位数是 0%。
把这三组数字放在一起,含义就完全变了:
- 正常写出来的 agent,彼此相似度基本贴着 0 走,最坏情况也就 1.5% 出头;
- 20 的警告线是这个「最坏正常值」的十倍以上;
- 40 的失败线更是二十几倍。
也就是说,这套阈值不是拿来做精细排序的,是拿来抓强异常的。一份提交只要跑出双位数,它就已经离整个库的正常分布非常远了——这时候你要做的不是纠结「28% 算不算抄」,而是直接去 diff 那两份文件,看看重叠的是哪几段。反过来,因为安全边界留得这么宽,一个正经原创的 agent 几乎不可能被误判成失败。
这也意味着一件反直觉的事:分数在 2% 和 15% 之间时,脚本什么都不会说,但它其实已经很值得你看一眼了。默认阈值是按「零误判」调的,不是按「零漏判」调的。如果你在维护自己的库、又对重复特别敏感,可以把警告线压低:
# 把警告线压到 10、失败线压到 25,只对本次要求更严
ORIGINALITY_WARN=10 ORIGINALITY_FAIL=25 ./scripts/check-agent-originality.sh
以上为按官方参数语义组合的示例,未逐项实测,以官方文档与脚本自身的用法说明为准。要提醒的是,1.5% / 0% 是这个库的库内校准值,不是对任意 agent 库的普适结论——你自己的库如果大量使用统一模板,正常基线本来就会高得多,照搬别人的阈值反而会天天误报。合理的做法是先在自己的库上跑一次全量审计,看清楚自己的基线分布长什么样,再决定阈值。
两种运行模式,分别用在什么位置
脚本的参数契约很简单,但两种模式对应的是完全不同的场景:
# 模式一:只查指定文件(CI 里对变更文件用)
./scripts/check-agent-originality.sh engineering/engineering-ai-engineer.md
# 模式二:不给参数,全库两两互查(审计模式)
./scripts/check-agent-originality.sh
给文件 = 增量把关。 这是放在 CI 里的用法:一个 PR 改动了哪几个 agent,就只拿这几个去和整个花名册比。成本随改动量走,适合每次提交都跑。
不给参数 = 全库审计。 两两互查,覆盖的是「在这套检查上线之前就已经合并进来的历史存量」。增量把关永远管不到存量,这两种模式谁也替代不了谁。库到了 255 个 agent 这个量级,全量互查的比较对数不小,把它放在定期审计而不是每次提交上,是合理的分工。
还有一个容易踩的坑:脚本依赖 python3,找不到就直接退出码 2。这个退出码要和 1 分清楚——1 是「查出重复了」,2 是「环境不对,压根没查成」。CI 里如果只判断「非零即失败」,这两种情况会被混成一锅,你会以为有人在抄,实际上是构建镜像里没装 python3。判断的动作很直接:看退出码是 1 还是 2,是 2 就先去查运行环境。Windows 侧同理,这一整套都是 shell 脚本,install.sh 的脚本注释里写的平台支持是 Linux、macOS(需要 bash 3.2+)与 Windows 的 Git Bash / WSL,在 PowerShell 里直接调是跑不起来的。
它管什么,不管什么
这一节比上面几节更重要,因为误解一个质量门能力边界的代价,比没有这个质量门还大。
同一个仓库里还有 scripts/lint-agents.sh,两者的分工是清楚的:
lint-agents.sh管结构。frontmatter 必须存在,且含name、description、color三个必填字段,缺一个报 ERROR;行尾必须是 LF,CRLF 直接 ERROR;推荐章节(Identity / Core Mission / Critical Rules)缺失只报 WARN;正文词数少于 50 也只是 WARN。check-agent-originality.sh管重复度。
两个加起来,也一个字都没有评估提示词写得好不好用。 一份结构完美、和谁都不重复、但内容空洞的 agent,可以干干净净地通过全部检查。这不是脚本的缺陷,是它们本来就没打算做这件事——提示词有没有用,只能靠人读、靠实际使用去判断。
WARN 和 ERROR 的区别也得记住:推荐章节缺失不会让检查失败。所以「CI 绿了」的准确含义是「结构没有硬伤、没有明显换皮」,不是「这个 agent 已经可以用了」。
想新增一个 agent,按什么顺序自查
把上面的机制翻译成一条可执行的路径:
- 先问自己一个问题:我要写的这个 agent,和库里已有的那个最像的,差别是不是只有「换了个地域 / 平台 / 行业名词」?如果是,中性化那一步会把这层差异直接抹掉,跑分没有悬念——这种情况该做的是扩写已有 agent 的适用范围,而不是新开一个文件。
- 写完先跑指定文件模式,拿到分数。
- 看分数落在哪个区间:贴近 0 就是正常;进了双位数,别管有没有报警告,直接去 diff;到了 40 以上会以退出码 1 失败,这时候基本可以确定是成段照搬。
- 别把通过当成质量认证。跑完这两个脚本,你验证的是结构和重复度,内容价值仍然要人来判断。
一个可以抄走的做法
最后提一个和本篇主题相关、但可以脱离这个仓库使用的工程细节:check-agent-originality.sh 里的 division 列表是直接读 divisions.json 的,而不是在脚本里再硬编码一份拷贝。源码注释写明了理由——硬编码的字面量会悄悄跟目录漂移,读文件则不可能。
这个仓库把「配置漂移」当成明确的敌人:check-divisions.sh 负责让 division 列表与磁盘目录、各脚本里的 AGENT_DIRS 数组、工作流的路径过滤器保持一致,不一致就让构建失败;新增一个 division 的官方步骤也是固定的——建目录、在 divisions.json 加条目、跑 scripts/check-divisions.sh、按它的报错去更新它指向的每一处。
任何一个「有一份清单,同时被三四个地方消费」的项目都会遇到同样的问题。与其指望每个人改完记得同步,不如让唯一的那份清单成为文件,其余全部读它,再加一个专门检查一致性的脚本。这个思路和原创性检查其实是同一件事的两面:把靠人自觉才能守住的规则,变成脚本能判定的东西。
延伸阅读
- 一份 JSON 管住五个脚本:单一真相源是怎么落地的
- agency-agents 的 agent 文件格式:name/description/color 是必填,emoji/vibe 不是
- 一个 agent 文件长什么样:frontmatter、角色声明、分节正文
本文依据 agency-agents 官方仓库(github.com/msitarzewski/agency-agents)的 README、
tools.json、divisions.json、scripts/ 下的安装与校验脚本整理,核对日 2026-08-09。
本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何 agent。
目录、脚本与安装路径随上游更新而变动,请以仓库最新内容与 ./scripts/install.sh --help 的实际输出为准。