Skip to content

Commit 79ceb4e

Browse files
authored
feat: spit the model name in the reviews. (#16)
1 parent ac5885b commit 79ceb4e

8 files changed

Lines changed: 79 additions & 1 deletion

File tree

action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ inputs:
9797
description: 'Model context window (tokens) used for sizing.'
9898
required: false
9999
default: '200000'
100+
staging:
101+
description: 'Set to "true" to mark this as a staging deployment. Published reviews then carry a note saying they were posted from staging.'
102+
required: false
103+
default: 'false'
100104
github_token:
101105
description: 'Token with pull-requests:write. Defaults to the job token.'
102106
required: false
@@ -146,6 +150,7 @@ runs:
146150
CONTEXT_SCRIPT_TIMEOUT: ${{ inputs.context_script_timeout }}
147151
REPO_CHECKOUT_PATH: ${{ inputs.repo_checkout_path }}
148152
TOOL_MAX_ITERATIONS: ${{ inputs.tool_max_iterations }}
153+
STAGING: ${{ inputs.staging }}
149154
HEADROOM_COMPRESS: ${{ inputs.headroom_compress }}
150155
HEADROOM_TARGET_RATIO: ${{ inputs.headroom_target_ratio }}
151156
HEADROOM_COMPRESS_USER_MESSAGES: ${{ inputs.headroom_compress_user_messages }}

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ environment variables in server modes.
3030
| `DEFAULT_REVIEW_RULES` | `default_review_rules` | general Python correctness and security rules | Fallback when no rules file exists. |
3131
| `ALLOW_APPROVE` | none | `false` | Allows publishing `APPROVE` events in App/web mode. |
3232
| `PERSONA_HEADER` | none | `🤗 **Serge** says:` | Prefix for failure comments and bot messages. |
33+
| `STAGING` | `staging` | `false` | Marks a non-production deployment. Published reviews then carry a note that they were posted from staging. |
3334

3435
## Context Compression
3536

reviewbot/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class Config:
8383
# off, and skip any remaining diff chunks. Set to 0 to disable.
8484
llm_max_input_tokens: int = 2_000_000
8585

86+
# When true, published reviews carry a note that they came from a
87+
# non-production (staging) deployment. Set via the STAGING env var.
88+
is_staging: bool = False
89+
8690
# Web-mode (reviewbot-web) settings. All optional in webhook/Action
8791
# modes; required only when require_web=True.
8892
github_oauth_client_id: Optional[str] = None
@@ -246,6 +250,7 @@ def from_env(
246250
# PRs complete without being forced to truncate.
247251
tool_max_iterations=_int_env("TOOL_MAX_ITERATIONS", 30),
248252
llm_max_input_tokens=_int_env("LLM_MAX_INPUT_TOKENS", 2_000_000),
253+
is_staging=_bool_env("STAGING", False),
249254
github_oauth_client_id=oauth_client_id,
250255
github_oauth_client_secret=oauth_client_secret,
251256
github_oauth_callback_url=oauth_callback_url,

reviewbot/reviewer.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ class ReviewDraft:
9999
# metrics_line string.
100100
prompt_tokens: int = 0
101101
completion_tokens: int = 0
102+
# Resolved LLM model id that produced this review (after endpoint
103+
# auto-discovery, if llm_model was unset). Surfaced in the published
104+
# review footer; None when no LLM call was made.
105+
model: Optional[str] = None
102106

103107

104108
@dataclass
@@ -1322,6 +1326,7 @@ def _emit(kind: str, text: str) -> None:
13221326
truncated_chunks=skipped_chunks_for_budget,
13231327
prompt_tokens=total_metrics.prompt_tokens,
13241328
completion_tokens=total_metrics.completion_tokens,
1329+
model=llm.model,
13251330
)
13261331

13271332

@@ -1373,8 +1378,15 @@ def publish_review(
13731378
f"budget; {draft.truncated_chunks} remaining diff chunk(s) were "
13741379
"not reviewed._"
13751380
)
1381+
if cfg.is_staging:
1382+
body += "\n\n_Note: posted from a staging deployment._"
1383+
footer_parts = []
1384+
if draft.model:
1385+
footer_parts.append(f"model: `{draft.model}`")
13761386
if draft.metrics_line:
1377-
body += f"\n\n_{draft.metrics_line}_"
1387+
footer_parts.append(draft.metrics_line)
1388+
if footer_parts:
1389+
body += f"\n\n_{' · '.join(footer_parts)}_"
13781390

13791391
gh.create_review(
13801392
draft.owner,

reviewbot/store.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,7 @@ def _encode_draft(draft: Optional[ReviewDraft]) -> Optional[str]:
636636
"event": draft.event,
637637
"rejected_count": draft.rejected_count,
638638
"metrics_line": draft.metrics_line,
639+
"model": draft.model,
639640
"comments": [dataclasses.asdict(c) for c in draft.comments],
640641
},
641642
ensure_ascii=False,
@@ -667,6 +668,7 @@ def decode_draft(s: Optional[str]) -> Optional[ReviewDraft]:
667668
comments=comments,
668669
rejected_count=data.get("rejected_count", 0),
669670
metrics_line=data.get("metrics_line", ""),
671+
model=data.get("model"),
670672
)
671673

