From 2287c38011542ceb359d07c4a9c969693f3e8e8f Mon Sep 17 00:00:00 2001 From: Fsocietyhhh <1211904451@qq.com> Date: Wed, 5 Aug 2026 23:52:34 +0800 Subject: [PATCH] feat: type reasoning token usage --- blockrun_llm/types.py | 12 ++++++++++++ tests/unit/test_reasoning_usage.py | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/unit/test_reasoning_usage.py diff --git a/blockrun_llm/types.py b/blockrun_llm/types.py index 0dc0108..b3da206 100644 --- a/blockrun_llm/types.py +++ b/blockrun_llm/types.py @@ -88,6 +88,18 @@ class ChatUsage(BaseModel): # headers are sent. Reads are cheaper; writes incur a one-time surcharge. cache_read_input_tokens: Optional[int] = None cache_creation_input_tokens: Optional[int] = None + # Provider-native token detail. reasoning_tokens is a subset of + # completion_tokens and must not be added again when calculating spend. + prompt_tokens_details: Optional[Dict[str, Any]] = None + completion_tokens_details: Optional[Dict[str, Any]] = None + + @property + def reasoning_tokens(self) -> Optional[int]: + """Return the upstream reasoning-token breakdown when available.""" + if not self.completion_tokens_details: + return None + value = self.completion_tokens_details.get("reasoning_tokens") + return value if isinstance(value, int) and value >= 0 else None class Config: extra = "allow" diff --git a/tests/unit/test_reasoning_usage.py b/tests/unit/test_reasoning_usage.py new file mode 100644 index 0000000..d200b3f --- /dev/null +++ b/tests/unit/test_reasoning_usage.py @@ -0,0 +1,21 @@ +from blockrun_llm.types import ChatUsage + + +def test_chat_usage_exposes_reasoning_breakdown_without_changing_totals() -> None: + usage = ChatUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + completion_tokens_details={"reasoning_tokens": 12}, + ) + + assert usage.reasoning_tokens == 12 + assert usage.completion_tokens == 20 + assert usage.model_dump(exclude_none=True)["completion_tokens_details"] == { + "reasoning_tokens": 12 + } + + +def test_chat_usage_reasoning_is_optional() -> None: + usage = ChatUsage(prompt_tokens=1, completion_tokens=2, total_tokens=3) + assert usage.reasoning_tokens is None