This project builds an ability that classifies a comment (or any submitted content) for spam and policy violations using the AI Client SDK. It returns a recommendation with a reason, it does not delete, trash, or approve anything on its own.
Course 5 for wp_ai_client_prompt(). A few test comments, including at least one that
reads as obvious spam, to confirm the classifier actually distinguishes cases.
Step 1: Register the ability
Input is a comment ID. Output is a classification, a confidence-style label, and a short reason, deliberately not a boolean “delete this” flag.
// File: content-moderator/content-moderator.php
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'content-moderator/classify-comment',
array(
'label' => __( 'Classify comment for moderation', 'content-moderator' ),
'description' => __( 'Classifies a comment as likely spam, policy violation, or acceptable, with a reason. Returns a recommendation only, never deletes or approves anything.', 'content-moderator' ),
'category' => 'moderation',
'input_schema' => array(
'type' => 'object',
'properties' => array( 'comment_id' => array( 'type' => 'integer' ) ),
'required' => array( 'comment_id' ),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'classification' => array( 'type' => 'string', 'enum' => array( 'acceptable', 'likely_spam', 'policy_violation' ) ),
'reason' => array( 'type' => 'string' ),
),
),
'permission_callback' => function () {
return current_user_can( 'moderate_comments' );
},
'execute_callback' => 'content_moderator_classify',
'meta' => array(
'annotations' => array( 'readonly' => true ),
'mcp' => array( 'public' => true ),
),
)
);
} );
readonly here is accurate in the strictest sense (this call itself changes
nothing), even though it logs its result to comment meta as a side effect, that logging
is informational, not a moderation action.
Step 2: Classify with the AI Client SDK and log the recommendation
// File: content-moderator/content-moderator.php
function content_moderator_classify( $input ) {
$comment = get_comment( $input['comment_id'] );
if ( ! $comment ) {
return new WP_Error( 'not_found', 'No comment found with that ID.' );
}
$prompt = <<<PROMPT
Classify this comment into exactly one category: "acceptable", "likely_spam", or "policy_violation".
Policy violations include harassment, hate speech, and threats. Spam includes promotional links, repeated
gibberish, or content unrelated to the post. Respond in exactly this format:
Classification: <category>
Reason: <one sentence>
Comment author: {$comment->comment_author}
Comment content: {$comment->comment_content}
PROMPT;
$response = wp_ai_client_prompt( $prompt )
->using_model_preference( 'anthropic/claude', 'openai/gpt' )
->generate_text();
if ( empty( $response ) ) {
return new WP_Error( 'empty_response', 'The AI provider returned no classification. Defaulting to manual review required.' );
}
$result = content_moderator_parse_response( $response );
// Log the recommendation for a human moderator, don't act on it here.
update_comment_meta( $comment->comment_ID, '_ai_moderation_classification', $result['classification'] );
update_comment_meta( $comment->comment_ID, '_ai_moderation_reason', $result['reason'] );
return $result;
}
function content_moderator_parse_response( $response ) {
$classification = 'acceptable';
$reason = '';
if ( preg_match( '/Classification:\s*(\w+)/i', $response, $m ) ) {
$classification = strtolower( trim( $m[1] ) );
}
if ( preg_match( '/Reason:\s*(.+)/i', $response, $m ) ) {
$reason = trim( $m[1] );
}
if ( ! in_array( $classification, array( 'acceptable', 'likely_spam', 'policy_violation' ), true ) ) {
$classification = 'acceptable'; // Unrecognized output defaults to the safest, least destructive label.
}
return array( 'classification' => $classification, 'reason' => $reason );
}
If the model’s output doesn’t parse cleanly into one of the three known categories,
the code defaults to acceptable, the option that results in the least automatic
consequence, rather than guessing toward policy_violation.
Step 3: Why this stops at a recommendation
It would be straightforward to add wp_set_comment_status( $comment_id, 'trash' )
when the classification is likely_spam. This project deliberately doesn’t. A model
misclassifying a real comment as spam, and that action being taken automatically,
silently removes a legitimate reader’s voice with no one aware it happened. Keeping a
human in the loop means the AI’s job stops at surfacing a clear, reasoned
recommendation, a moderator (or a separate, explicitly human-triggered “apply this
recommendation” action) still makes the actual call. This is the same instinct behind
the two-step preview/commit split in the Bulk-Editing Assistant project, just applied
to moderation instead of content rewrites.
Test it
Add a test comment that reads as obvious spam, then classify it:
wp-env run cli wp comment create --comment_post_ID=1 --comment_content="Buy cheap watches now at totally-legit-watches.example" --comment_author="Spammer"
wp-env run cli wp eval '
$id = get_comments( array( "number" => 1, "orderby" => "comment_ID", "order" => "DESC" ) )[0]->comment_ID;
print_r( wp_get_ability( "content-moderator/classify-comment" )->execute( array( "comment_id" => $id ) ) );
'
Confirm classification returns likely_spam with a reason mentioning the
promotional link, then run the same ability against a normal, on-topic comment and
confirm it returns acceptable.
If the model doesn’t follow the requested Classification: / Reason: format exactly,
for example wrapping its answer in extra commentary, the regex in
content_moderator_parse_response() can fail to match and silently fall back to
acceptable even for genuinely bad content. Log the raw, unparsed $response
somewhere during development so you can see when the model’s output format drifts, and
tighten the prompt or the parsing before relying on this in production.
Recap
This ability sends comment content to wp_ai_client_prompt() for classification into
one of three fixed categories, logs the classification and reasoning to comment meta
for a moderator to see, and returns that same recommendation to the caller. It never
calls wp_set_comment_status() or deletes anything itself, moderation action stays a
separate, human decision, with the AI’s role limited to surfacing a clear,
well-reasoned flag.
Resources & further reading
- PHP AI Client SDK repository, GitHub
- wp_set_comment_status() function reference, developer.wordpress.org
- Comment moderation (WordPress support), wordpress.org