672674

reviewbot/webapp.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,7 @@ def _draft_to_dict(draft: ReviewDraft) -> dict[str, Any]:
18341834
"event": draft.event,
18351835
"rejected_count": draft.rejected_count,
18361836
"metrics_line": draft.metrics_line,
1837+
"model": draft.model,
18371838
"comments": [dataclasses.asdict(c) for c in draft.comments],
18381839
}
18391840

tests/test_config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ def test_respects_helper_tools_path_override(self) -> None:
3838

3939
self.assertEqual(cfg.helper_tools_path, ".review/helpers.json")
4040

41+
def test_staging_defaults_off(self) -> None:
42+
with patch.dict(os.environ, {"LLM_API_KEY": "token"}, clear=True):
43+
cfg = Config.from_env(require_app=False)
44+
45+
self.assertFalse(cfg.is_staging)
46+
47+
def test_staging_enabled_via_env(self) -> None:
48+
with patch.dict(
49+
os.environ,
50+
{"LLM_API_KEY": "token", "STAGING": "true"},
51+
clear=True,
52+
):
53+
cfg = Config.from_env(require_app=False)
54+
55+
self.assertTrue(cfg.is_staging)
56+
4157

4258
if __name__ == "__main__":
4359
unittest.main()

tests/test_publish_review.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,42 @@ def test_rejected_count_note_in_body(self) -> None:
160160
body = gh.create_review.call_args.kwargs["body"]
161161
self.assertIn("3 suggested inline comment(s) were dropped", body)
162162

163+
def test_model_in_footer(self) -> None:
164+
cfg = _make_cfg()
165+
draft = _make_draft(model="acme/cool-model")
166+
gh = MagicMock()
167+
publish_review(cfg, gh, draft)
168+
body = gh.create_review.call_args.kwargs["body"]
169+
self.assertIn("model: `acme/cool-model`", body)
170+
# Model and metrics share one footer line.
171+
self.assertIn("model: `acme/cool-model` · 2 LLM turns", body)
172+
173+
def test_no_model_footer_when_unset(self) -> None:
174+
cfg = _make_cfg()
175+
draft = _make_draft(model=None)
176+
gh = MagicMock()
177+
publish_review(cfg, gh, draft)
178+
body = gh.create_review.call_args.kwargs["body"]
179+
self.assertNotIn("model:", body)
180+
# Metrics line still renders on its own.
181+
self.assertIn("2 LLM turns", body)
182+
183+
def test_staging_note_in_body(self) -> None:
184+
cfg = _make_cfg(is_staging=True)
185+
draft = _make_draft()
186+
gh = MagicMock()
187+
publish_review(cfg, gh, draft)
188+
body = gh.create_review.call_args.kwargs["body"]
189+
self.assertIn("posted from a staging deployment", body)
190+
191+
def test_no_staging_note_by_default(self) -> None:
192+
cfg = _make_cfg()
193+
draft = _make_draft()
194+
gh = MagicMock()
195+
publish_review(cfg, gh, draft)
196+
body = gh.create_review.call_args.kwargs["body"]
197+
self.assertNotIn("staging", body)
198+
163199
def test_no_persona_header_when_disabled(self) -> None:
164200
cfg = _make_cfg(persona_header="")
165201
draft = _make_draft()

0 commit comments

Comments
 (0)