feat(moderation): make moderation more lenient and add timeout fallback behavior
All checks were successful
Build Backend / build (push) Successful in 2m20s
Build Backend / build-docker (push) Successful in 1m7s

The moderation system now uses a "pass/review" strategy instead of "pass/review/block", treating hard violations the same as review (only routing to manual review rather than auto-rejecting). Add fallback-approve behavior in the hook system so slow moderation APIs never silently block normal content when they time out or get cancelled - timed-out moderation now approves with "ReviewedBy: system" instead of leaving the zero-value result that would reject content. Update post/comment/vote services and handlers to use the new unified behavior. Simplify moderation prompts to emphasize "宁可放过,不可误伤" (prefer false positives over false negatives).
This commit is contained in:
lafay
2026-07-08 01:47:48 +08:00
parent eb931bf1c6
commit a971fb0e29
9 changed files with 324 additions and 267 deletions

View File

@@ -223,15 +223,13 @@ func (s *postAIService) ModeratePost(ctx context.Context, title, content string,
zap.Error(err),
)
return s.moderateTextWithTencent(ctx, title+" "+content,
func(reason string) error { return &PostModerationRejectedError{Reason: reason} },
func(reason string) error { return &PostModerationReviewError{Reason: reason} },
)
}
// block 与 review 同等处理:均仅送人工复审。
switch resp.Result {
case openai.ModerationResultBlock:
return &PostModerationRejectedError{Reason: resp.Reason}
case openai.ModerationResultReview:
case openai.ModerationResultBlock, openai.ModerationResultReview:
return &PostModerationReviewError{Reason: resp.Reason}
default:
return nil
@@ -239,7 +237,6 @@ func (s *postAIService) ModeratePost(ctx context.Context, title, content string,
}
return s.moderateTextWithTencent(ctx, title+" "+content,
func(reason string) error { return &PostModerationRejectedError{Reason: reason} },
func(reason string) error { return &PostModerationReviewError{Reason: reason} },
)
}
@@ -256,15 +253,13 @@ func (s *postAIService) ModerateComment(ctx context.Context, content string, ima
zap.Error(err),
)
return s.moderateTextWithTencent(ctx, content,
func(reason string) error { return &CommentModerationRejectedError{Reason: reason} },
func(reason string) error { return &CommentModerationReviewError{Reason: reason} },
)
}
// block 与 review 同等处理:均仅送人工复审。
switch resp.Result {
case openai.ModerationResultBlock:
return &CommentModerationRejectedError{Reason: resp.Reason}
case openai.ModerationResultReview:
case openai.ModerationResultBlock, openai.ModerationResultReview:
return &CommentModerationReviewError{Reason: resp.Reason}
default:
return nil
@@ -272,7 +267,6 @@ func (s *postAIService) ModerateComment(ctx context.Context, content string, ima
}
return s.moderateTextWithTencent(ctx, content,
func(reason string) error { return &CommentModerationRejectedError{Reason: reason} },
func(reason string) error { return &CommentModerationReviewError{Reason: reason} },
)
}
@@ -293,10 +287,9 @@ func (s *postAIService) ModerateImage(ctx context.Context, imageURL string) erro
return nil
}
// block 与 review 同等处理:均仅送人工复审。
switch resp.Result {
case openai.ModerationResultBlock:
return &ImageModerationRejectedError{Reason: resp.Reason}
case openai.ModerationResultReview:
case openai.ModerationResultBlock, openai.ModerationResultReview:
return &ImageModerationReviewError{Reason: resp.Reason}
default:
return nil
@@ -315,15 +308,13 @@ func (s *postAIService) ModerateBio(ctx context.Context, bio string) error {
zap.Error(err),
)
return s.moderateTextWithTencent(ctx, bio,
func(reason string) error { return &BioModerationRejectedError{Reason: reason} },
func(reason string) error { return &BioModerationReviewError{Reason: reason} },
)
}
// block 与 review 同等处理:均仅送人工复审。
switch resp.Result {
case openai.ModerationResultBlock:
return &BioModerationRejectedError{Reason: resp.Reason}
case openai.ModerationResultReview:
case openai.ModerationResultBlock, openai.ModerationResultReview:
return &BioModerationReviewError{Reason: resp.Reason}
default:
return nil
@@ -331,7 +322,6 @@ func (s *postAIService) ModerateBio(ctx context.Context, bio string) error {
}
return s.moderateTextWithTencent(ctx, bio,
func(reason string) error { return &BioModerationRejectedError{Reason: reason} },
func(reason string) error { return &BioModerationReviewError{Reason: reason} },
)
}
@@ -339,7 +329,6 @@ func (s *postAIService) ModerateBio(ctx context.Context, bio string) error {
func (s *postAIService) moderateTextWithTencent(
ctx context.Context,
text string,
rejectFactory func(string) error,
reviewFactory func(string) error,
) error {
if !s.isTencentEnabled() {
@@ -366,10 +355,9 @@ func (s *postAIService) moderateTextWithTencent(
reason = resp.Label + ": " + strings.Join(resp.Keywords, ", ")
}
// 腾讯云 block 也仅送审,与 AI 同语义。
switch resp.Suggestion {
case tencent.SuggestionBlock:
return rejectFactory(reason)
case tencent.SuggestionReview:
case tencent.SuggestionBlock, tencent.SuggestionReview:
return reviewFactory(reason)
default:
return nil