Skip to content

feat(datagen): add a sidecar that replays a recorded trace corpus - #15614

Merged
anticorrelator merged 88 commits into
mainfrom
dustin/data-generation-sidecar
Aug 28, 2026
Merged

feat(datagen): add a sidecar that replays a recorded trace corpus#15614
anticorrelator merged 88 commits into
mainfrom
dustin/data-generation-sidecar

Conversation

@anticorrelator

@anticorrelator anticorrelator commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Adds a datagen sidecar that continuously replays a recorded trace corpus into Phoenix, so a development or demo instance always has realistic, varied session data.

Split into a three-PR stack (this PR stands alone and is what a Railway/Cloud Run deployment depends on):

  1. This PR — replayer runtime: src/phoenix/experimental/datagen/ + the datagen server subcommand
  2. feat(datagen): add the corpus generation tooling #15698 — corpus generation tooling (scripts/datagen/, stacked on this PR)
  3. docs(datagen): add deployment integration and docs #15699 — deployment integration + docs (compose, kustomize, helm, render, self-hosting page)

What it does

python -m phoenix.server.main datagen runs a loop that:

  • Fetches the published corpus from a public GCS bucket (no credentials): a content-addressed corpus.tar.gz resolved through a corpus.json pointer, cached locally by digest
  • Loads fragment and trace rows from the archive
  • Composes sessions: each session samples one (archetype, domain) cell — never mixing applications mid-session — with fragment-count-proportional cell sampling and per-archetype session-length profiles
  • Replays them with fresh trace/span/session IDs (session IDs prefixed by domain, e.g. customer_support-…), shifted timestamps, token/duration jitter, and a fat-tail slow-span outlier on a small fraction of traces
  • Exports OTLP HTTP to a Phoenix collector, honoring PHOENIX_COLLECTOR_ENDPOINT and PHOENIX_API_KEY

Configuration

Flags: --endpoint, --api-key, --corpus (local archive override), --project, --rate (mean traces/minute), --burstiness. A pull subcommand downloads and caches the published corpus without replaying. Environment: PHOENIX_COLLECTOR_ENDPOINT, PHOENIX_API_KEY, PHOENIX_CLIENT_HEADERS, PHOENIX_PROJECT_NAME.

Testing

Unit tests cover the composer's sampling invariants, loader, exporter retry behavior, replayer identity/jitter/session semantics, and the CLI command. No new runtime dependencies; the corpus tooling and its dependencies live entirely in #15698.

https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng

Adds scripts/datagen (mock OpenAI-compatible provider plus two PEP 723
scenario scripts using real OpenInference instrumenters) and two recorded
corpora (openai_chat_sessions, langchain_agent_rag) as OTLP protobuf-JSON
lines with manifests under src/phoenix/datagen/corpora.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi
Adds src/phoenix/datagen (corpus loader for local paths and URLs,
session-aware replayer with ID/timestamp rewriting, contamination-mixture
anomaly injection with ground-truth manifests, OTLP/HTTP export) and wires
a lazily imported 'phoenix datagen' subcommand with env-var/flag config.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi
Spans are grouped by recorded trace_id across all corpus lines, so corpora
batched one-request-per-span replay as intact traces; manifest validation
now counts distinct trace ids and total spans.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi
Bundles the recorded corpora in the wheel, restores parent-child end
containment after latency redraws, rebases span-event timestamps, honors
PHOENIX_CLIENT_HEADERS, preserves dangling recorded parents, and pins the
numeric, export, and corpus-fidelity contracts in tests.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi
Adds --project/PHOENIX_PROJECT_NAME with a datagen-<corpus> default emitted
as the openinference.project.name resource attribute, derives emitted IDs
from the seed plus a per-run nonce so same-seed reruns stay comparable
without colliding, and retries OTLP export with capped exponential backoff
instead of exiting on transport errors.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi
Adds an opt-in docker-compose datagen profile, a disabled-by-default Helm
datagen deployment, a kustomize datagen overlay, a commented-out Render
worker, and a self-hosting docs page covering local, Compose, Helm,
Kustomize, Render, Railway, and Cloud Run flows.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi
…stribution

