fix(tests): stop the LLM judge mistaking context for the response - #3546
Merged
Conversation
The judge prompt gave the response and criteria `##` headers but appended the
context as a bare "Context provided to the system:" line. A multi-line response
therefore ran straight into the context with nothing marking the boundary, and
the judge read across it.
It did so deterministically. test_facts_from_distinct_chunks_reach_the_answer
failed four times across four CI runs — on three different PRs, including one
that touched nothing but PL/pgSQL — always with the judge quoting the *context*
back as though it were the answer ("It only states that the memory data contained
two hobby facts"), while the real response was a two-bullet list naming both
facts and plainly met the criteria.
Tag each section instead of merely heading it: tags survive a response that is
itself markdown, which headers do not — a response containing "## Criteria" could
otherwise forge a section. The system prompt now says outright that only
<response> is judged and that <context> is background, never the subject.
Verified by A/B against the live judge with the exact inputs from the failing
test: the old prompt was judged "criteria not met" 4/4 times with the CI
reasoning verbatim; the new one 4/4 "met". The test itself passes again.
Prompt assembly is pulled into build_judge_messages() so it is covered by fast
unit tests rather than only by the LLM tests whose outcome it decides.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
test_reflect_split_synthesis::test_facts_from_distinct_chunks_reach_the_answerfailed four times across four CI runs, on three different PRs — including #3543, which changed nothing but PL/pgSQL maintenance routines. Every failure carried byte-identical judge reasoning, so it was never a flake.The response plainly meets the criteria. The judge's sentence is a near-verbatim paraphrase of the test's
context, not its response — and that is the whole bug.tests/llm_judge.pybuilt the prompt as:The response and criteria get
##headers; the context gets a bare line. So a multi-line response runs straight into the context with nothing marking where one ends and the other begins. This test's response is a two-bullet markdown list immediately followed by a context that opens "The memory data contained two hobby facts…" — and the judge read that opening sentence as the answer.Any judge call passing a prose
contextis exposed. This one hit it reliably because its context restates the facts.The fix
Tag each section rather than merely heading it. Headers alone cannot delimit, because a response may itself be markdown — a response containing
## Criteriawould forge a section. Tags survive that. The system prompt now states outright that only<response>is judged and that<context>is background, never the subject.Prompt assembly moves into
build_judge_messages()so it is covered by fast unit tests instead of only by the LLM tests whose outcome it silently decides.Verification
A/B against the live judge, using the exact response/criteria/context from the failing test, bypassing the reflect pipeline to isolate the prompt:
test_facts_from_distinct_chunks_reach_the_answerpasses again. Five new unit tests cover the assembly: sections stay separated, an absent context leaves no empty block, the system prompt scopes judgement, and a response containing its own##headers cannot forge a section. The regression test uses the exact bullet-list-plus-prose shape that broke.Risk
Scoping the judge strictly to
<response>could in principle flip a verdict in some other test that was passing because context leaked into the judgement.Core LLM testsand the six LLM-acceptance jobs exercise the whole judge surface, so CI is the check on that rather than my assertion.🤖 Generated with Claude Code