core/agent: share stream chunk builders across responses and messages bridges

This commit is contained in:
2026-08-01 00:48:44 +08:00
parent 06f1b386a0
commit 9aea91e77c
5 changed files with 150 additions and 183 deletions
-87
View File
@@ -30,14 +30,9 @@ from plyngent.lmproto.openai_compatible.model import (
AssistantFunctionTool,
AssistantFunctionToolCall,
ChatCompletionChoice,
ChatCompletionChunk,
ChatCompletionResponse,
ChatCompletionsParam,
ChunkChoice,
DeltaMessage,
DeveloperChatMessage,
StreamFunctionDelta,
StreamToolCallDelta,
SystemChatMessage,
ToolFunctionItem,
UserChatMessage,
@@ -290,88 +285,6 @@ def anthropic_response_to_chat_completion(
)
def finish_reason_chunk(
*,
model: str,
finish_reason: str,
created: int = 0,
) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="anthropic-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(),
finish_reason=cast("Any", finish_reason),
)
],
)
def text_delta_chunk(*, model: str, content: str, created: int = 0) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="anthropic-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(content=content),
)
],
)
def tool_call_delta_chunk(
*,
model: str,
index: int,
call_id: str | None = None,
name: str | None = None,
arguments: str | None = None,
created: int = 0,
) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="anthropic-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(
tool_calls=[
StreamToolCallDelta(
index=index,
id=call_id if call_id is not None else UNSET,
type="function",
function=StreamFunctionDelta(
name=name if name is not None else UNSET,
arguments=arguments if arguments is not None else UNSET,
),
)
]
),
)
],
)
def usage_chunk(*, model: str, usage: dict[str, Any], created: int = 0) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="anthropic-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[],
usage=usage,
)
def _max_tokens_from_param(param: ChatCompletionsParam) -> int:
if param.max_completion_tokens is not UNSET:
return int(param.max_completion_tokens)
+2
View File
@@ -13,6 +13,8 @@ from plyngent.agent.messages_bridge import (
anthropic_stop_to_finish_reason,
anthropic_usage_to_dict,
chat_param_to_anthropic_param,
)
from plyngent.agent.stream_chunks import (
finish_reason_chunk,
text_delta_chunk,
tool_call_delta_chunk,
+15 -93
View File
@@ -27,11 +27,7 @@ from plyngent.lmproto.openai_compatible.model import (
ChatCompletionChunk,
ChatCompletionResponse,
ChatCompletionsParam,
ChunkChoice,
DeltaMessage,
DeveloperChatMessage,
StreamFunctionDelta,
StreamToolCallDelta,
SystemChatMessage,
ToolFunctionItem,
UserChatMessage,
@@ -204,58 +200,6 @@ def response_to_chat_completion(response: Response) -> ChatCompletionResponse:
)
def finish_reason_chunk(
*,
model: str,
finish_reason: str,
created: int = 0,
) -> ChatCompletionChunk:
"""Terminal stream chunk carrying only ``finish_reason`` (no delta text)."""
return ChatCompletionChunk(
id="resp-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(),
finish_reason=cast("Any", finish_reason),
)
],
)
def text_delta_chunk(*, model: str, content: str, created: int = 0) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="resp-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(content=content),
)
],
)
def reasoning_delta_chunk(*, model: str, content: str, created: int = 0) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="resp-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(reasoning_content=content),
)
],
)
def tool_call_chunks_from_response(
response: Response,
*,
@@ -263,50 +207,28 @@ def tool_call_chunks_from_response(
created: int = 0,
) -> list[ChatCompletionChunk]:
"""Emit complete tool-call stream deltas (one chunk per call) for loop merge."""
from .stream_chunks import tool_call_delta_chunk
calls = response_function_calls(response)
chunks: list[ChatCompletionChunk] = []
for index, call in enumerate(calls):
chunks.append(
ChatCompletionChunk(
id=response.id,
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(
tool_calls=[
StreamToolCallDelta(
index=index,
id=call.call_id,
type="function",
function=StreamFunctionDelta(
name=call.name,
arguments=call.arguments,
),
)
]
),
)
],
)
return [
tool_call_delta_chunk(
model=model,
index=index,
call_id=call.call_id,
name=call.name,
arguments=call.arguments,
created=created,
)
return chunks
for index, call in enumerate(calls)
]
def usage_chunk_from_response(response: Response, *, model: str) -> ChatCompletionChunk | None:
from .stream_chunks import usage_chunk
if response.usage is UNSET or response.usage is None:
return None
created = int(response.created_at)
return ChatCompletionChunk(
id=response.id,
object="chat.completion.chunk",
created=created,
model=model,
choices=[],
usage=response.usage,
)
return usage_chunk(model=model, usage=response.usage, created=int(response.created_at))
def _merge_response_tools(
+5 -3
View File
@@ -16,15 +16,17 @@ from msgspec import UNSET
from plyngent.agent.responses_bridge import (
chat_param_to_responses_kwargs,
finish_reason_chunk,
reasoning_delta_chunk,
response_to_assistant_message,
response_to_chat_completion,
responses_status_to_finish_reason,
text_delta_chunk,
tool_call_chunks_from_response,
usage_chunk_from_response,
)
from plyngent.agent.stream_chunks import (
finish_reason_chunk,
reasoning_delta_chunk,
text_delta_chunk,
)
from plyngent.lmproto.openai.model import Response as ResponseModel
from plyngent.lmproto.openai.model import ResponsesCreateParam
+128
View File
@@ -0,0 +1,128 @@
"""Shared chat-completions stream chunk builders for protocol bridges.
Both the Responses bridge and the Anthropic Messages bridge synthesize
``ChatCompletionChunk`` objects so the agent loop can consume provider streams
uniformly. The generic builders below are provider-agnostic; provider bridges
map their events onto them.
"""
from __future__ import annotations
from typing import Any, cast
from msgspec import UNSET
from plyngent.lmproto.openai_compatible.model import (
ChatCompletionChunk,
ChunkChoice,
DeltaMessage,
StreamFunctionDelta,
StreamToolCallDelta,
)
def finish_reason_chunk(
*,
model: str,
finish_reason: str,
created: int = 0,
) -> ChatCompletionChunk:
"""Terminal stream chunk carrying only ``finish_reason`` (no delta text)."""
return ChatCompletionChunk(
id="bridge-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(),
finish_reason=cast("Any", finish_reason),
)
],
)
def text_delta_chunk(*, model: str, content: str, created: int = 0) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="bridge-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(content=content),
)
],
)
def reasoning_delta_chunk(*, model: str, content: str, created: int = 0) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="bridge-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(reasoning_content=content),
)
],
)
def tool_call_delta_chunk(
*,
model: str,
index: int,
call_id: str | None = None,
name: str | None = None,
arguments: str | None = None,
created: int = 0,
) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="bridge-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[
ChunkChoice(
index=0,
delta=DeltaMessage(
tool_calls=[
StreamToolCallDelta(
index=index,
id=call_id if call_id is not None else UNSET,
type="function",
function=StreamFunctionDelta(
name=name if name is not None else UNSET,
arguments=arguments if arguments is not None else UNSET,
),
)
]
),
)
],
)
def usage_chunk(*, model: str, usage: dict[str, Any], created: int = 0) -> ChatCompletionChunk:
return ChatCompletionChunk(
id="bridge-stream",
object="chat.completion.chunk",
created=created,
model=model,
choices=[],
usage=usage,
)
__all__ = [
"finish_reason_chunk",
"reasoning_delta_chunk",
"text_delta_chunk",
"tool_call_delta_chunk",
"usage_chunk",
]