Структура ответа и учёт токенов

Разбираем ответ модели по косточкам: где текст, почему она остановилась и сколько это стоило.

usage — блок ответа со счётчиками токенов входа и выхода; по нему считают стоимость запроса.

Из чего состоит ответ Anthropic

ПолеЧто значит
contentсписок блоков ответа (текст, вызовы инструментов и т.д.)
stop_reasonпочему модель остановилась
usageсчётчики токенов: input_tokens, output_tokens
modelкакая модель ответила

stop_reason — почему остановилась

end_turnмодель закончила ответ сама — нормальный случай
max_tokensупёрлась в лимит длины — ответ обрезан
stop_sequenceвстретила вашу стоп-строку
tool_useхочет вызвать инструмент (см. раздел 4)

Обрабатывайте stop_reason в коде: если это max_tokens, ответ неполный — стоит увеличить лимит или дозапросить продолжение; если tool_use — надо выполнить инструмент и вернуть результат.

Разбор готового JSON-ответа (запускаемо)

Возьмём типичный ответ Anthropic как строку и извлечём из него всё нужное — чистый Python:

import json

raw = '''
{
  "id": "msg_01ABC",
  "role": "assistant",
  "model": "claude-opus-4-8",
  "content": [
    {"type": "text", "text": "Вот три факта о Луне:"},
    {"type": "text", "text": " она спутник Земли."}
  ],
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 25, "output_tokens": 18}
}
'''

resp = json.loads(raw)

# 1. Собираем текст из всех текстовых блоков
text = "".join(b["text"] for b in resp["content"] if b["type"] == "text")
print("Текст:", text)

# 2. Проверяем причину остановки
if resp["stop_reason"] == "max_tokens":
    print("ВНИМАНИЕ: ответ обрезан по лимиту")
else:
    print("Остановка:", resp["stop_reason"])

# 3. Считаем стоимость по usage (пример цен)
u = resp["usage"]
cost = u["input_tokens"] / 1_000_000 * 5.0 + u["output_tokens"] / 1_000_000 * 25.0
print(f"Токены вход/выход: {u['input_tokens']}/{u['output_tokens']}")
print(f"Стоимость запроса: ${cost:.6f}")

Вывод:

Текст: Вот три факта о Луне: она спутник Земли.
Остановка: end_turn
Токены вход/выход: 25/18
Стоимость запроса: $0.000575

А у OpenAI?

Структура другая, смысл тот же: текст в choices[0].message.content, причина в finish_reason, токены в usage (prompt_tokens, completion_tokens, total_tokens).

{
  "choices": [
    {"message": {"role": "assistant", "content": "4"}, "finish_reason": "stop"}
  ],
  "usage": {"prompt_tokens": 10, "completion_tokens": 1, "total_tokens": 11}
}

Итог

  • Текст извлекают из content-блоков (Claude) или choices[].message.content (OpenAI).
  • stop_reason/finish_reason говорит, почему модель остановилась — это надо обрабатывать.
  • usage даёт точные токены для подсчёта стоимости и логов.
Проверьте себя
1. Что означает stop_reason равный max_tokens?
AМодель закончила ответ естественно
BОтвет обрезан по лимиту длины и, скорее всего, неполный
CМодель хочет вызвать инструмент
DПроизошла ошибка авторизации
2. Откуда брать точное число токенов запроса для подсчёта стоимости?
AИз заголовка retry-after
BИз блока usage в ответе (input_tokens/output_tokens)
CИз поля model
DЭто число недоступно
3. Как называется поле с причиной остановки у OpenAI?
Astop_reason
Bfinish_reason
Cend_turn
Dcompletion_status
Поддержать проект