Skip to content

Commit 915f3c8

Browse files
authored
fix: Spans loading out of order (#387)
1 parent 08ad4d6 commit 915f3c8

File tree

3 files changed

+110
-52
lines changed

3 files changed

+110
-52
lines changed

poetry.lock

Lines changed: 22 additions & 50 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ python-dateutil = "^2.8.0"
1717
langchain-core = { version = "^0.3.68", optional = true }
1818
openai = { version = "<1.96.0", optional = true }
1919
openai-agents = { version = "<0.2.1", optional = true }
20-
galileo-core = "~=3.67.1"
20+
galileo-core = "~=3.67.3"
2121
backoff = "^2.2.1"
2222
crewai = { version = ">=0.152.0,<=0.201.1", optional = true, python = ">=3.10,<3.14" }
2323

@@ -29,7 +29,7 @@ pytest-xdist = "^3.7.0"
2929
pytest-socket = "^0.7"
3030
pytest-asyncio = "^1.0.0"
3131
requests-mock = "^1.11.0"
32-
galileo-core = { extras = ["testing"], version = "~=3.67.1" }
32+
galileo-core = { extras = ["testing"], version = "~=3.67.3" }
3333

3434
pytest-env = "^1.1.5"
3535
langchain-core = "^0.3.68"

tests/test_logger_timestamps.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from datetime import datetime, timedelta, timezone
2+
from unittest.mock import Mock, patch
3+
4+
from galileo.logger import GalileoLogger
5+
from tests.testutils.setup import setup_mock_logstreams_client, setup_mock_projects_client
6+
7+
8+
@patch("galileo.logger.logger.LogStreams")
9+
@patch("galileo.logger.logger.Projects")
10+
def test_rapid_span_creation_ensures_uniqueness(mock_projects_client: Mock, mock_logstreams_client: Mock):
11+
"""Tests that creating spans in a tight loop results in unique, monotonically increasing timestamps."""
12+
setup_mock_projects_client(mock_projects_client)
13+
setup_mock_logstreams_client(mock_logstreams_client)
14+
logger = GalileoLogger(project="test", log_stream="test")
15+
logger.start_trace(input="test")
16+
for _ in range(5):
17+
logger.add_llm_span(input="test", output="test", model="test")
18+
logger.conclude(output="test")
19+
20+
trace = logger.traces[0]
21+
timestamps = [span.created_at for span in trace.spans]
22+
assert len(timestamps) == len(set(timestamps)), "Timestamps should be unique"
23+
assert timestamps == sorted(timestamps), "Timestamps should be monotonically increasing"
24+
25+
26+
@patch("galileo.logger.logger.LogStreams")
27+
@patch("galileo.logger.logger.Projects")
28+
def test_user_provided_timestamps_are_respected(mock_projects_client: Mock, mock_logstreams_client: Mock):
29+
"""Tests that timestamps provided by the user are not modified."""
30+
setup_mock_projects_client(mock_projects_client)
31+
setup_mock_logstreams_client(mock_logstreams_client)
32+
logger = GalileoLogger(project="test", log_stream="test")
33+
logger.start_trace(input="test")
34+
35+
# Create timestamps in reverse order to test that the logger doesn't alter them
36+
now = datetime.now(timezone.utc)
37+
timestamps = [now - timedelta(seconds=i) for i in range(5)]
38+
39+
for ts in timestamps:
40+
logger.add_llm_span(input="test", output="test", model="test", created_at=ts)
41+
42+
logger.conclude(output="test")
43+
44+
trace = logger.traces[0]
45+
span_timestamps = [span.created_at for span in trace.spans]
46+
assert span_timestamps == timestamps, "User-provided timestamps should be respected"
47+
48+
49+
@patch("galileo.logger.logger.LogStreams")
50+
@patch("galileo.logger.logger.Projects")
51+
def test_mixed_default_and_user_timestamps(mock_projects_client: Mock, mock_logstreams_client: Mock):
52+
"""Tests that the internal state for default timestamp generation is not affected by user-provided timestamps."""
53+
setup_mock_projects_client(mock_projects_client)
54+
setup_mock_logstreams_client(mock_logstreams_client)
55+
logger = GalileoLogger(project="test", log_stream="test")
56+
logger.start_trace(input="test")
57+
58+
# 1. Add a default span
59+
logger.add_llm_span(input="test", output="test", model="test")
60+
61+
# 2. Add a user-provided span with a timestamp in the past
62+
past_timestamp = datetime.now(timezone.utc) - timedelta(seconds=10)
63+
logger.add_llm_span(input="test", output="test", model="test", created_at=past_timestamp)
64+
65+
# 3. Add a user-provided span with a timestamp in the future
66+
future_timestamp = datetime.now(timezone.utc) + timedelta(seconds=10)
67+
logger.add_llm_span(input="test", output="test", model="test", created_at=future_timestamp)
68+
69+
# 4. Add a final default span
70+
logger.add_llm_span(input="test", output="test", model="test")
71+
72+
logger.conclude(output="test")
73+
trace = logger.traces[0]
74+
75+
default_span1_ts = trace.spans[0].created_at
76+
past_user_span_ts = trace.spans[1].created_at
77+
future_user_span_ts = trace.spans[2].created_at
78+
default_span2_ts = trace.spans[3].created_at
79+
80+
assert past_user_span_ts == past_timestamp, "User-provided past timestamp should be respected"
81+
assert future_user_span_ts == future_timestamp, "User-provided future timestamp should be respected"
82+
assert default_span2_ts > default_span1_ts, "Second default timestamp should be greater than the first"
83+
assert default_span2_ts > past_user_span_ts, "Default timestamps are separate from past user-provided timestamps"
84+
assert default_span2_ts < future_user_span_ts, (
85+
"Default timestamps are separate from future user-provided timestamps"
86+
)

0 commit comments

Comments
 (0)