A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 3, 17:45 UTC
[Refactor] Use shared utils in hermes tool parser (#42570)
Signed-off-by: sfeng33 <4florafeng@gmail.com>
Flora Feng committed
May 13, 2026 at 20:35 UTC
1087676a905d283aa71731fd6b94e10e91f58add
1 file changed
+11
-34
vllm/tool_parsers/hermes_tool_parser.py
+11
-34
@@ -25,33 +25,20 @@ from vllm.tool_parsers.abstract_tool_parser import (
25
Tool,
26
ToolParser,
27
)
28
+from vllm.tool_parsers.utils import is_complete_json, partial_tag_overlap
29
from vllm.utils.mistral import is_mistral_tokenizer
30
31
logger = init_logger(__name__)
32
33
33
-def _partial_tag_overlap(text: str, tag: str) -> int:
34
- """Length of the longest prefix of `tag` that matches a suffix of `text`.
35
-
36
- E.g. text ending in "<tool_" returns 6 when tag is "<tool_call>".
37
- Returns 0 if there is no overlap.
38
- """
39
- max_check = min(len(tag) - 1, len(text))
40
- for k in range(max_check, 0, -1):
41
- if text.endswith(tag[:k]):
42
- return k
43
- return 0
44
-
45
-
46
-def _is_valid_json(text: str) -> bool:
47
- try:
48
- json.loads(text)
49
- return True
50
- except (json.JSONDecodeError, ValueError):
51
- return False
52
-
53
-
34
class Hermes2ProToolParser(ToolParser):
35
+ tool_call_start_token: str = "<tool_call>"
36
+ tool_call_end_token: str = "</tool_call>"
37
+ tool_call_regex = re.compile(
38
+ r"<tool_call>(.*?)</tool_call>|<tool_call>(.*)", re.DOTALL
39
+ )
40
+ scratch_pad_regex = re.compile(r"<scratch_pad>(.*?)</scratch_pad>", re.DOTALL)
41
+
42
def __init__(self, tokenizer: TokenizerLike, tools: list[Tool] | None = None):
43
super().__init__(tokenizer, tools)
44
@@ -59,16 +46,6 @@ class Hermes2ProToolParser(ToolParser):
46
logger.error("Detected Mistral tokenizer when using a Hermes model")
47
self.model_tokenizer = tokenizer.tokenizer
48
62
- self.tool_call_start_token: str = "<tool_call>"
63
- self.tool_call_end_token: str = "</tool_call>"
64
-
65
- self.tool_call_regex = re.compile(
66
- r"<tool_call>(.*?)</tool_call>|<tool_call>(.*)", re.DOTALL
67
- )
68
- self.scratch_pad_regex = re.compile(
69
- r"<scratch_pad>(.*?)</scratch_pad>", re.DOTALL
70
- )
71
-
49
if not self.model_tokenizer:
50
raise ValueError(
51
"The model tokenizer must be passed to the ToolParser "
@@ -147,7 +124,7 @@ class Hermes2ProToolParser(ToolParser):
124
Holds back any suffix that could be a partial <tool_call> tag.
125
"""
126
if self.tool_call_start_token not in current_text:
150
- overlap_length = _partial_tag_overlap(
127
+ overlap_length = partial_tag_overlap(
128
current_text, self.tool_call_start_token
129
)
130
sendable_idx = len(current_text) - overlap_length
@@ -176,13 +153,13 @@ class Hermes2ProToolParser(ToolParser):
153
else:
154
raw = text[json_start:]
155
# Strip partial </tool_call> suffix if present.
179
- overlap = _partial_tag_overlap(raw, self.tool_call_end_token)
156
+ overlap = partial_tag_overlap(raw, self.tool_call_end_token)
157
if overlap:
158
raw = raw[:-overlap]
159
tc_json = raw.strip()
160
# Valid JSON without closing tag = complete body,
161
# tag tokens just haven't arrived yet.
185
- is_complete = _is_valid_json(tc_json) if tc_json else False
162
+ is_complete = is_complete_json(tc_json) if tc_json else False
163
results.append((tc_json, is_complete))
164
break
165
return results