From f97dc95bc7d8f4547577e343d4d4b51790d8842d Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:29:08 +0200 Subject: [PATCH] refactor: simplify usage extraction helper and condense comments Treat 0 as unreported directly inside _usage_int instead of appending "or None" at every call site, which carried that rule redundantly. The only kept zero is the one derived from the cache subtraction, which does not pass through the helper, so behavior is unchanged. Also condense the multi-line comments and the helper docstring so the code carries the mechanism and the comments only carry the why. --- backend/open_webui/routers/openai.py | 5 +-- backend/open_webui/utils/anthropic.py | 48 +++++++++------------------ 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 24c2c90cb0..08d1c3415f 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -1345,10 +1345,7 @@ async def generate_chat_completion( part.get('text', '') for part in message['content'] if part.get('type') in ('input_text', 'text') ) - # stream_options is only valid alongside "stream": true. The stream flag - # may have been overridden after the client payload was built (e.g. the - # model's stream_response param), and upstreams like OpenAI and vLLM - # reject non-streaming requests that still carry stream_options. + # Model params may have overridden stream; upstreams reject stream_options on non-streaming requests if not payload.get('stream'): payload.pop('stream_options', None) diff --git a/backend/open_webui/utils/anthropic.py b/backend/open_webui/utils/anthropic.py index 462d0173c4..7852cc8404 100644 --- a/backend/open_webui/utils/anthropic.py +++ b/backend/open_webui/utils/anthropic.py @@ -107,46 +107,36 @@ async def get_anthropic_models(url: str, key: str, user: UserModel = None) -> di def _usage_int(value) -> int | None: - """Return value as a non-negative int, or None when missing or invalid.""" + """Return value as a positive int; missing, invalid or 0 counts are unreported (None).""" if isinstance(value, bool) or not isinstance(value, (int, float)): return None value = int(value) - return value if value >= 0 else None + return value if value > 0 else None def _extract_anthropic_usage( usage: dict, ) -> tuple[int | None, int | None, int | None, int | None]: """ - Convert an OpenAI-compatible usage payload to Anthropic usage fields: - (input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens). - - OpenAI's prompt_tokens includes cached tokens, while Anthropic's - input_tokens excludes them (total input = input_tokens + - cache_creation_input_tokens + cache_read_input_tokens), so the cache - counts are subtracted whenever the input count is derived from - prompt_tokens. An Anthropic-native input_tokens key is taken as already - exclusive. The cache-read count falls back to OpenAI's - prompt_tokens_details.cached_tokens. - - A count that is missing, null, or 0 is treated as unreported (None) so - callers can keep previously known values; a 0 produced by the cache - subtraction is kept, since the upstream did report usage. + Map OpenAI-compatible usage to Anthropic (input_tokens, output_tokens, + cache_creation_input_tokens, cache_read_input_tokens); None means unreported. + OpenAI's prompt_tokens includes cached tokens while Anthropic's input_tokens + excludes them, so cache counts are subtracted when deriving from prompt_tokens. """ - cache_creation = _usage_int(usage.get('cache_creation_input_tokens')) or None - cache_read = _usage_int(usage.get('cache_read_input_tokens')) or None + cache_creation = _usage_int(usage.get('cache_creation_input_tokens')) + cache_read = _usage_int(usage.get('cache_read_input_tokens')) if cache_read is None: prompt_details = usage.get('prompt_tokens_details') if isinstance(prompt_details, dict): - cache_read = _usage_int(prompt_details.get('cached_tokens')) or None + cache_read = _usage_int(prompt_details.get('cached_tokens')) - input_tokens = _usage_int(usage.get('input_tokens')) or None + input_tokens = _usage_int(usage.get('input_tokens')) if input_tokens is None: - prompt_tokens = _usage_int(usage.get('prompt_tokens')) or None + prompt_tokens = _usage_int(usage.get('prompt_tokens')) if prompt_tokens is not None: input_tokens = max(prompt_tokens - (cache_creation or 0) - (cache_read or 0), 0) - output_tokens = _usage_int(usage.get('output_tokens')) or _usage_int(usage.get('completion_tokens')) or None + output_tokens = _usage_int(usage.get('output_tokens')) or _usage_int(usage.get('completion_tokens')) return input_tokens, output_tokens, cache_creation, cache_read @@ -464,8 +454,7 @@ def convert_anthropic_to_openai_payload( else: openai_payload[param] = anthropic_payload[param] - # Anthropic streaming clients always expect token usage in the final - # message_delta event, so ask OpenAI-compatible upstreams to report it. + # Anthropic clients expect usage in the final message_delta, so ask the upstream to report it if openai_payload.get('stream'): openai_payload['stream_options'] = {'include_usage': True} @@ -579,11 +568,8 @@ def convert_openai_to_anthropic_response( } ) - # Usage: prefer upstream-reported statistics; the locally counted - # input_tokens is only a fallback for upstreams that report nothing. - openai_usage = openai_response.get('usage') - if not isinstance(openai_usage, dict): - openai_usage = {} + # Prefer upstream-reported usage; the locally counted input_tokens is only a fallback + openai_usage = openai_response.get('usage') or {} usage_input, usage_output, cache_creation, cache_read = _extract_anthropic_usage(openai_usage) usage = { 'input_tokens': usage_input if usage_input is not None else (input_tokens or 0), @@ -949,9 +935,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str block_stop = {'type': 'content_block_stop', 'index': tool['block_index']} yield f'event: content_block_stop\ndata: {json.dumps(block_stop)}\n\n'.encode() - # Emit message_delta with stop reason and final usage. input_tokens and - # cache statistics are included so clients receive the upstream-reported - # values even when they differ from the estimate sent in message_start. + # Emit message_delta with stop reason and the upstream-reported usage final_usage = {'input_tokens': input_tokens, 'output_tokens': output_tokens} if cache_creation_input_tokens is not None: final_usage['cache_creation_input_tokens'] = cache_creation_input_tokens