Scoring Model Responses
generate_model_scores builds a prompt for each entry, asks a model for a 0 to 100 score, converts the reply to an integer, and returns the collected list.
Why Models Need Scoring
After a model has been fine-tuned, someone has to judge how good its answers are. One practical approach is to ask another model to grade each answer on a numeric scale. The function studied here does exactly that. It takes a dataset of entries, asks a model to score each stored response, and returns the list of scores it collected. The function is called generate_model_scores, and it is the core of the evaluation step in this chapter.
What do you think happens?
A grader model is asked to reply with a number, but for one entry it replies with the word eighty instead. Given that the loop must keep going, what do you think gets printed and what ends up in the final list?
Reveal answer
A message is printed, eighty is left out, and the remaining entries are still scored.
The conversion to an integer fails for eighty. The except branch prints Could not convert score followed by the reply, then continue moves the loop on. The value never reaches the list, and the run is not stopped.
Building the Prompt for Each Entry
The function is written so that you can read it top to bottom. It starts with an empty list, loops over the entries, builds a prompt for each entry, asks the model for a score, converts that score to an integer, and stores it. Every entry produces its own prompt.
The prompt contains three pieces of information: the formatted input of the entry, the correct output stored with that entry, and the model response that the entry holds under the key passed in as json_key. The prompt then asks for a score on a scale from 0 to 100, where 100 is the best score, and it demands an integer only. Asking for an integer only is what makes the next step, the conversion, possible at all.
Because the response being graded is read from json_key, the same function can score any response field in the dataset. The source calls it with the key model_response, and the model argument defaults to llama3 when it is not given. The helpers format_input and query_model are called here but are not shown in the source, so treat them as black boxes: format_input returns a formatted string, and query_model returns a model reply.
The Conversion Step
The conversion is the fragile step. Inside the loop, the function tries scores.append(int(score)). If the reply can be converted, the integer is stored. If it cannot, Python raises a ValueError, and the except ValueError branch runs instead. That branch prints the message Could not convert score, followed by the offending reply, and then continue moves the loop to the next entry. The function does not stop, and it does not store anything for the bad entry.
Trace the two paths with the reply eighty. The reply is the string eighty, not the integer 85. Calling int on that string fails, so the success path is closed. The failure path prints the message and continues. Now trace the reply 85. It is the string 85, which int converts to the integer 85, so it is appended and the loop moves on. The same check runs for every reply, so each entry ends at exactly one of the two paths.
Skipping Bad Scores Without Stopping
The try and except pattern lets one unusable reply be reported and skipped while the rest of the test set is still scored. Here is a small sketch of that pattern. It loops over three replies, where the middle reply is the word eighty. The replies are invented for this teaching example, because the source does not list the real replies of its test set.
Could not convert score: eighty
[85, 42]- The try block holds only the risky conversion and the store.
int("eighty")raisesValueError, so control jumps to the except branch.- continue moves the loop to the next reply instead of ending the run.
The verified run of this error path printed exactly two lines. The first line is the message from the except branch for the reply eighty. The second line is the list of integers the function collected, [85, 42]. The value eighty never reaches the list, and the entries that converted cleanly are kept in their original order.
Following One Entry From Prompt to List
It helps to follow a single entry through the whole function. The entry is formatted into an input string and combined with its correct output and its stored model response to build the prompt. The prompt goes to the model, and the model's reply comes back as text. The reply is converted to an integer. If the conversion works, the integer is added to the list that the function returns at the end.
Reading the Run Report
The source applies the function to the entire test_data set in a single call. It reports that this run takes about one minute on an M3 MacBook Air, so the time cost is real and should be expected when you evaluate a model. The reporting code that follows the call prints two things. The first line compares the number of scores produced with the number of entries in test_data. If the two numbers differ, some entries were not turned into scores. The second line prints the average of the scores.
Here $s_i$ is each stored score and $n$ is the number of stored scores. The denominator is the length of the returned list, not the number of entries in test_data. For the illustration above, the stored scores are 85 and 42. Their sum is 127 and their count is 2, so the average is $\frac{127}{2} = 63.5$. The average is computed over the usable scores only, so it describes the entries the model answered in a convertible way, not the whole test set.
Mistakes Beginners Make With Skipped Scores
Common mistake Assuming an unusable reply stops the whole run
What it looks likeThe reply eighty is reported and the loop still scores the entry that follows it.
Why it failsThe except branch ends with continue, which moves the loop on to the next entry instead of ending the function.
Do this insteadRead the except branch as a skip. The run keeps going and only the failed entry is left out of the list.
Common mistake Dividing the sum by the number of entries instead of the number of scores
What it looks likeWith three entries and two stored scores, dividing 127 by 3 gives the wrong average.
Why it failsA skipped entry adds nothing to the sum, so counting it in the denominator pulls the average down for no reason.
Do this insteadDivide by len of the returned list, which is the number of stored scores.
Common mistake Putting the whole function body inside the try block
What it looks likeA try block that wraps the prompt building and the model call makes every error look like a conversion failure.
Why it failsThe except
ValueErrorbranch is meant for the conversion step. Other errors would be hidden or misreported.Do this insteadKeep the try block small and focused on the step that can fail, here the int conversion and the append.
Common mistake Ignoring the printed count when it differs from the number of entries
Practice: Trace a Run
A test set has four entries. The model replies 90, then seventy, then 60, then 100. Predict the printed error line, the final list, and the average. Explain which step handled the bad reply and where the loop went next.
Hints
- Look at each reply in order and decide whether int can convert it.
- Only the converted integers go into the list, so the average uses only those values.
- The printed count of 3 would differ from the 4 entries, which signals one skip.
Key Takeaways
generate_model_scoresbuilds a prompt for each entry from its formatted input, its correct output, and its stored response, then asks the model for an integer from 0 to 100.- The conversion
scores.append(int(score))either stores an integer or raisesValueError, which the except branch reports and skips with continue. - The try and except pattern lets one unusable reply be reported while the rest of the test set is still scored.
- The average is the sum of the stored scores divided by how many scores were stored, so a lower printed count means the average covers fewer entries.