mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
perf(ollama_chat): estimate token counts only when Ollama omits them
`litellm.token_counter` was passed as the default argument of `dict.get`, so it ran on every response even though Ollama reports `prompt_eval_count` and `eval_count`. That spends a tokenization pass per call, and a counter failure discards a response Ollama already produced.
This commit is contained in:
parent
ff02d5cfc0
commit
cc65ba582d
2 changed files with 112 additions and 4 deletions
|
|
@ -386,10 +386,19 @@ class OllamaChatConfig(BaseConfig):
|
|||
model_response.choices[0].finish_reason = "tool_calls"
|
||||
model_response.created = int(time.time())
|
||||
model_response.model = "ollama_chat/" + model
|
||||
prompt_tokens = response_json.get("prompt_eval_count", litellm.token_counter(messages=messages))
|
||||
completion_tokens: Final = response_json.get(
|
||||
"eval_count",
|
||||
litellm.token_counter(text=response_json["message"]["content"]),
|
||||
# Ollama reports both counts, so only fall back to the estimator when a count is
|
||||
# missing. Passing token_counter as the default argument of dict.get evaluates it
|
||||
# on every response, which costs a tokenization pass per call and lets a counter
|
||||
# failure discard a response Ollama already produced.
|
||||
reported_prompt_tokens: Final = response_json.get("prompt_eval_count")
|
||||
prompt_tokens: Final = (
|
||||
reported_prompt_tokens if reported_prompt_tokens is not None else litellm.token_counter(messages=messages)
|
||||
)
|
||||
reported_completion_tokens: Final = response_json.get("eval_count")
|
||||
completion_tokens: Final = (
|
||||
reported_completion_tokens
|
||||
if reported_completion_tokens is not None
|
||||
else litellm.token_counter(text=response_json["message"]["content"])
|
||||
)
|
||||
setattr(
|
||||
model_response,
|
||||
|
|
|
|||
|
|
@ -906,3 +906,102 @@ class TestOllamaToolCallTransformation:
|
|||
assert tool_msg["content"] == "Sunny, 72°F"
|
||||
assert "tool_call_id" in tool_msg, "tool_call_id must be forwarded to Ollama"
|
||||
assert tool_msg["tool_call_id"] == "call_abc123"
|
||||
|
||||
|
||||
class TestOllamaChatUsageCounts:
|
||||
"""Tests for how usage is taken from Ollama's response."""
|
||||
|
||||
@staticmethod
|
||||
def _transform(config, ollama_response, messages):
|
||||
import json
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from litellm.types.utils import Choices, Message, ModelResponse
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = ollama_response
|
||||
mock_response.text = json.dumps(ollama_response)
|
||||
|
||||
model_response = ModelResponse()
|
||||
model_response.choices = [Choices(message=Message(content=""), index=0)]
|
||||
|
||||
return config.transform_response(
|
||||
model="qwen3:14b",
|
||||
raw_response=mock_response,
|
||||
model_response=model_response,
|
||||
logging_obj=MagicMock(),
|
||||
request_data={},
|
||||
messages=messages,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
encoding=None,
|
||||
api_key=None,
|
||||
json_mode=False,
|
||||
)
|
||||
|
||||
def test_reported_counts_skip_the_estimator(self):
|
||||
"""
|
||||
When Ollama reports both counts, `litellm.token_counter` must not run.
|
||||
|
||||
Passing it as the default argument of `dict.get` evaluates it on every
|
||||
response: it costs a tokenization pass per call, and a counter failure
|
||||
discards a response Ollama already produced. Content types the counter
|
||||
does not handle (a `video_url` block reaches Ollama's route because
|
||||
`extract_images_from_message` only collects `image_url`) therefore turned
|
||||
a 200 into a 500.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
import litellm
|
||||
|
||||
ollama_response = {
|
||||
"model": "qwen3:14b",
|
||||
"created_at": "2025-01-11T00:00:00.000000Z",
|
||||
"message": {"role": "assistant", "content": "Hello!"},
|
||||
"done": True,
|
||||
"prompt_eval_count": 100,
|
||||
"eval_count": 50,
|
||||
}
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Describe this."},
|
||||
{
|
||||
"type": "video_url",
|
||||
"video_url": {"url": "data:video/mp4;base64,AAAA"},
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
with patch.object(
|
||||
litellm, "token_counter", side_effect=AssertionError("estimator ran")
|
||||
):
|
||||
result = self._transform(OllamaChatConfig(), ollama_response, messages)
|
||||
|
||||
assert result.usage.prompt_tokens == 100
|
||||
assert result.usage.completion_tokens == 50
|
||||
assert result.usage.total_tokens == 150
|
||||
|
||||
def test_missing_counts_fall_back_to_the_estimator(self):
|
||||
"""When Ollama omits a count, the estimator fills it in."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import litellm
|
||||
|
||||
ollama_response = {
|
||||
"model": "qwen3:14b",
|
||||
"created_at": "2025-01-11T00:00:00.000000Z",
|
||||
"message": {"role": "assistant", "content": "Hello!"},
|
||||
"done": True,
|
||||
}
|
||||
messages = [{"role": "user", "content": "Hi"}]
|
||||
|
||||
with patch.object(litellm, "token_counter", return_value=7) as counter:
|
||||
result = self._transform(OllamaChatConfig(), ollama_response, messages)
|
||||
|
||||
assert counter.call_count == 2
|
||||
assert result.usage.prompt_tokens == 7
|
||||
assert result.usage.completion_tokens == 7
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue