Repository navigation
Description
Describe the bug
When two streaming chunks are added, StreamingContentMixin._merge_items_lists merges each incoming item into every compatible item in the list, not just the first one. The inner loop sets added = True after a successful item + other_item, but it doesn't break, so it keeps going and adds the same incoming item to every later item of the same type that accepts it.
semantic-kernel/python/semantic_kernel/contents/streaming_content_mixin.py
Lines 38 to 51 in 25bd1bb
| for other_item in other_items: | |
| added = False | |
| for id, item in enumerate(new_items_list): | |
| if type(item) is type(other_item) and hasattr(item, "__add__"): | |
| try: | |
| new_item = item + other_item # type: ignore | |
| new_items_list[id] = new_item | |
| added = True | |
| except (ValueError, ContentAdditionException) as ex: | |
| logger.debug(f"Could not add item {other_item} to {item}.", exc_info=ex) | |
| continue | |
| if not added: | |
| logger.debug(f"Could not add item {other_item} to any item in the list. Adding it as a new item.") | |
| new_items_list.append(other_item) |
This happens whenever a merged message holds two items of the same type that can both take the next chunk. One case is two StreamingTextContent items with the same choice_index, which the Google AI connector builds when a candidate has more than one text part (google_ai_chat_completion.py#L366-L376). Another is two FunctionCallContent items whose id/index don't tell them apart. Then the next chunk's text or arguments are copied into both.
To Reproduce
semantic-kernel 1.47.0. The file is the same on main (25bd1bb).
from semantic_kernel.contents import AuthorRole, StreamingChatMessageContent, StreamingTextContent
c1 = StreamingChatMessageContent(role=AuthorRole.ASSISTANT, choice_index=0, items=[
StreamingTextContent(choice_index=0, text="Part one. "),
StreamingTextContent(choice_index=0, text="Part two. "),
])
c2 = StreamingChatMessageContent(role=AuthorRole.ASSISTANT, choice_index=0, items=[
StreamingTextContent(choice_index=0, text="tail"),
])
print([i.text for i in (c1 + c2).items])Output:
['Part one. tail', 'Part two. tail']
Expected behavior
The incoming item is merged into one item only, so the text isn't duplicated: ['Part one. tail', 'Part two. '].
Platform
- Language: Python
- Source: semantic-kernel 1.47.0 (pip) /
mainat 25bd1bb - AI model: n/a (no model call needed)
- IDE: n/a
- OS: Windows 11, Python 3.12
Additional context
The fix is a break after added = True. I have it with a regression test in tests/unit/contents/test_streaming_chat_message_content.py and will open a PR.
Activity