Concepts / Extracting Model Responses

Extracting Model Responses

The generate function completes the response instruction step by producing token IDs from a formatted test input.

  • Programming

Asking the Model for an Answer

Once a pretrained model is loaded, the next question is whether it can actually answer an instruction. In this step, the model is given the formatted input for one test set entry, asked to continue it, and the continuation is read back as text. That text is then placed next to the expected answer from the test set so the two can be compared.

The method is the same generate function that was used earlier to pretrain the model, so the familiar conversions between text and token IDs appear again. Three names carry the text forward through the program: input_text, token_ids, and generated_text. This article follows one pass through that loop closely, because the most useful skill here is knowing exactly which line produced which piece of text. When a response looks wrong, the cause is almost always found by tracing where the text changed shape.

What do you think happens?

Once the program finishes, does generated_text hold only the new words the model added, or something else?

  • Only the new words the model added

  • The prompt followed by the model's new words

Reveal answer
Answer:

The prompt followed by the model's new words

generate returns token IDs for the prompt together with the newly produced tokens, and token_ids_to_text converts all of them back into one string. That is why generated_text still begins with the prompt.

How One Entry Becomes a Response

The source loop handles the first three entries of the test data. For each entry it formats the input, generates token IDs, converts those IDs back to text, and cleans up the response. Notice that the response is produced by slicing the generated text, not by the model returning a separate answer string. The model never receives the raw string directly: the formatted input is first converted into token IDs, generate produces a longer list of token IDs, and only then is that list converted back into text.

Formatted inputtextinput_textInput token IDstokenizer output, moved totarget deviceGenerated token IDsprompt IDs plus new IDsfrom generateGenerated textgenerated_text, viatoken_ids_to_textSliced stringgenerated_text[len(input_text):]response_textmarker removed, thenstrip()tokenizergeneratetoken_ids_to_textslicecleanup
In what order does a test prompt become token IDs, then model output, then a cleaned response, and what form does it take at each step?

The generate function sits between the two conversions. It takes the encoded input, along with the model and the length limits, and returns token IDs. Decoding happens afterward, so the text you finally read was built from the full token sequence rather than from the model's answer alone. Keep the sequence in mind: text, then token IDs, then generated token IDs, then text again, then the slice, then the cleanup.

Why the Output Repeats the Prompt

The generated text contains both the input and the model's continuation. This is expected behavior, not a bug. A pretrained model is built to continue text coherently, and for a text-completion model the most natural output is a continuation that reads smoothly from the prompt. Returning the prompt together with the continuation therefore produces legible text.

Prompt textinput_textPrompt token IDsstill at the startPrompt token IDsencoded inputNew token IDsappended by generateGenerated textprompt followed bycontinuationtokenizerappendeddecoded
How does the output sequence end up containing the original prompt tokens at its start, with new tokens appended after them?

Tracing One Test Entry Step by Step

Follow one test entry from start to finish, keeping a short written record of each variable. A useful habit from the source is to note the input text, the token IDs, the generated text, and the response at every line. The first record that looks unexpected tells you which line to inspect.

Worked example

Tracing a single pass through the loop

A test entry has the input text Name a color. After generate runs, generated_text holds Name a color. Blue. Trace each value to find which part is the model's response.

  1. 1
    Record input_text

    input_text is Name a color. It is 13 characters long, counting the period and not counting any trailing whitespace.

  2. 2
    Record token_ids

    The formatted input is encoded into token IDs, and generate returns a longer list containing those prompt IDs followed by the IDs for the new tokens.

  3. 3
    Record generated_text

    token_ids_to_text converts the full list back into one string, so generated_text is Name a color. Blue and still begins with the prompt.

  4. 4
    Apply the slice

    generated_text[len(input_text):] starts at position 13 and keeps everything after the prompt, which gives the string with a leading space followed by Blue.

  5. 5
    Clean up

    The cleanup step removes surrounding whitespace with strip(), leaving Blue as response_text.

Result

response_text is Blue, and it is the value printed next to the expected answer.

Input prefixName a color. (firstlen(input_text) characters)removedContinuationBlue (remaining characters)keptslice boundary at len(input_text)
If the generated sequence is the input followed by new text, which positions does the slice keep and which does it remove?

The Slice and strip() Line

