mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix: preserve logprobs on audio transcription responses
`convert_to_model_response_object` copied only `language/task/duration/words/segments` from a transcription response, so `logprobs` was silently dropped — even when the caller explicitly asked for it via `include[]=logprobs`. Effect: for `gpt-4o-transcribe` there was no way to read per-token confidence through the proxy. The only visible workaround was `stream=True`, which returns the raw SSE body as `text` with `usage: null` — and therefore silently disables cost tracking and budget enforcement for every transcription request. Adding `logprobs` to the copied keys makes the non-streaming path complete, so callers can have both confidence scores and correct spend accounting. Tests: two regression tests (with and without `logprobs`) in `tests/test_litellm/litellm_core_utils/llm_response_utils/test_convert_dict_to_response.py`.
This commit is contained in:
parent
f54cd287a2
commit
7dc382ecbb
2 changed files with 48 additions and 1 deletions
|
|
@ -809,7 +809,7 @@ def convert_to_model_response_object(
|
|||
if "text" in response_object:
|
||||
model_response_object.text = response_object["text"]
|
||||
|
||||
optional_keys: Final = ["language", "task", "duration", "words", "segments"]
|
||||
optional_keys: Final = ["language", "task", "duration", "words", "segments", "logprobs"]
|
||||
for key in optional_keys: # not guaranteed to be in response
|
||||
if key in response_object:
|
||||
setattr(model_response_object, key, response_object[key])
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from litellm.types.utils import (
|
|||
ChatCompletionMessageToolCall,
|
||||
Function,
|
||||
ModelResponse,
|
||||
TranscriptionResponse,
|
||||
)
|
||||
|
||||
OPENAI_CUSTOM_TOOL_CALL_RESPONSE = {
|
||||
|
|
@ -102,3 +103,49 @@ def test_handle_invalid_parallel_tool_calls_skips_custom_tool_calls():
|
|||
)
|
||||
result = _handle_invalid_parallel_tool_calls([custom_tool_call, function_tool_call])
|
||||
assert result == [custom_tool_call, function_tool_call]
|
||||
|
||||
|
||||
def test_audio_transcription_preserves_logprobs():
|
||||
"""`logprobs` must survive conversion (requested via `include[]=logprobs`).
|
||||
|
||||
Regression: only language/task/duration/words/segments were copied, so
|
||||
`logprobs` was silently dropped for `gpt-4o-transcribe` and callers had no
|
||||
way to read the per-token confidence without falling back to `stream=True`.
|
||||
"""
|
||||
response_object = {
|
||||
"text": "Guten Tag.",
|
||||
"logprobs": [
|
||||
{"token": "Guten", "logprob": -0.01, "bytes": [71]},
|
||||
{"token": " Tag", "logprob": -0.02, "bytes": [32]},
|
||||
],
|
||||
"usage": {
|
||||
"type": "tokens",
|
||||
"input_tokens": 30,
|
||||
"output_tokens": 4,
|
||||
"total_tokens": 34,
|
||||
"input_token_details": {"text_tokens": 0, "audio_tokens": 30},
|
||||
},
|
||||
}
|
||||
|
||||
result = convert_to_model_response_object(
|
||||
response_object=response_object,
|
||||
model_response_object=TranscriptionResponse(),
|
||||
response_type="audio_transcription",
|
||||
)
|
||||
|
||||
assert result.text == "Guten Tag."
|
||||
assert result.logprobs == response_object["logprobs"]
|
||||
# usage must keep working alongside it (cost tracking depends on it)
|
||||
assert result.usage.input_tokens == 30
|
||||
|
||||
|
||||
def test_audio_transcription_without_logprobs_is_unchanged():
|
||||
"""No `logprobs` in the response → attribute simply absent, no crash."""
|
||||
result = convert_to_model_response_object(
|
||||
response_object={"text": "Guten Tag."},
|
||||
model_response_object=TranscriptionResponse(),
|
||||
response_type="audio_transcription",
|
||||
)
|
||||
|
||||
assert result.text == "Guten Tag."
|
||||
assert getattr(result, "logprobs", None) is None
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue