返回 DeepSeek-Reasonix
e2e-bot.yml
根目录 / .github / workflows / e2e-bot.yml
1 name: e2e-bot
2
3 # Comment "/e2e" (fixed suite) or "/e2e diff" (generate tests for the PR's diff)
4 # on a pull request to run the e2e benchmark against the real provider and post a
5 # report back. Gated to trusted authors: the job checks out PR-head code and runs
6 # it with the provider API key, so only the repo owner, members, and collaborators
7 # may trigger it.
8
9 on:
10 issue_comment:
11 types: [created]
12
13 permissions:
14 contents: read
15 pull-requests: write
16
17 concurrency:
18 group: e2e-bot-${{ github.event.issue.number }}
19 cancel-in-progress: true
20
21 jobs:
22 e2e:
23 if: >-
24 github.event.issue.pull_request &&
25 contains(github.event.comment.body, '/e2e') &&
26 contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
27 runs-on: ubuntu-latest
28 # Running PR-head code with the provider secret is gated twice: the author_
29 # association check above, and this deployment environment. Configure the
30 # `e2e-bot` environment with required reviewers in repo settings to force a
31 # human approval per run (actions/untrusted-checkout-toctou).
32 environment: e2e-bot
33 steps:
34 - name: Acknowledge
35 uses: actions/github-script@v9
36 with:
37 script: |
38 await github.rest.reactions.createForIssueComment({
39 owner: context.repo.owner, repo: context.repo.repo,
40 comment_id: context.payload.comment.id, content: 'eyes',
41 });
42
43 # Default-branch checkout: this is where the harness (cmd/e2ebench), the
44 # suite, and a run --metrics-capable agent live.
45 - uses: actions/checkout@v7
46 with:
47 fetch-depth: 0
48
49 - uses: actions/setup-go@v7
50 with:
51 go-version-file: go.mod
52 cache: true
53
54 - uses: actions/setup-python@v7
55 with:
56 python-version: '3.12'
57
58 - name: Install and verify Linux sandbox backend
59 run: |
60 sudo apt-get update
61 sudo apt-get install -y bubblewrap
62 if sysctl -n kernel.unprivileged_userns_clone >/dev/null 2>&1; then
63 sudo sysctl -w kernel.unprivileged_userns_clone=1
64 fi
65 if sysctl -n kernel.apparmor_restrict_unprivileged_userns >/dev/null 2>&1; then
66 sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
67 fi
68 bwrap --ro-bind / / --dev /dev --proc /proc -- true
69
70 - name: Build harness + fallback agent from the default branch
71 # Harness (e2ebench) and suite always come from main-v2 so a PR can't weaken
72 # its own grader or tests. The agent is rebuilt from the PR head below; this
73 # main-v2 build is only the fallback for heads that predate `run --metrics`.
74 run: |
75 go build -o "$RUNNER_TEMP/reasonix-base" ./cmd/reasonix
76 go build -o "$RUNNER_TEMP/e2ebench" ./cmd/e2ebench
77 cp -r benchmarks/e2e "$RUNNER_TEMP/suite"
78
79 - name: Check out the PR head
80 env:
81 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
82 # Pin to the head commit resolved now and check it out detached, not the
83 # mutable PR ref: a force-push mid-run can't swap in different code after
84 # the trusted-author gate passed.
85 run: |
86 SHA=$(gh pr view ${{ github.event.issue.number }} --json headRefOid -q .headRefOid)
87 git fetch -q origin "$SHA"
88 git checkout -q --detach "$SHA"
89
90 - name: Build the agent from the PR head
91 # The whole point of the bot is to drive the PR's code, not main-v2's. Fall
92 # back to the main-v2 build only when the PR head can't yield a
93 # run --metrics-capable binary (build break or predates the flag).
94 id: agent
95 run: |
96 bin="$RUNNER_TEMP/reasonix-base"
97 src="main-v2 fallback (PR head lacks run --metrics)"
98 if go build -o "$RUNNER_TEMP/reasonix-pr" ./cmd/reasonix \
99 && "$RUNNER_TEMP/reasonix-pr" run -h 2>&1 | grep -q -- '-metrics'; then
100 bin="$RUNNER_TEMP/reasonix-pr"
101 src="PR head ($(git rev-parse --short HEAD))"
102 fi
103 echo "bin=$bin" >> "$GITHUB_OUTPUT"
104 echo "src=$src" >> "$GITHUB_OUTPUT"
105 echo "agent under test: $src"
106
107 - name: Write provider config
108 env:
109 REASONIX_HOME: ${{ runner.temp }}/reasonix-e2e-home
110 # User config covers suite tasks (they run in temp dirs); the repo-root copy
111 # covers diff mode (the agent runs in the repo root, where project config wins).
112 run: |
113 mkdir -p "$REASONIX_HOME"
114 cat > /tmp/reasonix-e2e.toml <<EOF
115 default_model = "e2e"
116
117 [[providers]]
118 name = "e2e"
119 kind = "openai"
120 base_url = "${{ vars.REASONIX_E2E_BASE_URL || 'https://api.deepseek.com' }}"
121 model = "${{ vars.REASONIX_E2E_MODEL || 'deepseek-v4-flash' }}"
122 api_key_env = "DEEPSEEK_API_KEY"
123 context_window = 20000 # small so the suite actually exercises compaction + cache churn
124
125 [providers.price]
126 cache_hit = ${{ vars.REASONIX_E2E_PRICE_CACHE_HIT || '0.02' }}
127 input = ${{ vars.REASONIX_E2E_PRICE_INPUT || '1' }}
128 output = ${{ vars.REASONIX_E2E_PRICE_OUTPUT || '2' }}
129 currency = "${{ vars.REASONIX_E2E_PRICE_CURRENCY || '¥' }}"
130
131 [permissions]
132 mode = "allow"
133
134 [codegraph]
135 enabled = false
136 EOF
137 cp /tmp/reasonix-e2e.toml "$REASONIX_HOME/config.toml"
138 cp /tmp/reasonix-e2e.toml ./reasonix.toml
139
140 - name: Run e2e
141 env:
142 DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
143 REASONIX_HOME: ${{ runner.temp }}/reasonix-e2e-home
144 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
145 COMMENT_BODY: ${{ github.event.comment.body }}
146 run: |
147 if [ -z "$DEEPSEEK_API_KEY" ]; then
148 printf 'missing DEEPSEEK_API_KEY secret\n\nAdd a repository secret named DEEPSEEK_API_KEY to enable the e2e bot.\n' > report.md
149 exit 1
150 fi
151 # Provider credentials are deliberately loaded only from Reasonix's
152 # global credential file, not inherited process variables. Keep this
153 # ephemeral runner copy private and outside the checked-out project.
154 umask 077
155 printf 'DEEPSEEK_API_KEY=%s\n' "$DEEPSEEK_API_KEY" > "$REASONIX_HOME/.env"
156 if printf '%s' "$COMMENT_BODY" | grep -q '/e2e[[:space:]]\+diff'; then
157 ATTEMPTS=$(printf '%s' "$COMMENT_BODY" | sed -nE 's@.*/e2e[[:space:]]+diff[[:space:]]+x([0-9]+).*@\1@p' | head -1)
158 [ -z "$ATTEMPTS" ] && ATTEMPTS=1
159 [ "$ATTEMPTS" -gt 5 ] && ATTEMPTS=5
160 BASE_REF=$(gh pr view ${{ github.event.issue.number }} --json baseRefName -q .baseRefName)
161 git fetch -q origin "$BASE_REF"
162 BASE=$(git merge-base "origin/$BASE_REF" HEAD)
163 "$RUNNER_TEMP/e2ebench" -mode diff -bin "${{ steps.agent.outputs.bin }}" -repo . -base "$BASE" -model e2e -attempts "$ATTEMPTS" -out report.md
164 else
165 # The current five-task baseline can exceed 400k after only three
166 # successful tasks. Keep bounded headroom so every scenario is graded.
167 "$RUNNER_TEMP/e2ebench" -bin "${{ steps.agent.outputs.bin }}" -suite "$RUNNER_TEMP/suite" -model e2e -out report.md -json report.json -budget 800000
168 node -e '
169 const results = require("./report.json");
170 const unsuccessful = results.filter((result) => !result.Passed || result.Skipped);
171 if (results.length === 0 || unsuccessful.length > 0) {
172 console.error(`e2e suite incomplete: ${unsuccessful.length}/${results.length} unsuccessful`);
173 process.exit(1);
174 }
175 '
176 fi
177
178 - name: Post report
179 if: always() && hashFiles('report.md') != ''
180 env:
181 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
182 TRIGGER_USER: ${{ github.event.comment.user.login }}
183 run: |
184 printf '\n> agent: %s · triggered by @%s\n' "${{ steps.agent.outputs.src }}" "$TRIGGER_USER" >> report.md
185 gh pr comment ${{ github.event.issue.number }} --body-file report.md
186
187 - name: Report failure
188 if: failure()
189 env:
190 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
191 run: gh pr comment ${{ github.event.issue.number }} --body "🤖 e2e bot failed — see the [run log](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})."
192
192 lines YAML