william-ricursive commited on
Commit
4f308b9
·
verified ·
1 Parent(s): 240d596

Fix dropped parallel tool calls in speculative streams

Browse files

Apply ripple-inference PR #17 (d68cca40e1). Reconcile streamed tool calls at finish so speculative deltas cannot drop trailing calls or argument suffixes. Validated with CPU parser and chat-format regression tests on stock SGLang 0.5.19.

https://github.com/ricursive/ripple-inference/pull/17

Files changed (1) hide show
  1. inference/server/ripple_format.py +31 -1
inference/server/ripple_format.py CHANGED
@@ -136,7 +136,7 @@ def _reasoning_detector_class():
136
 
137
 
138
  def _tool_detector_class():
139
- from sglang.srt.function_call.core_types import StructureInfo
140
  from sglang.srt.function_call.hermes_detector import HermesDetector
141
 
142
  class RippleToolDetector(HermesDetector):
@@ -147,6 +147,21 @@ def _tool_detector_class():
147
  super().__init__()
148
  self._held_newline = ""
149
  self._in_calls = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  def detect_and_parse(self, text, tools):
152
  res = super().detect_and_parse(text, tools)
@@ -156,7 +171,9 @@ def _tool_detector_class():
156
  return res
157
 
158
  def parse_streaming_increment(self, new_text, tools):
 
159
  res = super().parse_streaming_increment(new_text, tools)
 
160
  if self._in_calls: # text after the first call is dropped, as in detect_and_parse
161
  res.normal_text = ""
162
  return res
@@ -175,6 +192,19 @@ def _tool_detector_class():
175
 
176
  def finish(self, tools):
177
  res = super().finish(tools)
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  if self._in_calls:
179
  res.normal_text = ""
180
  elif self._held_newline:
 
136
 
137
 
138
  def _tool_detector_class():
139
+ from sglang.srt.function_call.core_types import StructureInfo, ToolCallItem
140
  from sglang.srt.function_call.hermes_detector import HermesDetector
141
 
142
  class RippleToolDetector(HermesDetector):
 
147
  super().__init__()
148
  self._held_newline = ""
149
  self._in_calls = False
150
+ # Hermes can leave whole calls buffered when one speculative delta spans
151
+ # multiple calls. Reconcile the full output with emitted deltas at finish.
152
+ self._seen = ""
153
+ self._emitted = {}
154
+
155
+ def _record(self, calls):
156
+ for call in calls:
157
+ emitted = self._emitted.setdefault(call.tool_index, ["", ""])
158
+ emitted[0] += call.name or ""
159
+ emitted[1] += call.parameters or ""
160
+ # SGLang's serving layer also flushes arguments after finish(). Keep
161
+ # its accounting current so it cannot send the same suffix twice.
162
+ while len(self.streamed_args_for_tool) <= call.tool_index:
163
+ self.streamed_args_for_tool.append("")
164
+ self.streamed_args_for_tool[call.tool_index] = emitted[1]
165
 
166
  def detect_and_parse(self, text, tools):
167
  res = super().detect_and_parse(text, tools)
 
171
  return res
172
 
173
  def parse_streaming_increment(self, new_text, tools):
174
+ self._seen += new_text
175
  res = super().parse_streaming_increment(new_text, tools)
176
+ self._record(res.calls)
177
  if self._in_calls: # text after the first call is dropped, as in detect_and_parse
178
  res.normal_text = ""
179
  return res
 
192
 
193
  def finish(self, tools):
194
  res = super().finish(tools)
195
+ self._record(res.calls)
196
+ if self.bot_token in self._seen:
197
+ for i, call in enumerate(self.detect_and_parse(self._seen, tools).calls):
198
+ name, args = self._emitted.get(i, ["", ""])
199
+ want = call.parameters or ""
200
+ if want.startswith(args):
201
+ suffix = want[len(args):]
202
+ if suffix or not name:
203
+ item = ToolCallItem(
204
+ tool_index=i, name=None if name else call.name, parameters=suffix,
205
+ )
206
+ res.calls.append(item)
207
+ self._record([item])
208
  if self._in_calls:
209
  res.normal_text = ""
210
  elif self._held_newline: