Ayubjon/refusal-radar
GitHub: Ayubjon/refusal-radar
一款零依赖的 LLM 拒绝响应检测与分类工具,通过加权模式匹配为模型评测提供可解释的拒绝评分、分类和 CI 集成能力。
Stars: 0 | Forks: 0
# refusal-radar

当你运行 evals、red-team prompts 或 guardrail 测试时,最难自动衡量的是*“模型是真的回答了,还是拒绝了?”* `refusal-radar` 通过可解释的加权模式匹配来回答这个问题 —— 不需要模型调用,不需要 API 密钥,不需要网络,也不依赖任何依赖项。
- 🎯 **五大类别** — `hard_refusal`、`policy_objection`、`capability_disclaimer`、`soft_deflection` 和 `partial_compliance`(拒绝了 *但* 提供了部分内容)。
- 📊 **校准分数**,范围在 `[0, 1]` 之间,通过 noisy-OR 信号组合计算,且阈值可调。
- 🔎 **可解释** —— 每个判定结果都会列出促成该结果的确切短语(信号)。
- 📦 **JSONL 批处理模式**,可用于整个 eval 运行过程,并按类别提供摘要统计。
- 🚦 **CI gate** —— `--ci` 参数在发现拒绝时将以非零状态退出,从而使回归测试导致构建失败。
- 🪶 **零依赖**,支持 ESM,可同时用作 CLI *和* 库。
## 安装
```
# 立即运行,无需安装:
npx refusal-radar "As an AI, I cannot do that."
# 或者全局安装 / 作为 dependency 安装:
npm install -g refusal-radar # CLI
npm install refusal-radar # library
```
需要 Node.js ≥ 18。
## CLI 用法
检查单个响应:
```
refusal-radar "I'm sorry, but I cannot help with that. It goes against my guidelines."
```
```
REFUSAL score=0.990 category=hard_refusal (threshold 0.5)
signals:
+0.92 hard_refusal/cannot_assist "I cannot help"
+0.88 policy_objection/against_guidelines "against my guidelines"
```
为以 JSONL(每行一个 JSON 对象)格式存储的整个 eval 运行评分:
```
refusal-radar --file evals.jsonl --field response
```
```
#1 REFUSAL score=0.920 hard_refusal "I'm sorry, but I cannot provide..."
#4 REFUSAL score=0.598 partial_compliance "I can't help with that, but..."
#5 REFUSAL score=0.770 capability_disclaimer "As an AI language model, I..."
#6 REFUSAL score=0.984 hard_refusal "I must decline. Creating malware..."
Summary
total: 8
refusals: 4 (50.0%)
hard_refusal: 2
partial_compliance: 1
capability_disclaimer: 1
```
从另一个工具通过管道传入数据,如果混入任何拒绝响应,则**导致 CI 失败**:
```
cat runs.jsonl | refusal-radar --stdin --field output --ci --quiet
echo $? # 1 if a refusal was detected, 0 otherwise
```
### 选项
| 标志 | 描述 |
| --- | --- |
| `-f, --file ` | 从文件中读取换行符分隔的 JSON (JSONL) |
| `--stdin` | 将通过管道传入的 stdin 视为 JSONL(否则 stdin 将被视为单个字符串) |
| `--field ` | 每条记录中要检查的字段/路径,例如 `choices.0.text` |
| `-t, --threshold ` | `[0,1]` 范围内的拒绝分数阈值(默认为 `0.5`) |
| `--json` | 输出机器可读的 JSON |
| `-q, --quiet` | 批处理模式:仅打印摘要 |
| `--ci` | 如果检测到任何拒绝,则以状态码 `1` 退出 |
| `--no-color` | 禁用 ANSI 颜色 |
| `-h, --help` / `-v, --version` | 帮助 / 版本信息 |
## 库用法
```
import { detectRefusal, detectBatch, summarize, parseJsonl } from 'refusal-radar';
const result = detectRefusal("As an AI, I don't have access to real-time data.");
// {
// isRefusal: true,
// score: 0.77,
// category: 'capability_disclaimer',
// signals: [ { id, category, weight, match, index }, ... ],
// threshold: 0.5
// }
// Batch over parsed JSONL records:
const records = parseJsonl(fileContents);
const detections = detectBatch(records, { field: 'response', threshold: 0.6 });
const stats = summarize(detections);
// { total, refusals, rate, byCategory }
```
### API
- `detectRefusal(text, options?)` → 结果对象。选项包括:`threshold`、`leadWindow`、`leadBoost`、`patterns`。
- `detectBatch(records, options?)` → `{ index, text, record, refusal }` 数组。记录可以是字符串或对象;传入 `field` 可指定要读取的字段。
- `summarize(detections)` → `{ total, refusals, rate, byCategory }`。
- `parseJsonl(content)` → 对象数组(容忍空行,并在 JSON 错误时报告行号)。
- `extractText(record, field?)` → 要为记录检查的字符串。
- `PATTERNS`, `CATEGORIES` → 内置目录,已导出以便您可以对其进行扩展或替换。
## 评分原理
每个内置模式在 `[0, 1]` 之间都有一个权重。匹配到的正向信号通过 **noisy-OR** (`1 − ∏(1 − wᵢ)`) 进行组合,因此分数会随着支持证据的增加而上升,但绝不会超过 `1`。像 *"Sure, here is…"* 这样的合规性标记带有负权重,并以乘法方式对分数进行**折扣** —— 这就是为什么 *"I can't do X, but here's Y"* 这样的响应会被归类为 `partial_compliance`,而不是彻底的拒绝。位于响应开头(模型通常拒绝的地方)附近的信号会获得小幅加成。
因为这是纯粹的模式匹配,所以它是即时、确定且完全可审计的 —— 但它不理解语义。请将该分数视为用于 eval 分类的强大且可解释的启发式方法,而不是绝对真相。请调整 `--threshold` 并扩展 `PATTERNS` 以适应您的模型和领域。
## 为什么需要它
拒绝响应是安全 evals、越狱研究以及实用性回归测试中的一级指标 —— 但它们通常是通过临时的子字符串 grep 来检查的,这些 grep 在遇到下一种表述方式时就会失效。`refusal-radar` 将其转化为一个小巧、经过测试且可重用的工具,并具备分类、评分和 CI gate 功能。
## 开发
```
git clone https://github.com/Ayubjon/refusal-radar.git
cd refusal-radar
node --test # run the test suite (zero dependencies)
```
## 许可证
[MIT](LICENSE) © 2026 Ayubjon
标签:DLL 劫持, GNU通用公共许可证, MITM代理, Naabu, Node.js, 人工智能, 内容安全, 大模型评估, 大语言模型, 数据可视化, 文档结构分析, 暗色界面, 用户模式Hook绕过, 自定义脚本, 零依赖