feat(aio): public beta captureAi with dedicated AI capture lane - #4289
Conversation
|
Size Change: +24.5 kB (+0.13%) Total Size: 19.3 MB 📦 View Changed
ℹ️ View Unchanged
|
|
This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, post a comment or remove the |
14379a8 to
e202647
Compare
|
posthog-node Compliance ReportDate: 2026-08-13 10:15:47 UTC ✅ All Tests Passed!111/111 tests passed Capture_V1 Tests✅ 94/94 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
posthog-js Compliance ReportDate: 2026-08-13 10:25:17 UTC ✅ All Tests Passed!26/26 tests passed Capture Tests✅ 26/26 tests passed View Details
|
e202647 to
eb4c850
Compare
eb4c850 to
cfea24b
Compare
Prompt To Fix All With AI### Issue 1
packages/node/src/client.ts:504-506
**Partial sub-batch success is retried**
When an earlier AI sub-batch is accepted and a later one fails with a network error, the core flush path retains the entire original queue batch and resends the accepted events on the next flush. Capture does not guarantee strict immediate UUID deduplication, so this produces duplicate AI events.
### Issue 2
packages/core/src/types.ts:275
**Reset discards queued AI events**
When `captureAi` queues an event and `reset()` runs before it is flushed, the inherited reset logic clears `AiCaptureQueue` because the new key is absent from its queue-preservation allowlist. The pending AI event is silently deleted and never delivered.
### Issue 3
packages/ai/src/vercel/middleware.ts:796
**Redundant output property expression**
The explicit `output: output` repeats the identifier unnecessarily; property shorthand expresses the same payload with less noise.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat: node ai capture lane" | Re-trigger Greptile |
Generated-By: PostHog Code Task-Id: 4b5e5cda-81f1-41a2-8fcd-4f335d19217e
cfea24b to
3bcd7c5
Compare
| * @param props - The event properties | ||
| * @returns The event UUID, or `undefined` when the client is disabled | ||
| */ | ||
| captureAi(props: EventMessage): string | undefined { |
There was a problem hiding this comment.
blocking: Expose the new methods through IPostHog
captureAi() and captureAiImmediate() are public here but absent from the exported IPostHog interface, so consumers typed against it (including @posthog/next) cannot call the advertised API without casting. Please add both signatures alongside capture() and captureImmediate().
| } | ||
|
|
||
| // Unlike /capture, forward a supplied uuid so it's echoed back to the caller. | ||
| const returnedUuid = state.client.captureAi({ |
There was a problem hiding this comment.
blocking: Discard the dedicated queue during reset
This activates ai_capture_queue, but discardClient() clears only queue and ai_queue. A later /reset can therefore send an event that should have been discarded when shutdown() drains this active route. Please clear ai_capture_queue too and cover /capture_ai followed by /reset.
| provider: 'azure', | ||
| input: sanitizeOpenAI(openAIParams.messages), | ||
| output: sanitizeOpenAIResponse(formattedOutput), | ||
| input: sanitizeOpenAI(openAIParams.messages, this.phClient), |
There was a problem hiding this comment.
blocking: Propagate full-capture behavior across all Azure paths
The client gate is added only to streaming chat here. Non-streaming chat and Azure Responses create/parse/streaming/background/error paths still sanitize without this.phClient, so enableFullAiCapture: true still redacts multimodal content. Please pass the gate through every Azure capture path and test streaming and non-streaming behavior.
| provider: 'openai', | ||
| input: formatOpenAIResponsesInput( | ||
| sanitizeOpenAIResponse(openAIParams.input), | ||
| sanitizeOpenAIResponse(openAIParams.input, this.phClient), |
There was a problem hiding this comment.
blocking: Complete gate propagation for OpenAI Responses
This makes streaming Responses input honor the gate, but the corresponding output still calls sanitizeOpenAIResponse(finalContent) without it, and background-response input has the same omission. Full capture is therefore only partial; please pass this.phClient to those sanitizers and add regression coverage.
| const generation: GenerationMetadata = { | ||
| name: runNameFound, | ||
| input: sanitizeLangChain(messages), | ||
| input: sanitizeLangChain(messages, this.client), |
There was a problem hiding this comment.
blocking: Apply the gate to trace and span state
This updates generation input, but $ai_trace/$ai_span input, output, and interrupt state still call sanitizeLangChain without this.client, so they remain redacted under enableFullAiCapture. Please propagate the client through all trace/span state sanitizer calls.
| } | ||
|
|
||
| const mapVercelPrompt = (messages: LanguageModelPrompt): PostHogInput[] => { | ||
| const mapVercelPrompt = (messages: LanguageModelPrompt, client?: FullAiCaptureGate): PostHogInput[] => { |
There was a problem hiding this comment.
blocking: Preserve opted-in Vercel tool results
Although this mapper now receives the client, its tool-result branch still calls sanitizeVercel(c.output) without it. Binary or multimodal tool results remain redacted under full capture; please pass the gate there and add a full-capture tool-result test.
| { | ||
| type: 'text', | ||
| text: truncate(toContentString(message.content)), | ||
| text: truncate(toContentString(message.content), client), |
There was a problem hiding this comment.
blocking: Skip aggregate prompt trimming under full capture
Individual truncation now honors the client gate, but the aggregate 200 KB trimming loop below still runs unconditionally and removes whole messages. Please bypass aggregate trimming when enableFullAiCapture is enabled, or document and consistently enforce a different contract.
There was a problem hiding this comment.
Went with bypassing aggregate trimming under full capture!
🦔 ReviewHog reviewed this pull requestFound 0 must fix, 4 should fix, 1 consider. Published 5 findings (view the review). |
|
ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏 |
There was a problem hiding this comment.
ReviewHog Report
Feature
Issues: 4 issues
Files (10)
packages/core/src/posthog-core-stateless.tspackages/core/src/types.tspackages/node/src/ai-capture/routing.tspackages/node/src/ai-capture/batching.tspackages/node/src/client.tspackages/node/src/types.tscompliance/node/adapter.js.changeset/node-ai-capture-lane.mdpackages/node/references/posthog-node-references-latest.jsonpackages/react-native/references/posthog-react-native-references-latest.json
What were the main changes
- @posthog/core: new AiCaptureQueue persisted property plus explicitRoute (enqueue/sendImmediate) and getBatchEndpointPath() seams so a caller can route events onto an isolated queue/endpoint
- posthog-node: new ai-capture/{routing,batching}.ts implement the 8 MiB per-event cap, ~5 MiB byte-aware sub-batching, and name+size-only drop logging for POST /i/v0/ai/batch/
- posthog-node client.ts adds public captureAi()/captureAiImmediate() (client-generated or caller-supplied UUID returned), lazy AI route activation, and in-lane recursive 413 bisection isolated from the analytics batch path
- posthog-node types.ts adds the single public enableFullAiCapture option
- compliance adapter implements POST /capture_ai (echoing a supplied uuid) and advertises capture_ai_v0/opts into the capture_ai sdk-test-harness suite
- Regenerated node + react-native API reference JSONs for the new methods/enum/option
- Review flags: partial AI sub-batch success can be retried on a later network error (duplicate events); reset() discards a queued-but-unflushed AI event because AiCaptureQueue isn't in the preservation allowlist; captureAi/captureAiImmediate missing from IPostHog interface; compliance adapter's discardClient() doesn't clear ai_capture_queue on reset
Feature
Issues: 1 issue
Files (13)
packages/ai/src/captureAiEvent.tspackages/ai/src/captureAiGeneration.tspackages/ai/src/sanitization.tspackages/ai/src/sanitization/binary_content_redactor.tspackages/ai/src/utils.tspackages/ai/src/otel/redact.tspackages/ai/src/anthropic/index.tspackages/ai/src/gemini/index.tspackages/ai/src/langchain/callbacks.tspackages/ai/src/openai/azure.tspackages/ai/src/openai/index.tspackages/ai/src/openai-agents/processor.tspackages/ai/src/vercel/middleware.ts
What were the main changes
- New captureAiEvent.ts defines FullAiCaptureGate/isFullAiCaptureEnabled plus internal captureAiEvent/captureAiEventImmediate routing helpers, replacing the previously-exported captureAiEvent/captureAiEventImmediate/isMultimodalCaptureEnabled/MultimodalCaptureGate/AiLaneCapableClient surface
- sanitization.ts and binary_content_redactor.ts gate redaction on the client's enableFullAiCapture flag instead of the removed _INTERNAL_LLMA_MULTIMODAL env var
- utils.ts truncate()/formatResponse()/formatResponseGemini() now accept an optional gate to skip truncation/redaction
- Provider wrappers (Anthropic, Gemini, LangChain callbacks, OpenAI, Azure OpenAI, OpenAI Agents processor, Vercel middleware) thread this.phClient/this.client through sanitize/truncate calls so enableFullAiCapture skips redaction, with privacy mode still enforced separately
- otel/redact.ts intentionally keeps unconditional redaction since the OTel export path has no per-client gate
- Review flags incomplete gate propagation: Azure non-streaming/Responses create/parse/streaming/background/error paths, OpenAI Responses output and background-response input, LangChain trace/span input/output/interrupt state, and Vercel tool-result output plus the aggregate 200KB prompt-trimming loop still redact/truncate under enableFullAiCapture; also a nit on redundant
output: outputshorthand
Generated-By: PostHog Code Task-Id: 4b5e5cda-81f1-41a2-8fcd-4f335d19217e
Problem
AI observability events routinely exceed analytics payload norms — multi-MB prompts and base64 media. Sending them through plain
capture()risks both the AI event (dropped at the analytics cap) and the analytics pipeline (huge events sharing its batches). Per RFC 1198 and the cross-SDK contract insdk-specs(capture-aicapability), posthog-node gets a dedicated AI capture surface — now as a public beta, matching the shape posthog-python ships.Changes
/i/v0/ai/batch/on an isolated queue route (PostHogPersistedProperty.AiCaptureQueue) with an 8 MiB per-event cap, byte-aware sub-batching (~5 MiB target), in-lane recursive 413 bisection that never trips the shared analytics batching, lazy activation, and name+size-only drop logging.captureAi(props): string | undefinedandcaptureAiImmediate(props): Promise<string | undefined>, both returning the event UUID (client-generated UUID when the caller supplies none; a supplied valid uuid wins and reaches the wire).undefinedonly when the client is disabled.enableFullAiCapture—@posthog/aiwrappers route through the AI endpoint, skip string truncation, and pass media through unredacted; privacy mode always wins. Replaces the internal_useAiLane/_enableMultimodalCapture(and the_INTERNAL_LLMA_MULTIMODALenv var); the OTel export path always redacts.@posthog/coreseams:explicitRouteonenqueue/sendImmediate,getBatchEndpointPath().@posthog/aino longer exports the five internal lane helpers (captureAiEvent,captureAiEventImmediate,isMultimodalCaptureEnabled,MultimodalCaptureGate,AiLaneCapableClient); the internal gate is nowisFullAiCaptureEnabled/FullAiCaptureGate.POST /capture_ai(forwarding a caller-supplieduuid) and advertisescapture_ai_v0, opting into the sdk-test-harnesscapture_aisuite.check:public-apiclean; fixed a TSDoc{@link}the reference generator silently dropped.How did you test this code?
posthog-node,@posthog/ai,@posthog/core.captureAiImmediateresolves only after the send completes.enableFullAiCaptureon.captureAi→ AI endpoint only;capture()never reroutes$ai_*names.Depends on: PostHog/sdk-specs@main...feat(aio)/add-capture-ai and PostHog/posthog.com@master...feat(aio)/capture-ai-docs
Created with PostHog Code