From 4d95016f35c4ff7acd75a9db49750d8a93763e9f Mon Sep 17 00:00:00 2001 From: as992949791 Date: Fri, 30 Oct 2026 12:37:40 +0800 Subject: [PATCH 1/2] fix(openai): count base64 embedding vector dimensions Generated with OpenAI Codex. --- langfuse/openai.py | 8 +++++++- tests/unit/test_openai.py | 40 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index 32637569b..692ca42e6 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -23,6 +23,7 @@ See docs for more details: https://langfuse.com/docs/integrations/openai """ +import base64 import json import os import types @@ -963,8 +964,13 @@ def _get_langfuse_data_from_default_response( if hasattr(first_embedding, "embedding") else first_embedding.get("embedding", []) ) + if isinstance(embedding_vector, str): + # Base64 embeddings contain one float32 per dimension. + dimensions = len(base64.b64decode(embedding_vector)) // 4 + else: + dimensions = len(embedding_vector) if embedding_vector else 0 completion = { - "dimensions": len(embedding_vector) if embedding_vector else 0, + "dimensions": dimensions, "count": len(data), } diff --git a/tests/unit/test_openai.py b/tests/unit/test_openai.py index a2a4f7f4f..2bde0e237 100644 --- a/tests/unit/test_openai.py +++ b/tests/unit/test_openai.py @@ -3,6 +3,7 @@ from types import SimpleNamespace from unittest.mock import patch +import httpx import pytest from openai.types.chat import ChatCompletion from openai.types.responses import ParsedResponseOutputMessage, ParsedResponseOutputText @@ -1015,6 +1016,45 @@ def test_embedding_exports_dimensions_and_count( } +@pytest.mark.parametrize("dimensions", [1, 2, 3]) +def test_base64_embedding_exports_vector_dimensions( + langfuse_memory_client, get_span, json_attr, dimensions +): + encoded_embedding = base64.b64encode(bytes(4 * dimensions)).decode() + + def respond(request): + return httpx.Response( + 200, + json={ + "model": "text-embedding-3-small", + "object": "list", + "data": [ + {"index": 0, "object": "embedding", "embedding": encoded_embedding} + ], + "usage": {"prompt_tokens": 2, "total_tokens": 2}, + }, + ) + + with lf_openai.OpenAI( + api_key="test", + http_client=httpx.Client(transport=httpx.MockTransport(respond)), + ) as openai_client: + result = openai_client.embeddings.create( + name="unit-openai-base64-embedding", + model="text-embedding-3-small", + input="hello world", + encoding_format="base64", + ) + + assert result.data[0].embedding == encoded_embedding + langfuse_memory_client.flush() + span = get_span("unit-openai-base64-embedding") + assert json_attr(span, LangfuseOtelSpanAttributes.OBSERVATION_OUTPUT) == { + "dimensions": dimensions, + "count": 1, + } + + def _make_chat_response(**extra_response_fields): return SimpleNamespace( model="gpt-4o-mini", From 4f61fd00ca9c5915470b4c04b83dc0a46163a13d Mon Sep 17 00:00:00 2001 From: as992949791 Date: Fri, 30 Oct 2026 12:42:24 +0800 Subject: [PATCH 2/2] fix(openai): preserve malformed embedding response passthrough Generated with OpenAI Codex. --- langfuse/openai.py | 8 ++++++-- tests/unit/test_openai.py | 14 +++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/langfuse/openai.py b/langfuse/openai.py index 692ca42e6..0b77e1729 100644 --- a/langfuse/openai.py +++ b/langfuse/openai.py @@ -24,6 +24,7 @@ """ import base64 +import binascii import json import os import types @@ -965,8 +966,11 @@ def _get_langfuse_data_from_default_response( else first_embedding.get("embedding", []) ) if isinstance(embedding_vector, str): - # Base64 embeddings contain one float32 per dimension. - dimensions = len(base64.b64decode(embedding_vector)) // 4 + try: + # Base64 embeddings contain one float32 per dimension. + dimensions = len(base64.b64decode(embedding_vector)) // 4 + except (binascii.Error, ValueError): + dimensions = len(embedding_vector) else: dimensions = len(embedding_vector) if embedding_vector else 0 completion = { diff --git a/tests/unit/test_openai.py b/tests/unit/test_openai.py index 2bde0e237..ae1bc1c58 100644 --- a/tests/unit/test_openai.py +++ b/tests/unit/test_openai.py @@ -1016,11 +1016,19 @@ def test_embedding_exports_dimensions_and_count( } -@pytest.mark.parametrize("dimensions", [1, 2, 3]) +@pytest.mark.parametrize( + "encoded_embedding, dimensions", + [ + ("AAAAAA==", 1), + ("AAAAAAAAAAA=", 2), + ("AAAAAAAAAAAAAAAA", 3), + pytest.param("!invalid!", 9, id="invalid-base64"), + pytest.param("\u2603", 1, id="non-ascii"), + ], +) def test_base64_embedding_exports_vector_dimensions( - langfuse_memory_client, get_span, json_attr, dimensions + langfuse_memory_client, get_span, json_attr, encoded_embedding, dimensions ): - encoded_embedding = base64.b64encode(bytes(4 * dimensions)).decode() def respond(request): return httpx.Response(