Skip to content

Commit f1d9eb4

Browse files
fix(tracing): pin eval ids for plain spans and keep them until the upload succeeds
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent ce0dcfa commit f1d9eb4

4 files changed

Lines changed: 39 additions & 7 deletions

File tree

‎src/agentex/lib/core/tracing/processors/sgp_tracing_processor.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,11 @@ async def on_spans_end(self, spans: list[Span]) -> None:
256256
for span in spans:
257257
sgp_span = _build_sgp_span(span, self.env_vars)
258258
sgp_span.end_time = span.end_time.isoformat() # type: ignore[union-attr]
259-
sgp_evals.release_span(span.id)
260259
sgp_spans.append(sgp_span)
261260
await client.spans.upsert_batch(items=[s.to_request_params() for s in sgp_spans])
261+
# Released only after the upload so a queue retry rebuilds the span with the same ids.
262+
for span in spans:
263+
sgp_evals.release_span(span.id)
262264
_metrics.record_export_success(
263265
event_type="end", span_count=len(spans), processor="sgp"
264266
)

‎src/agentex/lib/core/tracing/sgp_evals.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ def _lookup(span: Span) -> dict[str, Any]:
8181

8282

8383
def capture_for_span(span: Span) -> None:
84-
"""Pin the span's attrs at start. No-op while no eval task is registered in this process."""
85-
if not _attrs_by_task:
86-
return
84+
"""Pin the span's attrs at start, empty included, so an eval task registered later cannot claim it."""
8785
with _lock:
8886
_attrs_by_span[span.id] = _lookup(span)
8987
while len(_attrs_by_span) > _MAX_TASKS:

‎tests/lib/core/tracing/test_sgp_evals.py‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@
2929
from agentex.lib.core.clients.temporal.types import ConflictWorkflowPolicy
3030
from agentex.lib.core.temporal.workers.worker import AgentexWorker
3131
from agentex.lib.core.temporal.services.temporal_task_service import TemporalTaskService
32-
from agentex.lib.core.tracing.processors.sgp_tracing_processor import SGPSyncTracingProcessor, _sgp_metadata
32+
from agentex.lib.core.tracing.processors.sgp_tracing_processor import (
33+
SGPSyncTracingProcessor,
34+
SGPAsyncTracingProcessor,
35+
_sgp_metadata,
36+
)
3337

3438
EVAL_METADATA = {
3539
"sgp_evals": "generation-unit",
@@ -149,6 +153,34 @@ def test_plain_span_is_not_stamped_by_a_run_registered_after_it_started(self) ->
149153

150154
assert _sgp_metadata(span) == {"k": 1}
151155

156+
def test_plain_span_started_with_an_empty_registry_is_not_stamped_later(self) -> None:
157+
span = _span(trace_id="task-1", data={"k": 1})
158+
sgp_evals.capture_for_span(span)
159+
160+
sgp_evals.register_task_metadata("task-1", EVAL_METADATA)
161+
162+
assert _sgp_metadata(span) == {"k": 1}
163+
164+
async def test_async_processor_keeps_the_capture_when_the_upload_fails(self) -> None:
165+
sgp_evals.register_task_metadata("task-1", EVAL_METADATA)
166+
span = _span(data={})
167+
sgp_evals.capture_for_span(span)
168+
span.end_time = datetime.now(UTC)
169+
module = "agentex.lib.core.tracing.processors.sgp_tracing_processor"
170+
with patch(f"{module}.tracing.init"), patch(f"{module}.EnvironmentVariables"):
171+
processor = SGPAsyncTracingProcessor(SGPTracingProcessorConfig(sgp_api_key="", sgp_account_id=""))
172+
client = Mock()
173+
client.spans.upsert_batch = AsyncMock(side_effect=[RuntimeError("boom"), None])
174+
with patch.object(processor, "_get_client", return_value=client):
175+
with pytest.raises(RuntimeError):
176+
await processor.on_spans_end([span])
177+
sgp_evals.unregister_task("task-1")
178+
assert sgp_evals.attrs_for_span(span) == EXPECTED_ATTRS
179+
180+
await processor.on_spans_end([span])
181+
182+
assert sgp_evals.attrs_for_span(span) == {}
183+
152184
def test_release_drops_the_capture(self) -> None:
153185
sgp_evals.register_task_metadata("task-1", EVAL_METADATA)
154186
span = _span(data={})

‎uv.lock‎

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

0 commit comments

Comments
 (0)