Skip to content

Commit 185d8ed

Browse files
[responsesAPI][bugfix] serialize harmony messages (#26185)
Signed-off-by: Andrew Xia <[email protected]> Co-authored-by: Ye (Charlotte) Qi <[email protected]>
1 parent d9836d4 commit 185d8ed

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

tests/entrypoints/openai/test_response_api_with_harmony.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import pytest_asyncio
99
import requests
1010
from openai import BadRequestError, NotFoundError, OpenAI
11+
from openai_harmony import (
12+
Message,
13+
)
1114

1215
from ...utils import RemoteOpenAIServer
1316

@@ -326,6 +329,7 @@ async def test_streaming(client: OpenAI, model_name: str, background: bool):
326329
],
327330
stream=True,
328331
background=background,
332+
extra_body={"enable_response_messages": True},
329333
)
330334

331335
current_item_id = ""
@@ -334,6 +338,7 @@ async def test_streaming(client: OpenAI, model_name: str, background: bool):
334338
events = []
335339
current_event_mode = None
336340
resp_id = None
341+
checked_response_completed = False
337342
async for event in response:
338343
if event.type == "response.created":
339344
resp_id = event.response.id
@@ -346,6 +351,16 @@ async def test_streaming(client: OpenAI, model_name: str, background: bool):
346351
]:
347352
assert "input_messages" in event.response.model_extra
348353
assert "output_messages" in event.response.model_extra
354+
if event.type == "response.completed":
355+
# make sure the serialization of content works
356+
for msg in event.response.model_extra["output_messages"]:
357+
# make sure we can convert the messages back into harmony
358+
Message.from_dict(msg)
359+
360+
for msg in event.response.model_extra["input_messages"]:
361+
# make sure we can convert the messages back into harmony
362+
Message.from_dict(msg)
363+
checked_response_completed = True
349364

350365
if current_event_mode != event.type:
351366
current_event_mode = event.type
@@ -390,6 +405,7 @@ async def test_streaming(client: OpenAI, model_name: str, background: bool):
390405
assert len(events) > 0
391406
response_completed_event = events[-1]
392407
assert len(response_completed_event.response.output) > 0
408+
assert checked_response_completed
393409

394410
if background:
395411
starting_after = 5

vllm/entrypoints/openai/protocol.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
Field,
6464
TypeAdapter,
6565
ValidationInfo,
66+
field_serializer,
6667
field_validator,
6768
model_validator,
6869
)
@@ -2078,11 +2079,6 @@ class ResponsesResponse(OpenAIBaseModel):
20782079
model: str
20792080
object: Literal["response"] = "response"
20802081
output: list[ResponseOutputItem]
2081-
# These are populated when enable_response_messages is set to True
2082-
# TODO: Currently an issue where content of harmony messages
2083-
# is not available when these are serialized. Metadata is available
2084-
input_messages: Optional[list[ChatCompletionMessageParam]] = None
2085-
output_messages: Optional[list[ChatCompletionMessageParam]] = None
20862082
parallel_tool_calls: bool
20872083
temperature: float
20882084
tool_choice: ToolChoice
@@ -2102,6 +2098,49 @@ class ResponsesResponse(OpenAIBaseModel):
21022098
usage: Optional[ResponseUsage] = None
21032099
user: Optional[str] = None
21042100

2101+
# --8<-- [start:responses-extra-params]
2102+
# These are populated when enable_response_messages is set to True
2103+
# NOTE: custom serialization is needed
2104+
# see serialize_input_messages and serialize_output_messages
2105+
input_messages: Optional[list[ChatCompletionMessageParam]] = None
2106+
output_messages: Optional[list[ChatCompletionMessageParam]] = None
2107+
# --8<-- [end:responses-extra-params]
2108+
2109+
# NOTE: openAI harmony doesn't serialize TextContent properly,
2110+
# TODO: this fixes for TextContent, but need to verify for tools etc
2111+
# https://github.com/openai/harmony/issues/78
2112+
@field_serializer("output_messages", when_used="json")
2113+
def serialize_output_messages(self, msgs, _info):
2114+
if msgs:
2115+
serialized = []
2116+
for m in msgs:
2117+
if isinstance(m, dict):
2118+
serialized.append(m)
2119+
elif hasattr(m, "__dict__"):
2120+
serialized.append(m.to_dict())
2121+
else:
2122+
# fallback to pyandic dump
2123+
serialized.append(m.model_dump_json())
2124+
return serialized
2125+
return None
2126+
2127+
# NOTE: openAI harmony doesn't serialize TextContent properly, this fixes it
2128+
# https://github.com/openai/harmony/issues/78
2129+
@field_serializer("input_messages", when_used="json")
2130+
def serialize_input_messages(self, msgs, _info):
2131+
if msgs:
2132+
serialized = []
2133+
for m in msgs:
2134+
if isinstance(m, dict):
2135+
serialized.append(m)
2136+
elif hasattr(m, "__dict__"):
2137+
serialized.append(m.to_dict())
2138+
else:
2139+
# fallback to pyandic dump
2140+
serialized.append(m.model_dump_json())
2141+
return serialized
2142+
return None
2143+
21052144
@classmethod
21062145
def from_request(
21072146
cls,

vllm/entrypoints/openai/serving_responses.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,6 @@ async def empty_async_generator():
18761876
ResponseCompletedEvent(
18771877
type="response.completed",
18781878
sequence_number=-1,
1879-
response=final_response.model_dump(),
1879+
response=final_response,
18801880
)
18811881
)

0 commit comments

Comments
 (0)