From a21e5b6eac1420b52bc51fc6606cf056fcba2cc0 Mon Sep 17 00:00:00 2001 From: Mengsu Date: Wed, 14 Jan 2026 23:12:21 +0800 Subject: [PATCH 1/7] feat: support passing `cached_token` from dash call --- src/agentscope/model/_dashscope_model.py | 2 ++ src/agentscope/model/_model_usage.py | 28 ++++++++++++++++++++++-- src/agentscope/tracing/_attributes.py | 3 +++ src/agentscope/tracing/_extractor.py | 5 +++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/agentscope/model/_dashscope_model.py b/src/agentscope/model/_dashscope_model.py index c17a08e703..e82c95052c 100644 --- a/src/agentscope/model/_dashscope_model.py +++ b/src/agentscope/model/_dashscope_model.py @@ -368,6 +368,7 @@ async def _parse_dashscope_stream_response( input_tokens=chunk.usage.input_tokens, output_tokens=chunk.usage.output_tokens, time=(datetime.now() - start_datetime).total_seconds(), + **{k: v for k, v in dict(chunk.usage).items() if k not in ["input_tokens", "output_tokens"]}, ) parsed_chunk = ChatResponse( @@ -469,6 +470,7 @@ async def _parse_dashscope_generation_response( input_tokens=response.usage.input_tokens, output_tokens=response.usage.output_tokens, time=(datetime.now() - start_datetime).total_seconds(), + **{k: v for k, v in dict(chunk.usage).items() if k not in ["input_tokens", "output_tokens"]}, ) parsed_response = ChatResponse( diff --git a/src/agentscope/model/_model_usage.py b/src/agentscope/model/_model_usage.py index fcc76120ff..6cc55dbf5b 100644 --- a/src/agentscope/model/_model_usage.py +++ b/src/agentscope/model/_model_usage.py @@ -6,7 +6,7 @@ from .._utils._mixin import DictMixin -@dataclass +@dataclass(init=False) class ChatUsage(DictMixin): """The usage of a chat model API invocation.""" @@ -19,5 +19,29 @@ class ChatUsage(DictMixin): time: float """The time used in seconds.""" - type: Literal["chat"] = field(default_factory=lambda: "chat") + type: Literal["chat"] """The type of the usage, must be `chat`.""" + + def __init__(self, + input_tokens: int = 0, + output_tokens: int = 0, + time: float = 0, + **kwargs): + super().__init__(input_tokens=input_tokens, + output_tokens=output_tokens, + time = time, + **kwargs) + self.type = "chat" + + def __repr__(self): + base_info = (f"ChatUsage(" + f"input_tokens={self.input_tokens}, " + f"output_tokens={self.output_tokens}, " + f"time={self.time:.2f}, " + f"type='{self.type}'") + extra = {k: v for k, v in self.items() if k not in ["input_tokens", "output_tokens", "time", "type"]} + if extra: + extra_str = ", ".join(f"{k}={v!r}" for k, v in extra.items()) + base_info += f", {extra_str}" + base_info += ")" + return base_info diff --git a/src/agentscope/tracing/_attributes.py b/src/agentscope/tracing/_attributes.py index 991481b5af..5a92a5411b 100644 --- a/src/agentscope/tracing/_attributes.py +++ b/src/agentscope/tracing/_attributes.py @@ -68,6 +68,9 @@ class SpanAttributes: GEN_AI_USAGE_OUTPUT_TOKENS = GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS """The gen ai usage output tokens.""" + GEN_AI_USAGE_CACHED_TOKENS = "gen_ai.dash.usage.cached_tokens" + """The gen ai usage cached tokens.""" + # GenAI Message Attributes GEN_AI_INPUT_MESSAGES = GenAIAttributes.GEN_AI_INPUT_MESSAGES """The gen ai input messages.""" diff --git a/src/agentscope/tracing/_extractor.py b/src/agentscope/tracing/_extractor.py index 6ca71ea87e..cc95442a2d 100644 --- a/src/agentscope/tracing/_extractor.py +++ b/src/agentscope/tracing/_extractor.py @@ -378,6 +378,11 @@ def _get_llm_response_attributes( attributes[ SpanAttributes.GEN_AI_USAGE_OUTPUT_TOKENS ] = chat_response.usage.output_tokens + if hasattr(chat_response.usage, "prompt_tokens_details") and chat_response.usage.prompt_tokens_details: + print(f"[from as]chat_response.usage.prompt_tokens_details={chat_response.usage.prompt_tokens_details}") + attributes[ + SpanAttributes.GEN_AI_USAGE_CACHED_TOKENS + ] = chat_response.usage.prompt_tokens_details.get("cached_tokens") output_messages = _get_llm_output_messages(chat_response) if output_messages: From 718f72927a4b855069941aca347411723edd1373 Mon Sep 17 00:00:00 2001 From: Mengsu Date: Wed, 14 Jan 2026 23:29:21 +0800 Subject: [PATCH 2/7] remove typos --- src/agentscope/tracing/_extractor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/agentscope/tracing/_extractor.py b/src/agentscope/tracing/_extractor.py index cc95442a2d..17c62e8fe2 100644 --- a/src/agentscope/tracing/_extractor.py +++ b/src/agentscope/tracing/_extractor.py @@ -379,7 +379,6 @@ def _get_llm_response_attributes( SpanAttributes.GEN_AI_USAGE_OUTPUT_TOKENS ] = chat_response.usage.output_tokens if hasattr(chat_response.usage, "prompt_tokens_details") and chat_response.usage.prompt_tokens_details: - print(f"[from as]chat_response.usage.prompt_tokens_details={chat_response.usage.prompt_tokens_details}") attributes[ SpanAttributes.GEN_AI_USAGE_CACHED_TOKENS ] = chat_response.usage.prompt_tokens_details.get("cached_tokens") From 6110fee27a4a855d355078e69cbf17f03baad8ec Mon Sep 17 00:00:00 2001 From: Mengsu Date: Wed, 14 Jan 2026 23:36:25 +0800 Subject: [PATCH 3/7] fix typos --- src/agentscope/model/_dashscope_model.py | 12 +++++- src/agentscope/model/_model_usage.py | 50 ++++++++++++++---------- src/agentscope/tracing/_extractor.py | 5 ++- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/agentscope/model/_dashscope_model.py b/src/agentscope/model/_dashscope_model.py index e82c95052c..8614d6c856 100644 --- a/src/agentscope/model/_dashscope_model.py +++ b/src/agentscope/model/_dashscope_model.py @@ -368,7 +368,11 @@ async def _parse_dashscope_stream_response( input_tokens=chunk.usage.input_tokens, output_tokens=chunk.usage.output_tokens, time=(datetime.now() - start_datetime).total_seconds(), - **{k: v for k, v in dict(chunk.usage).items() if k not in ["input_tokens", "output_tokens"]}, + **{ + k: v + for k, v in dict(chunk.usage).items() + if k not in ["input_tokens", "output_tokens"] + }, ) parsed_chunk = ChatResponse( @@ -470,7 +474,11 @@ async def _parse_dashscope_generation_response( input_tokens=response.usage.input_tokens, output_tokens=response.usage.output_tokens, time=(datetime.now() - start_datetime).total_seconds(), - **{k: v for k, v in dict(chunk.usage).items() if k not in ["input_tokens", "output_tokens"]}, + **{ + k: v + for k, v in dict(response.usage).items() + if k not in ["input_tokens", "output_tokens"] + }, ) parsed_response = ChatResponse( diff --git a/src/agentscope/model/_model_usage.py b/src/agentscope/model/_model_usage.py index 6cc55dbf5b..d2341286f4 100644 --- a/src/agentscope/model/_model_usage.py +++ b/src/agentscope/model/_model_usage.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """The model usage class in agentscope.""" from dataclasses import dataclass, field -from typing import Literal +from typing import Literal, Any from .._utils._mixin import DictMixin @@ -22,26 +22,36 @@ class ChatUsage(DictMixin): type: Literal["chat"] """The type of the usage, must be `chat`.""" - def __init__(self, - input_tokens: int = 0, - output_tokens: int = 0, - time: float = 0, - **kwargs): - super().__init__(input_tokens=input_tokens, - output_tokens=output_tokens, - time = time, - **kwargs) - self.type = "chat" - - def __repr__(self): - base_info = (f"ChatUsage(" - f"input_tokens={self.input_tokens}, " - f"output_tokens={self.output_tokens}, " - f"time={self.time:.2f}, " - f"type='{self.type}'") - extra = {k: v for k, v in self.items() if k not in ["input_tokens", "output_tokens", "time", "type"]} + def __init__( + self, + input_tokens: int = 0, + output_tokens: int = 0, + time: float = 0, + **kwargs: Any, + ): + super().__init__( + input_tokens=input_tokens, + output_tokens=output_tokens, + time=time, + **kwargs, + ) + self.type = "chat" + + def __repr__(self) -> str: + base_info = ( + f"ChatUsage(" + f"input_tokens={self.input_tokens}, " + f"output_tokens={self.output_tokens}, " + f"time={self.time:.2f}, " + f"type='{self.type}'" + ) + extra = { + k: v + for k, v in self.items() + if k not in ["input_tokens", "output_tokens", "time", "type"] + } if extra: extra_str = ", ".join(f"{k}={v!r}" for k, v in extra.items()) base_info += f", {extra_str}" base_info += ")" - return base_info + return base_info diff --git a/src/agentscope/tracing/_extractor.py b/src/agentscope/tracing/_extractor.py index 17c62e8fe2..852410b251 100644 --- a/src/agentscope/tracing/_extractor.py +++ b/src/agentscope/tracing/_extractor.py @@ -378,7 +378,10 @@ def _get_llm_response_attributes( attributes[ SpanAttributes.GEN_AI_USAGE_OUTPUT_TOKENS ] = chat_response.usage.output_tokens - if hasattr(chat_response.usage, "prompt_tokens_details") and chat_response.usage.prompt_tokens_details: + if ( + hasattr(chat_response.usage, "prompt_tokens_details") + and chat_response.usage.prompt_tokens_details + ): attributes[ SpanAttributes.GEN_AI_USAGE_CACHED_TOKENS ] = chat_response.usage.prompt_tokens_details.get("cached_tokens") From e573c6135973f74340d192e26140fcb6192b8320 Mon Sep 17 00:00:00 2001 From: Mengsu Date: Wed, 14 Jan 2026 23:47:00 +0800 Subject: [PATCH 4/7] fix typos --- src/agentscope/model/_model_usage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agentscope/model/_model_usage.py b/src/agentscope/model/_model_usage.py index d2341286f4..63fb8f8b39 100644 --- a/src/agentscope/model/_model_usage.py +++ b/src/agentscope/model/_model_usage.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """The model usage class in agentscope.""" -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import Literal, Any from .._utils._mixin import DictMixin From 4ce6aeb92e0761446d0018d21ca57f9f42352757 Mon Sep 17 00:00:00 2001 From: Mengsu Date: Thu, 15 Jan 2026 16:26:33 +0800 Subject: [PATCH 5/7] edited to explicitly pass cached_token --- src/agentscope/model/_dashscope_model.py | 19 +++++----- src/agentscope/model/_model_usage.py | 45 ++++-------------------- src/agentscope/tracing/_extractor.py | 2 +- 3 files changed, 17 insertions(+), 49 deletions(-) diff --git a/src/agentscope/model/_dashscope_model.py b/src/agentscope/model/_dashscope_model.py index 8614d6c856..2141d728de 100644 --- a/src/agentscope/model/_dashscope_model.py +++ b/src/agentscope/model/_dashscope_model.py @@ -368,12 +368,12 @@ async def _parse_dashscope_stream_response( input_tokens=chunk.usage.input_tokens, output_tokens=chunk.usage.output_tokens, time=(datetime.now() - start_datetime).total_seconds(), - **{ - k: v - for k, v in dict(chunk.usage).items() - if k not in ["input_tokens", "output_tokens"] - }, + prompt_tokens_details=chunk.usage.get( + "prompt_tokens_details", + None, + ), ) + print(f"(from agentscope) ChatUsage: {ChatUsage}") parsed_chunk = ChatResponse( content=content_blocks, @@ -474,11 +474,10 @@ async def _parse_dashscope_generation_response( input_tokens=response.usage.input_tokens, output_tokens=response.usage.output_tokens, time=(datetime.now() - start_datetime).total_seconds(), - **{ - k: v - for k, v in dict(response.usage).items() - if k not in ["input_tokens", "output_tokens"] - }, + prompt_tokens_details=response.usage.get( + "prompt_tokens_details", + None, + ), ) parsed_response = ChatResponse( diff --git a/src/agentscope/model/_model_usage.py b/src/agentscope/model/_model_usage.py index 63fb8f8b39..e2eb7d39a1 100644 --- a/src/agentscope/model/_model_usage.py +++ b/src/agentscope/model/_model_usage.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- """The model usage class in agentscope.""" -from dataclasses import dataclass -from typing import Literal, Any +from dataclasses import dataclass, field +from typing import Literal from .._utils._mixin import DictMixin -@dataclass(init=False) +@dataclass class ChatUsage(DictMixin): """The usage of a chat model API invocation.""" @@ -19,39 +19,8 @@ class ChatUsage(DictMixin): time: float """The time used in seconds.""" - type: Literal["chat"] - """The type of the usage, must be `chat`.""" + prompt_tokens_details: dict = None + """The details of tokens. """ - def __init__( - self, - input_tokens: int = 0, - output_tokens: int = 0, - time: float = 0, - **kwargs: Any, - ): - super().__init__( - input_tokens=input_tokens, - output_tokens=output_tokens, - time=time, - **kwargs, - ) - self.type = "chat" - - def __repr__(self) -> str: - base_info = ( - f"ChatUsage(" - f"input_tokens={self.input_tokens}, " - f"output_tokens={self.output_tokens}, " - f"time={self.time:.2f}, " - f"type='{self.type}'" - ) - extra = { - k: v - for k, v in self.items() - if k not in ["input_tokens", "output_tokens", "time", "type"] - } - if extra: - extra_str = ", ".join(f"{k}={v!r}" for k, v in extra.items()) - base_info += f", {extra_str}" - base_info += ")" - return base_info + type: Literal["chat"] = field(default_factory=lambda: "chat") + """The type of the usage, must be `chat`.""" diff --git a/src/agentscope/tracing/_extractor.py b/src/agentscope/tracing/_extractor.py index 852410b251..5af90a562f 100644 --- a/src/agentscope/tracing/_extractor.py +++ b/src/agentscope/tracing/_extractor.py @@ -384,7 +384,7 @@ def _get_llm_response_attributes( ): attributes[ SpanAttributes.GEN_AI_USAGE_CACHED_TOKENS - ] = chat_response.usage.prompt_tokens_details.get("cached_tokens") + ] = _serialize_to_str(chat_response.usage.prompt_tokens_details) output_messages = _get_llm_output_messages(chat_response) if output_messages: From 0ab5261d788367dd0772a7634787955c7bc7d692 Mon Sep 17 00:00:00 2001 From: Mengsu Date: Thu, 15 Jan 2026 17:41:26 +0800 Subject: [PATCH 6/7] edited to explicitly pass cached_token --- src/agentscope/model/_dashscope_model.py | 1 - src/agentscope/model/_model_usage.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/agentscope/model/_dashscope_model.py b/src/agentscope/model/_dashscope_model.py index 2141d728de..99e2e66512 100644 --- a/src/agentscope/model/_dashscope_model.py +++ b/src/agentscope/model/_dashscope_model.py @@ -373,7 +373,6 @@ async def _parse_dashscope_stream_response( None, ), ) - print(f"(from agentscope) ChatUsage: {ChatUsage}") parsed_chunk = ChatResponse( content=content_blocks, diff --git a/src/agentscope/model/_model_usage.py b/src/agentscope/model/_model_usage.py index e2eb7d39a1..10d1ac6d5f 100644 --- a/src/agentscope/model/_model_usage.py +++ b/src/agentscope/model/_model_usage.py @@ -19,7 +19,7 @@ class ChatUsage(DictMixin): time: float """The time used in seconds.""" - prompt_tokens_details: dict = None + prompt_tokens_details: dict = field(default_factory=dict) """The details of tokens. """ type: Literal["chat"] = field(default_factory=lambda: "chat") From 220fa4c74d8115810d5e9803355896729efaa6b2 Mon Sep 17 00:00:00 2001 From: Mengsu Date: Thu, 15 Jan 2026 19:24:27 +0800 Subject: [PATCH 7/7] edited comments --- src/agentscope/model/_model_usage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agentscope/model/_model_usage.py b/src/agentscope/model/_model_usage.py index 10d1ac6d5f..5e314be6d3 100644 --- a/src/agentscope/model/_model_usage.py +++ b/src/agentscope/model/_model_usage.py @@ -20,7 +20,7 @@ class ChatUsage(DictMixin): """The time used in seconds.""" prompt_tokens_details: dict = field(default_factory=dict) - """The details of tokens. """ + """The details of cached tokens. """ type: Literal["chat"] = field(default_factory=lambda: "chat") """The type of the usage, must be `chat`."""