Repository navigation
fix(kosong): round-trip empty reasoning content - #2446
Conversation
Track whether a message contained ThinkPart separately from whether the reasoning text is non-empty so empty reasoning_content is still sent for Kimi and OpenAI legacy providers. Add regression coverage for empty reasoning content round-tripping.
There was a problem hiding this comment.
Pull request overview
This PR fixes message conversion in kosong so that an empty ThinkPart is still preserved as empty reasoning_content when converting to OpenAI-compatible request payloads, while avoiding emitting reasoning_content for messages that never had any thinking parts. This aligns Kimi and OpenAI legacy provider conversion behavior and adds regression tests to prevent reintroducing the issue.
Changes:
- Track the presence of
ThinkPartvia a boolean flag soreasoning_contentis emitted even when its text is empty. - Apply the same “emit only if a ThinkPart existed” logic to both Kimi and OpenAI legacy request conversion paths.
- Add snapshot/regression tests covering empty
reasoning_contentround-tripping.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/kosong/src/kosong/contrib/chat_provider/openai_legacy.py | Emit reasoning_content based on presence of ThinkPart (not string truthiness), enabling empty-string preservation. |
| packages/kosong/src/kosong/chat_provider/kimi.py | Same conversion fix for Kimi: preserve empty reasoning when a ThinkPart exists; avoid adding reasoning otherwise. |
| packages/kosong/tests/api_snapshot_tests/test_openai_legacy.py | Add regression test asserting empty reasoning_content is included in outgoing OpenAI legacy request body. |
| packages/kosong/tests/api_snapshot_tests/test_kimi.py | Extend message-conversion snapshot coverage for assistant messages with empty reasoning. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
🚩 Asymmetry between response parsing and message conversion for empty reasoning
The PR ensures empty reasoning_content is preserved when sending messages to the API (via has_reasoning flag). However, the response parsing side in both providers still uses walrus-operator truthiness checks that will silently drop empty reasoning content from API responses:
kimi.py:423:if reasoning_content := getattr(message, "reasoning_content", None):— empty string is falsy, soThinkPartwon't be yieldedkimi.py:456: same pattern for streamingopenai_legacy.py:271andopenai_legacy.py:305: same pattern
This means if an API returns reasoning_content: "", it won't be converted to a ThinkPart(think="") on ingestion. So while a manually-constructed ThinkPart(think="") is now properly round-tripped through message conversion, an API response with empty reasoning won't produce one in the first place. This is pre-existing behavior and may be intentional (APIs rarely return empty reasoning), but worth noting the asymmetry.
(Refers to lines 423-425)
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
reasoning_contentwhen aThinkPartexists even if its text is empty.reasoning_contentto messages that never contained reasoning content.Tests
reasoning_contentround-tripping.cd packages/kosong && uv run pytest tests/api_snapshot_tests/test_kimi.py tests/api_snapshot_tests/test_openai_legacy.py -v