[Refactor] Deprecate ResponsesParser wrapper, inline parsing into ParsableContext (#45431)
Signed-off-by: sfeng33 <4florafeng@gmail.com>
This commit is contained in:
+72
-99
@@ -1,10 +1,9 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Unit tests for ResponsesParser with the unified Parser interface.
|
||||
"""Unit tests for ParsableContext's parsing behavior.
|
||||
|
||||
These tests verify that ResponsesParser correctly delegates to the unified
|
||||
Parser (via parse) instead of calling separate ReasoningParser / ToolParser
|
||||
instances directly.
|
||||
These tests verify that ParsableContext correctly delegates to the unified
|
||||
Parser (via parse) and properly builds response output items.
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
@@ -18,12 +17,9 @@ from vllm.entrypoints.openai.engine.protocol import (
|
||||
FunctionCall,
|
||||
ToolCall,
|
||||
)
|
||||
from vllm.entrypoints.openai.parser.responses_parser import (
|
||||
ResponsesParser,
|
||||
get_responses_parser_for_simple_context,
|
||||
)
|
||||
from vllm.entrypoints.openai.responses.context import ParsableContext
|
||||
from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
|
||||
from vllm.outputs import CompletionOutput
|
||||
from vllm.outputs import CompletionOutput, RequestOutput
|
||||
from vllm.parser.abstract_parser import DelegatingParser
|
||||
|
||||
pytestmark = pytest.mark.skip_global_cleanup
|
||||
@@ -162,32 +158,42 @@ def _make_request(**overrides) -> ResponsesRequest:
|
||||
return ResponsesRequest.model_validate(defaults)
|
||||
|
||||
|
||||
def _make_output(
|
||||
def _make_request_output(
|
||||
text: str = "Hello, world!",
|
||||
token_ids: Sequence[int] = (1, 2, 3),
|
||||
finish_reason: str = "stop",
|
||||
) -> CompletionOutput:
|
||||
return CompletionOutput(
|
||||
index=0,
|
||||
text=text,
|
||||
token_ids=list(token_ids),
|
||||
cumulative_logprob=None,
|
||||
logprobs=None,
|
||||
finish_reason=finish_reason,
|
||||
) -> RequestOutput:
|
||||
return RequestOutput(
|
||||
request_id="test",
|
||||
prompt=None,
|
||||
prompt_token_ids=[],
|
||||
prompt_logprobs=None,
|
||||
outputs=[
|
||||
CompletionOutput(
|
||||
index=0,
|
||||
text=text,
|
||||
token_ids=list(token_ids),
|
||||
cumulative_logprob=None,
|
||||
logprobs=None,
|
||||
finish_reason=finish_reason,
|
||||
)
|
||||
],
|
||||
finished=True,
|
||||
)
|
||||
|
||||
|
||||
def _make_parser(parser_cls, **overrides):
|
||||
def _make_context(parser_cls, **overrides):
|
||||
defaults = dict(
|
||||
tokenizer=MagicMock(),
|
||||
parser_cls=parser_cls,
|
||||
response_messages=[],
|
||||
request=_make_request(),
|
||||
available_tools=None,
|
||||
chat_template=None,
|
||||
chat_template_content_format="auto",
|
||||
)
|
||||
defaults.update(overrides)
|
||||
return ResponsesParser(**defaults)
|
||||
return ParsableContext(**defaults)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -197,22 +203,22 @@ def _make_parser(parser_cls, **overrides):
|
||||
|
||||
def test_process_text_with_parser():
|
||||
"""Parser with no reasoning/tools returns a single message item."""
|
||||
parser = _make_parser(_NoOpParser)
|
||||
parser.process(_make_output(text="Hello!"))
|
||||
ctx = _make_context(_NoOpParser)
|
||||
ctx.append_output(_make_request_output(text="Hello!"))
|
||||
|
||||
assert len(parser.response_messages) == 1
|
||||
msg = parser.response_messages[0]
|
||||
assert len(ctx.response_messages) == 1
|
||||
msg = ctx.response_messages[0]
|
||||
assert msg.type == "message"
|
||||
assert msg.content[0].text == "Hello!"
|
||||
|
||||
|
||||
def test_process_text_without_parser():
|
||||
"""parser_cls=None falls back to plain text wrapping."""
|
||||
parser = _make_parser(None)
|
||||
parser.process(_make_output(text="Hello!"))
|
||||
ctx = _make_context(None)
|
||||
ctx.append_output(_make_request_output(text="Hello!"))
|
||||
|
||||
assert len(parser.response_messages) == 1
|
||||
msg = parser.response_messages[0]
|
||||
assert len(ctx.response_messages) == 1
|
||||
msg = ctx.response_messages[0]
|
||||
assert msg.type == "message"
|
||||
assert msg.content[0].text == "Hello!"
|
||||
|
||||
@@ -224,18 +230,18 @@ def test_process_text_without_parser():
|
||||
|
||||
def test_process_empty_text_without_parser():
|
||||
"""Empty text with no parser produces no output items."""
|
||||
parser = _make_parser(None)
|
||||
parser.process(_make_output(text=""))
|
||||
ctx = _make_context(None)
|
||||
ctx.append_output(_make_request_output(text=""))
|
||||
|
||||
assert len(parser.response_messages) == 0
|
||||
assert len(ctx.response_messages) == 0
|
||||
|
||||
|
||||
def test_process_empty_text_with_parser():
|
||||
"""Empty text with parser produces no output items."""
|
||||
parser = _make_parser(_NoOpParser)
|
||||
parser.process(_make_output(text=""))
|
||||
ctx = _make_context(_NoOpParser)
|
||||
ctx.append_output(_make_request_output(text=""))
|
||||
|
||||
assert len(parser.response_messages) == 0
|
||||
assert len(ctx.response_messages) == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -245,26 +251,28 @@ def test_process_empty_text_with_parser():
|
||||
|
||||
def test_process_extracts_reasoning():
|
||||
"""Parser that finds reasoning produces both reasoning and message items."""
|
||||
parser = _make_parser(_ReasoningOnlyParser)
|
||||
parser.process(_make_output(text="<think>Let me check</think>The answer is 42"))
|
||||
ctx = _make_context(_ReasoningOnlyParser)
|
||||
ctx.append_output(
|
||||
_make_request_output(text="<think>Let me check</think>The answer is 42")
|
||||
)
|
||||
|
||||
types = [m.type for m in parser.response_messages]
|
||||
types = [m.type for m in ctx.response_messages]
|
||||
assert "reasoning" in types
|
||||
assert "message" in types
|
||||
|
||||
reasoning_item = next(m for m in parser.response_messages if m.type == "reasoning")
|
||||
reasoning_item = next(m for m in ctx.response_messages if m.type == "reasoning")
|
||||
assert reasoning_item.content[0].text == "Let me check"
|
||||
|
||||
message_item = next(m for m in parser.response_messages if m.type == "message")
|
||||
message_item = next(m for m in ctx.response_messages if m.type == "message")
|
||||
assert message_item.content[0].text == "The answer is 42"
|
||||
|
||||
|
||||
def test_process_reasoning_only_no_content():
|
||||
"""When reasoning consumes all text, only a reasoning item is produced."""
|
||||
parser = _make_parser(_ReasoningOnlyParser)
|
||||
parser.process(_make_output(text="<think>Just thinking</think>"))
|
||||
ctx = _make_context(_ReasoningOnlyParser)
|
||||
ctx.append_output(_make_request_output(text="<think>Just thinking</think>"))
|
||||
|
||||
types = [m.type for m in parser.response_messages]
|
||||
types = [m.type for m in ctx.response_messages]
|
||||
assert "reasoning" in types
|
||||
assert "message" not in types
|
||||
|
||||
@@ -286,13 +294,13 @@ def test_process_extracts_tool_calls():
|
||||
}
|
||||
],
|
||||
)
|
||||
parser = _make_parser(_ToolCallingParser, request=request, enable_auto_tools=True)
|
||||
parser.process(_make_output(text="calling tool"))
|
||||
ctx = _make_context(_ToolCallingParser, request=request, enable_auto_tools=True)
|
||||
ctx.append_output(_make_request_output(text="calling tool"))
|
||||
|
||||
types = [m.type for m in parser.response_messages]
|
||||
types = [m.type for m in ctx.response_messages]
|
||||
assert "function_call" in types
|
||||
|
||||
tool_item = next(m for m in parser.response_messages if m.type == "function_call")
|
||||
tool_item = next(m for m in ctx.response_messages if m.type == "function_call")
|
||||
assert tool_item.name == "get_weather"
|
||||
assert tool_item.arguments == '{"location": "Paris"}'
|
||||
assert tool_item.status == "completed"
|
||||
@@ -304,15 +312,15 @@ def test_process_extracts_tool_calls():
|
||||
|
||||
|
||||
def test_finish_reason_tracked():
|
||||
"""finish_reason from CompletionOutput is stored on the parser."""
|
||||
parser = _make_parser(_NoOpParser)
|
||||
assert parser.finish_reason is None
|
||||
"""finish_reason from CompletionOutput is stored on the context."""
|
||||
ctx = _make_context(_NoOpParser)
|
||||
assert ctx.finish_reason is None
|
||||
|
||||
parser.process(_make_output(finish_reason="stop"))
|
||||
assert parser.finish_reason == "stop"
|
||||
ctx.append_output(_make_request_output(finish_reason="stop"))
|
||||
assert ctx.finish_reason == "stop"
|
||||
|
||||
parser.process(_make_output(finish_reason="length"))
|
||||
assert parser.finish_reason == "length"
|
||||
ctx.append_output(_make_request_output(finish_reason="length"))
|
||||
assert ctx.finish_reason == "length"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -321,62 +329,27 @@ def test_finish_reason_tracked():
|
||||
|
||||
|
||||
def test_multi_turn_accumulation():
|
||||
"""Multiple process() calls accumulate response_messages."""
|
||||
parser = _make_parser(_NoOpParser)
|
||||
"""Multiple append_output() calls accumulate response_messages."""
|
||||
ctx = _make_context(_NoOpParser)
|
||||
|
||||
parser.process(_make_output(text="First turn"))
|
||||
parser.process(_make_output(text="Second turn"))
|
||||
ctx.append_output(_make_request_output(text="First turn"))
|
||||
ctx.append_output(_make_request_output(text="Second turn"))
|
||||
|
||||
assert len(parser.response_messages) == 2
|
||||
texts = [m.content[0].text for m in parser.response_messages]
|
||||
assert len(ctx.response_messages) == 2
|
||||
texts = [m.content[0].text for m in ctx.response_messages]
|
||||
assert texts == ["First turn", "Second turn"]
|
||||
|
||||
|
||||
def test_num_init_messages_offset():
|
||||
"""Initial messages are preserved and offset works correctly."""
|
||||
init_messages = [MagicMock(type="message")]
|
||||
parser = _make_parser(_NoOpParser, response_messages=init_messages)
|
||||
ctx = _make_context(_NoOpParser, response_messages=init_messages)
|
||||
|
||||
assert parser.num_init_messages == 1
|
||||
assert ctx.num_init_messages == 1
|
||||
|
||||
parser.process(_make_output(text="New output"))
|
||||
ctx.append_output(_make_request_output(text="New output"))
|
||||
|
||||
assert len(parser.response_messages) == 2
|
||||
items = parser.make_response_output_items_from_parsable_context()
|
||||
assert len(ctx.response_messages) == 2
|
||||
items = ctx.make_response_output_items()
|
||||
assert len(items) == 1
|
||||
assert items[0].type == "message"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests: factory function
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_factory_function_creates_parser():
|
||||
"""get_responses_parser_for_simple_context returns a working parser."""
|
||||
rp = get_responses_parser_for_simple_context(
|
||||
tokenizer=MagicMock(),
|
||||
parser_cls=_NoOpParser,
|
||||
response_messages=[],
|
||||
request=_make_request(),
|
||||
chat_template=None,
|
||||
chat_template_content_format="auto",
|
||||
)
|
||||
assert isinstance(rp, ResponsesParser)
|
||||
|
||||
rp.process(_make_output(text="Works!"))
|
||||
assert len(rp.response_messages) == 1
|
||||
|
||||
|
||||
def test_factory_function_none_parser():
|
||||
"""Factory function works with parser_cls=None."""
|
||||
rp = get_responses_parser_for_simple_context(
|
||||
tokenizer=MagicMock(),
|
||||
parser_cls=None,
|
||||
response_messages=[],
|
||||
request=_make_request(),
|
||||
chat_template=None,
|
||||
chat_template_content_format="auto",
|
||||
)
|
||||
assert isinstance(rp, ResponsesParser)
|
||||
assert rp.parser_instance is None
|
||||
@@ -159,7 +159,7 @@ class HarmonyPythonTool(Tool):
|
||||
|
||||
assert isinstance(context, ParsableContext)
|
||||
|
||||
last_msg = context.parser.response_messages[-1]
|
||||
last_msg = context.response_messages[-1]
|
||||
args = json.loads(last_msg.arguments)
|
||||
|
||||
last_msg_harmony = Message(
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from openai.types.responses import ResponseFunctionToolCall, ResponseOutputItem
|
||||
from openai.types.responses.response_function_tool_call_output_item import (
|
||||
ResponseFunctionToolCallOutputItem,
|
||||
)
|
||||
from openai.types.responses.response_output_item import McpCall
|
||||
from openai.types.responses.response_output_message import ResponseOutputMessage
|
||||
from openai.types.responses.response_output_text import ResponseOutputText
|
||||
|
||||
from vllm.entrypoints.chat_utils import ChatTemplateContentFormatOption
|
||||
from vllm.entrypoints.openai.responses.protocol import (
|
||||
ResponseInputOutputItem,
|
||||
ResponsesRequest,
|
||||
)
|
||||
from vllm.entrypoints.openai.responses.utils import build_response_output_items
|
||||
from vllm.entrypoints.serve.utils.constants import MCP_PREFIX
|
||||
from vllm.outputs import CompletionOutput
|
||||
from vllm.parser.abstract_parser import Parser
|
||||
from vllm.tokenizers import TokenizerLike
|
||||
from vllm.utils import random_uuid
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResponsesParser:
|
||||
"""Incremental parser over completion tokens with reasoning support."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tokenizer: TokenizerLike,
|
||||
parser_cls: type[Parser] | None,
|
||||
response_messages: list[ResponseInputOutputItem],
|
||||
request: ResponsesRequest,
|
||||
chat_template: str | None,
|
||||
chat_template_content_format: ChatTemplateContentFormatOption,
|
||||
enable_auto_tools: bool = False,
|
||||
tool_call_id_type: str = "random",
|
||||
):
|
||||
self.response_messages: list[ResponseInputOutputItem] = (
|
||||
# TODO: initial messages may not be properly typed
|
||||
response_messages
|
||||
)
|
||||
self.num_init_messages = len(response_messages)
|
||||
self.tokenizer = tokenizer
|
||||
self.request = request
|
||||
|
||||
self.parser_instance: Parser | None = None
|
||||
if parser_cls is not None:
|
||||
chat_template_kwargs = _effective_chat_template_kwargs(
|
||||
request,
|
||||
chat_template=chat_template,
|
||||
chat_template_content_format=chat_template_content_format,
|
||||
)
|
||||
|
||||
self.parser_instance = parser_cls(
|
||||
tokenizer,
|
||||
tools=request.tools,
|
||||
chat_template_kwargs=chat_template_kwargs,
|
||||
)
|
||||
|
||||
self.enable_auto_tools = enable_auto_tools
|
||||
self.tool_call_id_type = tool_call_id_type
|
||||
|
||||
# Store the last finish_reason to determine response status
|
||||
self.finish_reason: str | None = None
|
||||
|
||||
def process(self, output: CompletionOutput) -> "ResponsesParser":
|
||||
# Store the finish_reason from the output
|
||||
self.finish_reason = output.finish_reason
|
||||
|
||||
if self.parser_instance is not None:
|
||||
reasoning, content, tool_calls = self.parser_instance.parse(
|
||||
output.text,
|
||||
self.request,
|
||||
enable_auto_tools=self.enable_auto_tools,
|
||||
)
|
||||
output_items = build_response_output_items(
|
||||
reasoning=reasoning,
|
||||
content=content,
|
||||
tool_calls=tool_calls,
|
||||
tool_call_id_type=self.tool_call_id_type,
|
||||
)
|
||||
self.response_messages.extend(output_items)
|
||||
else:
|
||||
# No parser configured, treat entire output as text content
|
||||
if output.text:
|
||||
self.response_messages.append(
|
||||
ResponseOutputMessage(
|
||||
type="message",
|
||||
id=f"msg_{random_uuid()}",
|
||||
status="completed",
|
||||
role="assistant",
|
||||
content=[
|
||||
ResponseOutputText(
|
||||
annotations=[], # TODO
|
||||
type="output_text",
|
||||
text=output.text,
|
||||
logprobs=None, # TODO
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
return self
|
||||
|
||||
def make_response_output_items_from_parsable_context(
|
||||
self,
|
||||
) -> list[ResponseOutputItem]:
|
||||
"""Given a list of sentences, construct ResponseOutput Items."""
|
||||
response_messages = self.response_messages[self.num_init_messages :]
|
||||
output_messages: list[ResponseOutputItem] = []
|
||||
for message in response_messages:
|
||||
if not isinstance(message, ResponseFunctionToolCallOutputItem):
|
||||
output_messages.append(message)
|
||||
else:
|
||||
if len(output_messages) == 0:
|
||||
raise ValueError(
|
||||
"Cannot have a FunctionToolCallOutput before FunctionToolCall."
|
||||
)
|
||||
if isinstance(output_messages[-1], ResponseFunctionToolCall):
|
||||
mcp_message = McpCall(
|
||||
id=f"{MCP_PREFIX}{random_uuid()}",
|
||||
arguments=output_messages[-1].arguments,
|
||||
name=output_messages[-1].name,
|
||||
server_label=output_messages[
|
||||
-1
|
||||
].name, # TODO: store the server label
|
||||
type="mcp_call",
|
||||
status="completed",
|
||||
output=message.output,
|
||||
# TODO: support error output
|
||||
)
|
||||
output_messages[-1] = mcp_message
|
||||
|
||||
return output_messages
|
||||
|
||||
|
||||
def get_responses_parser_for_simple_context(
|
||||
*,
|
||||
tokenizer: TokenizerLike,
|
||||
parser_cls: type[Parser] | None,
|
||||
response_messages: list[ResponseInputOutputItem],
|
||||
request: ResponsesRequest,
|
||||
chat_template: str | None,
|
||||
chat_template_content_format: ChatTemplateContentFormatOption,
|
||||
enable_auto_tools: bool = False,
|
||||
tool_call_id_type: str = "random",
|
||||
) -> ResponsesParser:
|
||||
"""Factory function to create a ResponsesParser with
|
||||
optional unified parser.
|
||||
|
||||
Returns:
|
||||
ResponsesParser instance configured with the provided parser
|
||||
"""
|
||||
return ResponsesParser(
|
||||
tokenizer=tokenizer,
|
||||
parser_cls=parser_cls,
|
||||
response_messages=response_messages,
|
||||
request=request,
|
||||
chat_template=chat_template,
|
||||
chat_template_content_format=chat_template_content_format,
|
||||
enable_auto_tools=enable_auto_tools,
|
||||
tool_call_id_type=tool_call_id_type,
|
||||
)
|
||||
|
||||
|
||||
def _effective_chat_template_kwargs(
|
||||
request: ResponsesRequest,
|
||||
chat_template: str | None,
|
||||
chat_template_content_format: ChatTemplateContentFormatOption,
|
||||
) -> dict[str, Any]:
|
||||
return request.build_chat_params(
|
||||
default_template=chat_template,
|
||||
default_template_content_format=chat_template_content_format,
|
||||
).chat_template_kwargs
|
||||
@@ -10,9 +10,13 @@ from contextlib import AsyncExitStack
|
||||
from dataclasses import replace
|
||||
from typing import TYPE_CHECKING, Any, Final, Union
|
||||
|
||||
from openai.types.responses import ResponseFunctionToolCall, ResponseOutputItem
|
||||
from openai.types.responses.response_function_tool_call_output_item import (
|
||||
ResponseFunctionToolCallOutputItem,
|
||||
)
|
||||
from openai.types.responses.response_output_item import McpCall
|
||||
from openai.types.responses.response_output_message import ResponseOutputMessage
|
||||
from openai.types.responses.response_output_text import ResponseOutputText
|
||||
from openai.types.responses.tool import Mcp
|
||||
from openai_harmony import Author, Message, Role, StreamState, TextContent
|
||||
|
||||
@@ -30,15 +34,15 @@ from vllm.entrypoints.openai.parser.harmony_utils import (
|
||||
get_streamable_parser_for_assistant,
|
||||
render_for_completion,
|
||||
)
|
||||
from vllm.entrypoints.openai.parser.responses_parser import (
|
||||
get_responses_parser_for_simple_context,
|
||||
)
|
||||
from vllm.entrypoints.openai.responses.protocol import (
|
||||
ResponseInputOutputItem,
|
||||
ResponseRawMessageAndToken,
|
||||
ResponsesRequest,
|
||||
)
|
||||
from vllm.entrypoints.openai.responses.utils import construct_tool_dicts
|
||||
from vllm.entrypoints.openai.responses.utils import (
|
||||
build_response_output_items,
|
||||
construct_tool_dicts,
|
||||
)
|
||||
from vllm.entrypoints.serve.utils.constants import MCP_PREFIX
|
||||
from vllm.outputs import RequestOutput
|
||||
from vllm.parser.abstract_parser import Parser
|
||||
@@ -286,16 +290,24 @@ class ParsableContext(ConversationContext):
|
||||
# not implemented yet for ParsableContext
|
||||
self.all_turn_metrics: list[TurnMetrics] = []
|
||||
|
||||
self.parser = get_responses_parser_for_simple_context(
|
||||
tokenizer=tokenizer,
|
||||
parser_cls=parser_cls,
|
||||
response_messages=response_messages,
|
||||
request=request,
|
||||
chat_template=chat_template,
|
||||
chat_template_content_format=chat_template_content_format,
|
||||
enable_auto_tools=enable_auto_tools,
|
||||
tool_call_id_type=tool_call_id_type,
|
||||
)
|
||||
self.response_messages: list[ResponseInputOutputItem] = response_messages
|
||||
self.num_init_messages = len(response_messages)
|
||||
self.finish_reason: str | None = None
|
||||
self.enable_auto_tools = enable_auto_tools
|
||||
self.tool_call_id_type = tool_call_id_type
|
||||
|
||||
self.parser_instance: Parser | None = None
|
||||
if parser_cls is not None:
|
||||
chat_template_kwargs = request.build_chat_params(
|
||||
default_template=chat_template,
|
||||
default_template_content_format=chat_template_content_format,
|
||||
).chat_template_kwargs
|
||||
self.parser_instance = parser_cls(
|
||||
tokenizer,
|
||||
tools=request.tools,
|
||||
chat_template_kwargs=chat_template_kwargs,
|
||||
)
|
||||
|
||||
self.parser_cls = parser_cls
|
||||
self.request = request
|
||||
|
||||
@@ -318,11 +330,44 @@ class ParsableContext(ConversationContext):
|
||||
self.num_output_tokens += len(output.outputs[0].token_ids or [])
|
||||
if output.kv_transfer_params is not None:
|
||||
self.kv_transfer_params = output.kv_transfer_params
|
||||
self.parser.process(output.outputs[0])
|
||||
output_token_ids = output.outputs[0].token_ids or []
|
||||
self._accumulated_token_ids.extend(output_token_ids)
|
||||
|
||||
# only store if enable_response_messages is True, save memory
|
||||
completion = output.outputs[0]
|
||||
self.finish_reason = completion.finish_reason
|
||||
|
||||
if self.parser_instance is not None:
|
||||
reasoning, content, tool_calls = self.parser_instance.parse(
|
||||
completion.text,
|
||||
self.request,
|
||||
enable_auto_tools=self.enable_auto_tools,
|
||||
)
|
||||
self.response_messages.extend(
|
||||
build_response_output_items(
|
||||
reasoning=reasoning,
|
||||
content=content,
|
||||
tool_calls=tool_calls,
|
||||
tool_call_id_type=self.tool_call_id_type,
|
||||
)
|
||||
)
|
||||
elif completion.text:
|
||||
self.response_messages.append(
|
||||
ResponseOutputMessage(
|
||||
type="message",
|
||||
id=f"msg_{random_uuid()}",
|
||||
status="completed",
|
||||
role="assistant",
|
||||
content=[
|
||||
ResponseOutputText(
|
||||
annotations=[],
|
||||
type="output_text",
|
||||
text=completion.text,
|
||||
logprobs=None,
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
self._accumulated_token_ids.extend(completion.token_ids or [])
|
||||
|
||||
if self.request.enable_response_messages:
|
||||
output_prompt = output.prompt or ""
|
||||
output_prompt_token_ids = output.prompt_token_ids or []
|
||||
@@ -342,18 +387,18 @@ class ParsableContext(ConversationContext):
|
||||
)
|
||||
self.output_messages.append(
|
||||
ResponseRawMessageAndToken(
|
||||
message=output.outputs[0].text,
|
||||
tokens=output.outputs[0].token_ids,
|
||||
message=completion.text,
|
||||
tokens=completion.token_ids,
|
||||
)
|
||||
)
|
||||
|
||||
def append_tool_output(self, output: list[ResponseInputOutputItem]) -> None:
|
||||
self.parser.response_messages.extend(output)
|
||||
self.response_messages.extend(output)
|
||||
|
||||
def need_builtin_tool_call(self) -> bool:
|
||||
"""Return true if the last message is a builtin tool call
|
||||
that the request has enabled."""
|
||||
last_message = self.parser.response_messages[-1]
|
||||
last_message = self.response_messages[-1]
|
||||
if last_message.type != "function_call":
|
||||
return False
|
||||
if last_message.name in ("code_interpreter", "python"):
|
||||
@@ -457,12 +502,12 @@ class ParsableContext(ConversationContext):
|
||||
return [message]
|
||||
|
||||
async def call_tool(self) -> list[ResponseInputOutputItem]:
|
||||
if not self.parser.response_messages:
|
||||
if not self.response_messages:
|
||||
return []
|
||||
last_msg = self.parser.response_messages[-1]
|
||||
last_msg = self.response_messages[-1]
|
||||
# change this to a mcp_ function call
|
||||
last_msg.id = f"{MCP_PREFIX}{random_uuid()}"
|
||||
self.parser.response_messages[-1] = last_msg
|
||||
self.response_messages[-1] = last_msg
|
||||
if last_msg.name == "code_interpreter":
|
||||
return await self.call_python_tool(self._tool_sessions["python"], last_msg)
|
||||
elif last_msg.name == "web_search_preview":
|
||||
@@ -473,6 +518,29 @@ class ParsableContext(ConversationContext):
|
||||
)
|
||||
return []
|
||||
|
||||
def make_response_output_items(self) -> list[ResponseOutputItem]:
|
||||
response_messages = self.response_messages[self.num_init_messages :]
|
||||
output_messages: list[ResponseOutputItem] = []
|
||||
for message in response_messages:
|
||||
if not isinstance(message, ResponseFunctionToolCallOutputItem):
|
||||
output_messages.append(message)
|
||||
else:
|
||||
if len(output_messages) == 0:
|
||||
raise ValueError(
|
||||
"Cannot have a FunctionToolCallOutput before FunctionToolCall."
|
||||
)
|
||||
if isinstance(output_messages[-1], ResponseFunctionToolCall):
|
||||
output_messages[-1] = McpCall(
|
||||
id=f"{MCP_PREFIX}{random_uuid()}",
|
||||
arguments=output_messages[-1].arguments,
|
||||
name=output_messages[-1].name,
|
||||
server_label=output_messages[-1].name,
|
||||
type="mcp_call",
|
||||
status="completed",
|
||||
output=message.output,
|
||||
)
|
||||
return output_messages
|
||||
|
||||
def render_for_completion(self):
|
||||
raise NotImplementedError("Should not be called.")
|
||||
|
||||
|
||||
@@ -702,7 +702,7 @@ class OpenAIServingResponses(OpenAIServing):
|
||||
elif isinstance(context, ParsableContext):
|
||||
(engine_input,) = await self._render_next_turn(
|
||||
context.request,
|
||||
context.parser.response_messages,
|
||||
context.response_messages,
|
||||
context.tool_dicts,
|
||||
context.parser_cls,
|
||||
context.chat_template,
|
||||
@@ -805,7 +805,7 @@ class OpenAIServingResponses(OpenAIServing):
|
||||
else:
|
||||
status = "incomplete"
|
||||
elif isinstance(context, ParsableContext):
|
||||
output = context.parser.make_response_output_items_from_parsable_context()
|
||||
output = context.make_response_output_items()
|
||||
|
||||
if request.enable_response_messages:
|
||||
input_messages = context.input_messages
|
||||
@@ -816,7 +816,7 @@ class OpenAIServingResponses(OpenAIServing):
|
||||
num_tool_output_tokens = 0
|
||||
|
||||
# Check finish reason from the parser
|
||||
if context.parser.finish_reason == "length":
|
||||
if context.finish_reason == "length":
|
||||
status = "incomplete"
|
||||
else:
|
||||
assert isinstance(context, SimpleContext)
|
||||
|
||||
Reference in New Issue
Block a user