mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-08-15 14:08:00 +00:00
content part fixes
This commit is contained in:
parent
e48d062233
commit
6bd215706d
5 changed files with 271 additions and 33 deletions
137
docs/_static/llama-stack-spec.html
vendored
137
docs/_static/llama-stack-spec.html
vendored
|
@ -8821,6 +8821,61 @@
|
||||||
"title": "OpenAIResponseOutputMessageMCPListTools",
|
"title": "OpenAIResponseOutputMessageMCPListTools",
|
||||||
"description": "MCP list tools output message containing available tools from an MCP server."
|
"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": {
|
"OpenAIResponseObjectStream": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
|
@ -8877,6 +8932,12 @@
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted"
|
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseCompleted"
|
"$ref": "#/components/schemas/OpenAIResponseObjectStreamResponseCompleted"
|
||||||
}
|
}
|
||||||
|
@ -8902,6 +8963,8 @@
|
||||||
"response.mcp_call.in_progress": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress",
|
"response.mcp_call.in_progress": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress",
|
||||||
"response.mcp_call.failed": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed",
|
"response.mcp_call.failed": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed",
|
||||||
"response.mcp_call.completed": "#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted",
|
"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"
|
"response.completed": "#/components/schemas/OpenAIResponseObjectStreamResponseCompleted"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8928,6 +8991,80 @@
|
||||||
"title": "OpenAIResponseObjectStreamResponseCompleted",
|
"title": "OpenAIResponseObjectStreamResponseCompleted",
|
||||||
"description": "Streaming event indicating a response has been completed."
|
"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": {
|
"OpenAIResponseObjectStreamResponseCreated": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
111
docs/_static/llama-stack-spec.yaml
vendored
111
docs/_static/llama-stack-spec.yaml
vendored
|
@ -6441,6 +6441,43 @@ components:
|
||||||
title: OpenAIResponseOutputMessageMCPListTools
|
title: OpenAIResponseOutputMessageMCPListTools
|
||||||
description: >-
|
description: >-
|
||||||
MCP list tools output message containing available tools from an MCP server.
|
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:
|
OpenAIResponseObjectStream:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseCreated'
|
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseCreated'
|
||||||
|
@ -6461,6 +6498,8 @@ components:
|
||||||
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress'
|
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed'
|
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted'
|
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted'
|
||||||
|
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartAdded'
|
||||||
|
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseContentPartDone'
|
||||||
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseCompleted'
|
- $ref: '#/components/schemas/OpenAIResponseObjectStreamResponseCompleted'
|
||||||
discriminator:
|
discriminator:
|
||||||
propertyName: type
|
propertyName: type
|
||||||
|
@ -6483,6 +6522,8 @@ components:
|
||||||
response.mcp_call.in_progress: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress'
|
response.mcp_call.in_progress: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallInProgress'
|
||||||
response.mcp_call.failed: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed'
|
response.mcp_call.failed: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallFailed'
|
||||||
response.mcp_call.completed: '#/components/schemas/OpenAIResponseObjectStreamResponseMcpCallCompleted'
|
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'
|
response.completed: '#/components/schemas/OpenAIResponseObjectStreamResponseCompleted'
|
||||||
"OpenAIResponseObjectStreamResponseCompleted":
|
"OpenAIResponseObjectStreamResponseCompleted":
|
||||||
type: object
|
type: object
|
||||||
|
@ -6504,6 +6545,76 @@ components:
|
||||||
OpenAIResponseObjectStreamResponseCompleted
|
OpenAIResponseObjectStreamResponseCompleted
|
||||||
description: >-
|
description: >-
|
||||||
Streaming event indicating a response has been completed.
|
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":
|
"OpenAIResponseObjectStreamResponseCreated":
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -624,19 +624,23 @@ class OpenAIResponseObjectStreamResponseMcpCallCompleted(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
@json_schema_type
|
@json_schema_type
|
||||||
class OpenAIResponseContentPart(BaseModel):
|
class OpenAIResponseContentPartOutputText(BaseModel):
|
||||||
"""Base class for response content parts."""
|
type: Literal["output_text"] = "output_text"
|
||||||
|
text: str
|
||||||
id: str
|
# TODO: add annotations, logprobs, etc.
|
||||||
type: str
|
|
||||||
|
|
||||||
|
|
||||||
@json_schema_type
|
@json_schema_type
|
||||||
class OpenAIResponseContentPartText(OpenAIResponseContentPart):
|
class OpenAIResponseContentPartRefusal(BaseModel):
|
||||||
"""Text content part for streaming responses."""
|
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
|
@json_schema_type
|
||||||
|
|
|
@ -20,7 +20,7 @@ from llama_stack.apis.agents.openai_responses import (
|
||||||
ListOpenAIResponseInputItem,
|
ListOpenAIResponseInputItem,
|
||||||
ListOpenAIResponseObject,
|
ListOpenAIResponseObject,
|
||||||
OpenAIDeleteResponseObject,
|
OpenAIDeleteResponseObject,
|
||||||
OpenAIResponseContentPartText,
|
OpenAIResponseContentPartOutputText,
|
||||||
OpenAIResponseInput,
|
OpenAIResponseInput,
|
||||||
OpenAIResponseInputFunctionToolCallOutput,
|
OpenAIResponseInputFunctionToolCallOutput,
|
||||||
OpenAIResponseInputMessageContent,
|
OpenAIResponseInputMessageContent,
|
||||||
|
@ -481,7 +481,6 @@ class OpenAIResponsesImpl:
|
||||||
# Track tool call items for streaming events
|
# Track tool call items for streaming events
|
||||||
tool_call_item_ids: dict[int, str] = {}
|
tool_call_item_ids: dict[int, str] = {}
|
||||||
# Track content parts for streaming events
|
# Track content parts for streaming events
|
||||||
content_part_id: str | None = None
|
|
||||||
content_part_emitted = False
|
content_part_emitted = False
|
||||||
|
|
||||||
async for chunk in completion_result:
|
async for chunk in completion_result:
|
||||||
|
@ -493,14 +492,12 @@ class OpenAIResponsesImpl:
|
||||||
if chunk_choice.delta.content:
|
if chunk_choice.delta.content:
|
||||||
# Emit content_part.added event for first text chunk
|
# Emit content_part.added event for first text chunk
|
||||||
if not content_part_emitted:
|
if not content_part_emitted:
|
||||||
content_part_id = f"cp_text_{uuid.uuid4()}"
|
|
||||||
content_part_emitted = True
|
content_part_emitted = True
|
||||||
sequence_number += 1
|
sequence_number += 1
|
||||||
yield OpenAIResponseObjectStreamResponseContentPartAdded(
|
yield OpenAIResponseObjectStreamResponseContentPartAdded(
|
||||||
response_id=response_id,
|
response_id=response_id,
|
||||||
item_id=message_item_id,
|
item_id=message_item_id,
|
||||||
part=OpenAIResponseContentPartText(
|
part=OpenAIResponseContentPartOutputText(
|
||||||
id=content_part_id,
|
|
||||||
text="", # Will be filled incrementally via text deltas
|
text="", # Will be filled incrementally via text deltas
|
||||||
),
|
),
|
||||||
sequence_number=sequence_number,
|
sequence_number=sequence_number,
|
||||||
|
@ -618,14 +615,13 @@ class OpenAIResponsesImpl:
|
||||||
tool_calls = None
|
tool_calls = None
|
||||||
|
|
||||||
# Emit content_part.done event if text content was streamed (before content gets cleared)
|
# 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)
|
final_text = "".join(chat_response_content)
|
||||||
sequence_number += 1
|
sequence_number += 1
|
||||||
yield OpenAIResponseObjectStreamResponseContentPartDone(
|
yield OpenAIResponseObjectStreamResponseContentPartDone(
|
||||||
response_id=response_id,
|
response_id=response_id,
|
||||||
item_id=message_item_id,
|
item_id=message_item_id,
|
||||||
part=OpenAIResponseContentPartText(
|
part=OpenAIResponseContentPartOutputText(
|
||||||
id=content_part_id,
|
|
||||||
text=final_text,
|
text=final_text,
|
||||||
),
|
),
|
||||||
sequence_number=sequence_number,
|
sequence_number=sequence_number,
|
||||||
|
|
|
@ -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, "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, "item_id"), "Content part added event should have item_id"
|
||||||
assert hasattr(added_event, "part"), "Content part added event should have part"
|
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"):
|
# TODO: enable this after the client types are updated
|
||||||
assert added_event.part.id, "Content part should have id"
|
# assert added_event.part.type == "output_text", "Content part should be an output_text"
|
||||||
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"
|
|
||||||
|
|
||||||
for done_event in content_part_done_events:
|
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, "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, "item_id"), "Content part done event should have item_id"
|
||||||
assert hasattr(done_event, "part"), "Content part done event should have part"
|
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
|
# TODO: enable this after the client types are updated
|
||||||
if hasattr(done_event.part, "text"):
|
# assert len(done_event.part.text) > 0, "Content part should have text when done"
|
||||||
# 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
|
|
||||||
|
|
||||||
# Basic pairing check: each output_item.added should be followed by some activity
|
# 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)
|
# (but we can't enforce strict 1:1 pairing due to the complexity of multi-turn scenarios)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue