diff --git a/docs/_static/llama-stack-spec.html b/docs/_static/llama-stack-spec.html
index 25f916d87..0549dda21 100644
--- a/docs/_static/llama-stack-spec.html
+++ b/docs/_static/llama-stack-spec.html
@@ -8821,6 +8821,61 @@
"title": "OpenAIResponseOutputMessageMCPListTools",
"description": "MCP list tools output message containing available tools from an MCP server."
},
+ "OpenAIResponseContentPart": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/OpenAIResponseContentPartOutputText"
+ },
+ {
+ "$ref": "#/components/schemas/OpenAIResponseContentPartRefusal"
+ }
+ ],
+ "discriminator": {
+ "propertyName": "type",
+ "mapping": {
+ "output_text": "#/components/schemas/OpenAIResponseContentPartOutputText",
+ "refusal": "#/components/schemas/OpenAIResponseContentPartRefusal"
+ }
+ }
+ },
+ "OpenAIResponseContentPartOutputText": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "output_text",
+ "default": "output_text"
+ },
+ "text": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "text"
+ ],
+ "title": "OpenAIResponseContentPartOutputText"
+ },
+ "OpenAIResponseContentPartRefusal": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "refusal",
+ "default": "refusal"
+ },
+ "refusal": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "refusal"
+ ],
+ "title": "OpenAIResponseContentPartRefusal"
+ },
"OpenAIResponseObjectStream": {
"oneOf": [
{
@@ -8877,6 +8932,12 @@
{
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted"
},
+ {
+ "$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded"
+ },
+ {
+ "$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone"
+ },
{
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseCompleted"
}
@@ -8902,6 +8963,8 @@
"response.mcp_call.in_progress": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress",
"response.mcp_call.failed": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed",
"response.mcp_call.completed": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted",
+ "response.content_part.added": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded",
+ "response.content_part.done": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone",
"response.completed": "#/components/schemas/OpenAIResponseObjectStreamResponseCompleted"
}
}
@@ -8928,6 +8991,80 @@
"title": "OpenAIResponseObjectStreamResponseCompleted",
"description": "Streaming event indicating a response has been completed."
},
+ "OpenAIResponseObjectStreamResponseContentPartAdded": {
+ "type": "object",
+ "properties": {
+ "response_id": {
+ "type": "string",
+ "description": "Unique identifier of the response containing this content"
+ },
+ "item_id": {
+ "type": "string",
+ "description": "Unique identifier of the output item containing this content part"
+ },
+ "part": {
+ "$ref": "#/components/schemas/OpenAIResponseContentPart",
+ "description": "The content part that was added"
+ },
+ "sequence_number": {
+ "type": "integer",
+ "description": "Sequential number for ordering streaming events"
+ },
+ "type": {
+ "type": "string",
+ "const": "response.content_part.added",
+ "default": "response.content_part.added",
+ "description": "Event type identifier, always \"response.content_part.added\""
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "response_id",
+ "item_id",
+ "part",
+ "sequence_number",
+ "type"
+ ],
+ "title": "OpenAIResponseObjectStreamResponseContentPartAdded",
+ "description": "Streaming event for when a new content part is added to a response item."
+ },
+ "OpenAIResponseObjectStreamResponseContentPartDone": {
+ "type": "object",
+ "properties": {
+ "response_id": {
+ "type": "string",
+ "description": "Unique identifier of the response containing this content"
+ },
+ "item_id": {
+ "type": "string",
+ "description": "Unique identifier of the output item containing this content part"
+ },
+ "part": {
+ "$ref": "#/components/schemas/OpenAIResponseContentPart",
+ "description": "The completed content part"
+ },
+ "sequence_number": {
+ "type": "integer",
+ "description": "Sequential number for ordering streaming events"
+ },
+ "type": {
+ "type": "string",
+ "const": "response.content_part.done",
+ "default": "response.content_part.done",
+ "description": "Event type identifier, always \"response.content_part.done\""
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "response_id",
+ "item_id",
+ "part",
+ "sequence_number",
+ "type"
+ ],
+ "title": "OpenAIResponseObjectStreamResponseContentPartDone",
+ "description": "Streaming event for when a content part is completed."
+ },
"OpenAIResponseObjectStreamResponseCreated": {
"type": "object",
"properties": {
diff --git a/docs/_static/llama-stack-spec.yaml b/docs/_static/llama-stack-spec.yaml
index 43e9fa95a..aa47cd58d 100644
--- a/docs/_static/llama-stack-spec.yaml
+++ b/docs/_static/llama-stack-spec.yaml
@@ -6441,6 +6441,43 @@ components:
title: OpenAIResponseOutputMessageMCPListTools
description: >-
MCP list tools output message containing available tools from an MCP server.
+ OpenAIResponseContentPart:
+ oneOf:
+ - $ref: '#/components/schemas/OpenAIResponseContentPartOutputText'
+ - $ref: '#/components/schemas/OpenAIResponseContentPartRefusal'
+ discriminator:
+ propertyName: type
+ mapping:
+ output_text: '#/components/schemas/OpenAIResponseContentPartOutputText'
+ refusal: '#/components/schemas/OpenAIResponseContentPartRefusal'
+ OpenAIResponseContentPartOutputText:
+ type: object
+ properties:
+ type:
+ type: string
+ const: output_text
+ default: output_text
+ text:
+ type: string
+ additionalProperties: false
+ required:
+ - type
+ - text
+ title: OpenAIResponseContentPartOutputText
+ OpenAIResponseContentPartRefusal:
+ type: object
+ properties:
+ type:
+ type: string
+ const: refusal
+ default: refusal
+ refusal:
+ type: string
+ additionalProperties: false
+ required:
+ - type
+ - refusal
+ title: OpenAIResponseContentPartRefusal
OpenAIResponseObjectStream:
oneOf:
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseCreated'
@@ -6461,6 +6498,8 @@ components:
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress'
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed'
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted'
+ - $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded'
+ - $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone'
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseCompleted'
discriminator:
propertyName: type
@@ -6483,6 +6522,8 @@ components:
response.mcp_call.in_progress: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress'
response.mcp_call.failed: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed'
response.mcp_call.completed: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted'
+ response.content_part.added: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded'
+ response.content_part.done: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone'
response.completed: '#/components/schemas/OpenAIResponseObjectStreamResponseCompleted'
"OpenAIResponseObjectStreamResponseCompleted":
type: object
@@ -6504,6 +6545,76 @@ components:
OpenAIResponseObjectStreamResponseCompleted
description: >-
Streaming event indicating a response has been completed.
+ "OpenAIResponseObjectStreamResponseContentPartAdded":
+ type: object
+ properties:
+ response_id:
+ type: string
+ description: >-
+ Unique identifier of the response containing this content
+ item_id:
+ type: string
+ description: >-
+ Unique identifier of the output item containing this content part
+ part:
+ $ref: '#/components/schemas/OpenAIResponseContentPart'
+ description: The content part that was added
+ sequence_number:
+ type: integer
+ description: >-
+ Sequential number for ordering streaming events
+ type:
+ type: string
+ const: response.content_part.added
+ default: response.content_part.added
+ description: >-
+ Event type identifier, always "response.content_part.added"
+ additionalProperties: false
+ required:
+ - response_id
+ - item_id
+ - part
+ - sequence_number
+ - type
+ title: >-
+ OpenAIResponseObjectStreamResponseContentPartAdded
+ description: >-
+ Streaming event for when a new content part is added to a response item.
+ "OpenAIResponseObjectStreamResponseContentPartDone":
+ type: object
+ properties:
+ response_id:
+ type: string
+ description: >-
+ Unique identifier of the response containing this content
+ item_id:
+ type: string
+ description: >-
+ Unique identifier of the output item containing this content part
+ part:
+ $ref: '#/components/schemas/OpenAIResponseContentPart'
+ description: The completed content part
+ sequence_number:
+ type: integer
+ description: >-
+ Sequential number for ordering streaming events
+ type:
+ type: string
+ const: response.content_part.done
+ default: response.content_part.done
+ description: >-
+ Event type identifier, always "response.content_part.done"
+ additionalProperties: false
+ required:
+ - response_id
+ - item_id
+ - part
+ - sequence_number
+ - type
+ title: >-
+ OpenAIResponseObjectStreamResponseContentPartDone
+ description: >-
+ Streaming event for when a content part is completed.
"OpenAIResponseObjectStreamResponseCreated":
type: object
properties:
diff --git a/llama_stack/apis/agents/openai_responses.py b/llama_stack/apis/agents/openai_responses.py
index d94a8aed4..591992479 100644
--- a/llama_stack/apis/agents/openai_responses.py
+++ b/llama_stack/apis/agents/openai_responses.py
@@ -624,19 +624,23 @@ class OpenAIResponseObjectStreamResponseMcpCallCompleted(BaseModel):
@json_schema_type
-class OpenAIResponseContentPart(BaseModel):
- """Base class for response content parts."""
-
- id: str
- type: str
+class OpenAIResponseContentPartOutputText(BaseModel):
+ type: Literal["output_text"] = "output_text"
+ text: str
+ # TODO: add annotations, logprobs, etc.
@json_schema_type
-class OpenAIResponseContentPartText(OpenAIResponseContentPart):
- """Text content part for streaming responses."""
+class OpenAIResponseContentPartRefusal(BaseModel):
+ type: Literal["refusal"] = "refusal"
+ refusal: str
- text: str
- type: Literal["text"] = "text"
+
+OpenAIResponseContentPart = Annotated[
+ OpenAIResponseContentPartOutputText | OpenAIResponseContentPartRefusal,
+ Field(discriminator="type"),
+]
+register_schema(OpenAIResponseContentPart, name="OpenAIResponseContentPart")
@json_schema_type
diff --git a/llama_stack/providers/inline/agents/meta_reference/openai_responses.py b/llama_stack/providers/inline/agents/meta_reference/openai_responses.py
index a48eac6a7..6aca4d68e 100644
--- a/llama_stack/providers/inline/agents/meta_reference/openai_responses.py
+++ b/llama_stack/providers/inline/agents/meta_reference/openai_responses.py
@@ -20,7 +20,7 @@ from llama_stack.apis.agents.openai_responses import (
ListOpenAIResponseInputItem,
ListOpenAIResponseObject,
OpenAIDeleteResponseObject,
- OpenAIResponseContentPartText,
+ OpenAIResponseContentPartOutputText,
OpenAIResponseInput,
OpenAIResponseInputFunctionToolCallOutput,
OpenAIResponseInputMessageContent,
@@ -481,7 +481,6 @@ class OpenAIResponsesImpl:
# Track tool call items for streaming events
tool_call_item_ids: dict[int, str] = {}
# Track content parts for streaming events
- content_part_id: str | None = None
content_part_emitted = False
async for chunk in completion_result:
@@ -493,14 +492,12 @@ class OpenAIResponsesImpl:
if chunk_choice.delta.content:
# Emit content_part.added event for first text chunk
if not content_part_emitted:
- content_part_id = f"cp_text_{uuid.uuid4()}"
content_part_emitted = True
sequence_number += 1
yield OpenAIResponseObjectStreamResponseContentPartAdded(
response_id=response_id,
item_id=message_item_id,
- part=OpenAIResponseContentPartText(
- id=content_part_id,
+ part=OpenAIResponseContentPartOutputText(
text="", # Will be filled incrementally via text deltas
),
sequence_number=sequence_number,
@@ -618,14 +615,13 @@ class OpenAIResponsesImpl:
tool_calls = None
# Emit content_part.done event if text content was streamed (before content gets cleared)
- if content_part_emitted and content_part_id:
+ if content_part_emitted:
final_text = "".join(chat_response_content)
sequence_number += 1
yield OpenAIResponseObjectStreamResponseContentPartDone(
response_id=response_id,
item_id=message_item_id,
- part=OpenAIResponseContentPartText(
- id=content_part_id,
+ part=OpenAIResponseContentPartOutputText(
text=final_text,
),
sequence_number=sequence_number,
diff --git a/tests/integration/non_ci/responses/test_responses.py b/tests/integration/non_ci/responses/test_responses.py
index 6d920ef99..04266eec8 100644
--- a/tests/integration/non_ci/responses/test_responses.py
+++ b/tests/integration/non_ci/responses/test_responses.py
@@ -731,27 +731,17 @@ def test_response_streaming_multi_turn_tool_execution(compat_client, text_model_
assert hasattr(added_event, "response_id"), "Content part added event should have response_id"
assert hasattr(added_event, "item_id"), "Content part added event should have item_id"
assert hasattr(added_event, "part"), "Content part added event should have part"
- # Part might be a dict or object, handle both cases
- if hasattr(added_event.part, "id"):
- assert added_event.part.id, "Content part should have id"
- assert added_event.part.type, "Content part should have type"
- else:
- assert "id" in added_event.part, "Content part should have id"
- assert "type" in added_event.part, "Content part should have type"
+
+ # TODO: enable this after the client types are updated
+ # assert added_event.part.type == "output_text", "Content part should be an output_text"
for done_event in content_part_done_events:
assert hasattr(done_event, "response_id"), "Content part done event should have response_id"
assert hasattr(done_event, "item_id"), "Content part done event should have item_id"
assert hasattr(done_event, "part"), "Content part done event should have part"
- # Part might be a dict or object, handle both cases
- # Note: In some scenarios (e.g., with tool calls), text content might be empty
- if hasattr(done_event.part, "text"):
- # Text can be empty in tool call scenarios, so we just check it exists
- assert hasattr(done_event.part, "text"), "Content part should have text field when done"
- else:
- # For dict case, text field might not be present if content was empty
- # This is valid behavior when tool calls are present
- pass
+
+ # TODO: enable this after the client types are updated
+ # assert len(done_event.part.text) > 0, "Content part should have text when done"
# Basic pairing check: each output_item.added should be followed by some activity
# (but we can't enforce strict 1:1 pairing due to the complexity of multi-turn scenarios)