@@ -744,6 +744,7 @@ def cleanup(self) -> None:
744744 """End all open LLM spans, TASK spans, and TOOL spans; clear thread-local."""
745745 for ctx in self ._contexts .values ():
746746 if ctx .llm_span :
747+ self ._mark_output_usage_unknown (ctx )
747748 ctx .llm_span .end ()
748749 ctx .llm_span = None
749750 if ctx .task_span :
@@ -884,6 +885,8 @@ def _handle_stream_event(self, message: Any) -> None:
884885
885886 def _handle_result (self , message : Any ) -> None :
886887 self ._active_key = None
888+ for ctx in self ._contexts .values ():
889+ self ._mark_output_usage_unknown (ctx )
887890 result_value = getattr (message , "result" , None )
888891 if result_value is not None :
889892 self ._result_output = result_value
@@ -901,11 +904,17 @@ def _handle_result(self, message: Any) -> None:
901904 if v is not None
902905 }
903906 result_metrics : dict [str , float ] = {}
904- if not self ._include_partial_messages :
905- raw_usage = getattr (message , "usage" , None )
906- _ , usage_metadata = extract_anthropic_usage (raw_usage )
907- result_metadata .update (usage_metadata )
908- aggregate_usage = _aggregate_model_usage (getattr (message , "model_usage" , None ))
907+ raw_usage = getattr (message , "usage" , None )
908+ _ , usage_metadata = extract_anthropic_usage (raw_usage )
909+ result_metadata .update (usage_metadata )
910+ aggregate_usage = _aggregate_model_usage (getattr (message , "model_usage" , None ))
911+ if self ._include_partial_messages :
912+ # Keep the complete turn-level total visible without adding it as
913+ # another token metric alongside the per-call LLM span metrics.
914+ complete_usage = aggregate_usage or _copy_usage (raw_usage )
915+ if complete_usage :
916+ result_metadata ["model_usage" ] = complete_usage
917+ else :
909918 usage = aggregate_usage or _copy_usage (raw_usage )
910919 result_metrics , _ = extract_anthropic_usage (usage )
911920 if result_metadata or result_metrics :
@@ -1008,6 +1017,7 @@ def _start_or_merge_llm_span(
10081017 first_token_time = time .time ()
10091018
10101019 if ctx .llm_span :
1020+ self ._mark_output_usage_unknown (ctx )
10111021 ctx .llm_span .end (end_time = resolved_start )
10121022
10131023 final_content , span = _create_llm_span_for_messages (
@@ -1047,6 +1057,12 @@ def _log_assistant_usage(
10471057 _ , metadata = extract_anthropic_usage (raw_message_usage , include_output = False )
10481058 ctx .llm_span .log (metrics = metrics or None , metadata = metadata or None )
10491059
1060+ def _mark_output_usage_unknown (self , ctx : _AgentContext ) -> None :
1061+ """Mark missing output only once an LLM span is finalized."""
1062+ if ctx .llm_span is None or ctx .llm_message_id in self ._final_output_usage_message_ids :
1063+ return
1064+ ctx .llm_span .log (metadata = {"usage_output_tokens_unknown" : True })
1065+
10501066 def _process_task_event (self , message : Any , agent_span_export : str | None ) -> None :
10511067 """Handle TaskStarted / TaskProgress / TaskNotification system messages."""
10521068 task_id = _msg_field (message , "task_id" )
0 commit comments