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 plyngent.typedef import Unset # noqa: TC001
from ...openai_compatible.model import AssistantChatMessage as BaseAssistantChatMessage
from ...openai_compatible.model import ( from ...openai_compatible.model import (
ChatMessage, AnyResponseFormat,
ReasoningEffort, ReasoningEffort,
ResponseFormat,
StreamOptions, StreamOptions,
SystemChatMessage,
ToolChoiceMode, ToolChoiceMode,
ToolFunctionItem, ToolFunctionItem,
UserChatMessage,
) )
from ...openai_compatible.model import AssistantChatMessage as BaseAssistantChatMessage
from ...openai_compatible.model import ToolChatMessage as BaseToolChatMessage from ...openai_compatible.model import ToolChatMessage as BaseToolChatMessage
type DeepSeekReasoningEffort = ReasoningEffort | Literal["max"] type DeepSeekReasoningEffort = ReasoningEffort | Literal["max"]
class NamedChatMessage(ChatMessage):
role: Literal["system", "user"]
name: str | Unset = UNSET
class AssistantChatMessage(BaseAssistantChatMessage): class AssistantChatMessage(BaseAssistantChatMessage):
prefix: bool | Unset = UNSET prefix: bool | Unset = UNSET
reasoning_content: str | Unset = UNSET reasoning_content: str | Unset = UNSET
@@ -32,7 +28,8 @@ class ToolChatMessage(BaseToolChatMessage):
pass pass
type AnyChatMessage = NamedChatMessage | AssistantChatMessage | ToolChatMessage type NamedChatMessage = SystemChatMessage | UserChatMessage
type AnyChatMessage = SystemChatMessage | UserChatMessage | AssistantChatMessage | ToolChatMessage
class ThinkingOptions(Struct): class ThinkingOptions(Struct):
@@ -45,7 +42,7 @@ class ChatCompletionsParam(Struct):
thinking: ThinkingOptions | Unset = UNSET thinking: ThinkingOptions | Unset = UNSET
reasoning_effort: DeepSeekReasoningEffort | Unset = UNSET reasoning_effort: DeepSeekReasoningEffort | Unset = UNSET
max_tokens: int | Unset = UNSET max_tokens: int | Unset = UNSET
response_format: ResponseFormat | Unset = UNSET response_format: AnyResponseFormat | Unset = UNSET
stop: str | list[str] | Unset = UNSET stop: str | list[str] | Unset = UNSET
stream: bool | Unset = UNSET stream: bool | Unset = UNSET
stream_options: StreamOptions | 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 ChatMessage as ChatMessage
from .model import ChunkChoice as ChunkChoice from .model import ChunkChoice as ChunkChoice
from .model import DeltaMessage as DeltaMessage from .model import DeltaMessage as DeltaMessage
from .model import DeveloperChatMessage as DeveloperChatMessage
from .model import FinishReason as FinishReason from .model import FinishReason as FinishReason
from .model import GrammarSyntax as GrammarSyntax from .model import GrammarSyntax as GrammarSyntax
from .model import JsonObjectResponseFormat as JsonObjectResponseFormat
from .model import Modality as Modality from .model import Modality as Modality
from .model import NamedChatMessage as NamedChatMessage from .model import NamedChatMessage as NamedChatMessage
from .model import NamedRole as NamedRole 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 SchemaResponseFormat as SchemaResponseFormat
from .model import ServiceTier as ServiceTier from .model import ServiceTier as ServiceTier
from .model import StreamOptions as StreamOptions 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 ToolChatMessage as ToolChatMessage
from .model import ToolChoiceMode as ToolChoiceMode from .model import ToolChoiceMode as ToolChoiceMode
from .model import ToolFunctionItem as ToolFunctionItem from .model import ToolFunctionItem as ToolFunctionItem
from .model import UserChatMessage as UserChatMessage
from .model import Verbosity as Verbosity from .model import Verbosity as Verbosity
from .model import VoiceName as VoiceName from .model import VoiceName as VoiceName
+35 -28
View File
@@ -23,11 +23,21 @@ class ChatMessage(Struct):
content: str content: str
class NamedChatMessage(ChatMessage): class SystemChatMessage(ChatMessage, tag_field="role", tag="system"):
role: NamedRole
name: str | Unset = UNSET 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): class IDObject(Struct):
id: str id: str
@@ -37,9 +47,8 @@ class AssistantFunctionTool(Struct):
arguments: str arguments: str
class AssistantFunctionToolCall(Struct): class AssistantFunctionToolCall(Struct, tag_field="type", tag="function"):
id: str id: str
type: Literal["function"]
function: AssistantFunctionTool function: AssistantFunctionTool
@@ -48,41 +57,44 @@ class AssistantCustomTool(Struct):
input: str input: str
class AssistantCustomToolCall(Struct): class AssistantCustomToolCall(Struct, tag_field="type", tag="custom"):
id: str id: str
type: Literal["custom"]
custom: AssistantCustomTool custom: AssistantCustomTool
type AnyAssistantToolCall = AssistantFunctionToolCall | AssistantCustomToolCall type AnyAssistantToolCall = AssistantFunctionToolCall | AssistantCustomToolCall
class AssistantChatMessage(ChatMessage): class AssistantChatMessage(ChatMessage, tag_field="role", tag="assistant"):
role: RoleAssistant
name: str | Unset = UNSET name: str | Unset = UNSET
audio: IDObject | Unset = UNSET audio: IDObject | Unset = UNSET
refusal: str | Unset = UNSET refusal: str | Unset = UNSET
tool_calls: list[AnyAssistantToolCall] | Unset = UNSET tool_calls: list[AnyAssistantToolCall] | Unset = UNSET
class ToolChatMessage(ChatMessage): class ToolChatMessage(ChatMessage, tag_field="role", tag="tool"):
role: RoleTool
tool_call_id: str tool_call_id: str
type AnyChatMessage = NamedChatMessage | AssistantChatMessage | ToolChatMessage type AnyChatMessage = (
SystemChatMessage | UserChatMessage | DeveloperChatMessage | AssistantChatMessage | ToolChatMessage
)
class ResponseFormat(Struct): class TextResponseFormat(Struct, tag_field="type", tag="text"):
type: Literal["text", "json_object"] pass
class SchemaResponseFormat(Struct): class JsonObjectResponseFormat(Struct, tag_field="type", tag="json_object"):
type: Literal["json_schema"] pass
class SchemaResponseFormat(Struct, tag_field="type", tag="json_schema"):
json_schema: JSONSchema json_schema: JSONSchema
type AnyResponseFormat = ResponseFormat | SchemaResponseFormat type ResponseFormat = TextResponseFormat | JsonObjectResponseFormat
type AnyResponseFormat = TextResponseFormat | JsonObjectResponseFormat | SchemaResponseFormat
class ToolFunction(Struct): class ToolFunction(Struct):
@@ -92,13 +104,12 @@ class ToolFunction(Struct):
strict: bool | Unset = UNSET strict: bool | Unset = UNSET
class ToolFunctionItem(Struct): class ToolFunctionItem(Struct, tag_field="type", tag="function"):
type: Literal["function"]
function: ToolFunction function: ToolFunction
class TextFormat(Struct): class TextFormat(Struct, tag_field="type", tag="text"):
type: Literal["text"] pass
class GrammarDefinition(Struct): class GrammarDefinition(Struct):
@@ -106,8 +117,7 @@ class GrammarDefinition(Struct):
definition: str definition: str
class GrammarFormat(Struct): class GrammarFormat(Struct, tag_field="type", tag="grammar"):
type: Literal["grammar"]
grammar: GrammarDefinition grammar: GrammarDefinition
@@ -117,8 +127,7 @@ class ToolCustom(Struct):
format: TextFormat | GrammarFormat | Unset = UNSET format: TextFormat | GrammarFormat | Unset = UNSET
class ToolCustomItem(Struct): class ToolCustomItem(Struct, tag_field="type", tag="custom"):
type: Literal["custom"]
custom: ToolCustom custom: ToolCustom
@@ -134,8 +143,7 @@ class ModerationOptions(Struct):
model: str model: str
class PredictionOptions(Struct): class PredictionOptions(Struct, tag_field="type", tag="content"):
type: Literal["content"]
content: str content: str
@@ -149,8 +157,7 @@ class AllowedTools(Struct):
tools: list[AnyToolItem] tools: list[AnyToolItem]
class AllowedToolChoice(Struct): class AllowedToolChoice(Struct, tag_field="type", tag="allowed_tools"):
type: Literal["allowed_tools"]
allowed_tools: AllowedTools allowed_tools: AllowedTools
+3 -2
View File
@@ -1,6 +1,7 @@
from typing import Any, Literal from typing import Any
from msgspec import UnsetType from msgspec import UnsetType
type JSONSchema = dict[str, Any] type JSONSchema = dict[str, Any]
type Unset = Literal[UnsetType.UNSET] # Assignment (not PEP 695 `type`) so msgspec recognizes UnsetType in unions.
Unset = UnsetType