core/lmproto: fix msgspec Unset and tag multi-struct unions

Use Unset=UnsetType (not PEP 695 alias) and tag role/type fields so
decoders and convert work for chat messages and tool calls.
This commit is contained in:
2026-07-14 17:25:09 +08:00
parent 123aadeadf
commit 8a1994a785
4 changed files with 50 additions and 40 deletions
@@ -4,25 +4,21 @@ from msgspec import UNSET, Struct
from plyngent.typedef import Unset # noqa: TC001
from ...openai_compatible.model import AssistantChatMessage as BaseAssistantChatMessage
from ...openai_compatible.model import (
ChatMessage,
AnyResponseFormat,
ReasoningEffort,
ResponseFormat,
StreamOptions,
SystemChatMessage,
ToolChoiceMode,
ToolFunctionItem,
UserChatMessage,
)
from ...openai_compatible.model import AssistantChatMessage as BaseAssistantChatMessage
from ...openai_compatible.model import ToolChatMessage as BaseToolChatMessage
type DeepSeekReasoningEffort = ReasoningEffort | Literal["max"]
class NamedChatMessage(ChatMessage):
role: Literal["system", "user"]
name: str | Unset = UNSET
class AssistantChatMessage(BaseAssistantChatMessage):
prefix: bool | Unset = UNSET
reasoning_content: str | Unset = UNSET
@@ -32,7 +28,8 @@ class ToolChatMessage(BaseToolChatMessage):
pass
type AnyChatMessage = NamedChatMessage | AssistantChatMessage | ToolChatMessage
type NamedChatMessage = SystemChatMessage | UserChatMessage
type AnyChatMessage = SystemChatMessage | UserChatMessage | AssistantChatMessage | ToolChatMessage
class ThinkingOptions(Struct):
@@ -45,7 +42,7 @@ class ChatCompletionsParam(Struct):
thinking: ThinkingOptions | Unset = UNSET
reasoning_effort: DeepSeekReasoningEffort | Unset = UNSET
max_tokens: int | Unset = UNSET
response_format: ResponseFormat | Unset = UNSET
response_format: AnyResponseFormat | Unset = UNSET
stop: str | list[str] | Unset = UNSET
stream: bool | Unset = UNSET
stream_options: StreamOptions | Unset = UNSET
@@ -16,8 +16,10 @@ from .model import ChatCompletionsParam as ChatCompletionsParam
from .model import ChatMessage as ChatMessage
from .model import ChunkChoice as ChunkChoice
from .model import DeltaMessage as DeltaMessage
from .model import DeveloperChatMessage as DeveloperChatMessage
from .model import FinishReason as FinishReason
from .model import GrammarSyntax as GrammarSyntax
from .model import JsonObjectResponseFormat as JsonObjectResponseFormat
from .model import Modality as Modality
from .model import NamedChatMessage as NamedChatMessage
from .model import NamedRole as NamedRole
@@ -28,8 +30,11 @@ from .model import RoleTool as RoleTool
from .model import SchemaResponseFormat as SchemaResponseFormat
from .model import ServiceTier as ServiceTier
from .model import StreamOptions as StreamOptions
from .model import SystemChatMessage as SystemChatMessage
from .model import TextResponseFormat as TextResponseFormat
from .model import ToolChatMessage as ToolChatMessage
from .model import ToolChoiceMode as ToolChoiceMode
from .model import ToolFunctionItem as ToolFunctionItem
from .model import UserChatMessage as UserChatMessage
from .model import Verbosity as Verbosity
from .model import VoiceName as VoiceName
+35 -28
View File
@@ -23,11 +23,21 @@ class ChatMessage(Struct):
content: str
class NamedChatMessage(ChatMessage):
role: NamedRole
class SystemChatMessage(ChatMessage, tag_field="role", tag="system"):
name: str | Unset = UNSET
class UserChatMessage(ChatMessage, tag_field="role", tag="user"):
name: str | Unset = UNSET
class DeveloperChatMessage(ChatMessage, tag_field="role", tag="developer"):
name: str | Unset = UNSET
type NamedChatMessage = SystemChatMessage | UserChatMessage | DeveloperChatMessage
class IDObject(Struct):
id: str
@@ -37,9 +47,8 @@ class AssistantFunctionTool(Struct):
arguments: str
class AssistantFunctionToolCall(Struct):
class AssistantFunctionToolCall(Struct, tag_field="type", tag="function"):
id: str
type: Literal["function"]
function: AssistantFunctionTool
@@ -48,41 +57,44 @@ class AssistantCustomTool(Struct):
input: str
class AssistantCustomToolCall(Struct):
class AssistantCustomToolCall(Struct, tag_field="type", tag="custom"):
id: str
type: Literal["custom"]
custom: AssistantCustomTool
type AnyAssistantToolCall = AssistantFunctionToolCall | AssistantCustomToolCall
class AssistantChatMessage(ChatMessage):
role: RoleAssistant
class AssistantChatMessage(ChatMessage, tag_field="role", tag="assistant"):
name: str | Unset = UNSET
audio: IDObject | Unset = UNSET
refusal: str | Unset = UNSET
tool_calls: list[AnyAssistantToolCall] | Unset = UNSET
class ToolChatMessage(ChatMessage):
role: RoleTool
class ToolChatMessage(ChatMessage, tag_field="role", tag="tool"):
tool_call_id: str
type AnyChatMessage = NamedChatMessage | AssistantChatMessage | ToolChatMessage
type AnyChatMessage = (
SystemChatMessage | UserChatMessage | DeveloperChatMessage | AssistantChatMessage | ToolChatMessage
)
class ResponseFormat(Struct):
type: Literal["text", "json_object"]
class TextResponseFormat(Struct, tag_field="type", tag="text"):
pass
class SchemaResponseFormat(Struct):
type: Literal["json_schema"]
class JsonObjectResponseFormat(Struct, tag_field="type", tag="json_object"):
pass
class SchemaResponseFormat(Struct, tag_field="type", tag="json_schema"):
json_schema: JSONSchema
type AnyResponseFormat = ResponseFormat | SchemaResponseFormat
type ResponseFormat = TextResponseFormat | JsonObjectResponseFormat
type AnyResponseFormat = TextResponseFormat | JsonObjectResponseFormat | SchemaResponseFormat
class ToolFunction(Struct):
@@ -92,13 +104,12 @@ class ToolFunction(Struct):
strict: bool | Unset = UNSET
class ToolFunctionItem(Struct):
type: Literal["function"]
class ToolFunctionItem(Struct, tag_field="type", tag="function"):
function: ToolFunction
class TextFormat(Struct):
type: Literal["text"]
class TextFormat(Struct, tag_field="type", tag="text"):
pass
class GrammarDefinition(Struct):
@@ -106,8 +117,7 @@ class GrammarDefinition(Struct):
definition: str
class GrammarFormat(Struct):
type: Literal["grammar"]
class GrammarFormat(Struct, tag_field="type", tag="grammar"):
grammar: GrammarDefinition
@@ -117,8 +127,7 @@ class ToolCustom(Struct):
format: TextFormat | GrammarFormat | Unset = UNSET
class ToolCustomItem(Struct):
type: Literal["custom"]
class ToolCustomItem(Struct, tag_field="type", tag="custom"):
custom: ToolCustom
@@ -134,8 +143,7 @@ class ModerationOptions(Struct):
model: str
class PredictionOptions(Struct):
type: Literal["content"]
class PredictionOptions(Struct, tag_field="type", tag="content"):
content: str
@@ -149,8 +157,7 @@ class AllowedTools(Struct):
tools: list[AnyToolItem]
class AllowedToolChoice(Struct):
type: Literal["allowed_tools"]
class AllowedToolChoice(Struct, tag_field="type", tag="allowed_tools"):
allowed_tools: AllowedTools
+3 -2
View File
@@ -1,6 +1,7 @@
from typing import Any, Literal
from typing import Any
from msgspec import UnsetType
type JSONSchema = dict[str, Any]
type Unset = Literal[UnsetType.UNSET]
# Assignment (not PEP 695 `type`) so msgspec recognizes UnsetType in unions.
Unset = UnsetType