Klepa AI
Установить
Часть 1 · Что внутри модели · Урок 1

Как модель пишет код: токены, внимание, окно контекста и галлюцинации

Почему модель иногда пишет отличный код, а иногда уверенно выдумывает функцию, которой нет. Разберём механику без формул, а потом — с ними.

~45 минут 4 интерактива 2 песочницы с проверками Примеры: JS · TS · PHP · Python

После урока вы сможете

  • объяснить, что LLM делает «под капотом», одной фразой;
  • посчитать токены и прикинуть, сколько стоит задача агента;
  • понять, почему модель «забывает» и почему ответы каждый раз разные;
  • назвать причины галлюцинаций и три способа их снизить.
1 Аналогия

Автодополнение, которое прочитало весь интернет

Представьте

Клавиатура в телефоне подсказывает следующее слово. LLM делает то же самое. Только она прочитала миллиарды страниц и подсказывает очень хорошо.

Сервер вернул
ошибку62%ответ21%5009%
Подсказка слова= предсказание следующего токена
Сколько текста видит клавиатура= окно контекста
Уверенно подставила не то слово= галлюцинация
Каждый раз немного разные подсказки= случайный выбор (temperature)

Запомните главное: модель не «ищет ответ». Она каждый раз выбирает продолжение, которое звучит правдоподобно. Правдоподобно и правильно — не одно и то же. Весь курс — про то, как построить вокруг модели среду, которая превращает правдоподобное в проверенное.

2 Объяснение

От простого к сложному

Уровень 1 · Суть

Модель — это функция «текст → следующий кусочек»

Для бэкендера проще всего думать так. LLM (большая языковая модель) — это чистая функция. На вход — текст. На выход — вероятности для следующего кусочка текста. Такой кусочек называют токеном.

Чтобы написать длинный ответ, программа вокруг модели крутит цикл: спросила модель, выбрала токен, дописала его в конец, спросила снова.

let text = "Код 404 означает, что";
while (!text.endsWith(".")) {
  const probs = model(text);      // { " ресурс": 0.61, " страница": 0.27, ... }
  const token = pick(probs);      // выбрать один токен
  text += token;                  // дописать и повторить
}
Что важноМодель не хранит состояние между запросами. Она не «помнит» прошлый разговор. Каждый раз ей заново отправляют весь нужный текст.
Уровень 2 · Детали

Токены, окно контекста и внимание

Токены. Модель режет текст не на буквы и не на слова, а на частые кусочки. Частое английское слово — обычно один токен. Русское слово часто режется на два-три. Поэтому одинаковый по смыслу текст на русском обычно стоит дороже. За токены платят: и за входные, и за выходные.

Окно контекста. Это весь текст, который модель видит за один вызов: системные правила, история диалога, файлы, результаты инструментов и сам ответ. У окна есть предел в токенах. Всё, что не влезло, для модели не существует.

Внимание (attention). Каждый токен «оглядывается» на остальные и решает, кто ему важен. В фразе «сервер вернул ошибку, потому что он перегружен» слово «он» больше всего смотрит на «сервер». Это и даёт модели понимание связей в тексте. Попробуете ниже в интерактиве.

Как выглядит запрос к модели в коде — JS, TS, PHP, Python

Обратите внимание: в messages мы сами передаём всю историю. Модель её не хранит. Ключ берётся из переменной окружения, в код его не пишем.

// Node 18+: fetch встроен
const history = [
  { role: "user", content: "Меня зовут Аня. Что значит HTTP 404?" },
  { role: "assistant", content: "Ресурс не найден на сервере." },
  { role: "user", content: "А как меня зовут?" }, // модель узнает имя только из истории выше
];

const res = await fetch("https://api.anthropic.com/v1/messages", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "x-api-key": process.env.ANTHROPIC_API_KEY,
    "anthropic-version": "2023-06-01",
  },
  body: JSON.stringify({ model: "claude-opus-5-5", max_tokens: 1024, messages: history }),
});
const data = await res.json();
console.log(data.content[0].text);           // ответ
console.log(data.usage);                     // { input_tokens, output_tokens } — за это платим
type Role = "user" | "assistant";
interface Message { role: Role; content: string }

async function ask(history: Message[]): Promise<string> {
  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-api-key": process.env.ANTHROPIC_API_KEY ?? "",
      "anthropic-version": "2023-06-01",
    },
    body: JSON.stringify({ model: "claude-opus-5-5", max_tokens: 1024, messages: history }),
  });
  if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
  const data = await res.json();
  return data.content.find((b: { type: string }) => b.type === "text")?.text ?? "";
}

