[Feature] Streaming Support for SSE and NDJSON (used for LLMs) #1558
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Added streaming support for contentypes:
These are streams delimited by newlines (
\n) that can be supported by Pocket Network without any change to the verification and accounting logic.The first one,
text/event-stream, is the one used by streaming LLM responses (OpenAI format) and is a functionality that we need asap.Primary Changes:
http_stream.gothat implements streaming responses of the mentioned types.sync.goto check for the backend response headers and implement either this streaming protocol or respond normally (read full response first and then relay)Secondary Changes:
serveSyncRequest()function, to keep code more tidy.processStreamRequestto provide an example on how to support streaming responses by an app.processStreamRequestto make code more modular and re-use functions between normal and streamed response.Checks:
I tested this in beta-testnet, against a vLLM enpoint, using this custom RM and the
pocketd relayminer relaycommand:pocketd relayminer relay \ --app=pokt1wvn4a8kj4mfnq0cjakadskxwr2zkev35psjxh9 \ --supplier=pokt19a3t4yunp0dlpfjrp7qwnzwlrzd5fzs2gjaaaj \ --node=https://shannon-testnet-grove-rpc.beta.poktroll.com \ --grpc-addr=shannon-testnet-grove-grpc.beta.poktroll.com:443 \ --grpc-insecure=false \ --payload='{"messages": [{"role": "user", "content": "Tell me how to properly eat a Choripan."}],"max_tokens":200, "model":"pocket_network", "stream":true}' \ --supplier-public-endpoint-override=http://localhost:8545/v1/chat/completions \ --network=betaresponse:
{"level":"warn","time":"2025-06-24T16:40:29-03:00","message":"⚠️ Using override endpoint URL: http://localhost:8546/v1/chat/completions"} {"level":"info","time":"2025-06-24T16:40:29-03:00","message":"✅ JSON-RPC request payload serialized."} {"level":"info","time":"2025-06-24T16:40:29-03:00","message":"✅ Relay request built."} {"level":"info","time":"2025-06-24T16:40:29-03:00","message":"✅ Retrieved private key for app pokt1wvn4a8kj4mfnq0cjakadskxwr2zkev35psjxh9"} {"level":"info","time":"2025-06-24T16:40:29-03:00","message":"✅ Relay request signed."} {"level":"info","time":"2025-06-24T16:40:29-03:00","message":"✅ Relay request marshaled."} {"level":"info","time":"2025-06-24T16:40:29-03:00","message":"✅ Endpoint URL parsed: http://localhost:8546/v1/chat/completions"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"🔍 Content-Type: text/event-stream; charset=utf-8"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"🌊 Handling streaming response with status:"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"📦 Read chunk of length 449"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"✅ Deserialized response body as JSON map: map[choices:[map[delta:map[content: role:assistant] finish_reason:<nil> index:0 logprobs:<nil>]] created:1.75079403e+09 id:chat-2b20014c1d124d1da32d6a71139ea77e model:pocket_network object:chat.completion.chunk]"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"📦 Read chunk of length 213"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"📦 Read chunk of length 432"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"✅ Deserialized response body as JSON map: map[choices:[map[delta:map[content:Ch] finish_reason:<nil> index:0 logprobs:<nil>]] created:1.75079403e+09 id:chat-2b20014c1d124d1da32d6a71139ea77e model:pocket_network object:chat.completion.chunk]"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"📦 Read chunk of length 213"} {"level":"info","time":"2025-06-24T16:40:30-03:00","message":"📦 Read chunk of length 432"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"✅ Deserialized response body as JSON map: map[choices:[map[delta:map[content:or] finish_reason:<nil> index:0 logprobs:<nil>]] created:1.75079403e+09 id:chat-2b20014c1d124d1da32d6a71139ea77e model:pocket_network object:chat.completion.chunk]"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"📦 Read chunk of length 213"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"📦 Read chunk of length 432"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"✅ Deserialized response body as JSON map: map[choices:[map[delta:map[content:ip] finish_reason:<nil> index:0 logprobs:<nil>]] created:1.75079403e+09 id:chat-2b20014c1d124d1da32d6a71139ea77e model:pocket_network object:chat.completion.chunk]"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"📦 Read chunk of length 213"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"📦 Read chunk of length 432"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"✅ Deserialized response body as JSON map: map[choices:[map[delta:map[content:an] finish_reason:<nil> index:0 logprobs:<nil>]] created:1.75079403e+09 id:chat-2b20014c1d124d1da32d6a71139ea77e model:pocket_network object:chat.completion.chunk]"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"📦 Read chunk of length 213"} {"level":"info","time":"2025-06-24T16:40:31-03:00","message":"📦 Read chunk of length 433"} {"level":"info","time":"2025-06-24T16:40:32-03:00","message":"✅ Deserialized response body as JSON map: map[choices:[map[delta:map[content: is] finish_reason:<nil> index:0 logprobs:<nil>]] created:1.75079403e+09 id:chat-2b20014c1d124d1da32d6a71139ea77e model:pocket_network object:chat.completion.chunk]"} ...Type of change
Select one or more from the following:
Sanity Checklist
assignees,reviewers,labels,project,iterationandmilestonemake docusaurus_startmake go_develop_and_testandmake test_e2edevnet-test-e2elabel to run E2E tests in CImake test_e2e_oneshot