第2207篇:多模态内容审核——图文并茂用户生成内容的安全审查
2026/4/30大约 6 分钟
第2207篇:多模态内容审核——图文并茂用户生成内容的安全审查
适读人群:需要做UGC内容安全的Java工程师 | 阅读时长:约15分钟 | 核心价值:图文混合内容审核的完整工程方案,重点解决文字安全但图片违规、或图文组合违规的场景
内容安全这件事,难度在两个地方。
第一个是:文字无害,图片有害——用户发一张正常文字配违规图片。传统只审文字的系统完全不防。
第二个是:图片无害,组合有害——单独看图片是正常的产品图,单独看文字也没问题,但组合在一起暗示了违规意图(比如打擦边球的广告)。
要处理这两种情况,都需要多模态审核:不只看文字,不只看图片,要理解图文的整体语义。
这篇文章写多模态内容审核的工程实现,以及在生产环境里的策略设计。
一、内容审核的层次设计
内容审核不是单一的模型调用,而是多层次的过滤体系:
为什么需要分层?
直接把所有内容都用VLM审核会有两个问题:
- 成本高——VLM审核成本是规则过滤的100-1000倍
- 延迟高——VLM需要2-5秒,用户体验差
分层设计让90%的明确内容(明确安全和明确违规)在第一层就过滤掉,只有10%的灰色内容才走到VLM。
二、图片内容的快速分类
第二层使用专用的图片分类模型(比开放VLM便宜10倍以上):
@Service
public class ImageContentClassifier {
private final RestTemplate restTemplate;
@Value("${content.safety.image-classifier-url:http://localhost:9001}")
private String classifierUrl;
/**
* 图片内容快速分类
* 使用本地部署的轻量级分类模型
*/
public ImageClassificationResult classify(byte[] imageBytes) {
String base64 = Base64.getEncoder().encodeToString(imageBytes);
Map<String, String> request = Map.of("image", base64);
ResponseEntity<Map> response = restTemplate.postForEntity(
classifierUrl + "/classify",
request, Map.class);
return parseClassificationResult(response.getBody());
}
@SuppressWarnings("unchecked")
private ImageClassificationResult parseClassificationResult(Map<String, Object> body) {
Map<String, Double> scores = (Map<String, Double>) body.get("scores");
// 分类维度
double pornScore = scores.getOrDefault("porn", 0.0);
double violenceScore = scores.getOrDefault("violence", 0.0);
double terrorismScore = scores.getOrDefault("terrorism", 0.0);
double adultScore = scores.getOrDefault("adult", 0.0);
// 综合判断
boolean requiresVLMAnalysis = pornScore > 0.3 || violenceScore > 0.3
|| terrorismScore > 0.2 || adultScore > 0.4;
boolean autoReject = pornScore > 0.85 || violenceScore > 0.85
|| terrorismScore > 0.7;
return new ImageClassificationResult(
pornScore, violenceScore, terrorismScore, adultScore,
autoReject, requiresVLMAnalysis);
}
public record ImageClassificationResult(
double pornScore, double violenceScore, double terrorismScore, double adultScore,
boolean autoReject, boolean requiresVLMAnalysis
) {
public String getHighestRiskCategory() {
Map<String, Double> scores = Map.of(
"色情", pornScore, "暴力", violenceScore,
"恐怖主义", terrorismScore, "成人内容", adultScore);
return scores.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey).orElse("安全");
}
}
}三、多模态VLM深度审核
第三层用VLM做图文整体语义理解,这是最关键的层:
@Service
public class MultimodalContentModerator {
private final VisionService visionService;
private final ObjectMapper objectMapper;
/**
* 图文整体内容审核
*/
public ModerationResult moderate(byte[] imageBytes, String textContent,
ContentContext context) {
String systemPrompt = """
你是一个内容安全审核系统。你的任务是判断图文内容是否违规。
审核维度:
1. 色情/低俗内容
2. 暴力/血腥内容
3. 违禁品宣传
4. 欺诈/误导性内容
5. 侵权内容
6. 图文组合的隐含违规(单独看无问题但组合违规)
你必须:
- 给出明确的裁决(通过/不通过/待人工审核)
- 说明理由
- 标注具体违规点
""";
String contextInfo = context != null ?
"\n内容来源:" + context.source() +
"\n用户历史违规次数:" + context.userViolationCount() : "";
String auditPrompt = String.format("""
请审核以下用户发布的图文内容。%s
文字内容:
%s
请综合图片和文字内容,返回审核结果(JSON格式):
{
"verdict": "PASS/REJECT/MANUAL_REVIEW",
"riskLevel": "LOW/MEDIUM/HIGH/CRITICAL",
"violations": [
{
"type": "违规类型",
"description": "具体违规描述",
"evidence": "证据(引用图片描述或文字)"
}
],
"imageSafe": true/false,
"textSafe": true/false,
"combinationRisk": "图文组合风险描述(如有)",
"confidence": 0.0-1.0
}
只返回JSON。
""", contextInfo, textContent != null ? textContent : "(无文字内容)");
VisionRequest request = VisionRequest.builder()
.images(imageBytes != null
? List.of(ImageInput.fromBytes(imageBytes, "image/jpeg"))
: List.of())
.systemPrompt(systemPrompt)
.prompt(auditPrompt)
.build();
String response = visionService.analyzeImage(request).getContent();
return parseModerationResult(response);
}
private ModerationResult parseModerationResult(String response) {
try {
String cleanJson = response.replaceAll("```json\\s*", "")
.replaceAll("```\\s*", "").trim();
JsonNode root = objectMapper.readTree(cleanJson);
String verdict = root.get("verdict").asText("MANUAL_REVIEW");
String riskLevel = root.get("riskLevel").asText("MEDIUM");
double confidence = root.has("confidence") ? root.get("confidence").asDouble() : 0.5;
List<Violation> violations = new ArrayList<>();
if (root.has("violations")) {
for (JsonNode v : root.get("violations")) {
violations.add(new Violation(
v.get("type").asText(),
v.get("description").asText(),
v.has("evidence") ? v.get("evidence").asText() : null
));
}
}
return new ModerationResult(verdict, riskLevel, violations, confidence,
root.has("combinationRisk") ? root.get("combinationRisk").asText() : null);
} catch (Exception e) {
// 解析失败,保守处理为人工审核
return new ModerationResult("MANUAL_REVIEW", "MEDIUM",
List.of(), 0.5, "审核系统异常");
}
}
public record Violation(String type, String description, String evidence) {}
public record ModerationResult(
String verdict, String riskLevel, List<Violation> violations,
double confidence, String combinationRisk
) {
public boolean shouldReject() { return "REJECT".equals(verdict); }
public boolean shouldPass() { return "PASS".equals(verdict); }
public boolean needsManualReview() { return "MANUAL_REVIEW".equals(verdict); }
}
public record ContentContext(String source, int userViolationCount) {}
}四、完整审核流水线
把三层审核整合起来:
@Service
public class ContentModerationPipeline {
private final TextKeywordFilter keywordFilter;
private final ImageHashDatabase hashDatabase;
private final ImageContentClassifier imageClassifier;
private final MultimodalContentModerator vlmModerator;
private final ModerationResultRepository resultRepository;
/**
* 完整的图文内容审核流水线
*/
public ModerationDecision moderate(ContentSubmission submission) {
long startTime = System.currentTimeMillis();
String contentId = submission.contentId();
try {
// === 第一层:规则和哈希过滤 ===
// 1.1 关键词过滤
if (submission.text() != null) {
KeywordCheckResult keywordResult = keywordFilter.check(submission.text());
if (keywordResult.isViolation()) {
return saveAndReturn(ModerationDecision.reject(contentId,
"关键词违规", keywordResult.violatedKeywords().toString()));
}
}
// 1.2 图片哈希黑库检查(已知违规图片)
if (submission.imageBytes() != null) {
String imageHash = DigestUtils.sha256Hex(submission.imageBytes());
if (hashDatabase.isInBlacklist(imageHash)) {
return saveAndReturn(ModerationDecision.reject(contentId,
"图片命中黑名单", "图片哈希匹配已知违规内容"));
}
}
// === 第二层:图片分类模型 ===
if (submission.imageBytes() != null) {
ImageContentClassifier.ImageClassificationResult classifyResult =
imageClassifier.classify(submission.imageBytes());
if (classifyResult.autoReject()) {
return saveAndReturn(ModerationDecision.reject(contentId,
classifyResult.getHighestRiskCategory(),
"图片分类模型高置信判定违规"));
}
// 如果分类模型认为低风险且文字也通过,可以直接放行
if (!classifyResult.requiresVLMAnalysis()
&& classifyResult.pornScore() < 0.1
&& classifyResult.violenceScore() < 0.1) {
return saveAndReturn(ModerationDecision.pass(contentId,
"图片安全+文字安全"));
}
}
// === 第三层:VLM深度审核 ===
MultimodalContentModerator.ModerationResult vlmResult =
vlmModerator.moderate(
submission.imageBytes(),
submission.text(),
new MultimodalContentModerator.ContentContext(
submission.source(), submission.userViolationHistory()));
ModerationDecision decision;
if (vlmResult.shouldReject()) {
String reasons = vlmResult.violations().stream()
.map(MultimodalContentModerator.Violation::type)
.collect(Collectors.joining(", "));
decision = ModerationDecision.reject(contentId, reasons,
vlmResult.violations().toString());
} else if (vlmResult.needsManualReview()) {
decision = ModerationDecision.manualReview(contentId,
"VLM审核不确定,转人工");
} else {
decision = ModerationDecision.pass(contentId, "VLM审核通过");
}
long elapsed = System.currentTimeMillis() - startTime;
decision.setLatencyMs(elapsed);
return saveAndReturn(decision);
} catch (Exception e) {
log.error("内容审核流水线异常: contentId={}", contentId, e);
// 审核异常时,保守处理为人工审核
return saveAndReturn(ModerationDecision.manualReview(contentId, "审核系统异常"));
}
}
private ModerationDecision saveAndReturn(ModerationDecision decision) {
resultRepository.save(decision);
return decision;
}
}五、审核系统的运营指标
几个必须监控的指标:
| 指标 | 说明 | 参考阈值 |
|---|---|---|
| 漏检率(False Negative Rate) | 通过了但实际违规的比例 | <0.1% |
| 误拦截率(False Positive Rate) | 拦截了但实际安全的比例 | <2% |
| VLM使用比例 | 最终到达第三层的内容比例 | 5-15% |
| 人工审核比例 | 最终需要人工处理的比例 | <5% |
| 平均审核延迟 | 端到端审核时间 | <3秒 |
误拦截率是一个容易忽视但非常重要的指标。过于严格的审核会拦截大量正常内容,损害用户体验,最终用户会放弃发内容。内容安全的目标是找到安全性和体验之间的最佳平衡点,而不是把准确率调到100%。