Adds the v2 fragment-bank schema and loader, a resumable USD-capped
generation control plane with OpenAI Batch support, deterministic fake
tools, self-play and scripted recording lanes, six archetype recorders,
MinHash dedup and quality gating with atomic bank packaging, an
archetype-safe replay session composer with lognormal session/gap knobs,
checksum-verified bank fetch/pull with lazy resolution, a datagen-assets
release workflow (--latest=false), a wheel starter-assets size gate, and
checksum-pinned container asset baking.

Claude-Session: https://claude.ai/code/session_01YF3zGrMPmFKZhQUjowsCJi
Every recorder script's PEP 723 block now resolves under the repository's
three-day `exclude-newer` window, and each recorder was run end-to-end,
keyless, against the in-repo mock provider to confirm it emits its
archetype's span kinds with session.id attached.

- tool_agent, graph_multi_agent: langchain-core 1.5.6, langchain-openai
  1.5.1, openinference-instrumentation-langchain 0.1.70. The previous
  langchain-openai/openai pins were mutually unsatisfiable, and the
  2024-era LangChain instrumenter predates get_attributes_from_context.
- langchain_agent_rag: llama-index-core 0.14.23, the newest release
  outside the freshness window.
- guardrailed_app: guardrails-ai 0.5.0. Every published OpenInference
  Guardrails instrumenter supports only `>=0.4.5,<0.5.1`, so the 0.6.7
  pin left the instrumentor disabled and no GUARDRAIL span was recorded.
- Each recorder that imports `openinference.instrumentation` directly now
  declares it, and openai is pinned at one version across the directory.

The mock provider now synthesizes tool-call arguments from the caller's
own declared tool schema instead of a fixed delivery-estimate shape, so
the tool-agent registry validates them, and it serves a server-sent-event
stream when a request asks for one, which the plain-chat recorder needs.
…d model backends

Versioned application-profile contract with canonical run snapshots;
matrix v2 draws every conversational field inside one profile with
reproducible per-field streams, 10% targeted-seed cells, and Beta(2,8)
seed intensities; structured OpenAI and codex-exec backends with
provider-aware attempt accounting (priced vs subscription); scripted and
self-play lanes consume profile draws.

Claude-Session: https://claude.ai/code/session_01Jrru1FDRB5uKGFGq6Rwxst
…ents

Profile seeds gain authored subtle/moderate/strong effect variants with a
closed vocabulary (corpus edits, tool-result overlays, simulator traits);
a pure materializer maps (cell_id, seed_id, intensity) to one variant and
projects a seed-metadata-free environment into both lanes; fake tools
overlay successful results after native handling; targeted cells add only
a natural route; transcripts reject internal seed language.

Claude-Session: https://claude.ai/code/session_01Jrru1FDRB5uKGFGq6Rwxst
Immutable judging-input and judgment sidecars keyed by cell and fragment;
recorded seed-engagement signals (tool overlay applications, perturbed-
document serving, trait seeds by construction) determine ambient proximity;
deterministic routing judges every trap-proximate fragment plus a 5%
stratified baseline; versioned three-label rubric (survived/degraded/failed)
through the frontier ModelBackend binding; outcomes project into schema-v2
quality metadata and never gate acceptance.

Claude-Session: https://claude.ai/code/session_01Jrru1FDRB5uKGFGq6Rwxst
Add --rate-schedule (flat|business-hours), --timezone, --backfill, and
--error-rate to phoenix datagen. The replayer paces a virtual timeline that
follows weekday/weekend hourly tiers, can start in the past and catch up at
exporter throughput, and can mark recorded LLM/TOOL spans as failed with an
exception event and ancestor status propagation. Anomaly manifest rows gain an
additive kind and timing fields. Flat defaults leave emitted requests unchanged.

Claude-Session: https://claude.ai/code/session_01EvQfhu4vtHASNDPReHB5t9
Runs can allocate one provider or tool fault per matrix cell
(--fault-fraction, --fault-modes) with base-bank lineage
(--base-scenario-name, --base-archive-sha256). The mock provider injects a
selected fault once so the real SDK and instrumenters record retry and
exception topology; tool exceptions cross the agent loop as error tool
messages. Every fault fragment is judged through a dedicated route.
bank.py gains package and merge commands that union a validated supplement
into its base archive, rebuilding aggregates and recording per-input
instrumenter provenance. README documents the supplemental procedure and the
prepare-only publication handoff.

