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
12 changes: 9 additions & 3 deletions langfuse/_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1947,9 +1947,15 @@ def create_score(
timestamp = timestamp.replace(tzinfo=timezone.utc)

try:
# The model turned bools and ints into floats (True -> 1.0); keep that.
if isinstance(value, (bool, int)):
value = float(value)
# The model coerced every non-str value to float (True -> 1.0,
# numpy.bool_ -> 1.0, Decimal("0.75") -> 0.75); keep that.
# Typed as float | str, but evaluators often return numpy or Decimal values.
raw_value: Any = value
if not isinstance(raw_value, (str, float)):
try:
value = float(raw_value)
except (TypeError, ValueError):
pass

score = {
"id": score_id,
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/test_score_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import threading
import time
from datetime import datetime, timezone
from decimal import Decimal
from fractions import Fraction
from queue import Queue
from typing import Any, Callable, Dict, List, Sequence

Expand Down Expand Up @@ -758,6 +760,8 @@ def test_create_score_payload_matches_request_model_serialization(client_factory
),
dict(name="int-value", value=1, trace_id="a" * 32),
dict(name="boolean", value=True, trace_id="a" * 32, data_type="BOOLEAN"),
dict(name="decimal-value", value=Decimal("0.75"), trace_id="a" * 32),
dict(name="fraction-value", value=Fraction(3, 4), trace_id="a" * 32),
dict(
name="categorical",
value="good",
Expand Down Expand Up @@ -809,6 +813,21 @@ def test_create_score_payload_matches_request_model_serialization(client_factory
assert sent == expected


def test_create_score_sends_numpy_values_as_numbers(client_factory):
np = pytest.importorskip("numpy")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 CI skips the NumPy test

The new test uses pytest.importorskip("numpy"), but numpy is absent from the declared dependencies and uv.lock. CI installs those locked dependencies, so it never checks the NumPy cases that motivated this fix. Install NumPy in at least one test job and run this test there.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/test_score_consumer.py
Line: 817

Comment:
**CI skips the NumPy test**

The new test uses `pytest.importorskip("numpy")`, but `numpy` is absent from the declared dependencies and `uv.lock`. CI installs those locked dependencies, so it never checks the NumPy cases that motivated this fix. Install NumPy in at least one test job and run this test there.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

recorder = Recorder(_responder(ACCEPTED))
client = client_factory(recorder)

# e.g. an evaluator returning np.mean(hits) > 0.5 or np.float32(score)
for value in (np.bool_(True), np.float32(0.5), np.int64(3)):
client.create_score(name="numpy", value=value, trace_id="a" * 32)
client.flush()

sent = [score["value"] for body in recorder.bodies for score in body]
assert sent == [1.0, 0.5, 3.0]
assert all(type(value) is float for value in sent)


@pytest.fixture
def non_utc_local_timezone(monkeypatch):
# Makes treating naive values as local time observable.
Expand Down