Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion langfuse/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ def _extract_chat_response(kwargs: Any) -> Any:
response.update({"tool_calls": kwargs["tool_calls"]})

if kwargs.get("audio") is not None:
audio = kwargs["audio"].__dict__
audio = kwargs["audio"].__dict__.copy()

if "data" in audio and audio["data"] is not None:
base64_data_uri = f"data:audio/{audio.get('format', 'wav')};base64,{audio.get('data', None)}"
Expand Down
61 changes: 61 additions & 0 deletions tests/unit/test_openai.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import asyncio
import base64
from types import SimpleNamespace
from unittest.mock import patch

import pytest
from openai.types.chat import ChatCompletion
from openai.types.responses import ParsedResponseOutputMessage, ParsedResponseOutputText
from pydantic import BaseModel

import langfuse.openai as lf_openai_module
from langfuse._client.attributes import LangfuseOtelSpanAttributes
from langfuse.media import LangfuseMedia
from langfuse.openai import openai as lf_openai


Expand Down Expand Up @@ -251,6 +254,64 @@ def _make_single_chunk_stream():
)


def test_chat_completion_audio_preserves_provider_response(
langfuse_memory_client, get_span, json_attr
):
encoded_audio = base64.b64encode(b"audio bytes").decode()
response = ChatCompletion.model_validate(
{
"id": "audio-response",
"object": "chat.completion",
"created": 0,
"model": "test-audio",
"choices": [
{
"index": 0,
"finish_reason": "stop",
"message": {
"role": "assistant",
"audio": {
"id": "audio-test",
"data": encoded_audio,
"expires_at": 1,
"transcript": "hello",
},
},
}
],
}
)
original = response.model_dump_json()
openai_client = lf_openai.OpenAI(api_key="test")
with (
patch.object(openai_client.chat.completions, "_post", return_value=response),
patch.object(
langfuse_memory_client._resources._media_manager,
"_process_upload_media_job",
),
):
result = openai_client.chat.completions.create(
name="unit-openai-audio",
model="test-audio",
messages=[{"role": "user", "content": "Say hello"}],
modalities=["text", "audio"],
audio={"voice": "alloy", "format": "wav"},
)
langfuse_memory_client.flush()

assert result is response
assert result.model_dump_json() == original
assert base64.b64decode(result.choices[0].message.audio.data) == b"audio bytes"
output = json_attr(
get_span("unit-openai-audio"), LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT
)
assert output["audio"]["transcript"] == "hello"
assert (
LangfuseMedia.parse_reference_string(output["audio"]["data"])["content_type"]
== "audio/wav"
)


def test_chat_completion_exports_generation_span(
langfuse_memory_client, get_span, json_attr
):
Expand Down