2626 Tool ,
2727 ToolParser ,
2828)
29+ from aphrodite .tool_parsers .utils import partial_tag_overlap
2930
3031logger = init_logger (__name__ )
3132
@@ -54,8 +55,8 @@ def __init__(self, tokenizer: TokenizerLike, tools: list[Tool] | None = None):
5455 self .tool_call_start_token : str = "<|DSML|function_calls>"
5556
5657 # Streaming state
57- self .is_tool_call_started : bool = False
5858 self .current_tool_index : int = 0
59+ self ._sent_content_idx : int = 0
5960
6061 # Regex patterns for complete parsing
6162 self .tool_call_complete_regex = re .compile (r"<|DSML|function_calls>(.*?)</|DSML|function_calls>" , re .DOTALL )
@@ -202,7 +203,7 @@ def extract_tool_calls(
202203 def _reset_streaming_state (self ):
203204 """Reset all streaming state."""
204205 self .current_tool_index = 0
205- self .is_tool_call_started = False
206+ self ._sent_content_idx = 0
206207 self .prev_tool_call_arr .clear ()
207208 self .streamed_args_for_tool .clear ()
208209
@@ -245,6 +246,24 @@ def _extract_delta_tool_calls(
245246
246247 return delta_tool_calls
247248
249+ def _extract_content (self , current_text : str ) -> str | None :
250+ """Return unsent non-tool-call text, or None.
251+
252+ Holds back any suffix that could be a partial start marker
253+ so that split markers are never leaked as content.
254+ """
255+ if self .tool_call_start_token not in current_text :
256+ overlap = partial_tag_overlap (current_text , self .tool_call_start_token )
257+ sendable_idx = len (current_text ) - overlap
258+ else :
259+ sendable_idx = current_text .index (self .tool_call_start_token )
260+
261+ if sendable_idx > self ._sent_content_idx :
262+ content = current_text [self ._sent_content_idx : sendable_idx ]
263+ self ._sent_content_idx = sendable_idx
264+ return content
265+ return None
266+
248267 def extract_tool_calls_streaming (
249268 self ,
250269 previous_text : str ,
@@ -266,29 +285,11 @@ def extract_tool_calls_streaming(
266285 if not previous_text :
267286 self ._reset_streaming_state ()
268287
269- # Detect whether we've entered the tool-call region.
270- # Use current_text (not delta_text) since the start token may
271- # be split across chunks.
272- content_before = None
273- if self .is_tool_call_started :
274- pass
275- elif self .tool_call_start_token in current_text :
276- # Tool-call region found, capture any plain text before it.
277- self .is_tool_call_started = True
278- start_idx = current_text .index (self .tool_call_start_token )
279- content_before = current_text [len (previous_text ) : start_idx ] or None
280- else :
281- # Still in plain-text region, forward as content.
282- return DeltaMessage (content = delta_text ) if delta_text else None
283-
284- # Inside tool-call region: emit any newly completed invokes.
288+ content = self ._extract_content (current_text )
285289 delta_tool_calls = self ._extract_delta_tool_calls (current_text , request )
286290
287- if delta_tool_calls or content_before :
288- return DeltaMessage (
289- content = content_before ,
290- tool_calls = delta_tool_calls ,
291- )
291+ if delta_tool_calls or content :
292+ return DeltaMessage (content = content , tool_calls = delta_tool_calls )
292293
293294 # Empty delta with token ids means EOS or closing tag; return
294295 # non-None so the serving framework can finalize finish_reason.
0 commit comments