Skip to content

LLM-assisted PR triage - #5281

Merged
dsmiley merged 13 commits into
apache:mainfrom
dsmiley:pr-triage-llm
Nov 6, 2026
Merged

dsmiley merged 13 commits into
apache:mainfrom
dsmiley:pr-triage-llm

Conversation

@dsmiley

@dsmiley dsmiley commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

🤖 Add a pr-triage-llm workflow that, once per PR, asks an LLM via the ASF gateway (llm.apache.org) whether the change warrants a changelog entry, Reference Guide update, upgrade notes, and tests. It judges the change on its merits, without seeing what the author included, then posts one comment comparing each verdict with what the PR contains and highlighting mismatches in either direction. The questions live in .github/pr-triage/judgements.yml.

Since whether a PR needs a changelog entry is now judged (advisory), the validate-changelog check no longer fails PRs without one; it only validates the structure of entries present. The no-changelog label and its /no-changelog and /needs-changelog comment commands are removed.

Requires repository secret ASF_LLM_API_KEY; optional variable LLM_MODEL.

Claude-Session: https://claude.ai/code/session_01KwTAxfG9oFYQQH7pZDB4op

Add a pr-triage-llm workflow that, once per PR, asks an LLM via the ASF
gateway (llm.apache.org) whether the change warrants a changelog entry,
Reference Guide update, upgrade notes, and tests. It judges the change on
its merits, without seeing what the author included, then posts one
comment comparing each verdict with what the PR contains and highlighting
mismatches in either direction. The questions live in
.github/pr-triage/judgements.yml.

Since whether a PR needs a changelog entry is now judged (advisory), the
validate-changelog check no longer fails PRs without one; it only validates
the structure of entries present. The no-changelog label and its
/no-changelog and /needs-changelog comment commands are removed.

Requires repository secret ASF_LLM_API_KEY; optional variable LLM_MODEL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KwTAxfG9oFYQQH7pZDB4op
@dsmiley
dsmiley requested review from epugh and janhoy November 3, 2026 02:29
@github-actions github-actions Bot added documentation Improvements or additions to documentation scripts labels Nov 3, 2026

@dsmiley dsmiley left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huge disclaimer here: I don't know python, nor GHA details... so really I'm the last one to try to author something of this nature. But I believe in this approach -- LLM based judgements. It's not gospel but it's something and we can tweak it to improve going forward. I could use any assistance in bringing this to reality. It'd be more helpful for reviewers to make changes rather than tell me what to do.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than give users a checklist for these things, we let an LLM make some judgements to help the user (and reviewers!).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inovative!

Comment thread .github/PULL_REQUEST_TEMPLATE.md Resolved
Comment thread .github/PULL_REQUEST_TEMPLATE.md Resolved
Comment thread .github/PULL_REQUEST_TEMPLATE.md Resolved
Comment thread .github/PULL_REQUEST_TEMPLATE.md Resolved
@nick-boss-tech

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖 (reviewed by Nick Shanin)

@dsmiley I put together a follow-up on top of your current head: changes from 3597068 to 6bcaa9e.

It implements the suggestions in your comments: an early warning for PRs targeting something other than main, an optional maintainer-edit reminder that accounts for organisation-owned forks, and an advisory Jira judgement in place of the corresponding template checkboxes. The Jira comparison checks for a title reference; it does not verify the issue itself.

I also fixed three cases in the helper: deleted files counted as included evidence, missing or malformed model answers became “no,” and model reasons could render active Markdown/HTML. The follow-up reports unknown answers explicitly and renders reasons as text. It uses the trusted workflow revision for executable code, installs PyYAML explicitly, and rejects changed or incomplete API snapshots. It adds 23 offline tests and a separate test workflow with no secrets or write permissions.

