Skip to content

feat: trace every request with prompts redacted by privacy class - #30

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/17-tracing
Oct 3, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
feat/17-tracing

Conversation

@Yash-Chindam

Copy link
Copy Markdown
Owner

Third PR closing gaps between the design spec and the implementation.

Gap this closes

§7.1 "Attach trace and routing metadata" and §14 "Redact sensitive prompts from traces". OpenTelemetry is in the §5 architecture diagram and appeared nowhere in the code.

What changed

  • tracing.py: one span per chat completion with tenant, effective and declared privacy, task, model, revision, adapter, cache result, score, candidate count, route reason, and token usage (GenAI semantic-convention names where they exist). The response quotes the trace as X-Trace-Id.
  • Only opentelemetry-api is a runtime dependency; the SDK and OTLP exporter are in a new tracing extra. With no ROUTER_OTLP_ENDPOINT, tracing is a no-op and no header is sent.
  • Redaction by class, evaluated after the tenant floor: restricted records length only (no digest), private adds a SHA-256 digest, public records a bounded content prefix only with ROUTER_TRACE_PROMPT_CONTENT=true. Completions are never recorded.
  • Failures record the error type, not the message, since an engine error can echo the prompt.
  • A streamed response owns its span until the stream ends.
  • The quota is now charged after the request is described, so a 429 is attributed to its tenant in the trace.

Test plan

  • 14 new tests, including: a restricted-floor tenant declaring public with content recording on is still fully redacted; an engine error echoing the prompt does not leak it; streamed spans carry usage and failure status
  • ruff format --check ., ruff check ., mypy clean
  • pytest tests/unit tests/integration: 205 passed, coverage 98%; the 3 errors are the local Windows temp-dir permission issue on tmp_path, which does not occur on CI
  • Export to a real OTLP collector is not exercised: the exporter path needs the tracing extra and a collector, and is excluded from coverage

🤖 Generated with Claude Code

Section 7.1 has the gateway attach trace and routing metadata and section
14 requires sensitive prompts to be redacted from traces. There was no
tracing at all.

Each chat completion is now one OpenTelemetry span carrying what explains
the route: tenant, effective and declared privacy, task, model, revision,
adapter, cache result, score, reason, and token usage. The response quotes
it as X-Trace-Id. Only the API is a runtime dependency, so tracing is a
no-op until ROUTER_OTLP_ENDPOINT points at a collector.

Redaction is evaluated after the tenant floor. Restricted prompts record
their length only, with no digest, because a digest of a short or
templated prompt can be reversed by guessing. Private prompts add a
digest. Content is recorded only for public prompts, only on operator
opt-in, and only as a bounded prefix. Completions are never recorded, and
a failure records its error type rather than its message because an
engine error can echo the request it rejected.

A streamed response owns its span until the stream ends, so usage and
failures after the handler returns are still recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation area/api area/tests dependencies labels Oct 3, 2026
@github-actions
github-actions Bot merged commit 0b77172 into main Oct 3, 2026
6 checks passed
@github-actions
github-actions Bot deleted the feat/17-tracing branch October 3, 2026 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api area/tests dependencies documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant