model: step3-vl-10b (#17513)
This commit is contained in:
@@ -17,6 +17,7 @@ from sglang.srt.function_call.deepseekv32_detector import DeepSeekV32Detector
|
||||
from sglang.srt.function_call.glm4_moe_detector import Glm4MoeDetector
|
||||
from sglang.srt.function_call.glm47_moe_detector import Glm47MoeDetector
|
||||
from sglang.srt.function_call.gpt_oss_detector import GptOssDetector
|
||||
from sglang.srt.function_call.hermes_detector import HermesDetector
|
||||
from sglang.srt.function_call.internlm_detector import InternlmDetector
|
||||
from sglang.srt.function_call.kimik2_detector import KimiK2Detector
|
||||
from sglang.srt.function_call.lfm2_detector import Lfm2Detector
|
||||
@@ -64,6 +65,7 @@ class FunctionCallParser:
|
||||
"minimax-m2": MinimaxM2Detector,
|
||||
"trinity": TrinityDetector,
|
||||
"interns1": InternlmDetector,
|
||||
"hermes": HermesDetector,
|
||||
}
|
||||
|
||||
def __init__(self, tools: List[Tool], tool_call_parser: str):
|
||||
|
||||
120
python/sglang/srt/function_call/hermes_detector.py
Normal file
120
python/sglang/srt/function_call/hermes_detector.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import List
|
||||
|
||||
from sglang.srt.entrypoints.openai.protocol import Tool
|
||||
from sglang.srt.function_call.base_format_detector import BaseFormatDetector
|
||||
from sglang.srt.function_call.core_types import (
|
||||
StreamingParseResult,
|
||||
StructureInfo,
|
||||
_GetInfoFunc,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HermesDetector(BaseFormatDetector):
|
||||
"""
|
||||
Detector for Hermes tool call format.
|
||||
|
||||
Format:
|
||||
<tool_call>{"name": "...", "arguments": {...}}</tool_call>
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.bot_token = "<tool_call>"
|
||||
self.eot_token = "</tool_call>"
|
||||
self.tool_call_regex = re.compile(
|
||||
r"<tool_call>(.*?)</tool_call>|<tool_call>(.*)", re.DOTALL
|
||||
)
|
||||
self._normal_text_buffer = ""
|
||||
|
||||
def has_tool_call(self, text: str) -> bool:
|
||||
return self.bot_token in text
|
||||
|
||||
def detect_and_parse(self, text: str, tools: List[Tool]) -> StreamingParseResult:
|
||||
"""
|
||||
One-time parsing: Detects and parses tool calls in the provided text.
|
||||
"""
|
||||
idx = text.find(self.bot_token)
|
||||
normal_text = text[:idx].strip() if idx != -1 else text
|
||||
if self.bot_token not in text:
|
||||
return StreamingParseResult(normal_text=normal_text, calls=[])
|
||||
|
||||
calls = []
|
||||
try:
|
||||
for match in self.tool_call_regex.findall(text):
|
||||
raw = match[0] or match[1]
|
||||
if not raw:
|
||||
continue
|
||||
parsed = json.loads(raw.strip())
|
||||
if isinstance(parsed, list):
|
||||
calls.extend(self.parse_base_json(parsed, tools))
|
||||
else:
|
||||
calls.extend(self.parse_base_json(parsed, tools))
|
||||
return StreamingParseResult(normal_text=normal_text, calls=calls)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in detect_and_parse: {e}")
|
||||
return StreamingParseResult(normal_text=text)
|
||||
|
||||
def _clean_normal_text(self, text: str) -> str:
|
||||
if not text:
|
||||
return text
|
||||
|
||||
self._normal_text_buffer += text
|
||||
|
||||
if self.eot_token in self._normal_text_buffer:
|
||||
cleaned = self._normal_text_buffer.replace(self.eot_token, "")
|
||||
self._normal_text_buffer = ""
|
||||
return cleaned
|
||||
|
||||
partial_len = self._ends_with_partial_token(
|
||||
self._normal_text_buffer, self.eot_token
|
||||
)
|
||||
if partial_len:
|
||||
safe_text = self._normal_text_buffer[:-partial_len]
|
||||
self._normal_text_buffer = self._normal_text_buffer[-partial_len:]
|
||||
return safe_text
|
||||
|
||||
cleaned = self._normal_text_buffer
|
||||
self._normal_text_buffer = ""
|
||||
return cleaned
|
||||
|
||||
def parse_streaming_increment(
|
||||
self, new_text: str, tools: List[Tool]
|
||||
) -> StreamingParseResult:
|
||||
"""
|
||||
Streaming parsing: handle normal text, partial tags, and tool calls.
|
||||
"""
|
||||
self._buffer += new_text
|
||||
current_text = self._buffer
|
||||
|
||||
if self.bot_token not in current_text:
|
||||
partial_len = self._ends_with_partial_token(current_text, self.bot_token)
|
||||
if partial_len:
|
||||
safe_text = current_text[:-partial_len]
|
||||
self._buffer = current_text[-partial_len:]
|
||||
else:
|
||||
safe_text = current_text
|
||||
self._buffer = ""
|
||||
return StreamingParseResult(normal_text=self._clean_normal_text(safe_text))
|
||||
|
||||
bot_pos = current_text.find(self.bot_token)
|
||||
if bot_pos > 0:
|
||||
normal_text = current_text[:bot_pos]
|
||||
self._buffer = current_text[bot_pos:]
|
||||
return StreamingParseResult(normal_text=normal_text)
|
||||
|
||||
result = super().parse_streaming_increment(new_text="", tools=tools)
|
||||
if result.normal_text:
|
||||
result.normal_text = self._clean_normal_text(result.normal_text)
|
||||
return result
|
||||
|
||||
def structure_info(self) -> _GetInfoFunc:
|
||||
return lambda name: StructureInfo(
|
||||
begin='<tool_call>{"name":"' + name + '", "arguments":',
|
||||
end="}</tool_call>",
|
||||
trigger="<tool_call>",
|
||||
)
|
||||
Reference in New Issue
Block a user