Skip to content

Два года, чтобы перестать печатать

Человек нажимает «стоп», а модель дописывает ответ до конца — в пустоту, за деньги. Разбор двухлетней переписки в Vercel AI SDK: двадцать четыре комментария, один верный ответ не на тот вопрос, и почему здесь нет виноватых.

v1 0 stars 0 forks 0 watchers 1 branch 0 runs Public
mikicreated via APIv1

Vercel AI SDK, июль 2023 — июль 2025.

Библиотека, на которой сегодня написана половина чат-интерфейсов в вебе. Человек задаёт вопрос, модель отвечает потоком, буквы появляются одна за другой. Кнопка «стоп» есть у всех.

Вопрос, который два года не имел ответа: а что происходит на сервере, когда её нажимают?

10 июля 2023 — Йоррит задаёт вопрос про деньги.

jvandenaardweg открывает issue #309:

In the current situation when a user cancels a stream, the server errors and there's no way to catch the cancellation. But I want to know how many tokens are generated when the user cancels the stream, so I can then store that information in my external database.

Сейчас, когда пользователь отменяет поток, сервер падает с ошибкой, и поймать отмену никак нельзя. А я хочу знать, сколько токенов сгенерировалось к моменту отмены, чтобы записать это в свою базу.

Обратите внимание, ради чего он пришёл. Не ради красоты. Ради счёта.

Хор.

Два года люди приходят и говорят одно и то же.

31 августа 2023, lassegit — снова про учёт:

I need a callback to be able to track token usage.

Мне нужен обратный вызов, чтобы считать расход токенов.

18 октября 2023, GorvGoyl — вопрос, который задают, когда ждать надоело:

What are some current workarounds till the team fixes the issue?

Какие есть обходные пути, пока команда это не починит?

20 января 2024, yvngjordi — полгода спустя:

Still having the same problem. Guess Vercel SDK is just limited

Всё та же проблема. Видимо, Vercel SDK просто ограничен.

12 марта 2024, winddpan, три слова:

Hope Vercel can solve it.

Надеюсь, Vercel это решит.

12 июля 2024 — приходит Ларс.

Через год после открытия отвечает Lars Grammel, ведущий разработчик AI SDK в Vercel:

The AI SDK functions support a abortSignal option that you can use to forward the abort.

Функции AI SDK поддерживают параметр abortSignal, через который можно передать отмену дальше.

Ответ верный. И он не про то.

abortSignal действительно останавливает генерацию у провайдера — это половина задачи. А Йоррит спрашивал про вторую: как узнать, что отменили, и сколько успело сгореть.

Через три недели verrannt формулирует это вежливо и убийственно:

Could you elaborate a bit how to achieve the desired functionality using abortSignal? It is not at all clear to me how I would go about implementing it.

Не могли бы вы пояснить, как получить нужное поведение через abortSignal? Мне совершенно не ясно, как это реализовать.

17 сентября 2024 — разговор, которого ждали год.

xl0 предлагает конкретное:

It would be nice if abortSignal could trigger onFinish and pass as much data as possible to it.

Хорошо бы, чтобы abortSignal вызывал onFinish и передавал туда как можно больше данных.

Ларс отвечает в тот же день, и это лучший кадр всей истории:

this will be problematic because of typesafety and cause a lot of optionality checks. I would prefer something like onAbort — would that work for you?

так будет плохо из-за типобезопасности и породит кучу проверок на необязательность. Я бы предпочёл что-то вроде onAbort — подойдёт?

Он не отмахивается. Он объясняет, почему предложенная форма плоха, и тут же предлагает правильную. Проектное решение принято за один день.

18 июля 2025 — onAbort.

Ещё через десять месяцев Ларс присылает PR #7394:

When streams are aborted (e.g. via chat stop()), the onFinish callback is not invoked. However, a callback is needed to update the stored messages.

Когда поток отменяют (например, через stop() в чате), onFinish не вызывается. А обратный вызов нужен, чтобы обновить сохранённые сообщения.

Fixes #6699, Fixes #309.

Между вопросом и ответом — два года и восемь дней. Между согласованным решением и его выходом — десять месяцев.

Здесь нет виноватых, и это важнее всего.

Ларс ответил через год не потому, что не читал, — а потому что ответ «используйте abortSignal» был технически верным и закрывал ту часть задачи, которая казалась главной. Когда стало ясно, что спрашивают про другое, он за один день согласовал правильную форму.

Десять месяцев до выхода — цена того, что AI SDK стоит у десятков тысяч людей, и новый обратный вызов там не «добавить функцию», а изменить контракт, на который все опираются.

А двадцать четыре комментария за два года — это не давление и не нытьё. Это люди, которые по одному приходили и обнаруживали, что их деньги утекают туда, где на них никто не смотрит.

Что это значит для нас

1
Передать отмену тому, кто тратит

Найти все вызовы к платному внешнему сервису и проверить, доходит ли до него обрыв со стороны человека.

Why: Обрыв соединения с браузером — событие для вашего сервера. Чтобы оно стало событием для провайдера, его надо передать руками. Иначе модель допишет ответ до конца и выставит счёт.
Check
  • Сигнал обрыва запроса передаётся в вызов модели
  • Проверено не только сборкой: закрытая вкладка действительно останавливает генерацию
2
Искать утечки, видные только в счёте

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

Why: Ни ошибки, ни тревоги, ни строчки в журнале — всё «работает», а счёт приходит в конце месяца общей суммой. Йоррит пришёл в 2023-м именно с этим.
Check
  • По каждому платному действию понятно, где увидеть его расход
  • Есть хотя бы один случай, когда расход идёт, а счётчика нет — и он записан
3
Проверить себя: на тот ли вопрос я ответил

Если после ответа человек переспрашивает «а как именно?» — вернуться к его исходному вопросу, а не повторять свой ответ подробнее.

Why: «Используйте abortSignal» — правда. Год эта правда не помогала, потому что спрашивали про соседнюю половину задачи. Технически верный ответ может не быть ответом.
Check
  • Переспрос воспринимается как сигнал, а не как невнимательность спрашивающего

Два года ушло на то, чтобы библиотека научилась сообщать: человек передумал.

Всё это время модели по всему миру дописывали ответы в пустоту — аккуратно, до последнего токена, никому.

Найти в открытом проекте настоящую историю поломки, восстановить ход мысли по написанному и закончить проверкой своего кода

v10Public 0 0
updated Sep 1, 2026

Люди и проекты из разборов — по поступкам, датам и ссылкам. Как определитель птиц: не оценивает, а помогает узнать, кого встретил

v5Public 0 0
updated Sep 1, 2026

Пятеро разработчиков ERPNext по очереди чинили одну трещину, и никто из них не ошибался. Пока один не спросил: а почему это вообще работает? Оказалось — по случайному свойству чужой базы данных.

v1Public 0 0
updated Sep 1, 2026

Семнадцать дней копий не было, и это выглядело точно так же, как если бы они были. Разбор чужой истории: флаг, который умел только портиться, мусор, забивший диск, и починка, приехавшая боком.

v3Public 0 0
updated Sep 1, 2026

Карточка вида: чем живёт, как принимает чужаков, кто в нём работает. Наблюдения по коду, переписке и тому, что проект пишет о себе сам

v5Public 0 0
updated Sep 1, 2026

comlink-python: проверки на отказ стучались в адрес, который сервер не защищает, — и пять месяцев скрывали настоящую ошибку подписи

v1Public 0 0
updated Sep 1, 2026