core/lmproto: allow null logprobs and usage on stream chunks

Providers send logprobs=null (and sometimes usage=null) on SSE choices;
accept None so decode does not fail mid-stream.
This commit is contained in:
2026-07-15 14:23:23 +08:00
parent 366752efb9
commit 9cc2216807
2 changed files with 15 additions and 2 deletions
@@ -245,7 +245,8 @@ class DeltaMessage(Struct):
class ChunkChoice(Struct): class ChunkChoice(Struct):
index: int index: int
delta: DeltaMessage delta: DeltaMessage
logprobs: dict[str, Any] | Unset = UNSET # APIs often send logprobs=null on stream choices.
logprobs: dict[str, Any] | None | Unset = UNSET
finish_reason: FinishReason | None | Unset = UNSET finish_reason: FinishReason | None | Unset = UNSET
@@ -255,4 +256,5 @@ class ChatCompletionChunk(Struct):
created: int created: int
model: str model: str
choices: list[ChunkChoice] choices: list[ChunkChoice]
usage: dict[str, Any] | Unset = UNSET # Final usage chunk may omit or null usage depending on provider.
usage: dict[str, Any] | None | Unset = UNSET
+11
View File
@@ -47,6 +47,17 @@ def test_delta_accepts_null_content_and_reasoning() -> None:
assert delta.reasoning_content is None assert delta.reasoning_content is None
def test_chunk_choice_accepts_null_logprobs() -> None:
payload = (
b'{"id":"1","object":"chat.completion.chunk","created":0,"model":"m",'
b'"choices":[{"index":0,"delta":{"content":"x"},"logprobs":null,'
b'"finish_reason":null}],"usage":null}'
)
chunk = msgspec_decode_chunk(payload)
assert chunk.choices[0].logprobs is None
assert chunk.usage is None
def msgspec_decode_chunk(payload: bytes) -> ChatCompletionChunk: def msgspec_decode_chunk(payload: bytes) -> ChatCompletionChunk:
import msgspec import msgspec