多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

GitHub的AI程序员“抄袭”算法大神代码!连原版注释都抄上了——用TaoToken统一Key复现AI代码溯源检测

GitHub的AI程序员“抄袭”算法大神代码!连原版注释都抄上了——用TaoToken统一Key复现AI代码溯源检测 1. 当 AI 把注释一起抄走代码溯源检测要怎么做AI 编程助手在补全代码时偶尔会把训练语料里的整段实现连同注释一起吐出来。我最近在 GitHub 上翻到一个讨论有人用注释关键词触发补全结果模型把一段经典快速平方根倒数算法的实现原样复现连那句标志性的吐槽注释都没落下。这件事本身不新鲜但它暴露了一个很实际的问题你提交的代码里可能藏着一段来源不明、许可证不清的片段而你自己完全没意识到。代码溯源检测要解决的就是这件事。它的核心思路不复杂把 AI 产出的代码片段和已知开源库做相似度比对重点看注释、变量命名、控制流结构这些“指纹”特征。如果相似度超过某个阈值就标记出来人工复核。适合谁用一是团队里负责代码合规的同学二是经常用 AI 补全、又需要往公开仓库提交代码的开发者三是想给自己项目加一道提交前检查的工程团队。这篇我会用一个可复制的检测脚本配合 TaoToken 的统一 Key 通道调用模型把“注释级溯源比对”跑通。你会拿到完整的配置片段、相似度阈值参数以及验证请求是否成功的具体动作。整个过程不需要你搭复杂的向量库先用最直接的方式把链路跑起来。先说清楚检测的基本单元。我习惯把待检测代码切成“函数级块”每块保留原始注释和空行。切块之后对每个块做两件事一是提取注释文本二是提取去掉注释后的代码骨架。注释文本用来做语义相似度代码骨架用来做结构相似度。两者加权得到一个综合分。为什么注释权重不能太低因为像“what the fuck”这种注释是极强的来源信号正常独立编写的代码几乎不会撞上同样的措辞。阈值怎么定我实测下来注释语义相似度超过 0.86、结构相似度超过 0.78就值得人工看一眼。如果两者同时超过 0.9基本可以判定是逐字复现。这个阈值不是绝对的你可以根据自己项目的容忍度调整。关键是先把检测跑起来拿到一批真实分数再回头校准。还有一个容易被忽略的点检测对象不只是 AI 刚生成的代码也包括你从 AI 对话里复制粘贴、然后手动改了几行的代码。改改变量名、调换两行顺序结构相似度可能只降一点点但注释如果没动语义相似度几乎不变。所以注释级比对是性价比最高的第一道筛子。2. TaoToken 统一 Key 前置把模型调用通道先打通要让检测脚本能调用模型做语义比对你得先有一个稳定的 API 通道。我用 TaoToken 的原因很简单它把多个模型的调用统一到一个 Key 和一套 Base URL 上检测脚本里不用为每个模型写不同的鉴权逻辑。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 注意 API 地址不带 UTM 参数。前置准备分三步。第一步注册并登录后进入控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。在控制台里你能看到账户余额和调用统计。第二步创建 API Key入口在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建时给它起个能认出来的名字比如 code-trace-detect方便后面排查是哪个脚本在调用。Key 只在创建时完整显示一次复制下来存到环境变量里别直接写进脚本。第三步确认你要用的模型 ID。检测脚本里做语义相似度我一般选一个上下文够长、对代码理解较好的模型。你可以在模型对话页面先手动试一次地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。在对话里贴一段代码和一段注释问它“这两段注释在语义上是否指向同一段实现”看返回是否合理。确认没问题后把模型 ID 记下来。环境变量这样设置Linux 或 macOS 下export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODEL你选定的模型IDWindows PowerShell 下$env:TAOTOKEN_API_KEYsk-你的key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api $env:TAOTOKEN_MODEL你选定的模型ID这里有个坑要提前说Base URL 末尾不要自己加/v1或者/chat/completions脚本里拼接路径时统一处理。我见过有人手动拼成https://taotoken.net/api/v1/v1/chat/completions结果一直 404。另外Key 不要提交到 Git 仓库用.env文件加.gitignore是最省事的做法。如果你打算长期跑检测、甚至把它接进 CI可以考虑 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它的定位是给长期编码和 Agent 类任务用的比按次调用更适合高频检测场景。不过第一版脚本先用按次调用验证逻辑跑通再考虑升级。3. 可复制配置检测脚本的 settings 与参数片段这一节给你可以直接落地的配置。我用的检测脚本是 Python依赖requests和numpy不需要额外装向量库语义相似度直接让模型打分。先建一个项目目录结构如下code-trace/ detect.py settings.json samples/ ai_output.pysettings.json是核心配置路径和字段名你直接照抄{ api: { base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, model: 你选定的模型ID, timeout_seconds: 60, max_retries: 3 }, detection: { comment_weight: 0.6, structure_weight: 0.4, comment_threshold: 0.86, structure_threshold: 0.78, combined_threshold: 0.9, min_block_lines: 4 }, output: { report_path: ./trace_report.json, verbose: true } }comment_weight和structure_weight加起来等于 1。我把注释权重设得更高因为注释是更强的来源信号。min_block_lines是切块的最小行数太短的块噪声大直接跳过。combined_threshold是综合分阈值超过就标红。然后是detect.py的关键部分。先读配置和环境变量import json import os import re import requests with open(settings.json, r, encodingutf-8) as f: cfg json.load(f) API_KEY os.environ.get(cfg[api][api_key_env]) BASE_URL cfg[api][base_url].rstrip(/) MODEL cfg[api][model]切块函数按空行和函数定义切保留注释def split_blocks(source: str, min_lines: int): lines source.splitlines() blocks, current [], [] for line in lines: if line.strip() and current: if len(current) min_lines: blocks.append(\n.join(current)) current [] else: current.append(line) if len(current) min_lines: blocks.append(\n.join(current)) return blocks提取注释和代码骨架def extract_comment(block: str) - str: comments [] for line in block.splitlines(): stripped line.strip() if stripped.startswith(#) or stripped.startswith(//): comments.append(stripped.lstrip(#/ ).strip()) return .join(comments) def extract_skeleton(block: str) - str: skeleton [] for line in block.splitlines(): stripped line.strip() if stripped.startswith(#) or stripped.startswith(//): continue skeleton.append(re.sub(r\s, , stripped)) return \n.join(skeleton)调用模型做注释语义比对这里用 chat completions 接口def comment_similarity(comment_a: str, comment_b: str) - float: prompt ( 你是代码溯源助手。判断下面两段注释是否指向同一段实现 只返回一个 0 到 1 之间的小数1 表示完全同源。\n f注释A{comment_a}\n注释B{comment_b} ) url f{BASE_URL}/v1/chat/completions headers { Authorization: fBearer {API_KEY}, Content-Type: application/json, } payload { model: MODEL, messages: [{role: user, content: prompt}], temperature: 0, } for attempt in range(cfg[api][max_retries]): try: resp requests.post( url, headersheaders, jsonpayload, timeoutcfg[api][timeout_seconds], ) resp.raise_for_status() text resp.json()[choices][0][message][content] match re.search(r0?\.\d|1\.0|1, text) return float(match.group()) if match else 0.0 except Exception as e: if attempt cfg[api][max_retries] - 1: raise return 0.0结构相似度先用简单的行级 Jaccard够用且不引入额外依赖def structure_similarity(skel_a: str, skel_b: str) - float: set_a set(skel_a.splitlines()) set_b set(skel_b.splitlines()) if not set_a or not set_b: return 0.0 inter len(set_a set_b) union len(set_a | set_b) return inter / union主流程把待检测块和参考库块两两比对算综合分def detect(ai_source: str, ref_source: str): det cfg[detection] ai_blocks split_blocks(ai_source, det[min_block_lines]) ref_blocks split_blocks(ref_source, det[min_block_lines]) results [] for ab in ai_blocks: best None for rb in ref_blocks: c_sim comment_similarity(extract_comment(ab), extract_comment(rb)) s_sim structure_similarity(extract_skeleton(ab), extract_skeleton(rb)) combined c_sim * det[comment_weight] s_sim * det[structure_weight] if best is None or combined best[combined]: best { combined: round(combined, 4), comment_sim: round(c_sim, 4), structure_sim: round(s_sim, 4), ref_block: rb[:200], } if best: best[flagged] ( best[combined] det[combined_threshold] or ( best[comment_sim] det[comment_threshold] and best[structure_sim] det[structure_threshold] ) ) results.append(best) return results跑的时候把 AI 产出和参考库分别读进来if __name__ __main__: with open(samples/ai_output.py, r, encodingutf-8) as f: ai_src f.read() with open(samples/reference.py, r, encodingutf-8) as f: ref_src f.read() report detect(ai_src, ref_src) with open(cfg[output][report_path], w, encodingutf-8) as f: json.dump(report, f, ensure_asciiFalse, indent2) for item in report: print(item[combined], item[flagged], item[comment_sim], item[structure_sim])这套配置的好处是所有阈值和权重都在settings.json里改参数不用动代码。你先把参考库换成你关心的开源项目片段比如某个 GPL 项目的核心函数再拿 AI 产出的代码去比。4. 验证请求与成功结果跑一次注释级溯源比对配置写好后先做一次最小验证确认 API 通道是通的。我建议单独写一个ping.py只发一条最简单的请求import os, requests url https://taotoken.net/api/v1/chat/completions headers { Authorization: fBearer {os.environ[TAOTOKEN_API_KEY]}, Content-Type: application/json, } payload { model: os.environ[TAOTOKEN_MODEL], messages: [{role: user, content: 回复 ok}], temperature: 0, } resp requests.post(url, headersheaders, jsonpayload, timeout30) print(resp.status_code) print(resp.json()[choices][0][message][content])如果返回 200 并且内容里有 ok说明 Key、Base URL、模型 ID 三件套都对。这一步别跳过我见过太多人直接跑完整脚本结果卡在鉴权上排查半天以为是检测逻辑的问题。通道验证通过后准备两个样本文件。samples/ai_output.py放一段 AI 生成的代码故意让它包含一段带注释的实现。samples/reference.py放你怀疑被参考的开源片段。为了演示效果我构造一个简化版场景AI 输出里有一段计算归一化向量的函数注释里带着一句很口语化的吐槽参考库里有一段结构几乎一样的实现注释措辞也接近。运行python detect.py成功时你会看到类似输出0.9312 True 0.95 0.9031 0.4123 False 0.4 0.4308第一行综合分 0.9312flagged为 True注释相似度 0.95结构相似度 0.9031。这说明检测脚本正确识别出了高相似块。第二行综合分 0.4123未标记属于正常独立代码。同时trace_report.json里会记录每个块的详细分数和参考块片段方便你人工复核。这里有个细节模型返回的相似度分数偶尔会带解释文字比如“相似度约为 0.95”。我的正则r0?\.\d|1\.0|1能从中抽出数字但如果模型返回“九成五”这种中文表述就会失败。所以 prompt 里明确要求“只返回一个 0 到 1 之间的小数”并且temperature设为 0降低随机性。实测下来这样返回纯数字的概率很高。验证成功后你可以把参考库换成真实项目。比如把某个 GPL 项目的核心算法文件下载下来去掉许可证头只留函数体作为参考块。然后拿 AI 生成的相似功能代码去比。如果综合分超过 0.9基本可以确认存在逐字复现风险提交前必须处理要么替换实现要么补充许可证声明。再补一个批量验证的思路。如果你有多个 AI 产出文件可以写个循环遍历samples/目录对每个文件跑一次detect把结果汇总成一张表。表里至少包含文件名、最高综合分、是否标记、命中的参考块前 50 字符。这样你一眼就能看出哪个文件风险最高。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth跑检测脚本时报错基本集中在几个地方。我按真实遇到的频率排一下每个都给你对照的排查动作。401 Unauthorized。最常见的原因是 Key 没读到或者读错了。先确认环境变量在当前 shell 里生效echo $TAOTOKEN_API_KEY如果输出为空说明没 export 成功或者你换了终端窗口。Windows 下用echo $env:TAOTOKEN_API_KEY。另一个原因是 Key 复制时带了空格或换行用strip()处理一下。还有一种情况是 Key 被禁用或额度耗尽去控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 看调用记录和余额。local proxy failed。这个报错通常出现在你本机设置了系统代理但代理进程没启动或者端口不对。检测脚本走的是requests它会读取HTTP_PROXY和HTTPS_PROXY环境变量。先检查echo $HTTP_PROXY echo $HTTPS_PROXY如果有值但代理不可用临时清掉再跑unset HTTP_PROXY HTTPS_PROXY注意这里说的是本机网络配置层面的排查不涉及任何绕过网络管理的手段。企业内网环境下直接联系网络管理员确认出口策略即可。reading choices 报错。典型信息是KeyError: choices或者list index out of range。这说明返回的 JSON 结构和你预期的不一样。先把原始返回打出来print(resp.status_code) print(resp.text[:500])常见原因是模型 ID 写错了接口返回了错误对象而不是正常补全结果。另一个原因是请求体里messages格式不对比如漏了role字段。对照本文第 3 节的 payload 结构逐字段检查。OAuth 相关报错。如果你在 Claude Code 或类似工具里配置过 OAuth 登录可能会遇到 token 过期或回调失败。这类工具通常需要三件套Base URL、API Key、Model ID。以 Claude Code 为例配置文件里要写全{ base_url: https://taotoken.net/api, api_key: sk-你的key, model: 你选定的模型ID }如果你用的是 Cline 或 CC Switch 这类工具MCP 配置里同样要把这三项写全缺一个就会在启动时报鉴权失败。Codex 的auth.json也是类似结构base_url指向https://taotoken.net/apiapi_key填你的 Keymodel填模型 ID。三件套不全会导致请求发不出去或者返回 401。还有一个隐蔽的坑脚本里拼接 URL 时多加了斜杠。比如BASE_URL末尾有/你又拼了/v1/chat/completions变成//v1/chat/completions。有些网关能容忍有些直接 404。统一用rstrip(/)处理。最后如果检测脚本跑得特别慢先看是不是每个块都调了一次模型。块多的时候请求数会线性增长。优化方向有两个一是先用结构相似度粗筛只对结构分超过 0.5 的块调模型做注释比对二是把多个块的注释合并成一个请求让模型批量打分。这两种改法都能显著降低调用次数。6. 把检测接进提交前流程CTA 与后续动作检测脚本跑通之后最有价值的动作是把它接进你的提交前流程。我自己的做法是在项目根目录放一个pre-commit钩子每次 commit 前自动对暂存区的 Python 文件跑一次检测如果出现flagged为 True 的块就中断提交并打印报告路径。这样你不需要记得手动跑风险代码在进入仓库前就被拦下来。钩子脚本大概长这样#!/bin/sh python code-trace/detect.py if grep -q flagged: true code-trace/trace_report.json; then echo 检测到高相似代码块请查看 trace_report.json 后手动确认 exit 1 fi放到.git/hooks/pre-commit加上执行权限即可。注意这个钩子只对本地提交生效团队协作时每个人都要装一次。如果你们用 CI可以把同样的逻辑写进流水线在合并请求阶段跑检测。如果你在检测过程中需要频繁调试模型返回可以直接用模型对话页面手动验证单条注释的相似度判断地址是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。把两段注释贴进去看模型给的分数是否和脚本一致。不一致的时候多半是 prompt 措辞需要调整。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的接口说明和参数列表。如果你要换模型或者调整超时时间先翻文档确认参数名别凭记忆改。长期跑检测、或者想把检测能力做成一个常驻 Agent 的话Coding Plan 会更合适入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它针对高频编码类调用做了优化比按次调用省心。最后说一个我踩过的坑检测阈值不要一次定死。先跑一周把每次标记的块人工复核一遍记录哪些是真阳性、哪些是误报。然后根据实际分布微调comment_threshold和combined_threshold。我一开始把综合阈值设成 0.85误报偏多调到 0.9 之后标记的基本都是值得看的。阈值这东西得用你自己的代码库喂出来才准。
返回列表