Verified on Linux: all 23 tests pass. Three regressions fail against the original helper and pass with the fixes. GFM rendering checks and actionlint with ShellCheck pass; ./gradlew tidy leaves the tree clean, and ./gradlew check -x test passes. The root check passed from a real clone after Antora rejected the worktree's .git file.

You can pull the tested commit into your PR branch with:

git switch pr-triage-llm
git fetch https://github.com/nick-boss-tech/solr.git pr-5281-triage-followup
git merge --ff-only 6bcaa9e1191fb8d0c6def2eaf005b602bf882e40

Live ASF gateway behavior and GitHub activation, including secret setup, event delivery, notifications and comment publication, still need a controlled smoke test once the workflow is available from the trusted branch.

@dsmiley

dsmiley commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Nick; I took your changes. I'm opinionated on dev-tools/README.md being at most 3 sentences per script -- each script should instead document itself. So I made an edit related to that.

I think we basically need to just merge & try this thing out, iterate from there. Accept that the first iteration is just that; not perfection. Not sure if it's feasible to evaluate before merging? Does anyone have concerns at all here?

Note: I'll need to get the LLM key and probably add it to the PMC's secret store.

@janhoy janhoy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be an interesting experiment and hopefully give contributors a quick and pleasant guidance. Let my 🤖🤖 analyze and below are the results. Some are arguably minor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inovative!

Comment thread dev-tools/scripts/pr-triage-llm.py Resolved
Comment thread .github/workflows/pr-triage-llm.yml Resolved
Comment thread .github/workflows/pr-triage-llm.yml Resolved
Comment thread dev-tools/scripts/pr-triage-llm.py Outdated
choice = data["choices"][0]
content = choice["message"].get("content") or ""
if not content.strip():
sys.exit(f"LLM returned no content (finish_reason={choice.get('finish_reason')})")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Worth also treating finish_reason == "length" and an empty parse_verdicts() as failures. A truncated or non-JSON reply currently posts a five-row all-❔ "No valid answer; needs human review" table, which is the likely outcome if the backend ignores chat_template_kwargs (vLLM-specific) and spends the 2000-token budget thinking.

Comment thread .github/workflows/pr-triage-llm.yml Resolved
Comment thread dev-tools/scripts/pr-triage-llm.py Resolved
# 'ready_for_review' covers PRs opened as drafts; drafts themselves are skipped.
types:
- opened
- ready_for_review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because triage runs only on these two events and never updates, the ⚠️ rows go stale the moment the author adds the changelog entry or test the comment asked for.

That may have been the intended behavior, but perhaps it should be clearly stated in the note that it will not be re-evaluated?

Comment thread .github/pr-triage/judgements.yml Outdated
- id: needs_ref_guide
title: "Reference Guide update"
help_url: "https://github.com/apache/solr/tree/main/solr/solr-ref-guide"
evidence_files: ["solr/solr-ref-guide/**"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This glob is a superset of the upgrade-notes glob below, so an upgrade-notes-only change also counts as Reference Guide evidence. Intended?

- Skip the comment when there is nothing to judge (only changelog/docs/test files changed)
- Never fail the PR: model errors, truncated or unusable replies post no comment; judge job is continue-on-error
- Dedup check reads one page of comments instead of paginating all
- Config is now JSON, so the privileged job needs no PyYAML install; drop requirements-pr-triage.txt
- Ref Guide evidence excludes upgrade-notes pages (evidence_exclude)
- State up front in the comment that triage is not re-run on later pushes
- Remove dead branches (empty-comment check, test-file diff ranking)

Generated-by: Claude Sonnet 5.5
…--verdicts)

- --from-git BASE triages the commits, staged, unstaged and untracked files since BASE; commit messages stand in for the PR title and description
- --verdicts FILE renders answers given by an agent instead of calling the model
- LLM_BASE_URL defaults to the ASF gateway; ASF_LLM_API_KEY is accepted as the key

Generated-by: Claude Sonnet 5.5
…ghten Jira and tests questions

