~/tools / 서버에서 로컬 LLM 실행하기 / Ollama Stream Reader

Ollama Stream Reader — Ollama 스트림을 합쳐 속도와 멈춘 이유 확인

Ollama의 스트리밍 응답(/api/chat, /api/generate 또는 OpenAI 호환 /v1/chat/completions의 것)을 붙여 넣으면, 조각난 부분을 전체 텍스트, thinking, tool calls로 다시 합칩니다. 모델이 멈춘 이유, 생성과 프롬프트의 속도, 스트림이 중간에 끊겼는지를 보여 주고, 다음 요청에 추가할 assistant 메시지도 만들어 줍니다. 데이터는 브라우저 밖으로 나가지 않습니다.

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.

다음 요청에 쓸 assistant 메시지

이것을 messages에 추가하고, tool call의 결과가 있으면 그 뒤에 이어 붙이세요.

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

eval_count에는 텍스트로 스트리밍되지 않는 tool calls의 토큰도 포함됩니다. 우리의 테스트에서는 tool call 한 번이 eval_count 20인 하나의 청크로 도착했습니다.

Ollama의 문서에 따르면 prompt_eval_duration은 캐시에 없던 프롬프트 토큰만 대상으로 합니다. 여기의 프롬프트 속도는 그 토큰만 나눈 값입니다. 모든 소요 시간은 나노초이며, OpenAI 형식의 timings는 정수 밀리초입니다.

OpenAI 호환 형식에서는 토큰 수가 stream_options.include_usage를 설정했을 때만, data: [DONE] 앞의 추가 청크로 전송됩니다. 스트림이 중간에 끊기면 그 청크는 도착하지 않습니다.

형식은 Ollama의 스트리밍 및 사용량 문서와 소스 코드(v0.40.15)를 따랐고, 2026-11-07에 Ollama 0.40.11의 실제 스트림으로 확인했습니다. 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 객체를 덧붙입니다. 이 도구는 세 가지 형식을 모두 읽고, HTTP 헤더가 붙은 curl -i의 출력도 처리하며, content, thinking(OpenAI 형식에서는 reasoning), tool calls를 합치고, 무엇이 잘못되었는지 목록으로 보여 줍니다. done 줄이 없거나 [DONE]이 없는 스트림은 중간에 끊긴 것입니다. {"error": …} 줄은 Ollama가 전송을 시작한 뒤 오류로 멈췄다는 뜻입니다. done_reason이 length이면 num_predict 또는 max_tokens에 도달한 것입니다. 생성 속도는 eval_count를 eval_duration으로 나눈 값입니다. 프롬프트의 경우 Ollama의 문서에 따르면 prompt_eval_duration은 캐시에 없던 프롬프트 토큰만 대상으로 하므로, 이 도구는 그 토큰만 그 시간으로 나눕니다. OpenAI 형식에서 Ollama 자체의 timings.prompt_per_second는 캐시된 토큰을 포함한 모든 프롬프트 토큰을 그 시간으로 나눕니다. 우리의 테스트에서, 35개 토큰의 프롬프트 가운데 34개가 캐시에서 왔고 남은 1개 토큰에 97 ms가 걸렸을 때 360.8 토큰/초로 보고되었습니다. OpenAI 형식의 tool calls는 같은 index를 공유하는 조각으로 나뉘어 도착할 수 있으므로, 이 도구는 index별로 arguments를 합칩니다. Ollama 자체는 각 tool call을 하나의 청크에 통째로 보냅니다. 우리는 이 도구를 Ollama 0.40.11과 qwen2.5:7b의 실제 스트림으로, 형식은 Ollama의 문서와 소스 코드로 확인했습니다.

이 도구의 핵심 로직은 MIT 라이선스의 오픈 소스로 공개되어 있습니다. github.com/seigo5555/toolkitbox-lib/src/ollama-stream.ts

이럴 때 사용

  • curl로 보낸 스트리밍 요청의 전체 답을 한 줄씩 읽지 않고 한눈에 보기
  • 스트림이 중간에 끊겼는지, length 한도에서 멈췄는지, 오류로 끝났는지 알아내기
  • 내 컴퓨터나 서버에서 모델의 생성 속도와 프롬프트 속도 측정하기

사용법

  1. curl -sN으로 요청을 실행해 출력을 파일에 저장하거나, 프로그램이 받은 내용을 복사하세요.
  2. 스트림 전체를 붙여 넣으세요. /api/chat, /api/generate, /v1/chat/completions 중 어느 것인지 도구가 알아냅니다.
  3. 문제점과 수치를 읽고, 텍스트나 다음 요청에 쓸 assistant 메시지를 복사하세요.

FAQ

/v1/chat/completions 스트림에 토큰 수가 없는 이유는 무엇인가요?

OpenAI 호환 형식에서는 요청에서 stream_options를 {"include_usage": true}로 설정했을 때만 토큰 수가 전송됩니다. data: [DONE] 바로 앞의, choices가 빈 추가 청크에 들어 있습니다. /api/chat과 /api/generate는 항상 마지막 줄에 넣습니다.

프롬프트 속도가 timings.prompt_per_second와 크게 다른 이유는 무엇인가요?

Ollama의 문서에 따르면 prompt_eval_duration은 캐시에 없던 프롬프트 토큰만 대상으로 합니다. Ollama의 timings.prompt_per_second는 캐시된 토큰을 포함한 모든 프롬프트 토큰을 그 시간으로 나누므로, 캐시가 쓰였을 때는 훨씬 빠르게 보입니다. 이 도구는 캐시에 없던 토큰만 나눕니다.

모델이 도구를 호출했는데 eval_count가 20입니다. 그 토큰들은 어디에 있나요?

tool call의 텍스트는 content로 스트리밍되지 않습니다. Ollama는 그것을 tool_calls로 바꿔 하나의 청크로 보냅니다. 토큰은 생성되고 집계됩니다. 우리의 테스트에서는 인수가 하나인 tool call 한 번이 eval_count 20인 하나의 청크로 도착했습니다.

모델이 도구를 호출했는데 /api/chat이 stop이라고 하는 이유는 무엇인가요?

/api/chat은 done_reason을 stop으로 유지합니다. OpenAI 호환 엔드포인트는 tool call이 전송되었을 때 finish_reason의 stop을 tool_calls로 바꿉니다.

assistant 메시지는 어떻게 하면 되나요?

Ollama의 문서에는 대화 기록을 유지하려면 스트리밍된 조각을 합쳐야 하며, 이는 tool calls에서 가장 중요하다고 나와 있습니다. 메시지를 messages에 추가하고, 각 tool call의 결과를 role이 tool인 메시지로 추가한 다음 다음 요청을 보내세요.

내 데이터가 어딘가로 전송되나요?

아니요. 스트림은 브라우저에서 읽습니다. curl 명령은 직접 실행할 텍스트일 뿐입니다.

관련 글

관련 도구