Add token-aware reasoning output - #1046
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
The internal token-aware split and Responses API reasoning events look good. For equivalent separation in OpenAI-compatible Chat Completions, could we add an optional reasoning_content member to ChatCompletionDelta, serialize it when present, and emit reasoning segments through that field instead of dropping them?
The expected reasoning chunk would be:
{
"choices": [{
"delta": {
"reasoning_content": "Analyzing project files..."
}
}]
}followed by visible output through:
{
"choices": [{
"delta": {
"content": "I found three issues."
}
}]
}Tests should cover reasoning-only and interleaved reasoning/content chunks so ordinary models continue emitting only delta.content.
| @@ -796,43 +743,39 @@ void ChatSession::ProcessChatCompletionsJson(const std::string& request_json, co | |||
| // REASONING segments: intentionally dropped from the Chat Completions stream. Never feed reasoning text to | |||
There was a problem hiding this comment.
Could we stream these segments through delta.reasoning_content instead of dropping them? The splitter already provides the necessary classification, and emitting a separate field would let Copilot/VS Code and other OpenAI-compatible clients hide, collapse, or style reasoning independently while preserving visible output in delta.content.
There was a problem hiding this comment.
Pull request overview
Adds token-aware reasoning segmentation, token accounting, ordered tool-call handling, and API serialization across the C++ SDK.
Changes:
- Detects reasoning boundaries using token IDs with text fallback.
- Preserves ordered reasoning, visible text, and tool-call events.
- Exposes reasoning-token usage and adds unit coverage.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
sdk_v2/cpp/src/contracts/chat_completions_converter.cc |
Filters reasoning and maps usage. |
sdk_v2/cpp/src/inferencing/generative/chat/chat_generator.h |
Exposes current token IDs. |
sdk_v2/cpp/src/inferencing/generative/chat/chat_session.cc |
Integrates reasoning and ordered events. |
sdk_v2/cpp/src/inferencing/generative/chat/chat_session.h |
Defines generated event handling. |
sdk_v2/cpp/src/inferencing/generative/chat/onnx_chat_generator.cc |
Captures generated token IDs. |
sdk_v2/cpp/src/inferencing/generative/chat/onnx_chat_generator.h |
Stores current token state. |
sdk_v2/cpp/src/inferencing/generative/chat/reasoning_stream_splitter.h |
Implements token-aware splitting. |
sdk_v2/cpp/src/inferencing/generative/openresponses/response_converter.cc |
Converts typed reasoning output. |
sdk_v2/cpp/src/inferencing/generative/preprocessor.cc |
Adds token-ID encoding. |
sdk_v2/cpp/src/inferencing/generative/preprocessor.h |
Declares token-ID encoding. |
sdk_v2/cpp/src/inferencing/generative/toolcalling/tool_call_stream_accumulator.h |
Adds ordered accumulator events. |
sdk_v2/cpp/src/inferencing/session/response.h |
Tracks reasoning-token usage. |
sdk_v2/cpp/src/service/chat_completions_handler.cc |
Streams reasoning usage. |
sdk_v2/cpp/src/service/responses_handler.cc |
Streams tool-call output items. |
sdk_v2/cpp/test/CMakeLists.txt |
Registers splitter tests. |
sdk_v2/cpp/test/internal_api/chat/reasoning_stream_splitter_test.cc |
Tests splitting and counting. |
sdk_v2/cpp/test/internal_api/chat_completions_converter_test.cc |
Tests filtered reasoning output. |
sdk_v2/cpp/test/internal_api/chat_completions_test.cc |
Tests usage serialization. |
sdk_v2/cpp/test/internal_api/response_converter_test.cc |
Tests ordered reasoning conversion. |
sdk_v2/cpp/test/internal_api/response_store_test.cc |
Tests stored reasoning usage. |
sdk_v2/cpp/test/internal_api/toolcalling/tool_call_stream_accumulator_test.cc |
Tests ordered tool events. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| auto parsed = ParseToolCalls(tool_call_buffer_, start_marker_, end_marker_); | ||
| for (auto& pc : parsed) { | ||
| out.events.emplace_back(pc); | ||
| out.ready_calls.push_back(std::move(pc)); | ||
| } |
| const int output_index = next_output_index++; | ||
| FunctionCallOutputItem function; | ||
| function.id = ResponseConverter::GenerateId("fc"); | ||
| function.call_id = call.call_id.empty() ? ResponseConverter::GenerateId("call") : call.call_id; | ||
| function.name = call.name; | ||
| function.arguments = call.arguments; | ||
|
|
||
| StreamEvent added; | ||
| added.type = StreamEventType::kOutputItemAdded; | ||
| added.sequence_number = seq++; | ||
| added.output_index = output_index; | ||
| added.item = function; | ||
| push_event("response.output_item.added", added); | ||
|
|
||
| function.status = ResponseStatus::kCompleted; | ||
|
|
||
| StreamEvent done; | ||
| done.type = StreamEventType::kOutputItemDone; | ||
| done.sequence_number = seq++; | ||
| done.output_index = output_index; | ||
| done.item = function; | ||
| push_event("response.output_item.done", done); |
| if (!assistant_history.empty()) { | ||
| history_.emplace_back(FOUNDRY_LOCAL_ROLE_ASSISTANT, std::move(assistant_history)); |
Classify reasoning from ORT GenAI BOR/EOR token metadata, preserve ordered reasoning, text, and tool events, and report reasoning usage consistently across Chat Completions and Responses. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d2795271-a7b7-497b-9a98-f3c4ebfe811d
3bf37c3 to
5a1baf4
Compare
Summary
Adds token-aware reasoning output to the C++ SDK, separating model reasoning from visible output and improving reasoning/tool-call streaming.
ReasoningStreamSplitterthat recognizes reasoning boundaries from token IDs, with decoded<think>...</think>matching as a fallback.usage.completion_tokens_details.reasoning_tokens) and the Responses API (usage.output_tokens_details.reasoning_tokens).Remaining gap
/v1/chat/completionsstill intentionally drops reasoning segments. The current implementation does not define or emitdelta.reasoning_content; only visible text is sent throughdelta.content. The PR therefore provides separate reasoning output through the Responses API, but does not yet provide the Copilot/DeepSeek-compatible Chat Completions streaming shape.