- skip_when_included: a judgement whose evidence is already present is not sent to the model; its row reads "Already present; not judged."
- Jira is warranted only if a changelog entry is
- Build, CI and developer tooling do not need tests

Generated-by: Claude Sonnet 5.5
…t with the entry

- The Changelog judgement chooses a type (added, changed, fixed, deprecated, removed, dependency_update, other) or "(not-worthy)", and the comment flags a missing entry, an unneeded one, or a type that differs from the entry's
- summary_from: the changelog entry's title is given to every judgement as the author's own summary
- Fixes to the build, tests or developer tooling are "other", not "fixed"
- Trim the script docstring; the config format is described in the config's "_doc" key

Generated-by: Claude Sonnet 5.5
@dsmiley

dsmiley commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Update on #5281 since the last round, including what differs from what was asked.

New

  • Changelog judgement now picks the type. The yes/no "Changelog entry" row is replaced by one that chooses added, changed, fixed, deprecated, removed, dependency_update or other, or (not-worthy). The comment flags a missing entry, an unneeded one, or a type that differs from the entry's. The question is built from the types table in dev-docs/changelog.adoc.
  • Changelog title as input. When the PR has an entry, its title is given to every judgement as the author's own summary. The rest of the entry stays hidden.
  • Jira is skipped when the title already has a SOLR-NNNN. Otherwise it is asked only for changes that warrant a changelog entry.
  • Build, CI and developer tooling need no tests, and a fix to the build is other, not fixed.
  • Config docs: the format is described in the _doc key of judgements.json, and the script's docstring is much shorter.
  • Run it on demand, locally. The script can triage the current branch without a PR (--from-git), and a Claude Code skill wraps it. It takes a PR URL, a number, or the current branch, and never posts to GitHub: https://github.com/dsmiley/solr-claude-skills/blob/main/skills/pr-judgements/SKILL.md. This also gives a way to re-check after new pushes.

Differences from the review feedback

  • I did not use a label to mark "already triaged", as janhoy suggested, since David wants no labels anywhere. The check reads one page of comments instead.
  • I did not add a /triage comment command. The skill covers re-runs.
  • PyYAML is gone to reduce dependencies. I took the JSON-config route, so the key-holding job installs nothing.

Tuning so far
I ran the judgements against 20 merged PRs by 20 different contributors, judged by Claude Sonnet, not the gateway model, and checked each result against what the PR actually did. On the changelog judgement, 15 of 18 agreed, and the three disagreements each led to a prompt change. This says little about how the real gateway model will behave.

Still open

  • The ASF_LLM_API_KEY secret and a real LLM_MODEL.
  • Re-tuning the questions against the gateway model once we have a key.

🤖 Generated with Claude Code

…verdict is false instead of answer no

Generated-by: Claude Sonnet 5.5
Generated-by: Claude Sonnet 5.5
@dsmiley

dsmiley commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

I set an API key using the gh CLI for secrets. I'm pleasantly surprised I could do it without involving infra. The key is attached to me + solr; https://llm.apache.org/help/faq has no info at all about possibly having only project attachment. Any way, if this is a problem, I'll look into it.

I got the best success with qwen3.8-27b so I'm going with that. Overall, this is a low volume usage use-case (just once per PR) so I don't mind using a relatively powerful model on this for better judgements. qwen3.5-9b wasn't too bad.

I have a skill I use locally to invoke this: https://github.com/dsmiley/solr-claude-skills/blob/main/skills/pr-judgements/

Ready to merge and reap the rewards!

@janhoy janhoy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reap the rewards!

@dsmiley
dsmiley merged commit 00a5f97 into apache:main Nov 6, 2026
3 checks passed
@dsmiley
dsmiley deleted the pr-triage-llm branch November 6, 2026 04:19
@nick-boss-tech

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖 (reviewed by Nick Shanin)