The most important line in the loop is the slice generated_text[len(input_text):]. The source code assumes the generated text starts with the formatted input, which is why the code can measure the input's length and cut it off. The slice subtracts the length of the input from the start of generated_text, so it skips the first len(input_text) characters and keeps the rest.

python
Output (expected)
Blue
Notes on the code
  • input_text is the prompt that the model was given
  • generated_text is the full string, prompt included
  • The slice skips the first len(input_text) characters
  • strip() acts on the slice result, not on generated_text

The two operations happen in a fixed order. The slice runs first, and strip() acts on what the slice returned. The slice does not change the prompt inside generated_text. Slicing produces a new string, so generated_text still contains the prompt after the slice, and only response_text holds the isolated answer.

Before
raw_slice = generated_text[len(input_text):]

raw_slice is ' Blue' with a leading space

After
response_text = raw_slice.strip()

response_text is 'Blue'

The strip() function removes leading or trailing whitespace from the sliced result. It does not remove the prompt itself. That is why the slice must come first: if strip() were applied to the full generated text, the prompt would still be there, and the length-based cut would no longer line up with the original input.

The source also removes the string "### Response:" during cleanup. That marker is part of the prompt format rather than the answer, so it is removed along with the surrounding whitespace before the value is printed.

Locating Where a Comparison Diverges

Response generation is compared against the expected test set answers, so a mismatch points to the stage where the text changed unexpectedly. The fastest diagnosis is to check each value in the pipeline in order, and the first stage whose value differs from what you expected is where the result diverged.

  1. 1
    Check input_text first and confirm it matches the formatted test entry.
  2. 2
    Check token_ids and confirm the prompt IDs appear at the start of the list.
  3. 3
    Check generated_text and confirm it begins with input_text.
  4. 4
    Check the sliced string and confirm it begins where the prompt ends.
  5. 5
    Check response_text and compare it with the expected answer.
  • Common mistake

    Expecting generated_text to contain only the new words

    What it looks like

    Printing generated_text and seeing Name a color. Blue instead of Blue

    Why it fails

    generate returns the prompt tokens together with the new tokens, and the decoded string keeps the prompt at its start

    Do this instead

    Apply the slice to remove the prompt before treating the result as the response

  • Common mistake

    Calling strip() before the slice

    What it looks like
    generated_text.strip()[len(input_text):]
    Why it fails

    Stripping changes the length of the string, so the cut no longer matches the length of the prompt that was measured

    Do this instead

    Slice first, then call strip() on the sliced result

  • Common mistake

    Treating a response that still contains the question as a model failure

    What it looks like

    response_text still begins with the question text

    Why it fails

    The slice was skipped or applied to the wrong string, so the prompt was never removed

    Do this instead

    Check that the slice is applied to generated_text and that input_text is the same string that was encoded

  • Common mistake

    Assuming a missing first character means the model made an error

    What it looks like

    response_text begins one character too late

    Why it fails

    The length measured from the prompt does not match the prompt that was actually used, so the cut lands one position off

    Do this instead

    Compare len(input_text) with the prompt that was actually encoded and fix the mismatch

Practicing the Trace

MEDIUM

Suppose input_text is Hello world and generated_text is Hello world! Hi there. Without running anything, write what the slice generated_text[len(input_text):] keeps, then write the final response_text after strip().

Hints
  • Count the characters in Hello world first, then skip exactly that many characters from generated_text.
  • The slice keeps everything after the prompt, including the leading space before the continuation.
  • strip() removes only leading and trailing whitespace, not characters in the middle of the response.

What do you think happens?

After the slice and strip() run in that practice, does generated_text still contain Hello world?

  • Yes, slicing produces a new string and leaves generated_text unchanged

  • No, the slice removes Hello world from generated_text

Reveal answer
Answer:

Yes, slicing produces a new string and leaves generated_text unchanged

Only response_text holds the isolated answer. The prompt remains inside generated_text.

Key Takeaways

  • The generate function takes encoded input and returns token IDs for the prompt together with the newly produced tokens, and token_ids_to_text turns them back into generated_text.
  • generated_text contains the prompt at its start because the model continues text, so the response must be isolated afterward.
  • The slice generated_text[len(input_text):] removes the input prefix, and strip() removes surrounding whitespace from that sliced result, in that order.
  • When a response looks wrong, check input_text, token_ids, generated_text, the sliced string, and response_text in order to find where the text diverged.

Related concepts

More concepts starting with E