@devtune/ai-traffic (英语)
测量AI爬行者访问您的站点时使用无依赖感应器,该感应器在边缘过滤并向前行走时只能匹配到DevTune的机器流量.
安装
pnpm add @devtune/ai-traffic
创建工程扫描的服务器侧摄入密钥, 并仅将其曝光到您的服务器运行时间 :
DEVTUNE_AI_TRAFFIC_INGEST_KEY=dt_ingest_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
在Vercel上,您可以使用工程ID而不是密钥. 见 无摄入键的 Vercel。 。 。 .
快速启动
下一个.js
下个.js 16, 加 proxy.ts。对于Lext.js的旧项目,请在 middleware.ts 和导出 middleware() 换句话说.
// proxy.ts
import { createDevTuneAiTrafficMiddleware } from "@devtune/ai-traffic";
import { NextResponse, type NextFetchEvent, type NextRequest } from "next/server";
const trackAiTraffic = createDevTuneAiTrafficMiddleware({
ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!,
});
export function proxy(request: NextRequest, event: NextFetchEvent) {
trackAiTraffic(request, event);
return NextResponse.next();
}
快递
import express from "express";
import { createDevTuneAiTrafficExpressMiddleware } from "@devtune/ai-traffic/express";
const app = express();
app.use(
createDevTuneAiTrafficExpressMiddleware({
ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!,
}),
);
中间软件呼叫 next() 立即并记录快递发布后的实际响应状态 finish。 。 。 .
节点 HTTP
import { createServer } from "node:http";
import { createDevTuneAiTrafficNodeHandler } from "@devtune/ai-traffic/node";
const trackAiTraffic = createDevTuneAiTrafficNodeHandler({
ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!,
});
createServer((request, response) => {
trackAiTraffic(request, response);
response.statusCode = 200;
response.end("ok");
}).listen(3000);
钩子记录了决赛 response.statusCode 不阻断或更改响应.
无摄入键的 Vercel
在Vercel上,传感器可以用每个部署的Vercel OIDC令牌进行认证,而不是一个摄入键,所以没有长生不老的秘密可以存储. 您的 DevTune 账户在 β 时必须启用 Vercel 链接 .
-
在DevTune, 打开 设置 QQ 集成 QGE 中间软件 选择 添加 Vercel 工程输入 Vercel 团队ID,显示为 团队标识 在球队 设置 QQ 常规,并显示为 项目标识 在项目中 设置 QQ 常规。 。 。 .
-
复制 DevTune 项目 ID 并添加到Vercel项目的环境变量中。 这不是秘密.
DEVTUNE_AI_TRAFFIC_PROJECT_ID=3f0c9a52-6b1e-4d7a-9c2f-1a2b3c4d5e6f -
通过它作为
projectId保留ingestKey迁移时:const trackAiTraffic = createDevTuneAiTrafficMiddleware({ projectId: process.env.DEVTUNE_AI_TRAFFIC_PROJECT_ID!, ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!, }); -
部署 一旦链接的项目显示 最后一次见到 在 DevTune 中,删除
ingestKey删除DEVTUNE_AI_TRAFFIC_INGEST_KEY变量.
一样 projectId 选项用于运行在Vercel上的快取和节点HTTP适配器.
若您使用 projectId 设置, 它跳过一些请求或呼叫 trackRequest() 回复后,给客户端打电话 captureVercelOidcToken(request) 在任何过滤之前, Vercel在每个请求上都设置了当前符号,因此浏览器请求保留了该符号用于待发电流.
两个选项设定后,如果Vercel或DevTune无法验证,传感器会用Vercel令牌发送并倒回摄入键. 仅限 projectId,没有从不是Vercel的主机发送任何信息,传感器记录每个过程一个警告。 如果链接停留 还没有看到 在部署后 接收到爬行者流量, 检查两个Vercel ID.
信使从每个收到的请求中读取,从不从环境,只被送到Vercel为DevTune获得信使. 该指使被缓存到其过期前不久,只被发送到摄入端.
设计隐私
传感器过滤器请求您的应用程序运行地点 。 它在咨询AI机器人注册前使用廉价的用户代理提示,只有匹配的机器命中才有资格被转发. 它使用没有饼干,没有指纹,也没有完整的请求日志.
从未提交过一个页面给自己的请求。 一个让页面运行 JavaScript 的爬行器,它的浏览器则会启动预配置,客户端导航等 fetch() 从每页内拨打。 数数他们就会报告它只从背景中获取的页面;在DevTune自己的网站上,一个渲染爬行者向DevTune提出了3个这样的请求 / 它装入的每一页。 传感器从浏览器附带的获取元数据中识别它们, 因此不需要配置 :
Sec-Fetch-Dest存在且不存在document, (中文(简体) ),iframe, (中文(简体) ),frame或者说fencedframe。 。 。 .empty标记 afetch()或来自一页的 XHR,以及诸如script或者说image标记子资源.Sec-Purpose或者说Purpose包含prefetch。这捕捉到猜测规则预发会,浏览器发送为document从客户端获取并标记预切, 寄出不Sec-Fetch-*头.Next-Router-Prefetch在场。 Next.js在中间软件运行前移除了这个头,因此它只适用于请求到达传感器完好无损的地方,如快递和节点适配器或路由处理器. 获取上面的元数据在中间软件中也起作用 .
不运行浏览器的 crawlers 通常不会发送取取元数据,其请求仍然被记录. 取舍:一旦AI浏览器加载了一页,其客户端在您站点内的导航都是由页面启动的获取,而不计数. 它的第一页负载是.
浏览器只向 HTTPS 源头发送获取元数据 。 在普通HTTP服务站点上,由页面启动的请求没有 Sec-Fetch-* 头条,因此传感器除了页面载荷和记录外,无法告诉他们.
例外是框架数据端点,传感器通过URL形状跳过,除非浏览器将请求标记为页面负载(Sec-Fetch-Dest: document, (中文(简体) ), iframe, (中文(简体) ), frame 或者说 fencedframe) (中文(简体) ). 多个框架在客户端导航和链接前置时从单独的URL中加载一个路由的数据,他们的服务器从不用页面回答这些URL:
- Next.js Pages 路由器: 下的一个路径
/_next/data/结束于.json,或载有x-nextjs-data。下一个.js 中间软件在页面自己的路径上看到这些请求,所以在页眉中可以识别这些请求. - Remix:任何带有
_data查询参数。 没有这个规则,/pricing?_data=routes/pricing将被记录为/pricing一旦查询被删除. - Remix 和 React 路由器单接: 结束于
.data,例如/pricing.data或者说/_root.data。 。 。 . - 反应路由器懒惰路由的发现:结束于
/__manifest带有paths查询参数(反应路由器 7.10+)或p(早期),例如:/__manifest?paths=/pricingA级/__manifest页面中没有它,则仍然被记录. - SvelteKit: 一条以
/__data.json或者说.html__data.json。 。 。 . - Nuxt: 路径结束于
/_payload.json,或/_payload.js当JSON的有效载荷被关闭.
匹配是准确的,所以页面如 /data 或者说 /blog/__data-tips 仍然被记录。 从一页内提出的其他框架请求,如Next.js App Router feels,仍然取决于取取取元数据.
对于匹配的爬行者,DevTune只收到:
path- 来源加路径, 删除查询参数
- 用户代理
- 适配器知道后的反应状态
- 时间戳
- 请求来自地址, 用于验证爬行者, 然后丢弃
- 对于签名的代理,其WebBot Auth签名材料,用于验证代理,然后被丢弃(见 签名代理人(中文(简体) )
请求机构, cookie, 不相关的信头, 和查询字符串从不发送 。 签名材料只转发给一个没有查询的请求,所以一个重设的链接,OAuth召回或搜索一个签名代理跟踪的URL从不离开网站. 只有这些请求信头才能以签名方式转发: accept, (中文(简体) ), accept-encoding, (中文(简体) ), accept-language, (中文(简体) ), cache-control, (中文(简体) ), content-length, (中文(简体) ), content-type, (中文(简体) ), date, (中文(简体) ), from, (中文(简体) ), host 财务报告和审定财务报表 user-agent,导出为 WEB_BOT_AUTH_FORWARDED_HEADERS其签名涵盖任何其他标题的请求,例如 cookie, (中文(简体) ), authorization 或 API 密钥, 完全不转发签名材料, 因此证书永远不会离开网站 。 发送一个地址只是为了用户代理与AI bot 注册匹配的请求,因此普通浏览器流量从没有到达DevTune. 注意该声明的限度:匹配在用户代理上,任何人都可以发送一个爬行者的用户代理,因此其客户端这样做的人就会被发送他们的地址. 这也是存在核查处理的事实,这也是检查和丢弃地址而不是储存地址的原因。 DevTune对照声称的运营商在收货时公布的网络进行核对,并保留判决,而不是地址. 设定 trustedProxy: "none" (d) DevTune随后记录的流量未经核实.
瓶体验证
用户代理是主张,而不是身份. 互联网上的任何信息都可以发送 GPTBot/1.3,所以一个感应器 报告用户代理 单独报告 陌生人说自己。 要求的地址是使索赔可以核实的地址.
该套件只读取了您部署来源的地址
trustedProxy | 来源 | 什么时候用 |
|---|---|---|
"vercel" | x-vercel-forwarded-for,由Vercel覆盖 | 默认时间 VERCEL=1 |
"connection" | 由快递和节点适配器提供的套接字端 | 其他任何地方的默认 |
"cloudflare" | cf-connecting-ip,或 cf-connecting-ipv6 pseudo IPv4 覆盖模式 | Cloudflare 软件前方 |
| "云头" | 最右边的地址 x-forwarded-for,由 CloudFront 附录 | CloudFront 直接连接到应用程序 |
| 数字 | x-forwarded-for,计算许多信任的跳 从右边 | 应用程序的另一个反向代理前列 |
"none" | 没什么 | 退出核查 |
x-forwarded-for 仅在您选择时读取 "cloudfront" 或通过一个数字偏移,因为任何客户端都可以附加它。 当它被读取时,条目从右侧被计算出,所以一个加成链无法将所信任的跳出范围. 不解析为 IPv4 或 IPv6 字元的值被删除 .
当已声明的代理或hopcount头没有时, 或当边缘运行时间既没有套接字也没有已声明的代理时, 没有发送地址 。 DevTune记录的流量是未经核实而不是猜测.
const trackAiTraffic = createDevTuneAiTrafficMiddleware({
ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!,
trustedProxy: "cloudflare",
});
签名代理人
一些特工在每项请求上签名 网络瓶 Auth,一个HTTP消息签名(RFC 9421),其运营商已发布的密钥进行验证. 查特GPT代理,谷歌的代理和DuckAssistBot do. 有效的签名可以识别代理 任何它运行的网络, 所以DevTune检查它与地址.
当一个匹配的请求包含所有三个 Signature, (中文(简体) ), Signature-Input 财务报告和审定财务报表 Signature-Agent,其活动包括: webBotAuth devTune 需要检查签名的对象 :
- 三个标题,逐字记录
- 请求方法
- 代理发送的绝对请求 URL。 Express和Node适配器从请求行和来接主机中取出而未解析. 获取兼容的运行时间, 如 Next.js 已经正常化
request.url感应器放回原物Host或者说X-Forwarded-Host运行时间从: 签名请求https://DOCS.example.com:443/guide转发, 不https://docs.example.com/guide。原始路径已经过去,因为运行时间可能已经重写/a/../guide作为/guide,并在 TLS 终止代理之后request.url可以说http:请求的代理人https:因此,其签名涵盖方案、路径或查询的请求(@scheme, (中文(简体) ),@path, (中文(简体) ),@target-uri, (中文(简体) ),@request-target或者说@query)在“获取”运行时间上未转发签名。 签署@authority财务报告和审定财务报表Signature-Agent这是WebBot Auth探员今天所签名的,仍然向前。 Express和节点适配器保留了生道并前进了两种. 请求无Host页眉转发无签名 。 一个配置origin更改事件url,从没有签名的 URL . - 用小写名将重复值合并为
a, b。只有标题在WEB_BOT_AUTH_FORWARDED_HEADERS被传送.
不需要配置,传感器从不核实、记录或存储任何配置。 DevTune在收到通知时予以核实,并维持判决,而不是材料。 如果任何部分超过 DevTune 的大小限制, 则持有一个请求头无法携带的字符, 或者签名覆盖了外部的一个头 WEB_BOT_AUTH_FORWARDED_HEADERS,传感器离开 webBotAuth 而不是切换它, 因为切换的签名永远无法验证。 这一事件同没有三个标题的任何请求一样,是作为未署名的.
一个签名对一个短窗口有效,DevTune根据事件的时间来检查. 快速和节点适配器 withDevTuneAiTrafficRoute() 在答复后记录一个请求,所以他们在请求到达时盖上签名事件印章。 未签名的事件保留记录的时间 。 如果你打电话 trackRequest() 答完后,请接受 client.signedArrivalTime(request) 当请求到达并作为第四个论点通过时:
const receivedAt = aiTraffic.signedArrivalTime(request);
const response = await render(request);
aiTraffic.trackRequest(request, event, response.status, receivedAt);
带有查询字符串, 甚至是赤裸的跟踪请求 ?,不转发签名材料,其事件完全作为未签名的事件发送。 查询字符串可携带重置的符号, OAuth 代码和搜索名词, 以及事件 url 所以DevTune永远无法比对一个签名 这种请求维持其地址裁决.
检测不发送
独立分类者不需要摄入密钥, 也从来不会向 DevTune 提出网络请求或推动数据 :
import { detectAiCrawler, detectAiReferrer } from "@devtune/ai-traffic";
const crawler = detectAiCrawler(request);
const referrer = detectAiReferrer(request);
每个函数也直接接受相关的字符串:用于 detectAiCrawler() 或裁判官 URL 用于 detectAiReferrer()。 。 。 .
detectAiCrawler() 返回匹配的机器人注册项或 null数字 :
type AiCrawlerDetection = {
uaPattern: string;
llmPlatform: string;
botClass: "training_crawler" | "index_bot" | "answer_fetcher" | "acting_agent";
label: string;
status?: "active" | "retired";
asnHints?: number[] | null;
notes?: string | null;
};
detectAiReferrer() 返回匹配的引用者注册项或 null数字 :
type AiReferrerDetection = {
hostname: string;
llmPlatform: string;
label: string;
};
两者都默认使用捆绑的注册快照,因此它们都离线和在CI中工作. 过 registryEntries 当您需要对照被封存或私人登记进行分类时,作为第二个论据:
const match = detectAiCrawler(userAgent, {
registryEntries: myRegistryEntries,
});
报告版本
(b) 便利和登记请求包括: x-devtune-sdk-version 包版头,所以
DevTune可以显示更新发布时的升级通知. 它没有访客数据.
机器交通和人员查询
传感器故意只测量机器流量. DevTune通过它的GA4集成,从AI产品获得人类访问,其中会话,接触,和转换提供了比中相器的参考比对更丰富的画面. 裁判所探测器可供本地分类使用,但感应器适配器从未向人类转发访问.
生产说明
断接和运行时间
默认客户端配置每摄入一个不变的有效载荷发送最多10个匹配事件,一个250ms的冲出窗口用于低容量流量. 设定 batchSize: 1 财务报告和审定财务报表 flushIntervalMs: 0 选择退出并立即发送每个匹配的事件.
Express和Node服务器寿命足够长,可以直接从默认中受益. Next.js 代理和中间软件通过延迟发送到 event.waitUntil() 当可用时,可以阻止响应等待,但可以使中间软件引用为短冲出窗口。 在无法可靠地保存被延迟的工作的短寿命运行时间中使用明确的选择退出,或者当最小化中间软件持续时间比请求合并更重要时:
const trackAiTraffic = createDevTuneAiTrafficMiddleware({
ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!,
batchSize: 1,
flushIntervalMs: 0,
});
登记册刷新用途 waitUntil 提供时,有5秒钟的取取时间。 它们在背景中运行,不会延迟响应. 设定 waitUntilRegistryRefresh: false 选择退出新鲜活的工作;短寿命的运行时间可能会在工作完成前停止.
速率限制检索
A 类 429 从摄入端点是服务器告诉我们的一次故障是暂时的,所以批次是重新交付而不是投放. 客户端等着回应 Retry-After – 延迟- 秒或 HTTP 日期 – 当信头缺失或无法使用时, 返回到250 ms 的指数后退. 被重试的批次保存完好,因此在后退期间到达的事件被单独发送,而不是折叠入其中.
复数被限制,所以短命的运行时间不能被节奏的终点所开. 预算属于一冲,而不是每批: maxRetryAttempts 默认为 3并 maxRetryDelayMs 盖子每等 5000 ms, 包括 a Retry-After 所以无论队列有多深 冲浪最多会增加15分 当预算用完时,用通常的抽样示警并停止排出水;不管后面有什么,都要排队排出下一个冲出水来,得到新的预算. maxRetryAttempts: 0 完全禁用等待:429在第一次拒绝时就放弃了批次,就像在这种行为存在之前那样。 排水管仍然停在那里,而不是在排水口后面掉下每一批,所以其余的排好队. 都说:
const trackAiTraffic = createDevTuneAiTrafficMiddleware({
ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!,
maxRetryAttempts: 2,
maxRetryDelayMs: 2_000,
});
因为退后让排队排出速度慢于交通到达,排队本身就被限制. maxQueuedEvents 默认值为1000;过去最古老的事件会用样本警告流出,所以持续的节流阀无法在没有限制的情况下生长记忆.
非429响应和网络出错没有改变:批次在一次尝试后被丢弃,因此硬出局永远无法让队列活下来. 一个例外是 503 以Vercel指使发送的批次, 没有配置摄入键 : DevTune 无法到达 Vercel 来验证指使, 因此它像重试一样 429 在同一预算范围内.
状态语义
Express和Node适配器观察完成的响应并报告其实际状态. Next.js 代理和中间软件在通过后无法观察最终的路由状态,因此便利中间软件省略了状态而不是猜测出200.
当一个 Next.js 代理直接返回响应时, 使用客户端并传递已知状态 :
import { createDevTuneAiTraffic } from "@devtune/ai-traffic";
import type { NextFetchEvent, NextRequest } from "next/server";
const aiTraffic = createDevTuneAiTraffic({
ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!,
defaultStatus: null,
});
export function proxy(request: NextRequest, event: NextFetchEvent) {
const response = new Response("Forbidden", { status: 403 });
aiTraffic.trackRequest(request, event, response.status);
return response;
}
使用 withDevTuneAiTrafficRoute() 您拥有一个兼容的路径处理器, 并想要自动捕捉它的确切响应状态 . notFoundPathPatterns 财务报告和审定财务报表 statusResolver 仍可用于可提供额外状态知识的应用程序.
书记官处的更新和倒置
0.5.0版本增加了亚马逊和DuckDuckGo的注册支持,同时明确 Claude Code,双子座CLI和OpenCode重试检测. 承认是自报的 客户索赔。 客户端运行的工具可以使用用户的网络,所以地址位于 主机爬行列表本身并不能证明身份不匹配. DevTune 执行 收据上的身份检查 一般浏览器或卷曲流量无法可靠地识别 aI客户端或其模式提供者.
从0.5.0起,新平台名称和流量等级可以通过远程注册到达,而无需升级包. DevTune处理其服务器上的分类和身份验证. SDK在使用前继续验证匹配模式.
客户端从捆绑快照开始. 第一个请求,然后是1小时缓存到期后的第一个请求,触发背景刷新 https://devtune.ai/api/v1/llm-traffic/registry。这包括普通浏览器请求,但注册GET没有访问者数据,只有匹配的机器请求发送到摄入. 节点,快件和中件使用相同的政策. 同时提出的请求共享一个刷新,以及 ETag 避免下载未修改的登记册.
一个失败的刷新保存了最后一个工作注册, 或在第一次成功刷新之前捆绑的快照 。 重复等待下一个缓存间隔 。 请求在刷新过程中使用当前缓存规则, 因此在刷新完成之前, 新添加的 bot 可能会被忽略 。 每次进程或运行时实例创建客户端或中间软件以重用其缓存 .
地址提取、托管适配器、安全修正或事件格式变化仍需要升级。 独立 detectAiCrawler() 使用它捆绑的快照,除非您提供 registryEntries;不自动刷新.
用户代理匹配是一个保守信号. 有的代理商 spoof 普通浏览器或要求网络级信号,所以所报道的计数是地板而不是准确的bot真相. Cloudflare-代理站点应该更喜欢DevTune的Cloudflare拉式在可用时的集成,因为它可以结合请求,bot-score,和网络信号而不运行中枢软件.
转发出处
Express和Node 适配器从请求协议和主机中构建事件 URL, 偏好标准中的第一个值 X-Forwarded-Proto 财务报告和审定财务报表 X-Forwarded-Host 锁链。 只接受那些信封 从一个信任的代理。 如果在您的部署中无法使用或不可信,请通过固定 origin数字 :
const trackAiTraffic = createDevTuneAiTrafficNodeHandler({
ingestKey: process.env.DEVTUNE_AI_TRAFFIC_INGEST_KEY!,
origin: "https://www.example.com",
});
失败行为
登记册刷新和吞噬 发送是火与忘却和守卫。 网络故障可能降低遥测,但不会阻断、扔入或改变客户的反应。 摄取端点和分批的电线格式不变.
文档
许可证
麻省理工学院 2026 PLGeek有限公司.
云纹
CloudFront 直接连接到运行传感器的应用程序时, 添加
trustedProxy: "cloudfront" 用于您现有的中间软件选项。 无云纹
需要修改原产地请求政策.
CloudFront 将查看器地址附加到 X-Forwarded-For,在任何条目后
提供取景器 。 感应器读出最右侧的条目,而忽略了更早的条目.
见 AWS记录的自定义行为。 。 。 .
仅在请求通过您信任的 CloudFront 到达应用程序时使用此设置
分发。 如果 CloudFront 和应用程序附件之间的 ALB 或其他代理
地址,使用数字 trustedProxy 改为: 0 选择最右边
条目, 1 跳过一个附件地址,等等。 确认实际标题链
页:1 CloudFront内部的代码执行本身就不同了
集成; 此设置用于源代码的中件 .