Skip to content

Commit e1e1615

Browse files
authored
feat(responses): add MCP argument streaming and content part events (#3136)
# What does this PR do? Adds content part streaming events to the OpenAI-compatible Responses API to support more granular streaming of response content. This introduces: 1. New schema types for content parts: `OpenAIResponseContentPart` with variants for text output and refusals 2. New streaming event types: - `OpenAIResponseObjectStreamResponseContentPartAdded` for when content parts begin - `OpenAIResponseObjectStreamResponseContentPartDone` for when content parts complete 3. Implementation in the reference provider to emit these events during streaming responses. Also emits MCP arguments just like function call ones. ## Test Plan Updated existing streaming tests to verify content part events are properly emitted
1 parent 8638537 commit e1e1615

File tree

6 files changed

+481
-36
lines changed

6 files changed

+481
-36
lines changed

docs/_static/llama-stack-spec.html

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8821,6 +8821,61 @@
88218821
"title": "OpenAIResponseOutputMessageMCPListTools",
88228822
"description": "MCP list tools output message containing available tools from an MCP server."
88238823
},
8824+
"OpenAIResponseContentPart": {
8825+
"oneOf": [
8826+
{
8827+
"$ref": "#/components/schemas/OpenAIResponseContentPartOutputText"
8828+
},
8829+
{
8830+
"$ref": "#/components/schemas/OpenAIResponseContentPartRefusal"
8831+
}
8832+
],
8833+
"discriminator": {
8834+
"propertyName": "type",
8835+
"mapping": {
8836+
"output_text": "#/components/schemas/OpenAIResponseContentPartOutputText",
8837+
"refusal": "#/components/schemas/OpenAIResponseContentPartRefusal"
8838+
}
8839+
}
8840+
},
8841+
"OpenAIResponseContentPartOutputText": {
8842+
"type": "object",
8843+
"properties": {
8844+
"type": {
8845+
"type": "string",
8846+
"const": "output_text",
8847+
"default": "output_text"
8848+
},
8849+
"text": {
8850+
"type": "string"
8851+
}
8852+
},
8853+
"additionalProperties": false,
8854+
"required": [
8855+
"type",
8856+
"text"
8857+
],
8858+
"title": "OpenAIResponseContentPartOutputText"
8859+
},
8860+
"OpenAIResponseContentPartRefusal": {
8861+
"type": "object",
8862+
"properties": {
8863+
"type": {
8864+
"type": "string",
8865+
"const": "refusal",
8866+
"default": "refusal"
8867+
},
8868+
"refusal": {
8869+
"type": "string"
8870+
}
8871+
},
8872+
"additionalProperties": false,
8873+
"required": [
8874+
"type",
8875+
"refusal"
8876+
],
8877+
"title": "OpenAIResponseContentPartRefusal"
8878+
},
88248879
"OpenAIResponseObjectStream": {
88258880
"oneOf": [
88268881
{
@@ -8877,6 +8932,12 @@
88778932
{
88788933
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted"
88798934
},
8935+
{
8936+
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded"
8937+
},
8938+
{
8939+
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone"
8940+
},
88808941
{
88818942
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseCompleted"
88828943
}
@@ -8902,6 +8963,8 @@
89028963
"response.mcp_call.in_progress": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress",
89038964
"response.mcp_call.failed": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed",
89048965
"response.mcp_call.completed": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted",
8966+
"response.content_part.added": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded",
8967+
"response.content_part.done": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone",
89058968
"response.completed": "#/components/schemas/OpenAIResponseObjectStreamResponseCompleted"
89068969
}
89078970
}
@@ -8928,6 +8991,80 @@
89288991
"title": "OpenAIResponseObjectStreamResponseCompleted",
89298992
"description": "Streaming event indicating a response has been completed."
89308993
},
8994+
"OpenAIResponseObjectStreamResponseContentPartAdded": {
8995+
"type": "object",
8996+
"properties": {
8997+
"response_id": {
8998+
"type": "string",
8999+
"description": "Unique identifier of the response containing this content"
9000+
},
9001+
"item_id": {
9002+
"type": "string",
9003+
"description": "Unique identifier of the output item containing this content part"
9004+
},
9005+
"part": {
9006+
"$ref": "#/components/schemas/OpenAIResponseContentPart",
9007+
"description": "The content part that was added"
9008+
},
9009+
"sequence_number": {
9010+
"type": "integer",
9011+
"description": "Sequential number for ordering streaming events"
9012+
},
9013+
"type": {
9014+
"type": "string",
9015+
"const": "response.content_part.added",
9016+
"default": "response.content_part.added",
9017+
"description": "Event type identifier, always \"response.content_part.added\""
9018+
}
9019+
},
9020+
"additionalProperties": false,
9021+
"required": [
9022+
"response_id",
9023+
"item_id",
9024+
"part",
9025+
"sequence_number",
9026+
"type"
9027+
],
9028+
"title": "OpenAIResponseObjectStreamResponseContentPartAdded",
9029+
"description": "Streaming event for when a new content part is added to a response item."
9030+
},
9031+
"OpenAIResponseObjectStreamResponseContentPartDone": {
9032+
"type": "object",
9033+
"properties": {
9034+
"response_id": {
9035+
"type": "string",
9036+
"description": "Unique identifier of the response containing this content"
9037+
},
9038+
"item_id": {
9039+
"type": "string",
9040+
"description": "Unique identifier of the output item containing this content part"
9041+
},
9042+
"part": {
9043+
"$ref": "#/components/schemas/OpenAIResponseContentPart",
9044+
"description": "The completed content part"
9045+
},
9046+
"sequence_number": {
9047+
"type": "integer",
9048+
"description": "Sequential number for ordering streaming events"
9049+
},
9050+
"type": {
9051+
"type": "string",
9052+
"const": "response.content_part.done",
9053+
"default": "response.content_part.done",
9054+
"description": "Event type identifier, always \"response.content_part.done\""
9055+
}
9056+
},
9057+
"additionalProperties": false,
9058+
"required": [
9059+
"response_id",
9060+
"item_id",
9061+
"part",
9062+
"sequence_number",
9063+
"type"
9064+
],
9065+
"title": "OpenAIResponseObjectStreamResponseContentPartDone",
9066+
"description": "Streaming event for when a content part is completed."
9067+
},
89319068
"OpenAIResponseObjectStreamResponseCreated": {
89329069
"type": "object",
89339070
"properties": {

docs/_static/llama-stack-spec.yaml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6441,6 +6441,43 @@ components:
64416441
title: OpenAIResponseOutputMessageMCPListTools
64426442
description: >-
64436443
MCP list tools output message containing available tools from an MCP server.
6444+
OpenAIResponseContentPart:
6445+
oneOf:
6446+
- $ref: '#/components/schemas/OpenAIResponseContentPartOutputText'
6447+
- $ref: '#/components/schemas/OpenAIResponseContentPartRefusal'
6448+
discriminator:
6449+
propertyName: type
6450+
mapping:
6451+
output_text: '#/components/schemas/OpenAIResponseContentPartOutputText'
6452+
refusal: '#/components/schemas/OpenAIResponseContentPartRefusal'
6453+
OpenAIResponseContentPartOutputText:
6454+
type: object
6455+
properties:
6456+
type:
6457+
type: string
6458+
const: output_text
6459+
default: output_text
6460+
text:
6461+
type: string
6462+
additionalProperties: false
6463+
required:
6464+
- type
6465+
- text
6466+
title: OpenAIResponseContentPartOutputText
6467+
OpenAIResponseContentPartRefusal:
6468+
type: object
6469+
properties:
6470+
type:
6471+
type: string
6472+
const: refusal
6473+
default: refusal
6474+
refusal:
6475+
type: string
6476+
additionalProperties: false
6477+
required:
6478+
- type
6479+
- refusal
6480+
title: OpenAIResponseContentPartRefusal
64446481
OpenAIResponseObjectStream:
64456482
oneOf:
64466483
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseCreated'
@@ -6461,6 +6498,8 @@ components:
64616498
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress'
64626499
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed'
64636500
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted'
6501+
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded'
6502+
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone'
64646503
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseCompleted'
64656504
discriminator:
64666505
propertyName: type
@@ -6483,6 +6522,8 @@ components:
64836522
response.mcp_call.in_progress: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress'
64846523
response.mcp_call.failed: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed'
64856524
response.mcp_call.completed: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted'
6525+
response.content_part.added: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded'
6526+
response.content_part.done: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone'
64866527
response.completed: '#/components/schemas/OpenAIResponseObjectStreamResponseCompleted'
64876528
"OpenAIResponseObjectStreamResponseCompleted":
64886529
type: object
@@ -6504,6 +6545,76 @@ components:
65046545
OpenAIResponseObjectStreamResponseCompleted
65056546
description: >-
65066547
Streaming event indicating a response has been completed.
6548+
"OpenAIResponseObjectStreamResponseContentPartAdded":
6549+
type: object
6550+
properties:
6551+
response_id:
6552+
type: string
6553+
description: >-
6554+
Unique identifier of the response containing this content
6555+
item_id:
6556+
type: string
6557+
description: >-
6558+
Unique identifier of the output item containing this content part
6559+
part:
6560+
$ref: '#/components/schemas/OpenAIResponseContentPart'
6561+
description: The content part that was added
6562+
sequence_number:
6563+
type: integer
6564+
description: >-
6565+
Sequential number for ordering streaming events
6566+
type:
6567+
type: string
6568+
const: response.content_part.added
6569+
default: response.content_part.added
6570+
description: >-
6571+
Event type identifier, always "response.content_part.added"
6572+
additionalProperties: false
6573+
required:
6574+
- response_id
6575+
- item_id
6576+
- part
6577+
- sequence_number
6578+
- type
6579+
title: >-
6580+
OpenAIResponseObjectStreamResponseContentPartAdded
6581+
description: >-
6582+
Streaming event for when a new content part is added to a response item.
6583+
"OpenAIResponseObjectStreamResponseContentPartDone":
6584+
type: object
6585+
properties:
6586+
response_id:
6587+
type: string
6588+
description: >-
6589+
Unique identifier of the response containing this content
6590+
item_id:
6591+
type: string
6592+
description: >-
6593+
Unique identifier of the output item containing this content part
6594+
part:
6595+
$ref: '#/components/schemas/OpenAIResponseContentPart'
6596+
description: The completed content part
6597+
sequence_number:
6598+
type: integer
6599+
description: >-
6600+
Sequential number for ordering streaming events
6601+
type:
6602+
type: string
6603+
const: response.content_part.done
6604+
default: response.content_part.done
6605+
description: >-
6606+
Event type identifier, always "response.content_part.done"
6607+
additionalProperties: false
6608+
required:
6609+
- response_id
6610+
- item_id
6611+
- part
6612+
- sequence_number
6613+
- type
6614+
title: >-
6615+
OpenAIResponseObjectStreamResponseContentPartDone
6616+
description: >-
6617+
Streaming event for when a content part is completed.
65076618
"OpenAIResponseObjectStreamResponseCreated":
65086619
type: object
65096620
properties:

llama_stack/apis/agents/openai_responses.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,62 @@ class OpenAIResponseObjectStreamResponseMcpCallCompleted(BaseModel):
623623
type: Literal["response.mcp_call.completed"] = "response.mcp_call.completed"
624624

625625

626+
@json_schema_type
627+
class OpenAIResponseContentPartOutputText(BaseModel):
628+
type: Literal["output_text"] = "output_text"
629+
text: str
630+
# TODO: add annotations, logprobs, etc.
631+
632+
633+
@json_schema_type
634+
class OpenAIResponseContentPartRefusal(BaseModel):
635+
type: Literal["refusal"] = "refusal"
636+
refusal: str
637+
638+
639+
OpenAIResponseContentPart = Annotated[
640+
OpenAIResponseContentPartOutputText | OpenAIResponseContentPartRefusal,
641+
Field(discriminator="type"),
642+
]
643+
register_schema(OpenAIResponseContentPart, name="OpenAIResponseContentPart")
644+
645+
646+
@json_schema_type
647+
class OpenAIResponseObjectStreamResponseContentPartAdded(BaseModel):
648+
"""Streaming event for when a new content part is added to a response item.
649+
650+
:param response_id: Unique identifier of the response containing this content
651+
:param item_id: Unique identifier of the output item containing this content part
652+
:param part: The content part that was added
653+
:param sequence_number: Sequential number for ordering streaming events
654+
:param type: Event type identifier, always "response.content_part.added"
655+
"""
656+
657+
response_id: str
658+
item_id: str
659+
part: OpenAIResponseContentPart
660+
sequence_number: int
661+
type: Literal["response.content_part.added"] = "response.content_part.added"
662+
663+
664+
@json_schema_type
665+
class OpenAIResponseObjectStreamResponseContentPartDone(BaseModel):
666+
"""Streaming event for when a content part is completed.
667+
668+
:param response_id: Unique identifier of the response containing this content
669+
:param item_id: Unique identifier of the output item containing this content part
670+
:param part: The completed content part
671+
:param sequence_number: Sequential number for ordering streaming events
672+
:param type: Event type identifier, always "response.content_part.done"
673+
"""
674+
675+
response_id: str
676+
item_id: str
677+
part: OpenAIResponseContentPart
678+
sequence_number: int
679+
type: Literal["response.content_part.done"] = "response.content_part.done"
680+
681+
626682
OpenAIResponseObjectStream = Annotated[
627683
OpenAIResponseObjectStreamResponseCreated
628684
| OpenAIResponseObjectStreamResponseOutputItemAdded
@@ -642,6 +698,8 @@ class OpenAIResponseObjectStreamResponseMcpCallCompleted(BaseModel):
642698
| OpenAIResponseObjectStreamResponseMcpCallInProgress
643699
| OpenAIResponseObjectStreamResponseMcpCallFailed
644700
| OpenAIResponseObjectStreamResponseMcpCallCompleted
701+
| OpenAIResponseObjectStreamResponseContentPartAdded
702+
| OpenAIResponseObjectStreamResponseContentPartDone
645703
| OpenAIResponseObjectStreamResponseCompleted,
646704
Field(discriminator="type"),
647705
]

0 commit comments

Comments
 (0)