Unit 03.01: Reading a response object, not just its text
Reading only .text throws away the four things you need when something goes wrong.
finish_reason, usage, model
A response object with all its fields, and what each finish reason means.
The code prints both.
response = {
"text": "Refunds are allowed within 7 days.",
"finish_reason": "stop",
"usage": {"input_tokens": 96, "output_tokens": 12},
"model": "some-model-2026-06",
}
print(f"text : {response['text']}")
print(f"finish_reason : {response['finish_reason']}")
print(f"tokens : {response['usage']}")
print(f"model : {response['model']}")
for reason, meaning in [("stop", "the model finished"),
("length", "TRUNCATED -- hit the token limit"),
("content_filter", "blocked; you were still charged"),
("tool_calls", "it wants to call a tool, not answer")]:
print(f" {reason:16} {meaning}")
# Reading only `.text` throws away the four things you need: whether it
# finished, what it cost, which model version produced it, and whether it was
# trying to do something other than answer.
finish_reason: length means the answer was truncated - it is well-formed prose that stops mid-thought, and if you were parsing JSON the parse fails somewhere that looks unrelated.
content_filter means you were charged and got nothing usable. usage is how you compute cost per successful answer, and model is how you explain a run after the provider updates.
The mistake this prevents
The mistake is wrapping the call in a helper that returns a string. It reads well and discards the truncation flag, the cost and the model version - which is everything you need during an incident.
Takeaway
Read the whole response object. finish_reason catches truncation and filtering, usage gives you cost per success, and model is what lets you explain an old run.
