Skip to content

Add token-aware reasoning output - #1046

Draft
Baiju Meswani (baijumeswani) wants to merge 1 commit into
sayanshaw/fl-tool-tagsfrom
baijumeswani/reasoning
Draft

Add token-aware reasoning output#1046
Baiju Meswani (baijumeswani) wants to merge 1 commit into
sayanshaw/fl-tool-tagsfrom
baijumeswani/reasoning

Conversation

@baijumeswani

@baijumeswani Baiju Meswani (baijumeswani) commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds token-aware reasoning output to the C++ SDK, separating model reasoning from visible output and improving reasoning/tool-call streaming.

  • Introduces a token-aware ReasoningStreamSplitter that recognizes reasoning boundaries from token IDs, with decoded <think>...</think> matching as a fallback.
  • Handles empty-decoded special tokens, multi-token markers, truncated reasoning, newline trimming, and EOS/control tokens.
  • Preserves ordered reasoning, visible text, and tool-call events without allowing tool-call-shaped reasoning to execute as a tool call.
  • Tracks reasoning token counts and exposes them through Chat Completions (usage.completion_tokens_details.reasoning_tokens) and the Responses API (usage.output_tokens_details.reasoning_tokens).
  • Represents reasoning as typed output in the Responses API and streams it separately from visible text.
  • Adds complete Responses API function-call streaming events, including argument delta/done events.
  • Preserves invalid tool-call blocks as visible text rather than silently dropping them.
  • Preserves an assistant history entry for reasoning-only turns, preventing consecutive user roles on the next request.
  • Adds unit coverage for reasoning boundaries, token accounting, interleaving, truncation, tool calls, and response conversion.

Remaining gap

/v1/chat/completions still intentionally drops reasoning segments. The current implementation does not define or emit delta.reasoning_content; only visible text is sent through delta.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.

@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
foundry-local Ready Ready Preview Aug 27, 2026 11:35pm

Request Review

@jiafatom David Fan (jiafatom) left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 120 to 124
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));
}
Comment on lines +481 to +502
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);
Comment on lines +814 to +815
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
@baijumeswani
Baiju Meswani (baijumeswani) changed the base branch from main to sayanshaw/fl-tool-tags August 27, 2026 23:35
@baijumeswani
Baiju Meswani (baijumeswani) changed the base branch from sayanshaw/fl-tool-tags to main August 28, 2026 00:03
@baijumeswani
Baiju Meswani (baijumeswani) changed the base branch from main to sayanshaw/fl-tool-tags August 28, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants