1111 post-label text and returns it to the caller; the caller decides whether
1212 to emit it as body content (so a mixed ``FINISH+TOOL`` reply never leaks
1313 prose into the answer area before the protocol is validated).
14- * Accumulates ``tool_calls`` deltas. When ``tool_label`` is set and tool-call
15- deltas arrive before the label resolves, force-resolves the label to that
16- value (tool-call presence is authoritative).
14+ * Accumulates ``tool_calls`` deltas. Tool-call presence alone does not choose
15+ the action label: the formal content stream must still begin with the
16+ caller's tool label (e.g. ``TOOL``), otherwise the caller's protocol repair
17+ path handles the missing label.
1718* When a reasoning model prepends a literal ``<think>...</think>`` block
1819 *before* the protocol label, that prelude is detected and streamed live
1920 into the reasoning sub-trace (same routing as the ``THINK`` label).
@@ -231,16 +232,10 @@ async def run_labeled_step(
231232 behavior — its cards only open when there is actual reasoning text to
232233 show, avoiding empty "Reasoning…" cards for direct FINISH replies.
233234
234- ``implicit_think_label`` lets a caller (e.g. chat) say "if a reasoning
235- model emits ``<think>...</think>`` without following it with one of my
236- protocol labels, treat the whole iteration as *this* label". The intent
237- is to gracefully accept native-format reasoning models — they think in
238- ``<think>`` blocks and may not parrot back the protocol's
239- ``\`\`THINK\`\``` token. Without this, the loop would see a missing
240- label and burn iterations on repair-retries. When the implicit
241- resolution fires, the prelude markers are preserved in the returned
242- ``text`` so the next iteration's assistant context still shows the
243- model's reasoning verbatim.
235+ ``implicit_think_label`` is kept for API compatibility with older
236+ callers, but is intentionally ignored. Reasoning traces from
237+ ``reasoning_content`` or inline ``<think>`` are trace data, not loop
238+ actions; the formal content stream must still provide the protocol label.
244239 """
245240 kwargs : dict [str , Any ] = {
246241 "model" : model ,
@@ -459,20 +454,10 @@ async def _ingest_pre_label(text: str) -> None:
459454 return
460455
461456 if len (label_buf ) > LABEL_PROBE_MAX_CHARS :
462- # Probe window exhausted with no protocol label match. If
463- # we previously consumed a ``<think>`` prelude AND the
464- # caller opted into implicit-THINK semantics, treat this
465- # iteration as an implicit ``THINK`` — the model is a
466- # reasoning model speaking its native dialect. Otherwise
467- # fall to ``LABEL_UNKNOWN`` so the caller can repair.
468- if (
469- saw_pre_label_think
470- and implicit_think_label
471- and implicit_think_label in allowed_labels
472- ):
473- label = implicit_think_label
474- else :
475- label = LABEL_UNKNOWN
457+ # Probe window exhausted with no protocol label match.
458+ # Reasoning traces are not action labels, so fall to
459+ # ``LABEL_UNKNOWN`` and let the caller repair.
460+ label = LABEL_UNKNOWN
476461 flushed = label_buf
477462 label_buf = ""
478463 await _emit_text (flushed )
@@ -603,21 +588,6 @@ async def _create_response_stream() -> Any:
603588 fn_for_chars = getattr (tc_delta , "function" , None )
604589 output_chars_seen += len (str (getattr (fn_for_chars , "name" , "" ) or "" ))
605590 output_chars_seen += len (str (getattr (fn_for_chars , "arguments" , "" ) or "" ))
606- # Tool-call deltas are authoritative for the tool branch. If
607- # we're still buffering a label when tool-call deltas arrive,
608- # force-resolve to ``tool_label`` so the buffered prose
609- # flushes into the reasoning sub-trace and subsequent prose
610- # continues there.
611- if label is None and tool_label :
612- label = tool_label
613- if in_prelude_think :
614- # Close out the prelude before treating any buffered
615- # prose as the tool branch's reasoning preamble.
616- await _close_prelude_artificially ()
617- flushed = label_buf
618- label_buf = ""
619- if flushed :
620- await _emit_text (flushed )
621591 idx = getattr (tc_delta , "index" , 0 )
622592 entry = tc_acc .setdefault (idx , {"id" : "" , "name" : "" , "arguments" : "" })
623593 if getattr (tc_delta , "id" , None ):
@@ -636,12 +606,9 @@ async def _create_response_stream() -> Any:
636606
637607 # Stream ended while still buffering a label. Decide how to resolve:
638608 #
639- # - If we saw a ``<think>`` prelude and the caller opted into
640- # implicit-THINK semantics, treat the iteration as an implicit
641- # ``THINK`` so the loop continues (reasoning models that natively
642- # speak ``<think>...</think>`` get accepted instead of treated as
643- # protocol violators).
644- # - Otherwise fall to ``LABEL_UNKNOWN`` and let the caller repair.
609+ # - Reasoning traces (``reasoning_content`` or inline ``<think>``) are
610+ # not action labels. If no formal content label appeared, fall to
611+ # ``LABEL_UNKNOWN`` and let the caller repair.
645612 if label is None :
646613 if in_prelude_think :
647614 # Stream ended mid-prelude — flush remaining reasoning live so
@@ -657,14 +624,7 @@ async def _create_response_stream() -> Any:
657624 label , after_label = final_parsed
658625 label_buf = ""
659626 await _emit_text (after_label )
660- if (
661- label is None
662- and saw_pre_label_think
663- and implicit_think_label
664- and implicit_think_label in allowed_labels
665- ):
666- label = implicit_think_label
667- elif label is None :
627+ if label is None :
668628 label = LABEL_UNKNOWN
669629 if label_buf :
670630 await _emit_text (label_buf )
@@ -692,16 +652,10 @@ async def _create_response_stream() -> Any:
692652 )
693653
694654 text = "" .join (content_acc )
695- # Preserve the literal ``<think>...</think>`` block when we resolved the
696- # iteration implicitly as ``THINK`` — the next iteration's assistant
697- # context should reflect the model's reasoning verbatim, not a stripped
698- # empty draft. For all other resolutions, fall through to the standard
699- # cleanup so downstream consumers (assistant messages, final-response
700- # text) aren't polluted with the prelude markers.
701- implicit_think_resolved = bool (
702- saw_pre_label_think and implicit_think_label and label == implicit_think_label
703- )
704- if (binding or saw_pre_label_think ) and not implicit_think_resolved :
655+ # Reasoning traces have already been streamed into the trace channel; the
656+ # returned formal text should not leak inline provider markers or private
657+ # pre-label thinking.
658+ if binding or saw_pre_label_think :
705659 text = clean_thinking_tags (text , binding , model )
706660 ordered_tool_calls = [tc_acc [k ] for k in sorted (tc_acc .keys ())]
707661 ordered_tool_calls = [tc for tc in ordered_tool_calls if tc .get ("name" )]
0 commit comments