mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-10-30 18:34:19 +00:00
* feat(ai): add $ai_completion_id and $ai_provider_metadata to OpenAI events
Adds two new auto-captured properties to `$ai_generation` events emitted by
the OpenAI and Azure OpenAI wrappers, enabling direct correlation between
PostHog events and OpenAI's Logs dashboard
(`platform.openai.com/logs/{completion_id}`):
- `$ai_completion_id` — provider-assigned response ID (e.g. `chatcmpl-…`,
`resp_…`). Generalises across providers, so it lives at the top of the
`$ai_*` namespace.
- `$ai_provider_metadata` — OpenAI-specific fields (`system_fingerprint`,
`request_id`) collected under a single blob rather than polluting the
shared schema. Sets the pattern for Anthropic / Gemini wrappers to follow.
Both options are accepted by the public `captureAiGeneration` primitive, so
external instrumentation produces identical events. Coverage spans
non-streaming and streaming Chat Completions plus Responses API
(`create` + `parse`) for both OpenAI and Azure OpenAI. Streaming paths
extract `id` / `system_fingerprint` from accumulated chunks; the
`x-request-id` header is read via the OpenAI SDK's semi-private
`_request_id` field through a small typed helper. Streaming
`request_id` capture (which needs `.withResponse()`) is left as a
follow-up — see review thread on #3306.
Addresses review feedback on #3306:
- restructure `system_fingerprint` / `request_id` under `$ai_provider_metadata`
- fix the TS2353 build error on the test mock by widening `ChatCompletion`
with `{ _request_id?: string }`
- consolidate the duplicated `(result as any)._request_id` cast into
`extractRequestId` with an explanatory comment in one place
- add Responses API `$ai_completion_id` / `request_id` test coverage
Co-authored-by: Hanzo Dev <[email protected]>
* fix(ai): address Greptile feedback on the revision
- Streaming error path now surfaces accumulated completion metadata.
When a stream fails mid-flight after consuming chunks that carried
`chunk.id` / `chunk.system_fingerprint`, the error event was being
emitted without `$ai_completion_id` / `$ai_provider_metadata` — the
exact correlation IDs this PR is meant to enable. Hoisted the
accumulators above the `try` block and pass them in the `catch`
capture for both `index.ts` and `azure.ts`, chat and Responses.
- Convert the `openai-utils` tests to parameterised `it.each` tables
per the repo's "always prefer parameterised tests" convention; each
input/output pair is now its own row so a failure points to the
specific case that broke.
* fix(aiobs): align system_fingerprint guard with surrounding style
Task-Id: 672e907b-e741-4f0e-abf2-76722b296982
---------
Co-authored-by: Hanzo Dev <[email protected]>
Co-authored-by: Carlos Marchal <[email protected]>
Co-authored-by: Richard Solomou <[email protected]>
47 lines
1.9 KiB
TypeScript
47 lines
1.9 KiB
TypeScript
import { extractRequestId, buildProviderMetadata } from '../src/openai/utils'
|
|
|
|
describe('extractRequestId', () => {
|
|
it.each<[name: string, input: unknown, expected: string | undefined]>([
|
|
['reads `_request_id` when present', { _request_id: 'req_abc123' }, 'req_abc123'],
|
|
['returns undefined when `_request_id` is absent', { id: 'chatcmpl-1' }, undefined],
|
|
['returns undefined when `_request_id` is null', { _request_id: null }, undefined],
|
|
['returns undefined for null input', null, undefined],
|
|
['returns undefined for undefined input', undefined, undefined],
|
|
['returns undefined for string input', 'not-an-object', undefined],
|
|
['returns undefined for numeric input', 42, undefined],
|
|
])('%s', (_name, input, expected) => {
|
|
expect(extractRequestId(input)).toBe(expected)
|
|
})
|
|
})
|
|
|
|
describe('buildProviderMetadata', () => {
|
|
it.each<
|
|
[
|
|
name: string,
|
|
input: Parameters<typeof buildProviderMetadata>[0],
|
|
expected: ReturnType<typeof buildProviderMetadata>,
|
|
]
|
|
>([
|
|
[
|
|
'includes both keys when both values are present',
|
|
{ systemFingerprint: 'fp_1', requestId: 'req_1' },
|
|
{ system_fingerprint: 'fp_1', request_id: 'req_1' },
|
|
],
|
|
[
|
|
'omits requestId when only systemFingerprint is present',
|
|
{ systemFingerprint: 'fp_1' },
|
|
{ system_fingerprint: 'fp_1' },
|
|
],
|
|
['omits systemFingerprint when only requestId is present', { requestId: 'req_1' }, { request_id: 'req_1' }],
|
|
['returns undefined for an empty input object', {}, undefined],
|
|
[
|
|
'returns undefined when both values are undefined',
|
|
{ systemFingerprint: undefined, requestId: undefined },
|
|
undefined,
|
|
],
|
|
['returns undefined when both values are null', { systemFingerprint: null, requestId: null }, undefined],
|
|
])('%s', (_name, input, expected) => {
|
|
expect(buildProviderMetadata(input)).toEqual(expected)
|
|
})
|
|
})
|