§7.7–7.8Extracting Responses & LLM-as-a-Judge Evaluation

Part III pp. 124–127 · ~5 min read

  • LLM-as-a-judge

The fine-tuned model generates responses — but are they any good? Unlike ch6’s accuracy, free-form text has no single right answer to compare against. This unit extracts the model’s test-set responses and scores them the modern way: with another LLM as the judge.

7.7 Extracting and saving responses

First, the landscape. Instruction-tuned LLMs (chatbots) are evaluated via several approaches:

ApproachHow it worksExample
Short-answer / multiple-choice benchmarksTest general knowledge with questions that HAVE checkable answersMMLU+
Human preference comparisonHumans vote between two models' responses to the same promptLMSYS Chatbot Arena+
Automated conversational benchmarksAnother LLM scores each response against a referenceAlpacaEval — the book's choice+
Three ways to evaluate an instruction-following model — the book uses the third.

Before judging, collect the evidence — generate a response for every test entry and file it next to the reference answer:

</> Generating and storing test-set responses
from tqdm import tqdm

for i, entry in tqdm(enumerate(test_data), total=len(test_data)):

  input_text = format_input(entry)  #A

  token_ids = generate(  #B
      model=model,
      idx=text_to_token_ids(input_text, tokenizer).to(device),
      max_new_tokens=256,
      context_size=BASE_CONFIG["context_length"],
      eos_id=50256
  )
  generated_text = token_ids_to_text(token_ids, tokenizer).replace(
      "<|endoftext|>", "")  # remove end-of-text token from output
  response_text = generated_text[len(input_text):].replace(
      "### Response:", "").strip()  #C

  test_data[i]["model_response"] = response_text  #D

with open("instruction-data-with-response.json", "w") as file:
  json.dump(test_data, file, indent=4)
  • #A The PROMPT half only (no response section) — inference completes what training practiced.
  • #B ch5's generate() with eos_id=50256: generation stops at the end-of-text token the collate function deliberately left unmasked. The training curriculum and the stopping rule are the same signal.
  • #C generate() returns prompt + continuation; slicing at len(input_text) and stripping the "### Response:" tag isolates the model's actual answer.
  • #D Each test entry now carries instruction, input, reference output AND model_response — everything a judge needs, saved to JSON.

7.8 Evaluating with an LLM judge

LLM-as-a-judge : for each entry, a prompt presents the judge model with the task, the reference answer, and the model’s answer — and asks for a score:

input: “Rewrite the sentenceusing a simile.”reference: “The car is as fastas lightning.”model response: “The car is asfast as a bullet.”judge prompt”…score the model response on ascale from 0 to 100…“judge LLM8B Llama(via Ollama)85/ 100repeat over the whole test set → the AVERAGE score quantifies the fine-tuned model with one number
One judgment: the bullet simile scores 85 against the lightning reference — both are valid similes, and the judge (unlike exact matching) can see that. Averaging over the test set yields the model’s overall score.
</> Scoring responses with the judge
for entry in test_data[:3]:
  prompt = (
      f"Given the input `{format_input(entry)}` "
      f"and correct output `{entry['output']}`, "
      f"score the model response `{entry['model_response']}`"
      f" on a scale from 0 to 100, where 100 is the best score. "
  )
  print("\nDataset response:")
  print(">>", entry['output'])
  print("\nModel response:")
  print(">>", entry["model_response"])
  print("\nScore:")
  print(">>", query_model(prompt))  #A

# Dataset response:
# >> The car is as fast as lightning.
#
# Model response:
# >> The car is as fast as a bullet.
#
# Score:
# >> I'd rate the model response "The car is as fast as a bullet."
#    an 85 out of 100.  #B
  • #A query_model sends the prompt to the judge (the locally-running Llama via Ollama, per the chapter summary) and returns its reply.
  • #B A sensible verdict — but wrapped in prose. For automated averaging, the reply must be a bare number…
</> The parseability tweak — ask for the integer only
prompt = (
  f"Given the input `{format_input(entry)}` "
  f"and correct output `{entry['output']}`, "
  f"score the model response `{entry[json_key]}`"
  f" on a scale from 0 to 100, where 100 is the best score. "
  f"Respond with the integer number only."  #A
)
  • #A One added sentence turns chatty verdicts into machine-readable scores — collect them across the test set and report the average. Constraining output format is half the craft of using LLM judges.

Key idea — evaluation is a generation task too

When outputs are free-form, grading becomes a language task — so a language model does it. The judge sees what string matching can’t: that two different similes are both correct. The result is a scalable, repeatable score for an otherwise fuzzy question, closing the loop the chapter opened: data → batches → fine-tuning → measured instruction-following. The finale wraps up — and points at what lies beyond.

📝 Check yourself: evaluating the instruction model

0 / 5
  1. 1.Why is evaluating an instruction-tuned model fundamentally harder than evaluating ch6's classifier?

  2. 2.In the extraction loop, why is generate() called with eos_id=50256?

  3. 3.After generation, why does the code slice generated_text[len(input_text):]?

  4. 4.How does the LLM-as-a-judge method score each test response?

  5. 5.Why append "Respond with the integer number only." to the judge prompt?