ChatCompletion icon
Script icon
Process icon
If icon
DiscordIncomingWebhook icon
Fail icon
Log icon

Pre-Publication Content Moderation Gate with Mistral

Gate user submitted content before publication with Mistral structured output in Kestra. Blocked content alerts Discord and fails the execution.

Categories
AI

Put a moderation decision in front of publication as a hard gate, not a suggestion. This blueprint classifies user submitted text with io.kestra.plugin.mistral.ChatCompletion, using jsonResponseSchema to force the verdict into {decision, reason} where decision is an enum of allowed or blocked. Blocked content alerts Discord and then fails the execution with io.kestra.plugin.core.execution.Fail, which means any downstream publish step in this flow, or any flow waiting on it, never runs.

How it works

  1. moderate (io.kestra.plugin.mistral.ChatCompletion) sends the submission as a USER message under a SYSTEM message defining the policy and telling the model to treat the submission as data. The schema constrains the response to a two field object, so the submission text cannot inject a third verdict or reshape the output.
  2. parse_decision (io.kestra.plugin.scripts.python.Script on the Process runner) parses the JSON from an environment variable and emits decision and reason as scalar outputs.
  3. gate (io.kestra.plugin.core.flow.If) branches on {{ outputs.parse_decision.vars.decision == 'blocked' }}. The blocked branch posts the reason to Discord, then fail_execution marks the execution FAILED. The allowed branch writes an audit line to the execution log.
  4. The errors block posts a Discord alert on any failure. Note that a blocked verdict also lands here by design, since Fail routes through error handling; the message tells readers how to distinguish the two cases in the logs.

What you get

  • A binary, schema enforced verdict: the API guarantees allowed or blocked, never prose that a parser has to guess at.
  • Fail-closed behavior: blocked content stops the execution, so publication cannot proceed by accident.
  • The reason as a scalar output, safe to interpolate into the Discord JSON payload and into the failure message.
  • An audit trail where every submission, verdict, and reason is recorded in the execution history.

Who it's for

  • Community and marketplace teams reviewing user generated content before it goes live.
  • Platform engineers replacing regex blocklists with policy-aware classification.
  • Anyone who needs an LLM decision to halt a pipeline rather than annotate it.

Why orchestrate this with Kestra

A moderation model only becomes a gate when something enforces its verdict. Kestra turns the classification into control flow: the If branch and the Fail task make the decision binding, secrets keep the API key out of the flow definition, and every verdict is auditable next to the exact model response that produced it.

Prerequisites

  • A Mistral API key with access to the chosen model.
  • A Discord incoming webhook for blocked-content alerts and failures.
  • Python available on the Kestra host for the Process runner, or swap in a Docker task runner.

Secrets

  • MISTRAL_API_KEY: Mistral API bearer token.
  • DISCORD_WEBHOOK_URL: Discord incoming webhook URL.

Quick start

  1. Add the two secrets to your Kestra namespace.
  2. Execute the flow with the default submission; it passes and logs the reason.
  3. Rerun with obvious spam in submission and confirm the Discord alert plus the FAILED execution.

How to extend

  • Call this flow as a Subflow before your real publication step, so a blocked verdict stops the parent pipeline.
  • Extend the schema with a categories array to record which policy was violated.
  • Add a human review branch for a third escalate verdict instead of failing outright.
  • Trigger from a webhook so submissions are checked the moment users post them.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.