Claude-Session: https://claude.ai/code/session_01EvQfhu4vtHASNDPReHB5t9
The recorder and corpus-pipeline tests import scripts.datagen.*, which
forced the Unit Tests CI job to check out all of scripts/ and forced a
mypy override for the un-followable script modules. That put dev tooling
on the wrong side of the product test boundary.

- Move the ten scripts-importing test files to scripts/datagen/tests/
  with their own conftest and fragment_bank fixture copy
- Restore the Unit Tests sparse-checkout to main's list
- Add a path-filtered Datagen Tooling Tests job that runs
  uv run pytest scripts/datagen/tests
- Drop the scripts.datagen.* mypy override

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng
@github-actions

Copy link
Copy Markdown
Contributor

Card links check

No broken Card links found. Checked external links in 19.3s

Split per review feedback: the generation tooling (scripts/datagen)
and the deployment integration + docs (compose, kustomize, helm,
render, self-hosting page) move to follow-up PRs. This PR keeps the
feature that stands alone: the phoenix.datagen runtime (fetcher,
loader, composer, replayer, exporter), the datagen server subcommand,
and their unit tests. Reverts the langchain test pins and mypy
carve-outs the generation tests had pushed into the shared dev
environment.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng
@anticorrelator anticorrelator changed the title feat(datagen): synthetic trace replay sidecar Add a datagen sidecar that replays a recorded trace corpus Aug 28, 2026
@anticorrelator anticorrelator changed the title Add a datagen sidecar that replays a recorded trace corpus feat(datagen): add a sidecar that replays a recorded trace corpus Aug 28, 2026
The unit-test tox env installs with -U and resolves pydantic-ai-slim
fresh, so it picked up 2.34.0, whose source no longer matches the
vendored data-stream protocol types in src/phoenix/db/types.
test_data_stream_protocol_compatibility now fails on every PR
(verified: 2.33.0 passes, 2.34.0 fails the request-type and schema
parity tests). Cap the unit env at 2.33 parity until the vendored
types are re-synced.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng
Comment thread src/phoenix/datagen/replayer.py Outdated
A span carrying only prompt and total counts (the embedding-span shape)
fell through to the independent total jitter, so the emitted total
could drop below the jittered prompt count. Recompute the total from
the jittered components whenever either one is present, leaving the
independent jitter for spans that carry only a total.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng
Comment thread src/phoenix/server/cli/commands/datagen.py
Comment thread src/phoenix/datagen/__init__.py Outdated
Comment thread src/phoenix/datagen/__init__.py Outdated
Main upgraded pydantic-ai-slim to 2.34 and re-synced the vendored
data-stream types (#15714), so the stopgap cap this branch carried is
no longer needed.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng
Main removed phoenix.utilities.re with the legacy client cleanup
(#15709), which deleted parse_env_headers from the server package.
Parse the W3C Baggage-style header string locally instead of reaching
into the client package.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng
Review feedback: hide the datagen subcommand from the top-level help
(help=SUPPRESS, matching db) while documenting it for direct --help
invocation, add the no-compatibility disclaimer to the package
docstring, and move the package to phoenix.experimental.datagen so the
wheel's import surface carries the same signal.

Claude-Session: https://claude.ai/code/session_014gvDFFS2FTKCnjCnQpcdng
@github-project-automation github-project-automation Bot moved this from 📘 Todo to 👍 Approved in phoenix Aug 28, 2026
@anticorrelator
anticorrelator enabled auto-merge (squash) August 28, 2026 19:04
@anticorrelator
anticorrelator merged commit b86acac into main Aug 28, 2026
61 checks passed
@anticorrelator
anticorrelator deleted the dustin/data-generation-sidecar branch August 28, 2026 19:12
@github-project-automation github-project-automation Bot moved this from 👍 Approved to ✅ Done in phoenix Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

2 participants