const history: Message[] = [{ role: "user", content: "Что значит HTTP 404?" }];
history.push({ role: "assistant", content: await ask(history) });
history.push({ role: "user", content: "А 410?" }); // каждый вызов — вся история заново
console.log(await ask(history));
<?php
// Чистый PHP + curl. В Symfony то же самое делает HttpClient.
$history = [
    ['role' => 'user', 'content' => 'Меня зовут Аня. Что значит HTTP 404?'],
    ['role' => 'assistant', 'content' => 'Ресурс не найден на сервере.'],
    ['role' => 'user', 'content' => 'А как меня зовут?'],
];

$ch = curl_init('https://api.anthropic.com/v1/messages');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'content-type: application/json',
        'x-api-key: ' . getenv('ANTHROPIC_API_KEY'),
        'anthropic-version: 2023-06-01',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'model' => 'claude-opus-5-5',
        'max_tokens' => 1024,
        'messages' => $history,
    ], JSON_UNESCAPED_UNICODE),
]);
$data = json_decode(curl_exec($ch), true, flags: JSON_THROW_ON_ERROR);
echo $data['content'][0]['text'], PHP_EOL;
echo 'Токенов на входе: ', $data['usage']['input_tokens'], PHP_EOL;
import os
import requests

history = [
    {"role": "user", "content": "Меня зовут Аня. Что значит HTTP 404?"},
    {"role": "assistant", "content": "Ресурс не найден на сервере."},
    {"role": "user", "content": "А как меня зовут?"},
]

res = requests.post(
    "https://api.anthropic.com/v1/messages",
    headers={
        "content-type": "application/json",
        "x-api-key": os.environ["ANTHROPIC_API_KEY"],
        "anthropic-version": "2023-06-01",
    },
    json={"model": "claude-opus-5-5", "max_tokens": 1024, "messages": history},
    timeout=60,
)
res.raise_for_status()
data = res.json()
print(data["content"][0]["text"])
print("Токенов на входе:", data["usage"]["input_tokens"])
Уровень 3 · Усложнение

Откуда берутся галлюцинации и нестабильность

Галлюцинация — уверенный, но неверный ответ. Причина в самой задаче модели: её учили продолжать текст правдоподобно, а не говорить правду. Если модель не знает API вашей библиотеки, правдоподобное продолжение — выдумать похожую функцию. Ответ «не знаю» в обучающих текстах встречается редко, поэтому модель его почти не выбирает.

Потеря контекста. Если важный факт выпал из окна, модели неоткуда его взять. Даже внутри окна факт в середине длинного текста модель замечает хуже, чем в начале или в конце. Это называют эффектом «потерянной середины» (lost in the middle).

Нестабильность. Следующий токен выбирается случайно, с учётом вероятностей. Поэтому один и тот же запрос даёт разные ответы. Степень случайности задаёт temperature. В новых моделях Claude этот параметр убран из API: стабильность достигают чётким форматом ответа, структурированным выводом и проверками.

Мост к следующим урокамНи одну из этих проблем не решить «правильной фразой» в запросе. Их решают инженерно: дают модели нужный контекст (уроки 2 и 3), инструменты для проверки (урок 4), автоматический контроль (урок 7) и тесты-эталоны (урок 8). Всё это вместе и называется harness — «упряжь» для модели.
Формула внимания и softmax без страха — для любопытных

Каждый токен превращается в вектор — список чисел. Из него получают три вектора:

  • Query (запрос) — «что я ищу»;
  • Key (ключ) — «что я могу предложить»;
  • Value (значение) — «что я отдам, если меня выберут».

Похоже на поиск в хеш-таблице, только нечёткий. Токен сравнивает свой Query со всеми Key (скалярное произведение), получает оценки, превращает их в веса через softmax и берёт взвешенную сумму Value.

Attention(Q, K, V) = softmax(Q · Kᵀ / √d) · V

softmax(x)ᵢ = exp(xᵢ / T) / Σ exp(xⱼ / T)     ← T — та самая temperature

Отсюда два практических вывода:

  • Каждый токен смотрит на каждый. Значит, стоимость вычислений растёт примерно как квадрат длины текста. Длинный контекст — медленно и дорого.
  • Softmax никогда не даёт ровно ноль. У любого, даже бредового продолжения есть шанс. Чем выше temperature, тем этот шанс больше.

Функцию softmax вы сами напишете в песочнице ниже.

Почему агент дорогой: история растёт на каждом шаге — важно для бюджета

Агент работает циклом: модель просит инструмент, программа его выполняет, результат дописывается в историю, и вся история снова уходит модели. Шаг 1 отправляет 5 тысяч токенов, шаг 10 — уже 25 тысяч, шаг 20 — 45 тысяч. Сумма растёт почти как квадрат числа шагов.

Помогают три вещи. Кэш промпта: одинаковое начало запроса (правила, описание инструментов) провайдер берёт из кэша в разы дешевле. Сжатие истории: старые шаги заменяют короткой сводкой. Субагенты: тяжёлое чтение уходит в отдельный контекст, а в основной возвращается только вывод (урок 2).

3 Интерактив

Потрогайте механику руками

Модель здесь игрушечная: вероятности заданы вручную. Но цикл, выборка и температура работают ровно как у настоящей.

Генерация по одному токену

интерактив 1 из 4

Кандидаты на следующий токен и их вероятности:

Посмотрите, насколько разные ответы при одной и той же температуре.

    Как текст режется на токены

    интерактив 2 из 4

    Напишите что угодно: русский текст, английский, код. Разбиение учебное и приблизительное. Точный счёт для своей модели проверьте в настоящем токенайзере (ссылки в конце урока).

    0символов
    0токенов (примерно)
    0символов на токен
    Почему русский текст «дороже» — коротко

    Словарь токенайзера собирают по частоте кусочков в обучающих текстах. Английского там больше, поэтому английские слова чаще попадают в словарь целиком. Русские слова чаще собираются из двух-трёх кусочков. На практике один и тот же смысл по-русски занимает заметно больше токенов. Точное соотношение зависит от модели.

    Внимание: на кого смотрит слово

    интерактив 3 из 4

    Нажмите на выделенное слово. Синяя полоска под другими словами показывает, насколько сильно модель «смотрит» на них, чтобы понять выбранное. Веса учебные, но картина типичная.

    Выберите «он», «ошибку» или «перегружен».

    Окно контекста: что модель видит прямо сейчас

    интерактив 4 из 4

    Окно здесь — 4 000 токенов. В первом сообщении пользователь назвал имя. Добавляйте реплики и файлы, а потом спросите имя. Затем включите сжатие и повторите.

    Занято: 0 из 4 000
      4 Практика

      Песочницы с автопроверкой

      Код выполняется прямо в браузере, без сервера. Нажмите «Запустить» или Ctrl+Enter. Ниже вывода появятся проверки, как в юнит-тестах. Ваш код сохраняется в браузере.

      Песочница 1. Напишите softmax с температурой

      JS

      Модель выдаёт «сырые оценки» (logits) для каждого кандидата. Превратите их в вероятности. Это та самая функция, которая крутилась в генераторе выше.

      Песочница 2. Сколько стоит задача агента

      JS

      Агент делает steps шагов. На каждом шаге модели заново уходит вся история: системные правила, задача и все прошлые шаги. Посчитайте входные токены и цену в долларах. Цены — за миллион токенов.

      Что этот расчёт говорит про harness — откройте после решения

      Вдвое больше шагов — почти вчетверо больше входных токенов. Поэтому хороший harness следит за длиной истории: сжимает старое, кэширует неизменное начало и выносит тяжёлое чтение в субагентов. Об этом — в уроках 2 и 7.

      Проверьте себя

      Что на самом деле делает LLM, когда «отвечает» на вопрос?

      Модель — функция «текст → вероятности следующего токена». Длинный ответ получается циклом. Ни поиска, ни запуска кода внутри модели нет: это добавляет harness через инструменты.

      Вы задали модели два вопроса подряд. Откуда во втором запросе она знает про первый?

      Модель не хранит состояние. Историю каждый раз шлёт клиент. Поэтому длинный диалог дорожает с каждым сообщением.

      Агент уверенно вызвал в коде метод, которого нет в вашей библиотеке. Что поможет надёжнее всего?

      Просьба снижает частоту, но не гарантирует результат. Проверяемость даёт среда: инструмент поиска даёт факты, тесты ловят ошибку автоматически.

      Что случится с фактом, который выпал за пределы окна контекста?

      Окно — всё, что модель видит. Чтобы факты не терялись, harness сжимает историю в сводку или хранит их во внешней памяти и подставляет по запросу (урок 3).

      Задание на своём проекте — 15 минут, без кода
      1. Откройте любой чат с LLM. Спросите, что делает функция из вашего рабочего проекта. Код не показывайте. Запишите ответ.
      2. Теперь вставьте код этой функции и задайте тот же вопрос. Сравните. Сколько в первом ответе было выдумано?
      3. Вставьте самый большой файл проекта в токенайзер. Сколько в нём токенов? Сколько будет стоить прочитать его на каждом из 20 шагов агента? Посчитайте функцией из песочницы 2.
      5 Чек-лист

      Отметьте, что усвоили

      • Могу одной фразой объяснить, что делает LLM: предсказывает следующий токен.
      • Знаю, что такое токен и почему русский текст обычно дороже английского.
      • Понимаю: модель не помнит прошлые запросы, историю каждый раз отправляет клиент.
      • Могу объяснить, что такое окно контекста и что бывает при переполнении.
      • Понимаю, почему «правдоподобно» не значит «правильно» и откуда галлюцинации.
      • Решил обе песочницы: softmax и стоимость агента.
      • Могу назвать три способа снизить галлюцинации: дать контекст, дать инструмент проверки, проверить результат тестами.
      Урок пройденОтлично. Теперь вы знаете, с каким «двигателем» работаете. Дальше соберём вокруг него машину.