Skip to content

Commit 06e802e

Browse files
committed
[Frontend] OpenAI Responses API supports Tool/Function calling
Signed-off-by: chaunceyjiang <chaunceyjiang@gmail.com>
1 parent f2b7dde commit 06e802e

File tree

2 files changed

+45
-103
lines changed

2 files changed

+45
-103
lines changed

vllm/entrypoints/openai/serving_responses.py

Lines changed: 2 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,6 @@
1414

1515
import jinja2
1616
from fastapi import Request
17-
from openai.types.chat import (
18-
ChatCompletionAssistantMessageParam,
19-
ChatCompletionMessageToolCallParam,
20-
ChatCompletionToolMessageParam,
21-
)
22-
from openai.types.chat.chat_completion_message_tool_call_param import (
23-
Function as FunctionCallTool,
24-
)
2517
from openai.types.responses import (
2618
ResponseCodeInterpreterCallCodeDeltaEvent,
2719
ResponseCodeInterpreterCallCodeDoneEvent,
@@ -49,7 +41,6 @@
4941
ResponseWebSearchCallCompletedEvent,
5042
ResponseWebSearchCallInProgressEvent,
5143
ResponseWebSearchCallSearchingEvent,
52-
ToolChoiceFunction,
5344
response_function_web_search,
5445
response_text_delta_event,
5546
)
@@ -59,7 +50,6 @@
5950
)
6051
from openai.types.responses.tool import Tool
6152
from openai_harmony import Message as OpenAIHarmonyMessage
62-
from pydantic import TypeAdapter
6353

