Skip to content

Commit 3e043be

Browse files
hassiebpcdepeuter
andauthored
feat(client): Add optional timestamp parameter to score creation (#1464)
feat(client): Add optional timestamp parameter to score creation methods (#1463) * add optional param to configure timestamp when scoring * move inline imports to top * alphabetic sorting of imports in case that matters Co-authored-by: Conrad <conrad.depeuter@gmail.com>
1 parent be1c0ab commit 3e043be

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

langfuse/_client/client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1972,6 +1972,7 @@ def create_score(
19721972
comment: Optional[str] = None,
19731973
config_id: Optional[str] = None,
19741974
metadata: Optional[Any] = None,
1975+
timestamp: Optional[datetime] = None,
19751976
) -> None: ...
19761977

19771978
@overload
@@ -1989,6 +1990,7 @@ def create_score(
19891990
comment: Optional[str] = None,
19901991
config_id: Optional[str] = None,
19911992
metadata: Optional[Any] = None,
1993+
timestamp: Optional[datetime] = None,
19921994
) -> None: ...
19931995

19941996
def create_score(
@@ -2005,6 +2007,7 @@ def create_score(
20052007
comment: Optional[str] = None,
20062008
config_id: Optional[str] = None,
20072009
metadata: Optional[Any] = None,
2010+
timestamp: Optional[datetime] = None,
20082011
) -> None:
20092012
"""Create a score for a specific trace or observation.
20102013
@@ -2023,6 +2026,7 @@ def create_score(
20232026
comment: Optional comment or explanation for the score
20242027
config_id: Optional ID of a score config defined in Langfuse
20252028
metadata: Optional metadata to be attached to the score
2029+
timestamp: Optional timestamp for the score (defaults to current UTC time)
20262030
20272031
Example:
20282032
```python
@@ -2069,7 +2073,7 @@ def create_score(
20692073
event = {
20702074
"id": self.create_trace_id(),
20712075
"type": "score-create",
2072-
"timestamp": _get_timestamp(),
2076+
"timestamp": timestamp or _get_timestamp(),
20732077
"body": new_body,
20742078
}
20752079

langfuse/_client/span.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ def score(
276276
data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None,
277277
comment: Optional[str] = None,
278278
config_id: Optional[str] = None,
279+
timestamp: Optional[datetime] = None,
279280
) -> None: ...
280281

281282
@overload
@@ -288,6 +289,7 @@ def score(
288289
data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL",
289290
comment: Optional[str] = None,
290291
config_id: Optional[str] = None,
292+
timestamp: Optional[datetime] = None,
291293
) -> None: ...
292294

293295
def score(
@@ -299,6 +301,7 @@ def score(
299301
data_type: Optional[ScoreDataType] = None,
300302
comment: Optional[str] = None,
301303
config_id: Optional[str] = None,
304+
timestamp: Optional[datetime] = None,
302305
) -> None:
303306
"""Create a score for this specific span.
304307
@@ -312,6 +315,7 @@ def score(
312315
data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL)
313316
comment: Optional comment or explanation for the score
314317
config_id: Optional ID of a score config defined in Langfuse
318+
timestamp: Optional timestamp for the score (defaults to current UTC time)
315319
316320
Example:
317321
```python
@@ -337,6 +341,7 @@ def score(
337341
data_type=cast(Literal["CATEGORICAL"], data_type),
338342
comment=comment,
339343
config_id=config_id,
344+
timestamp=timestamp,
340345
)
341346

342347
@overload
@@ -349,6 +354,7 @@ def score_trace(
349354
data_type: Optional[Literal["NUMERIC", "BOOLEAN"]] = None,
350355
comment: Optional[str] = None,
351356
config_id: Optional[str] = None,
357+
timestamp: Optional[datetime] = None,
352358
) -> None: ...
353359

354360
@overload
@@ -361,6 +367,7 @@ def score_trace(
361367
data_type: Optional[Literal["CATEGORICAL"]] = "CATEGORICAL",
362368
comment: Optional[str] = None,
363369
config_id: Optional[str] = None,
370+
timestamp: Optional[datetime] = None,
364371
) -> None: ...
365372

366373
def score_trace(
@@ -372,6 +379,7 @@ def score_trace(
372379
data_type: Optional[ScoreDataType] = None,
373380
comment: Optional[str] = None,
374381
config_id: Optional[str] = None,
382+
timestamp: Optional[datetime] = None,
375383
) -> None:
376384
"""Create a score for the entire trace that this span belongs to.
377385
@@ -386,6 +394,7 @@ def score_trace(
386394
data_type: Type of score (NUMERIC, BOOLEAN, or CATEGORICAL)
387395
comment: Optional comment or explanation for the score
388396
config_id: Optional ID of a score config defined in Langfuse
397+
timestamp: Optional timestamp for the score (defaults to current UTC time)
389398
390399
Example:
391400
```python
@@ -410,6 +419,7 @@ def score_trace(
410419
data_type=cast(Literal["CATEGORICAL"], data_type),
411420
comment=comment,
412421
config_id=config_id,
422+
timestamp=timestamp,
413423
)
414424

415425
def _set_processed_span_attributes(

tests/test_core_sdk.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import time
33
from asyncio import gather
4-
from datetime import datetime, timezone
4+
from datetime import datetime, timedelta, timezone
55
from time import sleep
66

77
import pytest
@@ -257,6 +257,60 @@ def test_create_categorical_score():
257257
assert trace["scores"][0]["stringValue"] == "high score"
258258

259259

260+
def test_create_score_with_custom_timestamp():
261+
langfuse = Langfuse()
262+
api_wrapper = LangfuseAPI()
263+
264+
# Create a span and set trace properties
265+
with langfuse.start_as_current_span(name="test-span") as span:
266+
span.update_trace(
267+
name="test-custom-timestamp",
268+
user_id="test",
269+
metadata="test",
270+
)
271+
# Get trace ID for later use
272+
trace_id = span.trace_id
273+
274+
# Ensure data is sent
275+
langfuse.flush()
276+
sleep(2)
277+
278+
custom_timestamp = datetime.now(timezone.utc) - timedelta(hours=1)
279+
score_id = create_uuid()
280+
langfuse.create_score(
281+
score_id=score_id,
282+
trace_id=trace_id,
283+
name="custom-timestamp-score",
284+
value=0.85,
285+
data_type="NUMERIC",
286+
timestamp=custom_timestamp,
287+
)
288+
289+
# Ensure data is sent
290+
langfuse.flush()
291+
sleep(2)
292+
293+
# Retrieve and verify
294+
trace = api_wrapper.get_trace(trace_id)
295+
296+
assert trace["scores"][0]["id"] == score_id
297+
assert trace["scores"][0]["dataType"] == "NUMERIC"
298+
assert trace["scores"][0]["value"] == 0.85
299+
300+
# Verify timestamp is close to our custom timestamp
301+
# Parse the timestamp from the API response
302+
response_timestamp = datetime.fromisoformat(
303+
trace["scores"][0]["timestamp"].replace("Z", "+00:00")
304+
)
305+
306+
# Check that the timestamps are within 1 second of each other
307+
# (allowing for some processing time and rounding)
308+
time_diff = abs((response_timestamp - custom_timestamp).total_seconds())
309+
assert time_diff < 1, (
310+
f"Timestamp difference too large: {time_diff}s. Expected < 1s. Custom: {custom_timestamp}, Response: {response_timestamp}"
311+
)
312+
313+
260314
def test_create_trace():
261315
langfuse = Langfuse()
262316
trace_name = create_uuid()

0 commit comments

Comments
 (0)