[Bugfix][Frontend] Fix Inkling tool name leaking into content after reasoning (#58792)

Signed-off-by: Baljinder Hothi <baljinder.hothi@cohere.com>
Co-authored-by: Flora Feng <4florafeng@gmail.com>
This commit is contained in:
Baljinder Hothi
2026-09-26 00:38:52 +00:00
committed by GitHub
co-authored by Flora Feng
parent da1aaec31f
commit fdcce47e9b
2 changed files with 38 additions and 3 deletions
+34
View File
@@ -23,8 +23,13 @@ from vllm.entrypoints.openai.chat_completion.protocol import (
ChatCompletionToolsParam,
FunctionDefinition,
)
from vllm.parser.abstract_parser import DelegatingParser
from vllm.parser.engine.events import EventType
from vllm.parser.engine.parser_engine_config import ParserState
from vllm.parser.engine.registered_adapters import (
InklingParserReasoningAdapter,
InklingParserToolAdapter,
)
from vllm.parser.engine.streaming_parser_engine import StreamingParserEngine
from vllm.parser.inkling import InklingParser, _inkling_arg_converter, inkling_config
from vllm.parser.parser_manager import ParserManager
@@ -166,6 +171,11 @@ def _delegating(mock_tokenizer, tools=None):
return parser_cls(mock_tokenizer, tools or [])
class _TwoPassInklingParser(DelegatingParser):
reasoning_parser_cls = InklingParserReasoningAdapter
tool_parser_cls = InklingParserToolAdapter
def _stream_delegating(parser, request, text, chunk_size, prompt_token_ids):
"""Stream ``text`` through ``DelegatingParser.parse_delta``, ``chunk_size``
tokens per delta; return ``(content, reasoning, ordered tool names,
@@ -759,6 +769,30 @@ class TestDelegatingTwoPass:
assert TOOL_JSON not in content
assert END_MESSAGE not in content
def test_function_name_not_in_content_after_reasoning(
self, mock_tokenizer, mock_request
):
name = "search_documents_by_keyword"
tools = [_function_tool(name)]
mock_request.tools = tools
text = (
f"{THINK_START} context.{END_MESSAGE}{MSG_MODEL}"
f"{name}{_tool_block(name, '{}')}"
)
content, reasoning, names, args = _stream_delegating(
_TwoPassInklingParser(mock_tokenizer, tools),
mock_request,
text,
chunk_size=12,
prompt_token_ids=self.GEN_PROMPT,
)
assert reasoning == " context."
assert content == ""
assert names == [name]
assert json.loads(args[0]) == {}
@pytest.mark.parametrize("opener", [TEXT_START, TOOL_TEXT, TOOL_ERROR])
@pytest.mark.parametrize("chunk_size", [1, 3, 64])
def test_visible_text_then_tool_streaming(
+4 -3
View File
@@ -167,11 +167,12 @@ def inkling_config() -> ParserEngineConfig:
(EventType.REASONING_END, EventType.TOOL_CALL_START),
),
}
# Block-end terminals behave identically regardless of label. A
# closed tool block returns to CONTENT (Inkling has no section wrapper;
# A closed tool block returns to CONTENT (Inkling has no section wrapper;
# blocks of any kind may follow), which also keeps the block-kind
# and role tokens out of the engine's tool-terminal set so the
# skip_tool_parsing reasoning pass still classifies reasoning.
# In a reasoning block, THINK_END defers REASONING_END to the next block
# opener so the function-name header is not emitted as content.
for end in ("THINK_END", "END_SAMPLING"):
transitions[(ParserState.CONTENT, end)] = Transition(
ParserState.CONTENT,
@@ -179,7 +180,7 @@ def inkling_config() -> ParserEngineConfig:
)
transitions[(ParserState.REASONING, end)] = Transition(
ParserState.CONTENT,
(EventType.REASONING_END,),
() if end == "THINK_END" else (EventType.REASONING_END,),
)
transitions[(ParserState.TOOL_ARGS, end)] = Transition(
ParserState.CONTENT,