feat(server): enable reasoning_effort on /v1/chat/completions (#2)
* feat(server): enable reasoning_effort on /v1/chat/completions * fix(server): glm thinking-off mislabeling and reasoning_effort mapping edge cases
This commit is contained in:
@@ -77,6 +77,7 @@ class ChatCompletionRequest(BaseModel):
|
||||
presence_penalty: float = 0.0
|
||||
frequency_penalty: float = 0.0
|
||||
chat_template_kwargs: dict[str, Any] = Field(default_factory=dict)
|
||||
reasoning_effort: str | None = None
|
||||
ignore_eos: bool = False
|
||||
tools: list[Tool] | None = None
|
||||
tool_choice: Literal["none", "auto", "required"] | ToolChoiceObject | None = None
|
||||
|
||||
@@ -317,6 +317,10 @@ def _make_reasoning_parser(spec: GenSpec, state: Any) -> ReasoningParser | None:
|
||||
# enable_thinking is explicitly false, so the model emits only the closing
|
||||
# </think>. Mirror that default here, else the chain-of-thought leaks into content.
|
||||
force_reasoning = (spec.chat_template_kwargs or {}).get("enable_thinking") is not False
|
||||
elif parser_name == "glm":
|
||||
# GLM's template honors enable_thinking (default on) even with tools; the
|
||||
# generic fallback would force thinking and mislabel disabled output as reasoning.
|
||||
force_reasoning = (spec.chat_template_kwargs or {}).get("enable_thinking") is not False
|
||||
elif parser_name == "gemma4":
|
||||
# Gemma4 defaults thinking off even when tools are present: its template injects an
|
||||
# empty thought channel before generation. Do not let Codex tool definitions make all
|
||||
|
||||
@@ -67,6 +67,32 @@ def think_toggle_kwargs(reasoning_parser: str | None, enabled: bool) -> dict:
|
||||
return think_chat_template_kwargs(reasoning_parser, gear)
|
||||
|
||||
|
||||
_THINKING_KWARG_KEYS = ("enable_thinking", "thinking", "thinking_mode", "reasoning_effort")
|
||||
_DISABLE_EFFORTS = ("none", "off")
|
||||
|
||||
|
||||
def effort_toggle_kwargs(
|
||||
reasoning_parser: str | None,
|
||||
effort: str | None,
|
||||
chat_template_kwargs: dict | None,
|
||||
) -> dict:
|
||||
"""Fold a protocol-level reasoning-effort request into the template kwargs.
|
||||
An explicit thinking-related key wins wholesale; unrelated extras ride along.
|
||||
Effort "none"/"off" (case-insensitive) disables thinking; any other or absent
|
||||
effort enables it, forwarded for templates that grade it (gpt-oss)."""
|
||||
ctk = dict(chat_template_kwargs or {})
|
||||
if any(key in ctk for key in _THINKING_KWARG_KEYS):
|
||||
return ctk
|
||||
if isinstance(effort, str):
|
||||
effort = effort.strip().lower()
|
||||
disabled = effort in _DISABLE_EFFORTS
|
||||
mapped = dict(think_toggle_kwargs(reasoning_parser, not disabled))
|
||||
if effort and not disabled:
|
||||
mapped.setdefault("reasoning_effort", effort)
|
||||
mapped.update(ctk)
|
||||
return mapped
|
||||
|
||||
|
||||
def moe_total_experts(config: Any) -> int:
|
||||
"""Total routed-expert slots the model has: experts per layer x MoE layers. Matches the
|
||||
engine's own basis (``Engine._resolve_auto_moe_cache_size``), so a residency rate derived
|
||||
|
||||
@@ -37,8 +37,17 @@ from .generation import (
|
||||
)
|
||||
|
||||
|
||||
def chat_request_to_genspec(req: ChatCompletionRequest, model_sampling: dict[str, Any]) -> GenSpec:
|
||||
def chat_request_to_genspec(
|
||||
req: ChatCompletionRequest,
|
||||
model_sampling: dict[str, Any],
|
||||
reasoning_parser: str | None = None,
|
||||
) -> GenSpec:
|
||||
"""OpenAI ChatCompletionRequest -> GenSpec (the OpenAI 'to_sampling_params')."""
|
||||
from .model_meta import effort_toggle_kwargs
|
||||
|
||||
ctk = req.chat_template_kwargs
|
||||
if req.reasoning_effort:
|
||||
ctk = effort_toggle_kwargs(reasoning_parser, req.reasoning_effort, ctk)
|
||||
return GenSpec(
|
||||
messages=render_messages([m.model_dump(exclude_none=True) for m in req.messages]),
|
||||
sampling_params=resolve_sampling(
|
||||
@@ -50,7 +59,7 @@ def chat_request_to_genspec(req: ChatCompletionRequest, model_sampling: dict[str
|
||||
model_sampling=model_sampling,
|
||||
stop=req.stop,
|
||||
),
|
||||
chat_template_kwargs=req.chat_template_kwargs,
|
||||
chat_template_kwargs=ctk,
|
||||
template_tools=_tools_for_template(req),
|
||||
parser_tools=(_all_tool_dicts(req.tools) if _should_parse_tools(req) else None),
|
||||
)
|
||||
@@ -133,7 +142,9 @@ async def handle_chat_completion(
|
||||
return create_error_response("Only n=1 is supported", param="n")
|
||||
|
||||
try:
|
||||
spec = chat_request_to_genspec(req, model_sampling)
|
||||
spec = chat_request_to_genspec(
|
||||
req, model_sampling, reasoning_parser=getattr(state.config, "reasoning_parser", None)
|
||||
)
|
||||
except ValueError as exc:
|
||||
return create_error_response(str(exc))
|
||||
uid = await submit_generation(spec, state)
|
||||
@@ -182,7 +193,9 @@ async def stream_chat_completion_chunks(
|
||||
) -> AsyncIterator[bytes]:
|
||||
"""Format generate_events() into the OpenAI chat.completion.chunk SSE stream."""
|
||||
if spec is None:
|
||||
spec = chat_request_to_genspec(req, {})
|
||||
spec = chat_request_to_genspec(
|
||||
req, {}, reasoning_parser=getattr(state.config, "reasoning_parser", None)
|
||||
)
|
||||
yield _sse(
|
||||
_chat_chunk(
|
||||
req,
|
||||
|
||||
@@ -230,19 +230,11 @@ def convert_responses_to_genspec(
|
||||
else:
|
||||
template_tools, parser_tools = split_tool_lists(raw_tools, selected)
|
||||
|
||||
# Thinking toggle: an explicit chat_template_kwargs extra field wins; else
|
||||
# the protocol-native `reasoning` object drives the template through the
|
||||
# per-family mapping in model_meta. effort "none" disables thinking (vLLM
|
||||
# semantics); other efforts enable it and are forwarded for templates that
|
||||
# grade them (gpt-oss).
|
||||
from .model_meta import think_toggle_kwargs
|
||||
from .model_meta import effort_toggle_kwargs
|
||||
|
||||
ctk = dict(getattr(req, "chat_template_kwargs", None) or {})
|
||||
if req.reasoning and not ctk:
|
||||
effort = req.reasoning.get("effort")
|
||||
ctk = dict(think_toggle_kwargs(reasoning_parser, effort != "none"))
|
||||
if effort and effort != "none":
|
||||
ctk.setdefault("reasoning_effort", effort)
|
||||
if req.reasoning:
|
||||
ctk = effort_toggle_kwargs(reasoning_parser, req.reasoning.get("effort"), ctk)
|
||||
|
||||
return GenSpec(
|
||||
messages=render_messages(messages),
|
||||
|
||||
Reference in New Issue
Block a user