Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -334,23 +334,33 @@ def _finalize_span(span: trace_api.Span, result: Any) -> None:
if model_name := getattr(result, "model", None):
_set_span_attribute(span, SpanAttributes.EMBEDDING_MODEL_NAME, model_name)

if result_data := result.data:
# Extract embedding vectors directly (litellm uses enumeration, not explicit index)
for index, embedding_item in enumerate(result_data):
# LiteLLM returns dicts with 'embedding' key
raw_vector = (
embedding_item.get("embedding") if hasattr(embedding_item, "get") else None
)
if not raw_vector:
if result_data := getattr(result, "data", None):
# Extract embedding vectors directly
for embedding_item in result_data:
# LiteLLM may return embedding items as dicts or OpenAIObject instances
if isinstance(embedding_item, dict):
raw_vector = embedding_item.get("embedding")
index = embedding_item.get("index")
else:
raw_vector = getattr(embedding_item, "embedding", None)
index = getattr(embedding_item, "index", None)

# Skip entries without a usable vector or explicit index
if raw_vector is None or index is None:
continue

vector = None
# Check if it's a list of floats
if isinstance(raw_vector, (list, tuple)) and raw_vector:
vector: Union[Tuple[Any, ...], str, None] = None
# Record numeric vectors as tuples
if isinstance(raw_vector, (list, tuple)):
if all(isinstance(x, (int, float)) for x in raw_vector):
vector = tuple(raw_vector)
# Record base64-encoded vectors directly
elif isinstance(raw_vector, str):
vector = raw_vector
else:
continue

if vector:
if vector is not None:
_set_span_attribute(
span,
f"{SpanAttributes.EMBEDDING_EMBEDDINGS}.{index}.{EmbeddingAttributes.EMBEDDING_VECTOR}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def test_batch_embedding(
f"{SpanAttributes.EMBEDDING_EMBEDDINGS}.{i}.{EmbeddingAttributes.EMBEDDING_VECTOR}"
)
assert vector is not None
assert isinstance(vector, tuple)
assert isinstance(vector, (tuple, str))
assert len(vector) > 0

# Check token counts
Expand Down Expand Up @@ -126,7 +126,7 @@ def test_single_string_embedding(
f"{SpanAttributes.EMBEDDING_EMBEDDINGS}.0.{EmbeddingAttributes.EMBEDDING_VECTOR}"
)
assert vector is not None
assert isinstance(vector, tuple)
assert isinstance(vector, (tuple, str))
assert len(vector) > 0

# Check token counts
Expand Down Expand Up @@ -194,7 +194,7 @@ def test_batch_embedding_with_different_model(
f"{SpanAttributes.EMBEDDING_EMBEDDINGS}.{i}.{EmbeddingAttributes.EMBEDDING_VECTOR}"
)
assert vector is not None
assert isinstance(vector, tuple)
assert isinstance(vector, (tuple, str))
assert len(vector) > 0

# Check token counts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from typing import Iterator
from typing import Generator, Iterator
from unittest.mock import MagicMock, patch

import litellm
import pytest
Expand All @@ -18,6 +19,17 @@ def instrument(
yield


@pytest.fixture
def patch_tiktoken_encoding() -> Generator[None, None, None]:
"""Patch `tiktoken.get_encoding` for LiteLLM to avoid network calls."""

with patch("tiktoken.get_encoding") as mock_get_encoding:
mock_encoding = MagicMock()
mock_encoding.encode.return_value = [1, 2, 3]
mock_get_encoding.return_value = mock_encoding
yield


class TestTokenCounts:
@pytest.mark.vcr(
decode_compressed_response=True,
Expand Down Expand Up @@ -79,6 +91,7 @@ def test_openai(
def test_anthropic(
self,
in_memory_span_exporter: InMemorySpanExporter,
patch_tiktoken_encoding: None,
) -> None:
messages = [{"role": "user", "content": "Hello!"}]
resp = litellm.completion(
Expand Down