Python: fix(python): Handle thread.message.completed event in Assistants API streaming#4333
Conversation
Previously, `thread.message.completed` events fell through to the catch-all `else` branch and yielded empty `ChatResponseUpdate` objects, silently discarding fully-resolved annotation data (file citations, file paths, and their character-offset regions). This commit adds a dedicated handler for `thread.message.completed` that: - Walks the completed ThreadMessage.content array - Extracts text blocks with their fully-resolved annotations - Maps FileCitationAnnotation and FilePathAnnotation to the framework's Annotation type with proper TextSpanRegion data - Yields a ChatResponseUpdate containing the complete text and annotations Fixes microsoft#4322
Tests cover: - File citation annotation extraction - File path annotation extraction - Multiple annotations on a single text block - Text-only messages (no annotations) - Non-text blocks are skipped - Mixed content blocks (text + image) - Conversation ID propagation
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug where thread.message.completed streaming events from the OpenAI/Azure Assistants API were falling through to a catch-all handler, yielding empty ChatResponseUpdate objects and discarding fully-resolved annotation metadata (file citations with IDs, quotes, and character offsets). The fix adds a dedicated event handler that extracts text content and annotations from completed messages, enabling proper citation rendering for users of the Assistants API with file_search or code_interpreter tools.
Changes:
- Added handler for
thread.message.completedevent in_process_stream_eventsto extract fully-resolved annotation data - Added comprehensive test suite covering annotation extraction, edge cases, and conversation ID propagation
- Added imports for
FileCitationAnnotation,FilePathAnnotation,ThreadMessage,Annotation, andTextSpanRegiontypes
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
python/packages/core/agent_framework/openai/_assistants_client.py |
Added imports and new elif branch in _process_stream_events to handle thread.message.completed events, extracting text content and mapping FileCitationAnnotation and FilePathAnnotation to framework Annotation objects with TextSpanRegion data |
python/packages/core/tests/openai/test_assistants_message_completed.py |
New test file with 7 test cases covering file citation extraction, file path extraction, multiple annotations, text-only messages, non-text block skipping, mixed content blocks, and conversation ID propagation |
python/packages/core/agent_framework/openai/_assistants_client.py
Outdated
Show resolved
Hide resolved
python/packages/core/agent_framework/openai/_assistants_client.py
Outdated
Show resolved
Hide resolved
…notations - Include `quote` from `annotation.file_citation.quote` in `additional_properties` for FileCitationAnnotation, preserving the exact cited text snippet from the source file - Add `else` clause to log unrecognized annotation types at debug level, consistent with the pattern in `_responses_client.py` - Add `import logging` and module-level logger
- test_message_completed_with_file_citation_quote: verifies quote is included in additional_properties - test_message_completed_with_file_citation_no_quote: verifies quote is omitted when None - test_message_completed_unrecognized_annotation_logged: verifies unknown annotation types are logged at debug level and skipped
|
Thanks @copilot for the review! Both suggestions were great catches. I've addressed them in the latest commits: 1. Quote field ( 2. Unrecognized annotation fallback ( 3. Test coverage (
|
|
Addressing the 2 Copilot review comments:
Will push both fixes shortly. |
|
Follow-up — both fixes are already in the latest commit (
No additional push needed. |
Python Test Coverage Report •
Python Unit Test Overview
|
||||||||||||||||||||||||||||||
python/packages/core/agent_framework/openai/_assistants_client.py
Outdated
Show resolved
Hide resolved
python/packages/core/agent_framework/openai/_assistants_client.py
Outdated
Show resolved
Hide resolved
… string Per @giles17's review: - Use logging.getLogger('agent_framework.openai') to match module convention - Simplify debug message to use annotation.type instead of type().__name__
|
Both nits addressed in commit
|
python/packages/core/tests/openai/test_assistants_message_completed.py
Outdated
Show resolved
Hide resolved
Per @giles17's review: moved all tests from test_assistants_message_completed.py into test_openai_assistants_client.py and deleted the standalone file.
|
Done! Moved all tests into Changes:
|
|
Hey @LEDazzio01 thanks for putting in this PR. There are some checks failing (mypy and lint). Can you make fixes for them and I can go ahead and approve |
- Remove duplicate type annotation for 'ann' variable (no-redef) - Return directly from fixture instead of unnecessary assignment (RET504)
|
@giles17 Fixed the mypy and lint errors in commit
Both files now pass |
|
@LEDazzio01 Sorry there's one more mypy fail: |
|
Thanks for the feedback @giles17! Tests have been consolidated into Could you take another look when you get a chance? 🙏 |
|
Hey @LEDazzio01, any update on the mypy fix? |
|
Hey @giles17! The mypy fix was already pushed — it was the CI status is still showing as pending/no checks reported — not sure if the status checks need to be re-triggered on the upstream side? Let me know if you see anything else that needs attention! 🙏 |
|
@LEDazzio01 I just triggered the checks again and there's this mypy error: I think in the |
|
Good catch @giles17! You're right — the Fixed in commit |
…onflict The 'annotation' loop variable in thread.message.completed has type FileCitationAnnotation | FilePathAnnotation, which conflicts with the delta block's 'annotation' of type FileCitationDeltaAnnotation | FilePathDeltaAnnotation. Renamed to 'completed_annotation' to avoid mypy 'Incompatible types in assignment' error.
python/packages/core/agent_framework/openai/_assistants_client.py
Outdated
Show resolved
Hide resolved
|
Thanks @giles17! You're absolutely right — Fixed in commit
Should be clean now! 🤞 |

Summary
Fixes #4322
Previously,
thread.message.completedstreaming events fell through to the catch-allelsebranch in_process_stream_events, yielding emptyChatResponseUpdateobjects. This silently discarded fully-resolved annotation data — file citations with IDs, quotes, and character-offset regions.Changes
_assistants_client.pyFileCitationAnnotation,FilePathAnnotation,Message as ThreadMessagefromopenai.types.beta.threads;Annotation,TextSpanRegionfrom_typeselifbranch forthread.message.completedin_process_stream_eventsthat:ThreadMessage.contentarrayimage_file)FileCitationAnnotation→Annotation(type="citation")withfile_idandTextSpanRegionFilePathAnnotation→ same mapping patternChatResponseUpdatewith the complete text and annotationstest_assistants_message_completed.py(new)7 test cases covering:
Impact
Users of the Assistants API with
file_searchorcode_interpreterwill now receive resolved citation annotations in streaming responses, enabling proper citation rendering.