Skip to content
Merged
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
22 changes: 22 additions & 0 deletions py/src/braintrust/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ async def fetch_fn(span_type):
assert len(result) == 3
assert {s.span_id for s in result} == {"span-1", "span-2", "span-3"}

@pytest.mark.asyncio
async def test_fetch_all_after_typed_fetch_has_no_duplicates(self):
"""A typed fetch followed by a full fetch must not duplicate spans."""
all_spans = [
make_span("fn-1", "function"),
make_span("llm-1", "llm"),
make_span("llm-2", "llm"),
]

async def fetch_fn(span_type):
if span_type:
return [s for s in all_spans if s.span_attributes["type"] in span_type]
return all_spans

fetcher = CachedSpanFetcher(fetch_fn=fetch_fn)
await fetcher.get_spans(["llm"])
result = await fetcher.get_spans()

span_ids = [s.span_id for s in result]
assert sorted(span_ids) == ["fn-1", "llm-1", "llm-2"]
assert len(span_ids) == len(set(span_ids)), f"duplicate spans: {span_ids}"

@pytest.mark.asyncio
async def test_fetch_preserves_span_result_fields(self):
"""Test that fetched spans preserve fields needed for full trace attachments."""
Expand Down
4 changes: 4 additions & 0 deletions py/src/braintrust/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ async def get_spans(

# If no filter requested, fetch everything
if not span_type or len(span_type) == 0:
# A full fetch is authoritative; reset the per-type cache first so a
# prior typed fetch's spans are not duplicated by re-fetching them
# (_fetch_spans appends).
self._span_cache = {}
await self._fetch_spans(None)
if self._span_cache: # Only cache if we got results
self._all_fetched = True
Expand Down