大模型安全测试garak工具搭建配置
原创
F1rstb100d
F1rstb100d
智佳网络安全
2026年10月18日 15:58
北京
去阅读
基础环境配置
https://github.com/ggml-org/llama.cpp/releases
下载对应cuda版本的exe和后面对应的DLLs 至于如何看cuda版本,命令nvidia-smi也行,去nvidia控制面板看也行
modelscope download --model diodel/Qwen3.5-4B-Q4_K_M-GGUF --local_dir ./
llama-server.exe -m D:\\llama_cpp\\llama-b10995-bin-win-cuda-13.4-x64\\models\\qwen3-5-4B-Q4_K_M.gguf -c 4096 --host 127.0.0.1 --port 9090 -ngl 99
curl http://127.0.0.1:9090/v1/models
大模型就跑起来了
安装garak
conda create --name garak_env \"python>=3.10,<=3.12\"
conda activate garak_env
python -m pip install -U garak
set OPENAI_API_KEY=sk-no-key-required
set OPENAI_BASE_URL=http://127.0.0.1:9090/v1
garak --target_type openai --target_name qwen3-5-4B-Q4_K_M.gguf --probes encoding --parallel_attempts 1 --generations 1
| 参数 | 缩写 | 默认值 | 作用与说明 |
|---|---|---|---|
--target_type |
-t / -m |
无(必填) | 指定生成器类型,如 openai、rest、huggingface、ollama |
--target_name |
-n |
无(必填) | 目标模型或服务名称,如 qwen3-5-4B-Q4_K_M.gguf |
--probes |
-p |
all |
探针列表,可指定 encoding、dan、promptinject、 all 或 garak --list_probes(建议新版本使用--spec) |
--probe_tags |
无 | 无 | 按标签筛选探针,如 owasp:llm01 |
--detectors |
-d |
all |
检测器列表,默认使用各探针推荐的检测器 |
--generations |
-g |
5 |
每个提示生成几个响应。本地模型建议设为 1 |
--parallel_attempts |
无 | 5 |
并行执行的探测尝试数。本地模型建议设为 1 |
--parallel_requests |
无 | 1 |
同一提示的并行请求数,对不支持多生成的模型会被忽略 |
--config |
无 | 无 | 指定 YAML/JSON 配置文件路径 |
--generator_option_file |
-G |
无 | 指定生成器配置文件路径(用于自定义 REST 接口) |
--detector_option_file |
无 | 无 | 指定检测器配置文件路径(用于配置裁判模型) |
--eval_threshold |
无 | 0.5 |
判定为“成功攻击”的最低阈值 |
--timeout |
无 | 视生成器而定 | 请求超时时间(秒),本地模型建议设大一些 |
--report_prefix |
无 | garak |
报告文件的前缀名 |
--seed |
-s |
随机 | 随机种子,用于结果可复现 |
--verbose |
-v |
0 |
增加日志详细程度,可叠加使用 |
坑1: 跑完之后发现模型的返回是空
经过检查发现是Qwen3.5自带思考,设置的窗口4096还没完成思考过程就截断了,导致garak没有收到完整的结果,也就默认判断为了通过
现在换成qwen2.5-3b-instruct-q4_k_m.gguf,这个没有思考过程,可以直接在content里返回模型输出结果
llama-server.exe -m ./models/qwen2.5-3b-instruct-q4_k_m.gguf -c 4096 --host 127.0.0.1 --port 9090 -ngl 99
garak --target_type openai --target_name qwen2.5-3b-instruct-q4_k_m.gguf --spec probes.promptinject --parallel_attempts 1 --generations 1
查看生成的jsonl结果文件,可以看到每个请求和响应
如果大模型提供接口和请求不是标准的格式,那么可能需要自己写对应的配置文件或Generator插件,相见下文,分别对应流式和非流式以及配置文件或插件处理方式
流式返回
方案一:使用RestGenerator配置文件
创建my_config.yaml
plugins:
generators:
rest:
RestGenerator:
name:\"MyAgent\"
uri:\"http://xxxx:8080/api/chat/stream\"
method:post
headers:
Content-Type:\"application/json\"
Accept:\"text/event-stream\"# 明确要求返回SSE流
User-Agent:\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\"
req_template_json_object:
message:\"$INPUT\"
session_id:\"garak_session_default\"
user_orgid:\"123456\"
user_orgnm:\"机构1\"
# 注意:因为返回是 text/event-stream,此处无法用 response_json 提取字段
response_json:false
执行
garak --target_type rest -G my_config.yaml --spec probes.encoding --parallel_attempts 1
方案二:编写自定义Generator插件
新建my_generator.py
import json
import requests
from garak.generators.base import Generator
class MyGenerator(Generator):
\"\"\"专门用于对接MyAgent自定义接口的生成器\"\"\"
# 声明支持的生成数量
supports_multiple_generations = False
# 默认参数,可在配置文件中覆盖
DEFAULT_PARAMS = Generator.DEFAULT_PARAMS | {
\"uri\": \"http://xxxxx:8080/api/chat/stream\",
\"headers\": {
\"Content-Type\": \"application/json\",
\"Accept\": \"text/event-stream\",
\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\"
}
}
def _call_model(self, prompt, generations_this_call=1):
# 构造请求体,替换 message 字段
payload = {
\"message\": prompt,
\"session_id\": \"garak_session_123\",
\"user_orgid\": \"123456\",
\"user_orgnm\": \"机构1\"
}
try:
# 发送请求,开启流式读取
response = requests.post(
self.uri,
headers=self.headers,
json=payload,
stream=True,
timeout=60
)
response.raise_for_status()
# 解析 SSE 流,寻找 type 为 message 的 data
final_message = \"\"
for line in response.iter_lines():
if line:
decoded_line = line.decode('utf-8')
if decoded_line.startswith(\"data:\"):
json_str = decoded_line[5:].strip() # 去掉 \"data:\" 前缀
try:
data_obj = json.loads(json_str)
if data_obj.get(\"type\") == \"message\":
final_message = data_obj.get(\"data\", \"\")
# 因为只有一条真正的 message,找到后可以 break(或者继续读取直到流结束)
break
except json.JSONDecodeError:
continue
# 如果没找到 message 类型的回复,返回兜底内容
ifnot final_message:
final_message = \"ERROR: No 'message' type found in SSE response.\"
return [final_message]
except Exception as e:
return [f\"ERROR: {str(e)}\"]
把这个文件放到garak安装目录下,比如:D:\\env\\anaconda3\\envs\\garak_env\\Lib\\site-packages\\garak\\generators\\my_generator.py
复制过去后,运行garak --list_generators,如果看到my_generator.MyGenerator,说明加载成功。
执行命令
garak --target_type my_generator --target_name qwen-agent --spec probes.promptinject --parallel_attempts 1 --generations 1
非流式返回
方案一:使用 RestGenerator 配置文件
创建my_json_config.yaml
plugins:
generators:
rest:
RestGenerator:
name:\"MyAgent普通接口\"
uri:\"http://xxxxx:8080/api/chat/stream\"# 换成实际接口地址
method:post
headers:
Content-Type:\"application/json\"
Accept:\"application/json\"
User-Agent:\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\"
req_template_json_object:
message:\"$INPUT\"
session_id:\"garak_session_default\"
user_orgid:\"123456\"
user_orgnm:\"机构1\"
response_json:true
# 重点:根据你的实际返回结构改写 JSONPath
# 假设返回的是 {\"data\": {\"content\": \"...\"}}
response_json_field:\"$.data.content\"
执行命令和流式一样:
garak --target_type rest -G my_json_config.yaml --spec probes.promptinject --parallel_attempts 1
方案二:编写自定义 Generator 插件
新建my_json_generator.py
import json
import requests
from garak.generators.base import Generator
class MyJsonGenerator(Generator):
\"\"\"用于对接返回普通 JSON 的MyAgent接口\"\"\"
supports_multiple_generations = False
DEFAULT_PARAMS = Generator.DEFAULT_PARAMS | {
\"uri\": \"http://xxxx:8080/api/chat/stream\",
\"headers\": {
\"Content-Type\": \"application/json\",
\"Accept\": \"application/json\",
\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\"
}
}
def _call_model(self, prompt, generations_this_call=1):
# 构造请求体,替换 message 字段
payload = {
\"message\": prompt,
\"session_id\": \"garak_session_123\",
\"user_orgid\": \"123456\",
\"user_orgnm\": \"机构1\"
}
try:
# 1. 发送请求,注意去掉了 stream=True
response = requests.post(
self.uri,
headers=self.headers,
json=payload,
timeout=60
)
response.raise_for_status()
# 2. 直接将整个响应解析为 JSON
resp_json = response.json()
# 3. 根据返回结构提取需要的字段
final_message = \"\"
# 假设普通 JSON 返回是类似 {\"code\":200, \"data\": [{\"type\":\"thought\", ...}, {\"type\":\"message\", \"data\":\"...\"}]}
data_list = resp_json.get(\"data\", [])
if isinstance(data_list, list):
for item in data_list:
if item.get(\"type\") == \"message\":
final_message = item.get(\"data\", \"\")
break# 找到就跳出循环
elif isinstance(data_list, dict):
# 如果是 {\"data\": {\"type\": \"message\", \"data\": \"...\"}}
if data_list.get(\"type\") == \"message\":
final_message = data_list.get(\"data\", \"\")
# 兜底:如果没找到对应字段,返回整个 JSON 字符串
ifnot final_message:
final_message = json.dumps(resp_json, ensure_ascii=False)
return [final_message]
except Exception as e:
return [f\"ERROR: {str(e)}\"]
同样复制到garak安装目录下,执行:
garak --target_type my_json_generator --target_name qwen-agent --spec probes.promptinject --parallel_attempts 1
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论