Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions python/packages/openai/agent_framework_openai/_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2474,6 +2474,29 @@ def _get_ann_value(key: str) -> Any:
raw_representation=event,
)
)
elif ann_type == "url_citation":
ann_url = _get_ann_value("url")
if ann_url:
ann_start = _get_ann_value("start_index")
ann_end = _get_ann_value("end_index")
annotation_obj = Annotation(
type="citation",
title=_get_ann_value("title") or "",
url=str(ann_url),
additional_properties={"annotation_index": event.annotation_index},
raw_representation=annotation,
)
Comment thread
giles17 marked this conversation as resolved.
if ann_start is not None and ann_end is not None:
annotation_obj["annotated_regions"] = [
TextSpanRegion(
type="text_span",
start_index=ann_start,
end_index=ann_end,
)
]
contents.append(
Content.from_text(text="", annotations=[annotation_obj], raw_representation=event)
Comment thread
eavanvalkenburg marked this conversation as resolved.
)
else:
logger.debug("Unparsed annotation type in streaming: %s", ann_type)
case "response.output_item.done":
Expand Down
88 changes: 85 additions & 3 deletions python/packages/openai/tests/openai/test_openai_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2570,8 +2570,65 @@ def test_streaming_annotation_added_with_container_file_citation() -> None:
assert content.additional_properties.get("end_index") == 50


def test_streaming_annotation_added_with_unknown_type() -> None:
"""Test streaming annotation added event with unknown type is ignored."""
def test_streaming_annotation_added_with_url_citation() -> None:
"""Test streaming annotation added event with url_citation type produces citation annotation."""
client = OpenAIChatClient(model="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}

mock_event = MagicMock()
mock_event.type = "response.output_text.annotation.added"
mock_event.annotation_index = 0
mock_event.annotation = {
"type": "url_citation",
"url": "https://example.sharepoint.com/sites/my-site/doc.pdf",
"title": "doc.pdf",
"start_index": 100,
"end_index": 112,
}

response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)

assert len(response.contents) == 1
content = response.contents[0]
assert content.type == "text"
assert content.annotations is not None
assert len(content.annotations) == 1
annotation = content.annotations[0]
assert annotation["type"] == "citation"
assert annotation["title"] == "doc.pdf"
assert annotation["url"] == "https://example.sharepoint.com/sites/my-site/doc.pdf"
assert annotation["additional_properties"]["annotation_index"] == 0
assert annotation["raw_representation"] == mock_event.annotation
assert annotation["annotated_regions"] is not None
assert len(annotation["annotated_regions"]) == 1
region = annotation["annotated_regions"][0]
assert region["type"] == "text_span"
assert region["start_index"] == 100
assert region["end_index"] == 112


def test_streaming_annotation_added_with_url_citation_no_url() -> None:
"""Test streaming annotation added event with url_citation but missing url is ignored."""
client = OpenAIChatClient(model="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}

mock_event = MagicMock()
mock_event.type = "response.output_text.annotation.added"
mock_event.annotation_index = 0
mock_event.annotation = {
"type": "url_citation",
"title": "doc.pdf",
}

response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)

assert len(response.contents) == 0

Comment thread
giles17 marked this conversation as resolved.

def test_streaming_annotation_added_with_url_citation_no_indices() -> None:
"""Test streaming annotation with url_citation that has url but no start_index/end_index."""
client = OpenAIChatClient(model="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
Expand All @@ -2582,11 +2639,36 @@ def test_streaming_annotation_added_with_unknown_type() -> None:
mock_event.annotation = {
"type": "url_citation",
"url": "https://example.com",
"title": "Example",
}

response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)

assert len(response.contents) == 1
annotation = response.contents[0].annotations[0]
assert annotation["type"] == "citation"
assert annotation["title"] == "Example"
assert annotation["url"] == "https://example.com"
assert annotation["additional_properties"]["annotation_index"] == 0
assert "annotated_regions" not in annotation


def test_streaming_annotation_added_with_unknown_type() -> None:
"""Test streaming annotation added event with unknown type is ignored."""
client = OpenAIChatClient(model="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}

mock_event = MagicMock()
mock_event.type = "response.output_text.annotation.added"
mock_event.annotation_index = 0
mock_event.annotation = {
"type": "some_future_annotation_type",
"data": "test",
}

response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)

# url_citation should not produce HostedFileContent
assert len(response.contents) == 0


Expand Down
Loading