Responses API: surface Qwen3 <think> blocks as reasoning items
#5
Closed
opened 2026-05-31 08:18:54 +00:00 by grenade
·
1 comment
No Branch/Tag Specified
main
feat/397-replicas
feat/385-acestep-memory
fix/376-models-unhealthy-node
feat/music-surprise-genres
feat/web-decisions-route
fix/cold-load-serialise
fix/music-hide-stale-on-compose
feat/audio-disk-guard
feat/music-studio-ux
fix/cortex-glibc-floor
feat/music-history-sidebar
feat/audio-flac-mp3
feat/393-heartmula-serving
feat/391-heartmula-port
feat/387-acestep-rollout
feat/383-acestep-engine
feat/390-heartmula-fixtures
feat/381-acestep-fixtures
feat/402-music-ui
feat/386-audio-rates
feat/382-audio-job-api
feat/356-systemone-playground
fix/bench-skip-decision-models
feat/341-laya-residency
feat/341-laya-rollout
feat/337-laya-perf
feat/338-neuron-systemone
feat/335-laya-prompt-builder
feat/336-laya-engine
feat/339-laya-lang-detect
feat/338-systemone-surface
feat/334-laya-reference-fixtures
feat/96-mtp-probe
fix/324-qsa-visible-set
feat/310-ple-random-advice
feat/neuron-phase-timing
fix/qwen4-ple-conv-launch-bound
feat/318-per-layer-activation-hop
fix/318-release-host-memory
feat/318-per-model-max-in-flight
fix/318-logits-last-position
fix/318-cached-experts-residency
feat/318-host-source-tensors
feat/318-host-resident-experts
fix/322-cache-telemetry
feat/322-isq-cache
test/323-parity-long-prompt
fix/323-layer-type-alias
fix/323-qsa-scores-in-f32
feat/310-mmap-ngram-table
fix/quant-fallback-match-llamacpp
fix/qwen4-exp-expert-row-width
fix/qwen4-exp-load-reporting-and-pool-trim
fix/320-isq-reachable-and-log-levels
feat/qwen4-exp-quantised-experts
feat/qwen4-exp-expert-bank
feat/qwen4-exp-mtp-head
docs/mtp-wiring-settled
feat/qwen4-exp-snapshot
feat/rustingface-always
feat/qwen4-exp-harness
feat/qwen4-exp-qsa-oracle
feat/qwen4-exp-model
feat/qwen4-exp-decoder
feat/qwen4-exp-linear-attn
feat/qwen4-exp-moe
feat/qwen4-exp-full-attn
feat/qwen4-exp-qsa-indexer
feat/qwen4-exp-config
feat/qwen4-exp-qsa-selection
feat/qwen4-exp-ple-consumption
fix/seed-cutlass-anonymously
feat/qwen4-exp-ple-hashing
fix/seed-cutlass-in-cuda-ci
feat/hf-endpoint-env
feat/qwen4-exp-hyper-connections
feat/qwen4-exp-norm-primitives
fix/alias-test-temp-catalogue-collision
chore/retire-vestigial-reasoning-ladder
fix/context-limit-honours-kv-budget
feat/server-advertised-reasoning-ladder
feat/web-chat-collapsible-working
fix/router-sync-restarts-only-on-settings-change
feat/ci-owns-the-router-config
fix/infra-setup-ships-ratelimit-to-gateway
fix/nginx-preflight-checks-every-grant
fix/restore-max-in-flight-8
fix/retire-qwen36-27b
fix/nginx-sync-must-not-block-cortex-deploy
feat/nginx-search-alias-and-sync
fix/wire-surface-in-neuron-spans
fix/295-ignore-tiny-prefill-samples
fix/300-anthropic-streaming-thinking
fix/300-return-the-models-reasoning
feat/operator-repetition-knobs
fix/292-prefix-cache-budget
feat/stamp-preserve-thinking-state
fix/296-one-step-one-assistant-turn
feat/preserve-thinking-ab-gate
fix/client-capability-misattributions
fix/291-max-in-flight-and-config-only-ci
feat/290-reasoning-rungs-from-the-model
fix/284-gdn-state-f32
feat/bench-measurement-regimes
feat/285-nonstreaming-through-engine
fix/288-bench-authenticates
feat/283-operator-sampling-override
fix/286-clippy-all-targets
fix/replay-only-the-latest-reasoning
fix/developer-role-maps-to-system
fix/223-reasoning-effort-off
fix/fetch-the-models-own-prompt-format
fix/278-advertise-the-real-limits
fix/277-responses-reasoning-roundtrip
fix/272-sampling-gaps
fix/clippy-1.98-result-large-err
feat/sampling-defaults
fix/streaming-completion-log
fix/prometheus-histogram-buckets
fix/responses-reasoning-events
fix/stream-reasoning-content
fix/cortex-stream-read-timeout
fix/sse-keepalive-during-prefill
fix/anonymous-yields-to-authenticated
fix/upstream-test-fixture-collisions
fix/docs-directory-index
feat/angels-internal-vhost
v0.1.16
v0.1.15
v0.1.14
v0.1.12
v0.1.11
v0.1.10
v0.1.9
v0.1.8
v0.1.7
v0.1.6
v0.1.5
v0.1.4
v0.1.3
v0.1.2
v0.1.1
v0.1.0
Labels
Clear labels
out-of-scope
p1-now
p2-next
p3-later
Outside helexa's constraining principles: near-frontier models on consumer hardware, served transparently and predictably
Active pain or unblocking work — pick up first
Engine levers — start once P1 baselines exist
Dependent on earlier work, or demand-driven
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: helexa/helexa#5
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Scope cut from Step 2 (commit
957f704)InferenceEvent::ReasoningDelta(crates/neuron/src/wire/event.rs) is defined but never emitted by the candle harness. As a result:response.reasoning.*event family that should carry this).Qwen3 emits
<think>…</think>blocks inline in its content stream today, and the harness folds them into plainInferenceEvent::TextDeltas indistinguishable from real assistant output. Downstream consumers (helexa-acp, the Responses client) parse the marker tags themselves.See
crates/neuron/src/harness/candle.rs::run_inference_streaming(CPU),stream_inference_via_worker(CUDA single-GPU), andchat_completion_tp_stream(CUDA TP) for the three sites that emitTextDelta.Why it was cut
Splitting
<think>from regular content requires a tag-state machine inside the inference loop (or one layer up, beforeemit_delta). The existing detokeniser doesn't know about model-specific markers. Adding it touched three hot loops in candle.rs that we wanted to leave alone for Step 1's refactor and Step 2's new surface.What implementation looks like
neuron::wire::event— a small struct that takes incremental text chunks and emits a stream of(Visible | Reasoning, String)tuples. Handles tag boundaries split across decoder steps (the same byte-streaming concerntokenizers::DecodeStreamalready solves for UTF-8).emit_delta/emit_delta_blocking— instead of always emittingTextDelta, route each parser output through the rightInferenceEventvariant.<think>); future models may use different markers (e.g. o-series uses internal tokens). Either:<think>parser, others → passthrough).reasoning_tag_pattern: Option<&str>field.project_responses_streamto emitresponse.reasoning_summary_part.added,response.reasoning_summary_text.delta, etc. when it seesReasoningDelta. The exact event names need to be cross-referenced against OpenAI's docs (the family lives alongsideoutput_text.*).<think>tags itself (crates/helexa-acp/src/qwen3.rs::ThinkParser) and just consume the typed event.Acceptance
TextDeltaevents (final answer) andReasoningDeltaevents (the<think>body) on the InferenceEvent stream.response.reasoning_*events for the thought content andresponse.output_text.*for the final answer.ThinkParserbecomes redundant for cortex-backed sessions; tests that exercise it still pass because the parser is the fallback for other endpoints.Tracking
Cosmetic improvement for Responses API consumers (they can render the thinking spinner properly). Reduces duplicate parsing in helexa-acp. Depends on nothing currently blocked.
Closing in favour of a model-agnostic reframe — see #8 (strip reasoning content by default on chat completions) and #9 (chat_template_kwargs passthrough).
Why this issue is wrong as written
The original proposal — "route Qwen3
<think>toReasoningDelta" — assumed Qwen3-specific tag parsing. Investigating the actual leak (Zed's commit-message generator showing<think>blocks in the field) surfaced two problems:crates/open_ai/src/completion.rs— nochat_template_kwargs, no Responses-API capability detection). The wire format has no slot for reasoning, so anything inside<think>arrives as plain content. #5's proposed fix wouldn't help that path because there's no reasoning-event family in chat completions to route to.What replaces it
The leak fixes cleanly with a model-agnostic seam: at model load time, probe the tokenizer's
added_tokensfor any token whose content matches a known reasoning-marker convention. Store the open/close token IDs onLoadedModel(orNonefor non-reasoning models). The inference loop's token-level state machine routes betweenTextDeltaandReasoningDeltawithout any hardcoded model knowledge.The chat-completions projector then drops
ReasoningDeltaby default (matching the wire format's lack of a reasoning slot), opt-in via header for callers like helexa-acp that want the markers back.That's #8. Companion is #9 (pass
chat_template_kwargsthrough to the chat template at tokenisation), which gives clients a request-side lever to suppress thinking at generation time — also model-agnostic since neuron doesn't interpret the kwarg, just forwards it.The Responses-API mapping (ReasoningDelta →
response.reasoning_summary_text.delta) is still worth doing eventually but only matters once a Responses-API consumer of cortex exists; tracking under #7's reasoning sub-bullet rather than as a separate issue today.