~/tools / Локальная LLM на своём сервере / Ollama Stream Reader

Ollama Stream Reader — Соберите потоковый ответ Ollama, узнайте скорость и причину остановки

Вставьте потоковый ответ Ollama, от /api/chat, /api/generate или OpenAI-совместимого /v1/chat/completions, и инструмент соберёт части обратно в полный текст, thinking и вызовы инструментов. Он покажет, почему модель остановилась, как быстро она генерировала и обрабатывала промпт и был ли поток оборван, а также выдаст сообщение ассистента для следующего запроса. Данные не покидают ваш браузер.

Как сохранить поток с помощью curl

-N отключает буферизацию curl, а > stream.txt сохраняет поток в файл. Замените модель и сообщение на свои.

curl -sN http://localhost:11434/api/chat -d '{"model":"qwen2.5:7b","messages":[{"role":"user","content":"Hello"}]}' > stream.txt

curl -sN http://localhost:11434/v1/chat/completions -H "Content-Type: application/json" -d '{"model":"qwen2.5:7b","stream":true,"stream_options":{"include_usage":true},"messages":[{"role":"user","content":"Hello"}]}' > stream.txt

Прочитано как поток /api/chat (по одному JSON-объекту в строке).

Сводка

Модель
qwen2.5:7b
Причина остановки
stop
Фрагменты
7
Сгенерированные токены
7
Скорость генерации
10.4 токенов/с
Токены промпта
35
Скорость промпта (токены не из кэша)
64.1 токенов/с
Время загрузки модели
4.923 с
Общее время
6.310 с
От первого до последнего фрагмента
0.675 с

Текст

Hello, nice to meet.

Сообщение ассистента для следующего запроса

Добавьте его в messages, а за ним результаты вызовов инструментов, если они были.

{
  "role": "assistant",
  "content": "Hello, nice to meet."
}

eval_count учитывает и токены вызовов инструментов, которые не передаются потоком как текст. В нашем тесте один вызов инструмента пришёл одним фрагментом с eval_count 20.

В документации Ollama сказано, что prompt_eval_duration охватывает только те токены промпта, которых не было в кэше. Скорость промпта здесь делит только их. Все длительности даны в наносекундах; в форме OpenAI timings даны в целых миллисекундах.

В OpenAI-совместимой форме число токенов отправляется только при stream_options.include_usage, в дополнительном фрагменте перед data: [DONE]. Если поток оборван, этот фрагмент не приходит.

Форматы по документации Ollama о потоковой передаче и usage и по её исходному коду (v0.40.15), проверены на настоящих потоках Ollama 0.40.11 2026-11-07. Ollama: Streaming

Всё выполняется в вашем браузере. Инструмент ни к чему не подключается.

Ollama по умолчанию передаёт ответы потоком. /api/chat и /api/generate отправляют по одному JSON-объекту в строке (newline-delimited JSON): каждая строка содержит несколько символов ответа, а в последней строке done равно true, и в ней есть число токенов и длительности в наносекундах. OpenAI-совместимый /v1/chat/completions передаёт поток, только если stream равно true. Тогда он отправляет server-sent events: строки, начинающиеся с data:, отдельный фрагмент с пустым delta и finish_reason, а в конце data: [DONE]. Число токенов приходит, только если запрос задаёт stream_options.include_usage, в дополнительном фрагменте с пустым списком choices, и Ollama добавляет к нему объект timings. Этот инструмент читает все три формы, включая вывод curl -i с заголовками HTTP, склеивает content, thinking (reasoning в форме OpenAI) и вызовы инструментов и перечисляет, что не так: поток без строки done или без [DONE] был оборван; строка {"error": …} означает, что Ollama остановилась с ошибкой уже после того, как начала отправку; done_reason length означает, что достигнут num_predict или max_tokens. Скорость генерации равна eval_count, делённому на eval_duration. Для промпта в документации Ollama сказано, что prompt_eval_duration охватывает только те токены промпта, которых не было в кэше, поэтому инструмент делит на это время только их. В форме OpenAI собственное timings.prompt_per_second у Ollama делит на это время все токены промпта, включая взятые из кэша. В нашем тесте оно показало 360.8 токена в секунду для промпта, в котором 34 из 35 токенов пришли из кэша, а один оставшийся токен занял 97 ms. Вызовы инструментов в форме OpenAI могут приходить частями с общим index, поэтому инструмент склеивает аргументы по index; сама Ollama отправляет каждый вызов инструмента целиком, одним фрагментом. Мы проверили инструмент на настоящих потоках Ollama 0.40.11 с qwen2.5:7b, а форматы сверили с документацией и исходным кодом Ollama.

Основная логика этого инструмента открыта под лицензией MIT. github.com/seigo5555/toolkitbox-lib/src/ollama-stream.ts

Когда пригодится

  • Увидеть весь ответ потокового запроса, сделанного через curl, вместо чтения по кусочку в каждой строке
  • Выяснить, был ли поток оборван, остановился ли на лимите длины или закончился ошибкой
  • Измерить скорость генерации и обработки промпта модели на вашем компьютере или сервере

Как пользоваться

  1. Выполните запрос с curl -sN и сохраните вывод в файл или скопируйте то, что получила ваша программа.
  2. Вставьте весь поток. Инструмент определит, откуда он: от /api/chat, /api/generate или /v1/chat/completions.
  3. Прочитайте проблемы и числа и скопируйте текст или сообщение ассистента для следующего запроса.

Вопросы

Почему в моём потоке /v1/chat/completions нет числа токенов?

В OpenAI-совместимой форме числа отправляются, только если запрос задаёт stream_options равным {"include_usage": true}. Тогда они приходят в дополнительном фрагменте с пустым списком choices, перед самым data: [DONE]. /api/chat и /api/generate всегда кладут их в последнюю строку.

Почему скорость промпта так сильно отличается от timings.prompt_per_second?

В документации Ollama сказано, что prompt_eval_duration охватывает только те токены промпта, которых не было в кэше. timings.prompt_per_second у Ollama делит на это время все токены промпта, включая взятые из кэша, поэтому при использовании кэша значение выглядит намного быстрее. Инструмент делит только токены, которых не было в кэше.

Модель вызвала инструмент, но eval_count равно 20. Где эти токены?

Текст вызова инструмента не передаётся потоком как content; Ollama превращает его в tool_calls и отправляет одним фрагментом. Токены всё равно генерируются и считаются. В нашем тесте один вызов инструмента с одним аргументом пришёл одним фрагментом с eval_count 20.

Почему /api/chat возвращает stop, когда модель вызвала инструмент?

/api/chat оставляет done_reason равным stop. OpenAI-совместимый эндпоинт меняет stop на tool_calls в finish_reason, если был отправлен вызов инструмента.

Что делать с сообщением ассистента?

В документации Ollama отмечено, что для сохранения истории разговора нужно склеить части потока, что особенно важно для вызовов инструментов. Добавьте сообщение в messages, добавьте результат каждого вызова инструмента как сообщение с ролью tool и отправьте следующий запрос.

Отправляются ли мои данные куда-либо?

Нет. Поток читается в вашем браузере. Команды curl — это лишь текст, который вы запускаете сами.

Похожие статьи

Похожие