dsh-similarity
v0.1.0
Published
Similarity toolkit for DeepSeek Harness: edit distance (Levenshtein / Dice / Jaccard / Hamming), near-duplicate pair discovery across a list, and fuzzy best-match lookup. Zero runtime dependencies, read-only.
Maintainers
Readme
dsh-similarity
DeepSeek Harness 的相似度工具箱:edit_distance 比较两段文本的距离与相似度,similar_pairs 在一组条目里找出所有近似重复的对,best_match 为一段输入从候选列表中挑出最接近的若干条。支持 levenshtein / dice / jaccard / hamming 四种算法。只读、零运行时依赖。
- 源码:https://github.com/geeklei/dsh-plugins/tree/main/dsh-similarity
- npm:https://www.npmjs.com/package/dsh-similarity
功能说明
「看着一样却不相等」「到底是同一个还是两个」「用户输入的枚举值拼错了」——这类判断凭肉眼很容易出错:差一个字符、多一个空格、大小写不同,结果就完全不同。本插件把「有多像」变成一个可比较的数字。
edit_distance —— 两段文本的相似度
算法:levenshtein(区分大小写;保留空白)
左侧:8 字符;右侧:8 字符
距离:1
相似度:0.8750(87.5%)
编辑操作:需要 1 次插入 / 删除 / 替换即可把左侧变成右侧。
口径:按 Unicode 码点比较(emoji 记 1 个字符);相似度 = 1 - 距离 / 较长一侧长度。四种算法的口径不同,按场景选择:
| 算法 | 距离含义 | 相似度口径 | 适合 |
|---|---|---|---|
| levenshtein(默认) | 插入 / 删除 / 替换的最少次数 | 1 - 距离 / 较长一侧长度 | 错别字、少字多字、拼写纠错 |
| dice | 1 - 相似度 | bigram 多重集合的 Dice 系数 | 中文长文本、名称、句子的近似度 |
| jaccard | 1 - 相似度 | bigram 去重集合的交并比 | 关注「出现过哪些片段」而非次数 |
| hamming | 等长逐位不同的位数 | 1 - 距离 / 长度 | 定长编码、版本号、等长 ID |
算法:jaccard(区分大小写;保留空白)
左侧:15 字符;右侧:15 字符
距离:0.0714
相似度:0.9286(92.9%)
说明:dice / jaccard 无编辑距离概念,距离按 1 - 相似度 给出。
口径:按 Unicode 码点切成 bigram,Jaccard 相似度 = 交集 / 并集(bigram 去重)。ignore_case(忽略大小写)与 ignore_whitespace(忽略全部空白)只管比较口径,不改动原文本:苹果手机 iPhone 15 Pro 与 苹果手机 iphone 15 pro 在 ignore_case=true 时相似度为 1.0000。
similar_pairs —— 在一组条目里找近似重复
来源:(内联文本)
条目:5 条(空行已跳过)
算法:dice;阈值 ≥ 0.5;区分大小写;保留空白;含完全相同
比较:10 对,命中 3 对(阈值 ≥ 0.5)
| # | 相似度 | 条目 A | 条目 B |
| --- | ------ | ------------------------------- | ----------------------------- |
| 1 | 0.9600 | #0 深圳市深智城科技发展有限公司 | #1 深圳市深智城科技发展有限公 |
| 2 | 0.6957 | #0 深圳市深智城科技发展有限公司 | #2 深圳深智城科技有限公司 |
| 3 | 0.6364 | #1 深圳市深智城科技发展有限公 | #2 深圳深智城科技有限公司 |条目按行给出(内联 text 或 path 指向的文件),空行自动跳过,每行两端空白会被裁掉。列表会被两两比较(n × (n-1) / 2 对),结果按相似度降序返回 Top N。
include_identical 默认 true,完全相同的重复项也会被列出(找重复数据时通常正是想要的);只想看「不完全相同但很像」的对时设为 false。
best_match —— 从候选里挑最像的
查询:深智城(3 字符)
候选来源:(内联文本)
候选:3 条(空行已跳过)
算法:dice;阈值 ≥ 0.2;区分大小写;保留空白
命中:1 条(阈值 ≥ 0.2)
| # | 相似度 | 距离 | 候选 |
| --- | ------ | ------ | ---------------------------- |
| 1 | 0.2667 | 0.7333 | 深圳市深智城科技发展有限公司 |
最佳匹配:深圳市深智城科技发展有限公司(相似度 0.2667)没有任何候选达到阈值时,会给出「最接近的一条」作为参考,而不是只说「没找到」,方便判断是拼写问题还是真的没有对应值。
安装
npm install dsh-similarity安装后在 cordis.patch.yml 中加入:
- insert:
- id: similarity
name: "dsh-similarity"使用说明
edit_distance
| 参数 | 类型 | 说明 |
|---|---|---|
| left | string | 左侧文本(必填) |
| right | string | 右侧文本(必填) |
| algorithm | string | levenshtein(默认)| dice | jaccard | hamming |
| ignore_case | boolean | 忽略大小写,默认 false |
| ignore_whitespace | boolean | 忽略全部空白字符,默认 false |
| max_chars | number | 输出字符上限,默认 20000,上限 200000 |
用户:这两个订单号差了几个字符
edit_distance(left="订单A-1001", right="订单A-1002")用户:这两段文案是不是只差大小写
edit_distance(left="苹果手机 iPhone 15 Pro", right="苹果手机 iphone 15 pro", algorithm="dice", ignore_case=true)similar_pairs
| 参数 | 类型 | 说明 |
|---|---|---|
| path | string | 条目文件路径,每行一条(与 text 二选一) |
| text | string | 内联条目列表,每行一条(优先于 path) |
| algorithm | string | 同上,默认 dice |
| threshold | number | 相似度阈值,0~1,默认 0.6 |
| limit | number | 最多返回对数,默认 20,上限 500 |
| include_identical | boolean | 是否把完全相同的条目也计入,默认 true |
| ignore_case / ignore_whitespace | boolean | 同上 |
| max_items | number | 最多比较条目数,默认 1000,上限 5000 |
| max_chars / max_size | number | 输出字符上限 / 文件大小上限 |
用户:这份客户名单里有没有重复或很像的名字
similar_pairs(path="customers.txt", threshold=0.8, limit=30)用户:只看近似重复,不要完全一样的
similar_pairs(path="customers.txt", include_identical=false, threshold=0.7)best_match
| 参数 | 类型 | 说明 |
|---|---|---|
| query | string | 查询文本(必填) |
| path / text | string | 候选列表,每行一条(二选一,text 优先) |
| algorithm | string | 同上,默认 dice |
| threshold | number | 相似度阈值,默认 0.3 |
| limit | number | 最多返回候选数,默认 5,上限 500 |
| ignore_case / ignore_whitespace | boolean | 同上 |
| max_items / max_chars / max_size | number | 同上 |
用户:用户输入的城市名拼错了,帮我匹配到规范名称
best_match(query="深洲市", path="cities.txt", threshold=0.4)用户:这几个状态值里哪个最接近 "pendding"
best_match(query="pendding", text="pending\npaid\ncancelled\nrefunded", ignore_case=true)典型场景
- 数据去重:对导入的客户名、公司名、地址做
similar_pairs,先看相似度最高的几对,再人工确认是否同一实体。 - 拼写纠错:用
best_match把用户输入或 OCR 结果映射到已有枚举值、字典项。 - 变更核对:用
edit_distance判断两个版本号、两条配置、两行日志是「同一条」还是「改动过的」。 - 近似搜索:把候选集交给
best_match,用相似度排序代替严格相等匹配。 - 模糊查重:标题、摘要、商品名这类重复但不完全一致的场景,用
dice更稳。 - 定长编码比对:
hamming直接给出等长串(哈希前缀、序列号)差异位数。 - 大小写/空格脏数据:先用
ignore_case、ignore_whitespace归一,避免格式差异淹没真实差异。
安全边界
- 只读:三个工具都不写入、不修改、不删除文件;测试含源文件 size/mtime 不变的只读性断言。
- 不访问网络:比较全部在本地完成,无任何外部请求。
- 不执行输入:只做字符串比较,不做
eval、不解析表达式、不加载模块、不跑正则回溯。 - 二进制拒绝:读取时检测 NUL 字节,疑似二进制文件直接拒绝解析。
- 多层上限:文件大小上限 51200 KB、输出字符上限 200000、返回对数上限 500;
similar_pairs两两比较的条目数上限 5000(默认 1000),避免平方级增长失控。 - 编辑距离长度上限:
levenshtein/hamming单侧上限 5000 字符,超限直接报错并建议改用dice/jaccard(线性复杂度)。 - 不脱敏:条目原样输出,长条目在表格中按显示宽度截断(仅影响展示,不影响计算)。若数据含密钥等敏感信息,请先用
dsh-credential-scan检查。
其他插件集成说明
| 协作插件 | 集成方式 | |---|---| | dsh-csv-explorer | 先预览、筛选出目标列,再导出成一列条目交给 similar_pairs | | dsh-line-ops | 用行操作去重、排序、截取后再做相似度比较 | | dsh-text-diff | text-diff 给出「差异在哪」,similarity 给出「差多少」,配合判断改动幅度 | | dsh-text-stats | 先用文本统计了解条目规模,再决定 threshold 与 limit | | dsh-unicode-toolkit | 比较结果异常时,用 inspect_codepoints 排查零宽字符、全半角导致的「看着一样却不相等」 | | dsh-json-yaml-toolkit | 从 JSON / YAML 中取出字段值列表,再做模糊匹配 | | dsh-todo-audit | 对 TODO 文本做近似聚类,找出重复登记的待办 | | dsh-workspace-journal | 对日记条目做近似检测,避免重复记录同一件事 | | dsh-credential-scan | 处理前扫描数据中是否混入凭据、密钥 |
版本说明
- 0.1.0(2026-10-02):初始版本。
edit_distance:levenshtein / dice / jaccard / hamming 四种算法,输出距离与相似度百分比,支持忽略大小写与空白similar_pairs:条目两两比较,按阈值过滤、按相似度排序返回 Top N,可选择是否计入完全相同项best_match:为查询文本返回 Top N 候选及分数,无命中时给出最接近项- 全部按 Unicode 码点比较(emoji 记 1 个字符);表格按显示宽度对齐(中日韩字符按 2 列宽)
- 二进制拒绝、多层上限与平方级比较的条目数保护
RoadMap
v0.2(计划)
similar_pairs增加cluster:对命中的相似对做并查集聚类,直接输出「疑似重复分组」edit_distance增加damerau(允许相邻换位)与token(按词而非字符比较)best_match增加all_matches:返回所有超过阈值的候选并标注并列- 新增
normalize_key:统一的键归一化(大小写、全半角、空白、标点)供三个工具共用
v0.3(展望)
similar_pairs增加minhashes近似分桶,支持十万级条目的快速查重- 词级权重与停用词支持,提升中文长句的比较质量
- 输出 JSON 结构,便于管道化处理
- 与
dsh-import-graph、dsh-deps-audit联动,检测项目中近似重复的模块名与依赖名
FAQ
Q:为什么默认用 dice 而不是 levenshtein?
similar_pairs 和 best_match 面向的是「成组条目」的比较,中文长度普遍偏长,编辑距离对少数字符差异过于敏感;bigram 系数更贴近人的直觉。单对短字符串比较用 edit_distance 的默认 levenshtein 即可。
Q:为什么 similar_pairs 的算法默认和 edit_distance 不一样?
同上:前者用于成组近似查重,后者用于两段文本的精确差异度量,默认值按各自最常用场景设定,都可显式覆盖。
Q:相似度 0.93 算不算重复?
没有通用阈值。短字符串(小于 6 个字符)建议 0.8 以上,中文长名称 0.6~0.75 往往就已经是重复了。建议先用 threshold=0.5 看一轮分布,再决定卡在哪。
Q:hamming 遇到长度不同的两侧会怎样?
edit_distance 直接报错并给出两侧长度;similar_pairs 与 best_match 跳过长度不匹配的组合,并在输出中提示跳过了多少条。
Q:为什么 best_match 有时候返回 0.2667 这种很低的分数?
分数低说明两者只是部分片段相同。best_match 会如实给出分数,不会因为「它是第一名」就抬高它;是否需要采用由阈值决定。
Q:similar_pairs 条目很多会不会很慢?
比较量随条目数平方增长。默认上限 1000 条(约 50 万次比较),超过会被截断并提示(返回结果会标注「已忽略」)。条目多时先降低 max_items、提高 threshold,或先用其他工具把范围缩小。
Q:ignore_whitespace 会把换行也去掉吗?
会。该项会移除全部空白字符(空格、制表符、换行)。条目本身按行拆分后再比较,行内的空白差异才会被该项影响。
Q:为什么 emoji 算 1 个字符?
按 Unicode 码点切分,而不按 UTF-16 单元,所以 😀 记为 1 个字符。这对中日韩与表情混排的文本更符合直觉。
Q:能直接改文件里的条目吗? 不能。本插件只读,输出的是分析结果,不写回文件。
源码与发布地址
- 源码:https://github.com/geeklei/dsh-plugins/tree/main/dsh-similarity
- npm:https://www.npmjs.com/package/dsh-similarity
许可证
MIT
