Files
unslothai--unsloth/studio/backend/tests/test_openai_citation_markers.py
T
wehub-resource-sync e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

244 lines
9.1 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Tests for the OpenAI Responses-API citation marker rewriter.
The stream interleaves text deltas with ``\\ue200cite\\ue202SOURCE_ID\\ue201``
markers. The rewriter resolves each to `[N](URL)` once the annotation arrives
and drops it otherwise; the URL list still flows to Sources via
`_record_url_citation`.
Reference: https://developers.openai.com/api/docs/guides/citation-formatting
"""
import pytest
from core.inference.external_provider import (
_replace_openai_citation_markers,
_rewrite_citation_markers_partial,
)
# Citation marker control codepoints (private-use area):
CITE_START = ""
CITE_STOP = ""
CITE_DELIM = ""
def _marker(source_id: str, locator: str | None = None) -> str:
payload = f"{CITE_START}cite{CITE_DELIM}{source_id}"
if locator:
payload = f"{payload}{CITE_DELIM}{locator}"
return f"{payload}{CITE_STOP}"
def _has_marker_codepoints(text: str) -> bool:
return any(c in text for c in (CITE_START, CITE_STOP, CITE_DELIM))
def test_passthrough_when_no_marker_present():
text = "Plain text with no citation markers."
assert _replace_openai_citation_markers(text, []) == text
def test_marker_rewritten_to_link_when_annotation_known():
text = f"The capital is Paris {_marker('turn0view0')}."
citations = [
{
"source_id": "turn0view0",
"url": "https://example.com/paris",
"title": "Paris",
},
]
out = _replace_openai_citation_markers(text, citations)
assert not _has_marker_codepoints(out)
assert "[[1]](https://example.com/paris)" in out
def test_unknown_source_marker_dropped_silently():
text = f"Foo {_marker('turn9view9')} bar."
out = _replace_openai_citation_markers(text, [])
# Marker stripped, no garbled "E202" glyph leaks, surrounding text intact.
assert not _has_marker_codepoints(out)
assert "E202" not in out
assert "turn9view9" not in out
assert "Foo" in out and "bar" in out
def test_multiple_concatenated_markers_resolved_in_order():
"""Real-world wire shape: markers butted together after a sentence (user-reported bug)."""
markers = "".join(_marker(f"turn{i}view{j}") for i, j in [(1, 0), (1, 1), (3, 0)])
text = f"All animals ranked. {markers}"
citations = [
{"source_id": "turn1view0", "url": "https://a.example/dog", "title": "Dog"},
{"source_id": "turn1view1", "url": "https://a.example/cat", "title": "Cat"},
{"source_id": "turn3view0", "url": "https://a.example/tiger", "title": "Tiger"},
]
out = _replace_openai_citation_markers(text, citations)
assert "[[1]](https://a.example/dog)" in out
assert "[[2]](https://a.example/cat)" in out
assert "[[3]](https://a.example/tiger)" in out
assert not _has_marker_codepoints(out)
def test_marker_with_locator_resolves():
text = f"See {_marker('turn2file0', 'L8-L13')}."
citations = [
{"source_id": "turn2file0", "url": "https://example.com/doc.txt"},
]
out = _replace_openai_citation_markers(text, citations)
assert "[[1]](https://example.com/doc.txt)" in out
assert "L8-L13" not in out # locator detail dropped; we just link.
assert not _has_marker_codepoints(out)
def test_mixed_known_and_unknown_markers():
known = _marker("turn0view0")
unknown = _marker("turn0view99")
text = f"Known {known} and unknown {unknown}."
citations = [
{"source_id": "turn0view0", "url": "https://example.com/known"},
]
out = _replace_openai_citation_markers(text, citations)
assert "[[1]](https://example.com/known)" in out
# Unknown markers leave no trace, but surrounding prose stays.
assert "Known" in out and "unknown" in out
assert not _has_marker_codepoints(out)
assert "E202" not in out
def test_empty_text_returns_verbatim():
assert _replace_openai_citation_markers("", []) == ""
def test_idempotent_on_pre_stripped_text():
"""Pre-stripped text (no private-use codepoints) returns verbatim."""
text = "citeturn1view0 plain"
assert _replace_openai_citation_markers(text, []) == text
@pytest.mark.parametrize(
"citation",
[
{"url": "https://example.com/a"}, # no source_id at all
{"source_id": None, "url": "https://example.com/b"},
{"source_id": "", "url": "https://example.com/c"},
],
)
def test_citation_without_source_id_does_not_crash(citation):
text = f"X {_marker('turnXviewY')} Y"
out = _replace_openai_citation_markers(text, [citation])
# No mapping, marker stripped. Crash-free is the contract.
assert not _has_marker_codepoints(out)
assert "turnXviewY" not in out
def test_multiple_source_id_aliases_resolve_to_same_url():
"""Every alias for the same URL must resolve, not just the first (Codex P1 regression)."""
a = _marker("turn0view0")
b = _marker("turn0view0_span_1")
c = _marker("turn0view0_span_2")
text = f"Triple {a}{b}{c} cite."
citations = [
{
"source_ids": ["turn0view0", "turn0view0_span_1", "turn0view0_span_2"],
"url": "https://example.com/paris",
"title": "Paris",
},
]
out = _replace_openai_citation_markers(text, citations)
# All three aliases collapse onto citation [1] -- same URL, so showing
# three different numbers would mislead.
assert out.count("[[1]](https://example.com/paris)") == 3
assert not _has_marker_codepoints(out)
def test_source_ids_list_and_legacy_source_id_both_resolve():
"""Mixed-shape citation: legacy ``source_id`` plus newer ``source_ids`` aliases both resolve."""
legacy = _marker("legacy_id")
alias = _marker("alias_id")
text = f"Both {legacy} and {alias} work."
citations = [
{
"source_id": "legacy_id",
"source_ids": ["alias_id"],
"url": "https://example.com/doc",
},
]
out = _replace_openai_citation_markers(text, citations)
assert out.count("[[1]](https://example.com/doc)") == 2
assert not _has_marker_codepoints(out)
# _rewrite_citation_markers_partial: deferred-annotation tests. OpenAI emits
# url_citation annotations on a later SSE event; this helper reports
# `has_unresolved` so the stream loop defers emission. See PR #5713 audit.
def test_partial_known_marker_resolves_and_clears_unresolved():
text = f"Foo {_marker('s1')} bar."
out, unresolved = _rewrite_citation_markers_partial(
text,
[{"source_id": "s1", "url": "https://example.com/a"}],
)
assert "[[1]](https://example.com/a)" in out
assert unresolved is False
assert not _has_marker_codepoints(out)
def test_partial_unknown_marker_preserves_verbatim_and_flags():
text = f"Foo {_marker('s1')} bar."
out, unresolved = _rewrite_citation_markers_partial(text, [])
assert unresolved is True
# Codepoints must remain so a follow-up pass can re-parse.
assert _has_marker_codepoints(out)
assert "Foo" in out and "bar." in out
def test_partial_resolves_after_late_annotation():
"""Two-pass: first call sees no citations; second resolves after annotation."""
text = f"See {_marker('s1')} for details."
out1, unresolved1 = _rewrite_citation_markers_partial(text, [])
assert unresolved1 is True
citations = [{"source_id": "s1", "url": "https://example.com/x"}]
out2, unresolved2 = _rewrite_citation_markers_partial(out1, citations)
assert unresolved2 is False
assert "[[1]](https://example.com/x)" in out2
assert not _has_marker_codepoints(out2)
def test_partial_multi_source_partial_resolution_keeps_marker_pending():
"""Any unresolved token in a multi-source marker leaves the whole marker verbatim with ``unresolved`` True until every id resolves or end-of-stream flushes."""
cite = f"{CITE_START}cite{CITE_DELIM}known{CITE_DELIM}locator{CITE_STOP}"
text = f"Pre {cite} post."
citations = [{"source_id": "known", "url": "https://example.com/y"}]
out, unresolved = _rewrite_citation_markers_partial(text, citations)
assert unresolved is True
assert cite in out
# End-of-stream force flush: drop the unresolved token, keep the resolved
# link. The streamer routes pending segments through
# `_replace_openai_citation_markers` at force=True for this.
forced = _replace_openai_citation_markers(out, citations)
assert "[[1]](https://example.com/y)" in forced
assert "locator" not in forced
assert not _has_marker_codepoints(forced)
def test_partial_idempotent_on_marker_free_text():
text = "Plain text."
out, unresolved = _rewrite_citation_markers_partial(text, [])
assert out == text
assert unresolved is False
def test_partial_mixed_known_and_pending_markers_flags_unresolved():
known = _marker("known")
pending = _marker("pending")
text = f"{known} {pending}"
citations = [{"source_id": "known", "url": "https://example.com/k"}]
out, unresolved = _rewrite_citation_markers_partial(text, citations)
assert unresolved is True # the pending marker drives the flag
assert "[[1]](https://example.com/k)" in out
# The pending marker stays verbatim for the next pass.
assert CITE_START in out and "pending" in out