LLM Pull Request Review
Overview
DPsim ships an optional, non-blocking pull-request reviewer that runs a series of specialised passes over the diff of a pull request using a large language model and posts a single review comment. It is intended as an assistive first pass: it never requests changes and cannot block a merge, so a human review remains authoritative.
The reviewer lives under .github/llm-review/ (the prompts in prompts.py and a
pure-standard-library runner in review.py) and is driven by two workflows:
llm-review-collect.yml, which runs on the pull request itself, and
llm-review.yml, which performs the review. The split is what makes reviewing
pull requests from forks safe (see Fork pull requests). It
communicates with any OpenAI-compatible chat endpoint, configured through the
environment variables described below.
How it works
For each pull request the runner reads the base..head diff and sends it, in
turn, to a set of focused review stages, each with its own prompt. The stages
cover model equations and their derivation, MNA stamping and domain modeling,
numerical correctness, task scheduling and attribute usage, real-time safety,
C++ class design and reuse, naming and in-code documentation, logging discipline,
the Python bindings, input parsing, the build system and dependencies, testing
and component coverage, and licensing and pull-request hygiene. Each stage returns
a strict JSON list of findings. A final synthesis pass deduplicates and
prioritises them, and the runner posts them as one review, anchoring inline
comments only to lines present in the diff.
The prompts encode DPsim’s documented conventions (see Guidelines) and the recurring points raised in past reviews, so the feedback stays specific to this project rather than generic.
Configuration
The workflow requires one repository secret:
RWTH_LLM_TOKEN: the bearer API key for the chat endpoint.
The following repository Actions variables are optional and override the defaults baked into the workflow:
LLM_BASE_URL: the OpenAI-compatible base URL.LLM_MODEL: the model identifier.LLM_CHAT_PATH: the chat path appended to the base URL (default/chat/completions).LLM_REVIEW_RUNNER: the runner label (defaultubuntu-latest; see Runner selection).
The workflow’s baked-in defaults target an OpenAI-compatible deployment; override
LLM_BASE_URL and LLM_MODEL to point at a different endpoint or model.
Obtaining an API key
Obtain a bearer API key from the chosen OpenAI-compatible provider and store it as
the RWTH_LLM_TOKEN repository secret. The key is only exposed to workflow runs on
pull requests from the repository itself, never from forks.
Before storing the secret, a single request confirms that the key reaches the model:
curl -sS -X POST "$LLM_BASE_URL/chat/completions" \
-H "Authorization: Bearer $RWTH_LLM_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"model\":\"$LLM_MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"ok\"}]}"
Running locally
The runner has a dry-run mode that executes the full pipeline and prints the assembled review instead of posting it. It needs no GitHub token and no Actions runner, only network access to the endpoint:
cd .github/llm-review
export LLM_BASE_URL='<openai-compatible-base-url>'
export LLM_MODEL='<model-identifier>'
export LLM_CHAT_PATH='/chat/completions'
export LLM_API_KEY="$RWTH_LLM_TOKEN"
export BASE_SHA=$(git rev-parse origin/main) HEAD_SHA=$(git rev-parse HEAD) PR_NUMBER=0
python3 review.py --dry-run
Runner selection
The workflow defaults to a GitHub-hosted ubuntu-latest runner. If the chosen
endpoint is only reachable from within a particular network, set the
LLM_REVIEW_RUNNER variable to a self-hosted runner label registered inside that
network; no change to the workflow is required.
Fork pull requests
Reviewing pull requests from forks requires care, because a fork’s code is
untrusted and must never gain access to the secret. The reviewer uses the
workflow_run pattern for this, rather than pull_request_target, and splits the
work into two workflows:
llm-review-collect.ymlruns on thepull_requestevent, including from forks. GitHub withholds secrets from forkpull_requestruns, so this job has no key. It checks out nothing and runs no code from the pull request; it only records the PR number and commit SHAs, taken from trusted GitHub context, into an artifact.llm-review.ymlruns onworkflow_run, after the collect job completes, in the base repository context where the secret is available. It checks out the base repository’s own code, never the pull request’s, and reads the diff as data through the GitHub API. It never builds or executes anything from the pull request.
Two properties keep the key safe. First, the key is only ever sent as an
Authorization header to the configured LLM endpoint, and is never placed in the
model prompt, so a prompt-injection payload in the diff cannot reveal it. Second,
the privileged job runs only trusted base-repository code, so untrusted pull
request code never executes with the secret in scope. The PR metadata read from
the collect artifact is validated (numeric PR number, hexadecimal SHAs) before
use. For this to operate, both the workflows and the secret must reside on the
repository the pull requests target.