chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
"""Template utilities for Jinja template processing.
|
||||
|
||||
This module provides utilities for analyzing and processing Jinja chat templates,
|
||||
including content format detection and message processing.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import jinja2
|
||||
import transformers.utils.chat_template_utils as hf_chat_utils
|
||||
|
||||
from sglang.srt.utils import ImageData
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ============================================================================
|
||||
# JINJA TEMPLATE CONTENT FORMAT DETECTION
|
||||
# ============================================================================
|
||||
#
|
||||
# This adapts vLLM's approach for detecting chat template content format:
|
||||
# https://github.com/vllm-project/vllm/blob/02f0c7b220422792f5e53de2a7d51d2d3ff2df28/vllm/entrypoints/chat_utils.py#L296-L313
|
||||
# - Analyzes Jinja template AST to detect content iteration patterns
|
||||
# - 'openai' format: templates with {%- for content in message['content'] -%} loops
|
||||
# - 'string' format: templates that expect simple string content
|
||||
# - Processes content accordingly to match template expectations
|
||||
|
||||
|
||||
def _is_var_access(node: jinja2.nodes.Node, varname: str) -> bool:
|
||||
"""Check if node is a variable access like {{ varname }}"""
|
||||
if isinstance(node, jinja2.nodes.Name):
|
||||
return node.ctx == "load" and node.name == varname
|
||||
return False
|
||||
|
||||
|
||||
def _is_attr_access(node: jinja2.nodes.Node, varname: str, key: str) -> bool:
|
||||
"""Check if node is an attribute access like {{ varname['key'] }} or {{ varname.key }}"""
|
||||
if isinstance(node, jinja2.nodes.Getitem):
|
||||
return (
|
||||
_is_var_access(node.node, varname)
|
||||
and isinstance(node.arg, jinja2.nodes.Const)
|
||||
and node.arg.value == key
|
||||
)
|
||||
|
||||
if isinstance(node, jinja2.nodes.Getattr):
|
||||
return _is_var_access(node.node, varname) and node.attr == key
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _is_var_or_elems_access(
|
||||
node: jinja2.nodes.Node,
|
||||
varname: str,
|
||||
key: str = None,
|
||||
) -> bool:
|
||||
"""Check if node accesses varname or varname[key] with filters/tests"""
|
||||
if isinstance(node, jinja2.nodes.Filter):
|
||||
return node.node is not None and _is_var_or_elems_access(
|
||||
node.node, varname, key
|
||||
)
|
||||
if isinstance(node, jinja2.nodes.Test):
|
||||
return _is_var_or_elems_access(node.node, varname, key)
|
||||
|
||||
if isinstance(node, jinja2.nodes.Getitem) and isinstance(
|
||||
node.arg, jinja2.nodes.Slice
|
||||
):
|
||||
return _is_var_or_elems_access(node.node, varname, key)
|
||||
|
||||
return _is_attr_access(node, varname, key) if key else _is_var_access(node, varname)
|
||||
|
||||
|
||||
def _try_extract_ast(chat_template: str):
|
||||
"""Try to parse the Jinja template into an AST"""
|
||||
try:
|
||||
jinja_compiled = hf_chat_utils._compile_jinja_template(chat_template)
|
||||
return jinja_compiled.environment.parse(chat_template)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error when compiling Jinja template: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def detect_jinja_template_content_format(chat_template: str) -> str:
|
||||
"""
|
||||
Detect whether a chat template expects 'string' or 'openai' content format.
|
||||
|
||||
- 'string': content is a simple string (like DeepSeek templates)
|
||||
- 'openai': content is a list of structured dicts (like Llama4 templates)
|
||||
|
||||
Detection logic:
|
||||
- If template has loops like {%- for content in message['content'] -%} → 'openai'
|
||||
- Otherwise → 'string'
|
||||
"""
|
||||
# Shortcut for multimodal templates
|
||||
if any(
|
||||
keyword in chat_template for keyword in ["image", "audio", "video", "vision"]
|
||||
):
|
||||
return "openai"
|
||||
|
||||
jinja_ast = _try_extract_ast(chat_template)
|
||||
if jinja_ast is None:
|
||||
return "string"
|
||||
|
||||
try:
|
||||
# Look for patterns like: {%- for content in message['content'] -%}
|
||||
for loop_ast in jinja_ast.find_all(jinja2.nodes.For):
|
||||
loop_iter = loop_ast.iter
|
||||
|
||||
# Check if iterating over message['content'] or similar
|
||||
if _is_var_or_elems_access(loop_iter, "message", "content"):
|
||||
return "openai" # Found content iteration → openai format
|
||||
|
||||
# Also check for patterns like: {%- for item in msg.content -%} or {%- for item in m.content -%}
|
||||
if _is_var_or_elems_access(
|
||||
loop_iter, "msg", "content"
|
||||
) or _is_var_or_elems_access(loop_iter, "m", "content"):
|
||||
return "openai" # Found content iteration → openai format (glm4v)
|
||||
|
||||
return "string" # No content loops found → string format
|
||||
except Exception as e:
|
||||
logger.debug(f"Error when parsing AST of Jinja template: {e}")
|
||||
return "string"
|
||||
|
||||
|
||||
def process_content_for_template_format(
|
||||
msg_dict: dict,
|
||||
content_format: str,
|
||||
image_data: list,
|
||||
video_data: list,
|
||||
audio_data: list,
|
||||
modalities: list,
|
||||
use_dpsk_v32_encoding: bool = False,
|
||||
) -> dict:
|
||||
"""
|
||||
Process message content based on detected template format.
|
||||
|
||||
Args:
|
||||
msg_dict: Message dictionary with content
|
||||
content_format: 'string' or 'openai' (detected via AST analysis)
|
||||
image_data: List to append extracted image URLs
|
||||
video_data: List to append extracted video URLs
|
||||
audio_data: List to append extracted audio URLs
|
||||
modalities: List to append modalities
|
||||
use_dpsk_v32_encoding: If True, extract multimodal data and convert content to string (for DeepSeek-V3.2 encoding)
|
||||
|
||||
Returns:
|
||||
Processed message dictionary
|
||||
"""
|
||||
if not isinstance(msg_dict.get("content"), list):
|
||||
# Already a string or None, no processing needed
|
||||
return {k: v for k, v in msg_dict.items() if v is not None}
|
||||
|
||||
if content_format == "openai" or use_dpsk_v32_encoding:
|
||||
# OpenAI format: preserve structured content list, normalize types
|
||||
# V32 encoding: extract multimodal data but convert content to string
|
||||
processed_content_parts = []
|
||||
text_parts = []
|
||||
for chunk in msg_dict["content"]:
|
||||
if isinstance(chunk, dict):
|
||||
chunk_type = chunk.get("type")
|
||||
|
||||
if chunk_type in ("image_url", "input_image"):
|
||||
image_obj = chunk.get("image_url") or {}
|
||||
if isinstance(image_obj, str):
|
||||
image_obj = {"url": image_obj, "detail": chunk.get("detail")}
|
||||
mdp = image_obj.get("max_dynamic_patch", None)
|
||||
# Also allow flat style: chunk["max_dynamic_patch"]
|
||||
image_data.append(
|
||||
ImageData(
|
||||
url=image_obj["url"],
|
||||
detail=image_obj.get("detail") or "auto",
|
||||
max_dynamic_patch=mdp,
|
||||
)
|
||||
)
|
||||
|
||||
if chunk.get("modalities"):
|
||||
modalities.append(chunk.get("modalities"))
|
||||
# Normalize to simple 'image' type for template compatibility
|
||||
processed_content_parts.append({"type": "image"})
|
||||
elif chunk_type == "video_url":
|
||||
video_obj = chunk.get("video_url") or {}
|
||||
mdp = video_obj.get("max_dynamic_patch", None)
|
||||
if mdp is None:
|
||||
video_data.append(chunk["video_url"]["url"])
|
||||
else:
|
||||
# Keep structured info for backend, but template only sees {"type":"video"}
|
||||
video_data.append(
|
||||
{
|
||||
"url": video_obj["url"],
|
||||
"max_dynamic_patch": mdp,
|
||||
}
|
||||
)
|
||||
if chunk.get("modalities"):
|
||||
modalities.append(chunk.get("modalities"))
|
||||
# Normalize to simple 'video' type for template compatibility
|
||||
processed_content_parts.append({"type": "video"})
|
||||
elif chunk_type == "audio_url":
|
||||
audio_data.append(chunk["audio_url"]["url"])
|
||||
# Normalize to simple 'audio' type
|
||||
processed_content_parts.append({"type": "audio"})
|
||||
elif chunk_type in ("text", "input_text"):
|
||||
# For v32 encoding, collect text parts separately
|
||||
if use_dpsk_v32_encoding:
|
||||
text_parts.append(chunk["text"])
|
||||
else:
|
||||
# Keep text content as-is for openai format
|
||||
processed_content_parts.append(
|
||||
{"type": "text", "text": chunk["text"]}
|
||||
)
|
||||
elif chunk_type == "tool_reference":
|
||||
# GLM-specific extension: pass through so the chat template
|
||||
# can match tool_reference.name against tools[*].function.name
|
||||
# and render the referenced tool schemas inline.
|
||||
processed_content_parts.append(chunk)
|
||||
|
||||
new_msg = {
|
||||
k: v for k, v in msg_dict.items() if v is not None and k != "content"
|
||||
}
|
||||
if use_dpsk_v32_encoding:
|
||||
new_msg["content"] = " ".join(text_parts) if text_parts else ""
|
||||
else:
|
||||
new_msg["content"] = processed_content_parts
|
||||
return new_msg
|
||||
|
||||
elif content_format == "string":
|
||||
# String format: flatten to text only (for templates like DeepSeek)
|
||||
text_parts = []
|
||||
for chunk in msg_dict["content"]:
|
||||
if isinstance(chunk, dict) and chunk.get("type") in ("text", "input_text"):
|
||||
text_parts.append(chunk["text"])
|
||||
# Note: For string format, we ignore images/audio since the template
|
||||
# doesn't expect structured content - multimodal placeholders would
|
||||
# need to be inserted differently
|
||||
|
||||
new_msg = msg_dict.copy()
|
||||
new_msg["content"] = " ".join(text_parts) if text_parts else ""
|
||||
new_msg = {k: v for k, v in new_msg.items() if v is not None}
|
||||
return new_msg
|
||||
|
||||
else:
|
||||
raise ValueError(f"Invalid content format: {content_format}")
|
||||
Reference in New Issue
Block a user