Skip to content

Commit 7862ad6

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

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

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

0 commit comments

Comments
 (0)