Files
vLLM/vllm/parser/utils.py
T
2026-10-10 06:50:34 +00:00

151 lines
4.8 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from collections.abc import Iterable, Sequence
from openai.types.responses import ResponseFunctionToolCall
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
from vllm.entrypoints.openai.responses.protocol import (
ResponseInputOutputItem,
ResponsesRequest,
)
from vllm.renderers.chat_utils import ChatCompletionMessageParam
def count_tool_calls(tool_calls: object) -> int:
if tool_calls is None:
return 0
if isinstance(tool_calls, (str, bytes, dict)):
return 1
if isinstance(tool_calls, Iterable):
return sum(1 for _ in tool_calls)
return 1
def count_chat_history_tool_calls(
messages: Sequence[ChatCompletionMessageParam],
) -> int:
return sum(
count_tool_calls(msg.get("tool_calls"))
for msg in messages
if isinstance(msg, dict) and msg.get("role") == "assistant"
)
def count_response_history_tool_calls(
response_items: Sequence[ResponseInputOutputItem],
) -> int:
count = 0
for item in response_items:
if isinstance(item, ResponseFunctionToolCall):
count += 1
continue
if isinstance(item, dict):
item_type = item.get("type")
if item_type == "function_call":
count += 1
elif item.get("role") == "assistant":
count += count_tool_calls(item.get("tool_calls"))
return count
def count_history_tool_calls(
request: ChatCompletionRequest | ResponsesRequest,
) -> int:
if isinstance(request, ChatCompletionRequest):
return count_chat_history_tool_calls(request.messages)
request_input = request.input
if isinstance(request_input, str):
return 0
return count_response_history_tool_calls(request_input)
_JSON_WS = " \t\r\n"
def scan_json_object_end(raw: str, start: int) -> int | None:
"""Return the end index (exclusive) of the JSON object starting at
``raw[start]`` (which must be ``{``), or ``None`` when it is still
unterminated. String- and escape-aware so braces inside strings do not
mislead the depth count."""
depth = 0
in_string = False
escape = False
for i in range(start, len(raw)):
ch = raw[i]
if escape:
escape = False
continue
if in_string:
if ch == "\\":
escape = True
elif ch == '"':
in_string = False
continue
if ch == '"':
in_string = True
elif ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return i + 1
return None
def extract_json_member_object(raw: str, key: str) -> str | None:
"""Extract the raw text span of the top-level object-valued member ``key``
from a (possibly incomplete) ``{...}`` JSON wrapper.
Returns the verbatim substring (prefix-stable as the input grows, which
the engine's argument-delta diffing relies on), possibly an unterminated
object prefix; ``None`` when the value has not started. Raises
``ValueError`` when the member is present but its value is not a JSON
object. A verbatim substring (not re-serialized JSON) is required because
the engine diffs successive converter outputs and expects each to extend
the previous one; the scanner is string/escape-aware so a matching literal
inside another value cannot mislead it, and it recovers a partial span when
the wrapper is truncated (where ``json.loads`` would fail)."""
depth = 0
in_string = False
escape = False
string_start = -1
last_string: str | None = None
for i, ch in enumerate(raw):
if escape:
escape = False
continue
if in_string:
if ch == "\\":
escape = True
elif ch == '"':
in_string = False
if depth == 1:
last_string = raw[string_start + 1 : i]
continue
if ch == '"':
in_string = True
string_start = i
elif ch == ":" and depth == 1 and last_string == key:
value_start = i + 1
while value_start < len(raw) and raw[value_start] in _JSON_WS:
value_start += 1
if value_start >= len(raw):
return None
if raw[value_start] != "{":
raise ValueError(f"member {key!r} must be a JSON object")
value_end = scan_json_object_end(raw, value_start)
if value_end is None:
return raw[value_start:]
return raw[value_start:value_end]
elif ch in "{[":
depth += 1
elif ch in "}]":
depth -= 1
return None