Skip to content

feat: add opt-in GenAI payload capture (PayloadStore Phase 1)#1981

Open
nickaggarwal wants to merge 1 commit into
llm-d:mainfrom
nickaggarwal:feat/payload-store-phase1
Open

feat: add opt-in GenAI payload capture (PayloadStore Phase 1)#1981
nickaggarwal wants to merge 1 commit into
llm-d:mainfrom
nickaggarwal:feat/payload-store-phase1

Conversation

@nickaggarwal

Copy link
Copy Markdown

Summary

First implementation PR (Phase 1) of the GenAI payload events proposal (llm-d/llm-d#1206, closes llm-d/llm-d#1108 design): the PayloadStore interface, the noop/inline backends, and opt-in prompt capture on the EPP gateway span — all behind a default-off flag.

What's in this PR

Piece Where
PayloadStore interface, PayloadRef/PayloadKind, NoopStore, InlineStore pkg/epp/payload/store.go
Env-var config surface (LLMD_PAYLOAD_CAPTURE_ENABLED, LLMD_PAYLOAD_BACKEND, LLMD_PAYLOAD_INLINE_THRESHOLD) with safe fallbacks pkg/epp/payload/config.go
Capturer: converts the parsed request body to gen_ai.input.messages / gen_ai.system_instructions and records the gen_ai.client.inference.operation.details span event pkg/epp/payload/capture.go
Wiring into the ext_proc Process() loop after scheduling, nil-safe and default-off pkg/epp/handlers/server.go, pkg/epp/server/runserver.go
Operator docs docs/payload-capture.md
Unit tests (stores, config, capture incl. multimodal/truncation paths) pkg/epp/payload/*_test.go

Note: the proposal sketches the interface at pkg/telemetry/payload_store.go; it lands here as pkg/epp/payload to match this repo's layout (EPP-owned per the proposal's ownership section).

Behaviour

  • Default off. With LLMD_PAYLOAD_CAPTURE_ENABLED=false (or the noop backend, the proposal's secondary kill switch) no events are emitted and the request path gains one nil check.
  • When enabled with backend: inline, the gateway records the prompt on the sampled gateway.request span per the upstream GenAI semantic conventions (Opt-In/Development attributes). Chat-completions, completions, and Anthropic-messages request shapes are covered.
  • Payloads above inlineSizeThresholdBytes (default 4096) and blob media parts (data URLs, base64 audio — no object-store backend until Phase 2) are dropped with llm_d.payload.truncated: true; external media URLs are kept as schema-standard uri parts. Capture never fails the request.
  • gcs/s3/filesystem backends are recognised but fall back to noop with a startup warning until Phase 2.

Testing

  • go test ./pkg/epp/payload/... ./pkg/epp/handlers/... ./pkg/epp/server/... — pass
  • golangci-lint run (v2.8.0, the CI-pinned version) on the touched packages — 0 issues
  • go build ./... — pass

Follow-ups (per the proposal's phased plan)

  • Phase 2: GCSStore/S3Store/FilesystemStore + blob offload with in-place bloburi rewrite
  • Phase 3: redaction pipeline
  • Phase 4: vLLM-native capture (Python mirror)

🤖 Generated with Claude Code

Implements Phase 1 of the GenAI payload events proposal
(llm-d/llm-d#1206): the PayloadStore interface with the noop and
inline backends, opt-in prompt capture on the EPP gateway span, the
LLMD_PAYLOAD_* environment surface, and unit tests.

When enabled (default off), the gateway records the parsed request as
a gen_ai.client.inference.operation.details span event carrying
gen_ai.input.messages / gen_ai.system_instructions per the upstream
GenAI semantic conventions. Chat-completions, completions and
Anthropic-messages shapes are covered; payloads over the inline
threshold and blob media parts (no object-store backend until
Phase 2) are dropped with llm_d.payload.truncated=true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nilesh Agarwal <nick.aggarwal@gmail.com>
Copilot AI review requested due to automatic review settings July 13, 2026 04:17
@nickaggarwal nickaggarwal requested a review from a team as a code owner July 13, 2026 04:17
@nickaggarwal nickaggarwal requested review from ahg-g and liu-cong July 13, 2026 04:17
@github-actions github-actions Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Jul 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Phase 1 of an opt-in GenAI request payload capture pipeline for the EPP gateway. It adds a PayloadStore abstraction with noop/inline backends and wires a capturer into the ext_proc request path to emit GenAI semantic-convention span events when explicitly enabled via env vars.

Changes:

  • Add PayloadStore contract (PayloadRef/PayloadKind) with NoopStore and InlineStore implementations.
  • Add env-var configuration loading (LLMD_PAYLOAD_CAPTURE_ENABLED, LLMD_PAYLOAD_BACKEND, LLMD_PAYLOAD_INLINE_THRESHOLD) and a Capturer that emits gen_ai.client.inference.operation.details events.
  • Wire capturer creation into server startup and call capture after scheduling; add unit tests and operator documentation.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/epp/server/runserver.go Creates a payload capturer from env and injects it into the streaming server when enabled.
pkg/epp/payload/store.go Introduces the PayloadStore interface and Phase 1 noop/inline backends.
pkg/epp/payload/store_test.go Unit tests for NoopStore and InlineStore threshold behavior.
pkg/epp/payload/config.go Env-var config surface and safe fallbacks for payload capture settings.
pkg/epp/payload/config_test.go Tests env-config parsing and capturer gating behavior.
pkg/epp/payload/capture.go Implements extraction + span-event emission for supported request shapes.
pkg/epp/payload/capture_test.go Tests capture behavior (chat/completions/anthropic, multimodal, truncation, gating).
pkg/epp/handlers/server.go Adds capturer field + setter and invokes capture after scheduling.
docs/payload-capture.md Documents configuration, capture semantics, and security considerations.

Comment on lines +463 to +465
// Opt-in GenAI payload capture: record the prompt on the gateway
// span once the request has been scheduled. Never fails the request.
s.payloadCapturer.CaptureRequest(ctx, parseResult.Body)
Comment on lines +183 to +186
if _, err := c.store.Store(ctx, ref, data); err != nil {
*truncated = true
return attribute.KeyValue{}, false
}
Comment on lines +154 to +158
if kv, ok := c.inlineJSON(ctx, ref, AttrInputMessages, ext.messages, len(ext.messages) > 0, &ext.truncated); ok {
attrs = append(attrs, kv)
}
if kv, ok := c.inlineJSON(ctx, ref, AttrSystemInstructions, ext.system, len(ext.system) > 0, &ext.truncated); ok {
attrs = append(attrs, kv)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Capture GenAI prompts and completions as events or attributes

2 participants