Skip to content

Commit 86d6317

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

File tree

1 file changed

+70
-65
lines changed

1 file changed

+70
-65
lines changed

vllm/entrypoints/openai/serving_responses.py

Lines changed: 70 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,6 @@ async def responses_full_generator(
309309
assert final_res is not None
310310
assert len(final_res.outputs) == 1
311311
final_output = final_res.outputs[0]
312-
print("-"*70)
313-
print(f"Final output: {final_output}")
314312
if self.reasoning_parser:
315313
try:
316314
reasoning_parser = self.reasoning_parser(tokenizer)
@@ -324,85 +322,92 @@ async def responses_full_generator(
324322
else:
325323
reasoning_content = None
326324
content = final_output.text
327-
328-
outputs = []
329-
output = None
330-
if self.tool_parser:
331-
function_calls: list[FunctionCall] = []
332-
if request.tool_choice and \
333-
isinstance(request.tool_choice,
334-
ToolChoiceFunction):
335-
# Forced Function Call
336-
function_calls.append(
337-
FunctionCall(name=request.tool_choice.name,
338-
arguments=content))
339-
elif request.tool_choice is None or request.tool_choice == "none":
340-
pass
341-
elif request.tool_choice == "required":
342-
assert content is not None
343-
tool_calls = TypeAdapter(
344-
list[FunctionDefinition]).validate_json(content)
345-
function_calls.extend([
346-
FunctionCall(name=tool_call.name,
347-
arguments=json.dumps(tool_call.parameters,
348-
ensure_ascii=False))
349-
for tool_call in tool_calls
350-
])
351-
elif request.tool_choice == "auto":
352-
try:
353-
tool_parser = self.tool_parser(tokenizer)
354-
except RuntimeError as e:
355-
logger.exception("Error in tool parser creation.")
356-
return self.create_error_response(str(e))
357-
tool_call_info = tool_parser.extract_tool_calls(
358-
content if content is not None else "", request=request)
359-
if tool_call_info is not None and tool_call_info.tools_called:
360-
function_calls.extend(
361-
FunctionCall(
362-
name=tool_call.function.name,
363-
arguments=tool_call.function.arguments,
364-
) for tool_call in tool_call_info.tool_calls)
365-
else:
366-
logger.warning(
367-
"Unknown tool choice: %s. "
368-
"Using 'none' as the default tool choice.",
369-
request.tool_choice)
370-
if function_calls:
371-
output = [
372-
ResponseFunctionToolCall(
373-
id=f"fc_{random_fc_uuid()}",
374-
call_id=f"call_{random_uuid()}",
375-
type="function_call",
376-
status="completed",
377-
name=tool_call.name,
378-
arguments=tool_call.arguments,
379-
) for tool_call in function_calls
380-
]
381-
# If no tool call is generated, we still need to return an output.
382-
if reasoning_content and output is None:
383-
output = ResponseReasoningItem(
325+
reasoning_item = None
326+
message_item = None
327+
if reasoning_content:
328+
reasoning_item = ResponseReasoningItem(
384329
text=reasoning_content,
385330
status=None, # NOTE: Only the last output item has status.
386331
)
387-
# If no tool call is generated, we still need to return an output.
388-
if content and output is None:
332+
if content:
389333
output_text = ResponseOutputText(
390334
text=content,
391335
annotations=[], # TODO
392336
type="output_text",
393337
logprobs=None, # TODO
394338
)
395-
output = ResponseOutputMessage(
339+
message_item = ResponseOutputMessage(
396340
id=f"msg_{random_uuid()}",
397341
content=[output_text],
398342
role="assistant",
399343
status="completed",
400344
type="message",
401345
)
402-
if isinstance(output, list):
403-
outputs.extend(output)
346+
outputs = []
347+
function_calls: list[FunctionCall] = []
348+
if (not self.enable_auto_tools or not self.tool_parser):
349+
# Tools are not enabled
350+
if reasoning_item:
351+
outputs.append(reasoning_item)
352+
if message_item:
353+
outputs.append(message_item)
354+
elif request.tool_choice is None or request.tool_choice == "none":
355+
# No tool calls.
356+
if reasoning_item:
357+
outputs.append(reasoning_item)
358+
if message_item:
359+
outputs.append(message_item)
360+
elif request.tool_choice and \
361+
isinstance(request.tool_choice,
362+
ToolChoiceFunction):
363+
# Forced Function Call
364+
function_calls.append(
365+
FunctionCall(name=request.tool_choice.name, arguments=content))
366+
elif request.tool_choice == "required":
367+
assert content is not None
368+
tool_calls = TypeAdapter(
369+
list[FunctionDefinition]).validate_json(content)
370+
function_calls.extend([
371+
FunctionCall(name=tool_call.name,
372+
arguments=json.dumps(tool_call.parameters,
373+
ensure_ascii=False))
374+
for tool_call in tool_calls
375+
])
376+
elif request.tool_choice == "auto":
377+
try:
378+
tool_parser = self.tool_parser(tokenizer)
379+
except RuntimeError as e:
380+
logger.exception("Error in tool parser creation.")
381+
return self.create_error_response(str(e))
382+
tool_call_info = tool_parser.extract_tool_calls(
383+
content if content is not None else "", request=request)
384+
if tool_call_info is not None and tool_call_info.tools_called:
385+
function_calls.extend(
386+
FunctionCall(
387+
name=tool_call.function.name,
388+
arguments=tool_call.function.arguments,
389+
) for tool_call in tool_call_info.tool_calls)
390+
else:
391+
# No tool calls.
392+
if reasoning_item:
393+
outputs.append(reasoning_item)
394+
if message_item:
395+
outputs.append(message_item)
404396
else:
405-
outputs.append(output)
397+
return self.create_error_response(
398+
f"Invalid tool_choice: {request.tool_choice}")
399+
400+
if function_calls:
401+
outputs.extend([
402+
ResponseFunctionToolCall(
403+
id=f"fc_{random_fc_uuid()}",
404+
call_id=f"call_{random_uuid()}",
405+
type="function_call",
406+
status="completed",
407+
name=tool_call.name,
408+
arguments=tool_call.arguments,
409+
) for tool_call in function_calls
410+
])
406411

407412
# Calculate usage.
408413
assert final_res.prompt_token_ids is not None

0 commit comments

Comments
 (0)