The test workflow merged here has its unittest command outside run: |; a3a930a fixes the indentation, with workflow lint, all 44 offline tests, tidy and check -x test passing on Linux. Would you like a small follow-up PR?

@nick-boss-tech

Copy link
Copy Markdown
Contributor

🤖 AI text below 🤖 (reviewed by Nick Shanin)

I tried the existing runner on my workflow repair using the supported no-key agent mode (--dry-run / --verdicts); it completed with all five judgements answered and no mismatches. I also have draft Solr skills for contribution preparation, source/docs navigation and code review; would any of those be useful as repository-local follow-ups?

@dsmiley

dsmiley commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Nick; I directly took that indentation change and pushed (without a PR, while I still have privileges to do so).

I also have draft Solr skills for contribution preparation, source/docs navigation and code review; would any of those be useful as repository-local follow-ups?

Sounds like a dev list thread. If you do that, include a link to such skills (probably in a repo you have but be a draft PR).

@dsmiley

dsmiley commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Here's the first result from a PR!


Automated PR triage (LLM-generated; may be wrong). Checked once, when the PR was opened or marked ready; not updated on later pushes.

1 possible mismatch(es) between what this change warrants and what the PR includes.

Warranted? In PR? Confidence Why
✅ Jira issue in title — yes — Already present; not judged.
⚠️ Changelog — missing added no entry high The PR introduces a new `secure` example for `bin/solr start -e`, which is a new opt-in feature for users to evaluate a secure Solr setup, warranting an 'added' changelog entry.
✅ Reference Guide update yes yes high The PR adds a new example type `secure` to the CLI, which is documented in the Reference Guide's getting started section and CLI reference, and the description explicitly mentions adding a tutorial page.
✅ Upgrade notes no no high The change is purely additive, introducing a new example option without modifying existing behavior, parameters, or defaults.
✅ Tests no no high The changes are primarily to the CLI example launcher (`RunExampleTool.java`) and shell scripts (`bin/solr`), which fall under developer tooling and build/CI infrastructure that does not require new unit tests, and the PR already includes comprehensive BATS integration tests.

⚠️ mismatch, ❔ uncertain or unanswered, ✅ agrees. Matching changelog, docs and test files were hidden from the model; the PR description may still mention them. File presence does not establish adequacy. Assessed commit 86818a97fdfd2b29d82cfcd50a7397769bdffafc.


Overall I love seeing this in action and I think it's a great step forward 🎉 .

It is kinda large & wordy. I think I'd prefer a series of bullets instead of a table. When there's full agreement -- no "Why" explanation needed. Maybe insert as a comment that can be viewed in the markdown view. Maybe confidence doesn't need to be shown when it's "high".

WDYT @epugh ?

@dsmiley

dsmiley commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Proposal: drop the "already triaged" check from the PR triage workflow.

The judge job currently reads the PR's comments and skips if the bot already posted a triage comment (<!-- llm-pr-triage -->). Its only purpose is to stop a PR that goes draft → ready → draft → ready from being judged twice. Authors rarely toggle draft, and an additional review in this case is totally fine! So I propose removing it:

  • Delete the "already triaged" step and its gh api .../comments call from .github/workflows/pr-triage-llm.yml.
  • The <!-- llm-pr-triage --> marker at the start of the comment then has no use, so it can go too.
  • A PR opened ready is still judged on opened, and one opened as a draft on ready_for_review, as now.

What changes: a PR that goes back to draft and then ready again gets a second triage comment and a second model call. The first comment stays and is not updated. If the author added a changelog entry or tests in between, the second comment reflects that.

🤖 Generated with Claude Code (and Smiley edited)

@janhoy

janhoy commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

I ported this to solr-operator :) apache/solr-operator#887 and it works beautifully

@dsmiley

dsmiley commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Nice Jan!
I followed up with the improvements I proposed above: #5308

@dsmiley dsmiley added this to the 11.x milestone Nov 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation scripts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants