Skip to content

Commit 801c92f

Browse files
committed
fix(timeline): prune out-of-interval citations
1 parent d1b21c2 commit 801c92f

3 files changed

Lines changed: 42 additions & 6 deletions

File tree

‎backends/advanced/src/advanced_omi_backend/services/timeline/executor.py‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,25 @@ def validate_agent_result(
2323
raise ValueError(
2424
f"episode {index} references unknown evidence: {sorted(unknown)}"
2525
)
26+
overlapping_evidence_ids: list[str] = []
2627
for evidence_id in episode.evidence_ids:
2728
item = evidence[evidence_id]
2829
item_end = item.ended_at or item.started_at
29-
if item.started_at >= episode.ended_at or item_end <= episode.started_at:
30-
raise ValueError(
31-
f"episode {index} evidence {evidence_id} does not overlap its interval"
32-
)
30+
if item.started_at < episode.ended_at and item_end > episode.started_at:
31+
overlapping_evidence_ids.append(evidence_id)
32+
if not overlapping_evidence_ids:
33+
raise ValueError(f"episode {index} has no temporally overlapping evidence")
34+
episode.evidence_ids = overlapping_evidence_ids
35+
bound_evidence = set(overlapping_evidence_ids)
3336
for assertion in episode.assertions:
34-
if not set(assertion.evidence_ids).issubset(set(episode.evidence_ids)):
35-
raise ValueError(f"episode {index} assertion has unbound evidence")
37+
assertion.evidence_ids = [
38+
evidence_id
39+
for evidence_id in assertion.evidence_ids
40+
if evidence_id in bound_evidence
41+
]
42+
episode.assertions = [
43+
assertion for assertion in episode.assertions if assertion.evidence_ids
44+
]
3645
if episode.representative_evidence_id:
3746
representative = evidence.get(episode.representative_evidence_id)
3847
if (

‎backends/advanced/src/advanced_omi_backend/services/timeline/prompt.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def build_prompt(output_path: str) -> str:
144144
- Accessibility/OCR can include background or offscreen text.
145145
- Assistant-generated text is not evidence that its claims happened.
146146
- Every important boundary/assertion must cite supplied evidence IDs.
147+
- Episode citations must temporally overlap the episode interval.
147148
- Every episode must include at least one supplied evidence ID; otherwise leave the
148149
interval unassigned instead of creating an ungrounded episode.
149150
- Never return both `episodes` and `unassigned_intervals` empty when evidence exists.

‎backends/advanced/tests/test_timeline_executor.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,29 @@ def test_unavailable_representative_image_is_dropped_without_losing_episode():
9292
result.episodes[0].representative_evidence_id = "observation:one"
9393
validate_agent_result(result, _manifest())
9494
assert result.episodes[0].representative_evidence_id is None
95+
96+
97+
def test_non_overlapping_citation_is_pruned_when_episode_remains_grounded():
98+
manifest = _manifest()
99+
outside = TimelineEvidenceItem(
100+
evidence_id="observation:outside",
101+
kind="observation",
102+
started_at=manifest.started_at + timedelta(minutes=27),
103+
ended_at=manifest.started_at + timedelta(minutes=28),
104+
role="application_state",
105+
excerpt="Later application state",
106+
)
107+
manifest.evidence.append(outside)
108+
result = _result()
109+
result.episodes[0].evidence_ids.append(outside.evidence_id)
110+
validate_agent_result(result, manifest)
111+
assert result.episodes[0].evidence_ids == ["observation:one"]
112+
113+
114+
def test_episode_without_overlapping_citation_is_rejected():
115+
manifest = _manifest()
116+
result = _result()
117+
result.episodes[0].started_at = manifest.started_at + timedelta(minutes=26)
118+
result.episodes[0].ended_at = manifest.started_at + timedelta(minutes=27)
119+
with pytest.raises(ValueError, match="no temporally overlapping evidence"):
120+
validate_agent_result(result, manifest)

0 commit comments

Comments
 (0)