6454
from vllm import envs
6555
from vllm.engine.protocol import EngineClient
@@ -89,15 +79,12 @@
8979
from vllm.entrypoints.openai.protocol import (
9080
DeltaMessage,
9181
ErrorResponse,
92-
FunctionCall,
93-
FunctionDefinition,
9482
InputTokensDetails,
9583
OutputTokensDetails,
9684
RequestResponseMetadata,
9785
ResponseCompletedEvent,
9886
ResponseCreatedEvent,
9987
ResponseInProgressEvent,
100-
ResponseInputOutputItem,
10188
ResponseReasoningPartAddedEvent,
10289
ResponseReasoningPartDoneEvent,
10390
ResponsesRequest,
@@ -107,6 +94,7 @@
10794
)
10895
from vllm.entrypoints.openai.serving_engine import OpenAIServing
10996
from vllm.entrypoints.openai.serving_models import OpenAIServingModels
97+
from vllm.entrypoints.responses_utils import construct_chat_message_with_tool_call
11098
from vllm.entrypoints.tool_server import ToolServer
11199
from vllm.inputs.data import TokensPrompt as EngineTokensPrompt
112100
from vllm.logger import init_logger
@@ -876,95 +864,6 @@ def _make_response_output_items(
876864
outputs.extend(tool_call_items)
877865
return outputs
878866

879-
def _parse_tool_calls_from_content(
880-
self,
881-
request: ResponsesRequest,
882-
tokenizer: AnyTokenizer,
883-
content: str | None = None,
884-
) -> tuple[list[FunctionCall] | None, str | None]:
885-
function_calls = list[FunctionCall]()
886-
887-
if not self.enable_auto_tools or not self.tool_parser:
888-
# Tools are not enabled
889-
return None, content
890-
elif request.tool_choice is None:
891-
# No tool calls.
892-
return None, content
893-
elif request.tool_choice and isinstance(
894-
request.tool_choice, ToolChoiceFunction
895-
):
896-
# Forced Function Call
897-
function_calls.append(
898-
FunctionCall(name=request.tool_choice.name, arguments=content)
899-
)
900-
content = None # Clear content since tool is called.
901-
elif request.tool_choice == "required":
902-
assert content is not None
903-
tool_calls = TypeAdapter(list[FunctionDefinition]).validate_json(content)
904-
function_calls.extend(
905-
[
906-
FunctionCall(
907-
name=tool_call.name,
908-
arguments=json.dumps(tool_call.parameters, ensure_ascii=False),
909-
)
910-
for tool_call in tool_calls
911-
]
912-
)
913-
content = None # Clear content since tool is called.
914-
elif request.tool_choice == "auto" or request.tool_choice == "none":
915-
try:
916-
tool_parser = self.tool_parser(tokenizer)
917-
except RuntimeError as e:
918-
logger.exception("Error in tool parser creation.")
919-
raise e
920-
tool_call_info = tool_parser.extract_tool_calls(
921-
content if content is not None else "",
922-
request=request, # type: ignore
923-
)
924-
if tool_call_info is not None and tool_call_info.tools_called:
925-
# extract_tool_calls() returns a list of tool calls.
926-
function_calls.extend(
927-
FunctionCall(
928-
name=tool_call.function.name,
929-
arguments=tool_call.function.arguments,
930-
)
931-
for tool_call in tool_call_info.tool_calls
932-
)
933-
content = tool_call_info.content
934-
else:
935-
# No tool calls.
936-
return None, content
937-
else:
938-
raise ValueError(f"Invalid tool_choice: {request.tool_choice}")
939-
return function_calls, content
940-
941-
def _construct_chat_message_with_tool_call(
942-
self, item: ResponseInputOutputItem
943-
) -> ChatCompletionMessageParam:
944-
if isinstance(item, ResponseFunctionToolCall):
945-
# Append the function call as a tool call.
946-
return ChatCompletionAssistantMessageParam(
947-
role="assistant",
948-
tool_calls=[
949-
ChatCompletionMessageToolCallParam(
950-
id=item.call_id,
951-
function=FunctionCallTool(
952-
name=item.name,
953-
arguments=item.arguments,
954-
),
955-
type="function",
956-
)
957-
],
958-
)
959-
elif item.get("type") == "function_call_output":
960-
# Append the function call output as a tool message.
961-
return ChatCompletionToolMessageParam(
962-
role="tool",
963-
content=item.get("output"),
964-
tool_call_id=item.get("call_id"),
965-
)
966-
return item # type: ignore
967-
968867
def _make_response_output_items_with_harmony(
969868
self,
970869
context: HarmonyContext,
@@ -1017,7 +916,7 @@ def _construct_input_messages(
1017916
messages.append({"role": "user", "content": request.input})
1018917
else:
1019918
for item in request.input:
1020-
messages.append(self._construct_chat_message_with_tool_call(item))
919+
messages.append(construct_chat_message_with_tool_call(item))
1021920
return messages
1022921

1023922
def _construct_harmony_system_input_message(
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
4+
from openai.types.chat.chat_completion_message_tool_call_param import (
5+
Function as FunctionCallTool,
6+
)
7+
from openai.types.responses import ResponseFunctionToolCall
8+
9+
from vllm.entrypoints.openai.protocol import (
10+
ChatCompletionAssistantMessageParam,
11+
ChatCompletionMessageParam,
12+
ChatCompletionMessageToolCallParam,
13+
ChatCompletionToolMessageParam,
14+
ResponseInputOutputItem,
15+
)
16+
17+
18+
def construct_chat_message_with_tool_call(
19+
item: ResponseInputOutputItem,
20+
) -> ChatCompletionMessageParam:
21+
if isinstance(item, ResponseFunctionToolCall):
22+
# Append the function call as a tool call.
23+
return ChatCompletionAssistantMessageParam(
24+
role="assistant",
25+
tool_calls=[
26+
ChatCompletionMessageToolCallParam(
27+
id=item.call_id,
28+
function=FunctionCallTool(
29+
name=item.name,
30+
arguments=item.arguments,
31+
),
32+
type="function",
33+
)
34+
],
35+
)
36+
elif item.get("type") == "function_call_output":
37+
# Append the function call output as a tool message.
38+
return ChatCompletionToolMessageParam(
39+
role="tool",
40+
content=item.get("output"),
41+
tool_call_id=item.get("call_id"),
42+
)
43+
return item # type: ignore

0 commit comments

Comments
 (0)