0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-08 17:53:55 +08:00
discourse/plugins/discourse-ai
Gabriel Grubba 9c09e988b3
FEATURE: Approve AI moderation actions inline in bot chat DMs (#41565)
> Was stacked on #41497, which has since merged. This PR is now rebased
onto `main` and contains only the chat-approval work.

### What

Lets a moderator approve or reject an AI-bot moderation tool action
(`suspend_user` / `silence_user`) **inside a Chat direct message with
the bot**, instead of leaving for the `/review` queue — the chat
counterpart to the base PR's inline PM/topic card.

When the bot queues one of these actions in a DM, it posts a message
with **Approve / Reject** buttons rendered via the Chat plugin's native
interactive **blocks**. Clicking performs the queued action through the
existing `ReviewableAiToolAction` backend (credited to the approving
moderator) and rewrites the message to its resolved state, removing the
buttons.

### How

- `bot.rb` — `enqueue_tool_for_approval` branches on chat context: in
chat it emits a `:chat_approval` signal; in PM/topic it keeps the
existing inline card.
- `playground.rb` — `reply_to_chat_message` posts a bot chat message
carrying the Approve/Reject blocks, in the **same DM thread as the bot's
reply** (AI-bot DM replies are threaded by design). DM channels only.
- `chat_tool_approval.rb` — builds/parses the button `action_id`s,
builds the blocks, and handles the `chat_message_interaction` event:
performs the reviewable and rewrites the message. Runs synchronously so
the buttons clear before the request returns.
- `entry_point.rb` — registers the `:chat_message_interaction` listener.

### Authorization

`Chat::CreateMessageInteraction` only checks channel visibility, so
staff-gating is enforced here: the handler requires
`Reviewable.viewable_by(user)` **and** `Reviewable#perform` re-checks
(`ensure_performed_by_is_a_real_person!` + the approver's guardian).
Non-staff clicks are ignored. A crafted `action_id` can't target another
reviewable — core only matches `action_id`s present in that message's
own blocks.

### Core-chat changes (3 lines)

The blocks system was built for **create-time-only** blocks; nothing had
ever mutated a message's `blocks` after creation. Clearing the buttons
on approve/reject is the first such case, which required:

- `chat-message.js` — make `blocks` a `@tracked` property (so
reassigning it re-renders).
- `chat-channel-subscription-manager.js` +
`chat-channel-thread-subscription-manager.js` — refresh `message.blocks`
in `handleEditMessage` (so the block-clearing edit reaches the client,
in both the channel and thread views).

All are no-ops for the only other block user (category blocks, which are
never edited after creation).

### Testing

`plugins/discourse-ai/spec/lib/ai_bot/chat_tool_approval_spec.rb` —
action-id round-trip, block shape, staff gating, foreign/stale
action-ids, approve/reject, failure surfacing, and an end-to-end run
through the real `Chat::CreateMessageInteraction` service. The existing
`playground_spec.rb` chat-DM tests (threaded conversation + context)
continue to pass. Verified manually in a bot DM.
2026-07-13 17:08:35 -03:00
..
admin/assets/javascripts/discourse FEATURE: Add category scope for AI translations (#41585) 2026-07-10 09:39:20 +08:00
app FEATURE: Add AI agent suspend/silence tools with inline approval review (#41497) 2026-07-10 10:59:51 -03:00
assets FEATURE: Add AI agent suspend/silence tools with inline approval review (#41497) 2026-07-10 10:59:51 -03:00
config FEATURE: Approve AI moderation actions inline in bot chat DMs (#41565) 2026-07-13 17:08:35 -03:00
db FEATURE: Add category scope for AI translations (#41585) 2026-07-10 09:39:20 +08:00
discourse_automation DEV: Enable Style/RedundantSelf rubocop rule (#40098) 2026-05-19 19:27:45 +02:00
discourse_workflows/nodes/ai_agent FIX: do not reset llm_id on agent change (#41256) 2026-06-30 06:58:48 +10:00
evals DEV: Update models in config and the DE runner (#41082) 2026-06-22 21:29:07 +08:00
lib FEATURE: Approve AI moderation actions inline in bot chat DMs (#41565) 2026-07-13 17:08:35 -03:00
public DEV: Gate AI bot docked composer behind upcoming change (#39708) 2026-05-04 09:26:32 -07:00
spec FEATURE: Approve AI moderation actions inline in bot chat DMs (#41565) 2026-07-13 17:08:35 -03:00
svg-icons
test/javascripts FEATURE: Add AI agent suspend/silence tools with inline approval review (#41497) 2026-07-10 10:59:51 -03:00
.prettierignore
about.json DEV: Update discourse-ai's requiredPlugins (#37987) 2026-02-23 17:15:09 +01:00
package.json DEV: Add a script for generating external types in discourse-types (#37095) 2026-03-09 20:37:43 +01:00
plugin.rb FEATURE: Add AI agent suspend/silence tools with inline approval review (#41497) 2026-07-10 10:59:51 -03:00
README.md DEV: AI persona to agent migration (#38319) 2026-03-10 15:59:45 +11:00
tsconfig.json DEV: Add a script for generating external types in discourse-types (#37095) 2026-03-09 20:37:43 +01:00

Discourse AI Plugin

Plugin Summary

For more information, please see: https://meta.discourse.org/t/discourse-ai/259214?u=falco

Evals

The directory evals contains AI evals for the Discourse AI plugin. You may create a local config by copying config/eval-llms.yml to config/eval-llms.local.yml and modifying the values.

To run them use:

cd evals ./run --help

Usage: evals/run [options]
    -e, --eval NAME                  Name of the evaluation to run
    -m, --models NAME                Models to evaluate (comma separated, defaults to all)
    -l, --list                       List eval ids
        --list-models                List configured LLMs
        --list-features              List feature keys available to evals
        --list-agents              List agent definitions under evals/agents
    -f, --feature KEY                Filter evals by feature (module_name:feature_name)
    -j, --judge NAME                 LLM config used as a judge (defaults to gpt-4o when available)
        --agent-keys KEYS          Comma-separated list of agent keys (or repeat the flag) to run sequentially
        --compare MODE               Run comparisons (MODE: agents or llms)
        --dataset PATH               Path to a CSV dataset file (requires --feature)

To run evals you will need to configure API keys in your environment:

OPENAI_API_KEY=your_openai_api_key ANTHROPIC_API_KEY=your_anthropic_api_key GEMINI_API_KEY=your_gemini_api_key

Custom agents for evals

Eval runs can swap the built-in agents with YAML definitions stored in plugins/discourse-ai/evals/agents. Use --list-agents to discover available entries; the special key default always refers to the built-in agent prompt. Pass --agent-keys key1,key2 (or repeat --agent-keys key) to apply them:

./run --eval simple_summarization --models gpt-4o-mini --agent-keys topic_summary_eval,another_prompt

Each agent file only needs a system_prompt (and optional description). When specified, that prompt replaces the default system prompt of whichever agent the eval runner would normally use. Pass multiple keys (including default) to rerun the same evals with different prompts without restarting the CLI. Add new files under that directory to compare alternate prompts without touching the database.

When running agent comparisons (--compare agents) the CLI automatically prepends the built-in default agent so you can benchmark your YAML prompts against the stock behavior. Non-comparison runs still execute only the agents you list.

Dataset-driven evals

Supply --dataset path/to/file.csv along with --feature module:feature_name to generate eval cases from a CSV instead of YAML files. Each row must include content and expected_output columns; rows are converted into individual eval ids (prefixed with the dataset filename) that reuse the selected features runner. Example:

./run --dataset evals/datasets/spam.csv --feature spam:inspect_posts --models gpt-4o-mini

Comparison matrix

Use the --compare flag to ask the CLI to judge multiple runs together:

  • --compare agents: require a single --models value and at least one agent key (the built-in default agent is implicitly added). Each eval is executed for every agent; the judge LLM scores them side-by-side and announces the winner plus individual ratings.
  • --compare llms: require at least two --models and exactly one agent (default unless you pass --agent-keys custom_agent). Every eval runs once and the judge compares the outputs from each LLM. Logs include the agent key (or default) so you can correlate recordings.

Both modes reuse the rubric declared under the evals judge block and stream the comparison summary to STDOUT. The structured log files continue to be written for each underlying run so you can drill into the raw outputs if the judges reasoning needs inspection.