diff --git a/docs/onboarding/ai-observability/_snippets/go-otel-tab.tsx b/docs/onboarding/ai-observability/_snippets/go-otel-tab.tsx new file mode 100644 index 000000000000..2dce199acf41 --- /dev/null +++ b/docs/onboarding/ai-observability/_snippets/go-otel-tab.tsx @@ -0,0 +1,8 @@ +import { PROSE_LANGUAGE } from '../../steps' + +/** A Go tab for provider code samples whose PostHog wrapper only exists for Python and Node. */ +export const GO_OTEL_TAB = { + language: PROSE_LANGUAGE, + file: 'Go', + code: "PostHog doesn't have a Go wrapper for this SDK yet. Use the [OpenTelemetry integration](https://posthog.com/docs/ai-observability/installation/opentelemetry) instead.", +} diff --git a/docs/onboarding/ai-observability/_snippets/openai-compatible.tsx b/docs/onboarding/ai-observability/_snippets/openai-compatible.tsx index 3fbf5911c23b..b1a68db17078 100644 --- a/docs/onboarding/ai-observability/_snippets/openai-compatible.tsx +++ b/docs/onboarding/ai-observability/_snippets/openai-compatible.tsx @@ -1,6 +1,7 @@ import { OnboardingComponentsContext } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper' import { StepDefinition } from '../../steps' +import { GO_OTEL_TAB } from './go-otel-tab' export interface OpenAICompatibleConfig { /** Display name, e.g. 'DeepSeek' */ @@ -58,6 +59,7 @@ export const getOpenAICompatibleSteps = ( npm install @posthog/ai posthog-node openai `, }, + GO_OTEL_TAB, ]} /> diff --git a/docs/onboarding/ai-observability/_snippets/otel-session-id.tsx b/docs/onboarding/ai-observability/_snippets/otel-session-id.tsx index 6f9e1c4091d8..7a29b4516c64 100644 --- a/docs/onboarding/ai-observability/_snippets/otel-session-id.tsx +++ b/docs/onboarding/ai-observability/_snippets/otel-session-id.tsx @@ -4,7 +4,7 @@ import { StepDefinition } from '../../steps' export interface OtelSessionIdConfig { /** Language tabs the surrounding page shows, in the order it shows them. */ - languages: ('Python' | 'Node')[] + languages: ('Python' | 'Node' | 'Go')[] } const PYTHON_CODE = ` @@ -67,9 +67,44 @@ class SessionIdSpanProcessor implements SpanProcessor { const reply = await sessionStore.run('conversation-abc', () => handleTurn(userMessage)) `.trim() +const GO_CODE = ` +import ( + "context" + + "go.opentelemetry.io/otel/attribute" + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +type sessionKey struct{} + +// WithAISession returns a context whose spans carry this session ID. +func WithAISession(ctx context.Context, sessionID string) context.Context { + return context.WithValue(ctx, sessionKey{}, sessionID) +} + +type SessionIDSpanProcessor struct{} + +func (SessionIDSpanProcessor) OnStart(parent context.Context, span sdktrace.ReadWriteSpan) { + if sessionID, ok := parent.Value(sessionKey{}).(string); ok { + span.SetAttributes(attribute.String("$ai_session_id", sessionID)) + } +} +func (SessionIDSpanProcessor) OnEnd(sdktrace.ReadOnlySpan) {} +func (SessionIDSpanProcessor) Shutdown(context.Context) error { return nil } +func (SessionIDSpanProcessor) ForceFlush(context.Context) error { return nil } + +// Register it on the same provider as the PostHog processor: +// sdktrace.WithSpanProcessor(SessionIDSpanProcessor{}) + +// Every span started from this context carries the session ID +ctx = WithAISession(ctx, "conversation-abc") +reply := handleTurn(ctx, userMessage) +`.trim() + const CODE_BY_LANGUAGE = { Python: { language: 'python', file: 'Python', code: PYTHON_CODE }, Node: { language: 'typescript', file: 'Node', code: NODE_CODE }, + Go: { language: 'go', file: 'Go', code: GO_CODE }, } export const getOtelSessionIdStep = (ctx: OnboardingComponentsContext, config: OtelSessionIdConfig): StepDefinition => { diff --git a/docs/onboarding/ai-observability/anthropic.tsx b/docs/onboarding/ai-observability/anthropic.tsx index 835ecac799a7..f9ebb51b9377 100644 --- a/docs/onboarding/ai-observability/anthropic.tsx +++ b/docs/onboarding/ai-observability/anthropic.tsx @@ -1,6 +1,7 @@ import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper' import { StepDefinition } from '../steps' +import { GO_OTEL_TAB } from './_snippets/go-otel-tab' export const getAnthropicSteps = (ctx: OnboardingComponentsContext): StepDefinition[] => { const { CodeBlock, CalloutBox, Markdown, Blockquote, dedent, snippets } = ctx @@ -40,6 +41,7 @@ export const getAnthropicSteps = (ctx: OnboardingComponentsContext): StepDefinit npm install @posthog/ai posthog-node @anthropic-ai/sdk `, }, + GO_OTEL_TAB, ]} /> diff --git a/docs/onboarding/ai-observability/aws-bedrock.tsx b/docs/onboarding/ai-observability/aws-bedrock.tsx index cba3cee9a5ee..1d42198a39b1 100644 --- a/docs/onboarding/ai-observability/aws-bedrock.tsx +++ b/docs/onboarding/ai-observability/aws-bedrock.tsx @@ -1,6 +1,6 @@ import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper' -import { StepDefinition } from '../steps' +import { PROSE_LANGUAGE, StepDefinition } from '../steps' import { getOtelSessionIdStep } from './_snippets/otel-session-id' export const getAWSBedrockSteps = (ctx: OnboardingComponentsContext): StepDefinition[] => { @@ -34,6 +34,11 @@ export const getAWSBedrockSteps = (ctx: OnboardingComponentsContext): StepDefini npm install @aws-sdk/client-bedrock-runtime @opentelemetry/instrumentation-aws-sdk @opentelemetry/sdk-node @opentelemetry/resources @posthog/ai `, }, + { + language: PROSE_LANGUAGE, + file: 'Go', + code: "There are no Go instrumentation libraries for the AWS SDK yet. Register PostHog's span processor and set the `gen_ai.*` attributes on a span around each Bedrock call, as shown in the Go tab of the [OpenTelemetry integration](https://posthog.com/docs/ai-observability/installation/opentelemetry).", + }, ]} /> diff --git a/docs/onboarding/ai-observability/azure-openai.tsx b/docs/onboarding/ai-observability/azure-openai.tsx index 79a750bac02d..467846cc443e 100644 --- a/docs/onboarding/ai-observability/azure-openai.tsx +++ b/docs/onboarding/ai-observability/azure-openai.tsx @@ -1,6 +1,7 @@ import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper' import { StepDefinition } from '../steps' +import { GO_OTEL_TAB } from './_snippets/go-otel-tab' export const getAzureOpenAISteps = (ctx: OnboardingComponentsContext): StepDefinition[] => { const { CodeBlock, CalloutBox, Markdown, Blockquote, dedent, snippets } = ctx @@ -41,6 +42,7 @@ export const getAzureOpenAISteps = (ctx: OnboardingComponentsContext): StepDefin npm install @posthog/ai posthog-node openai `, }, + GO_OTEL_TAB, ]} /> diff --git a/docs/onboarding/ai-observability/google.tsx b/docs/onboarding/ai-observability/google.tsx index 1097af45947b..8894c87eb43e 100644 --- a/docs/onboarding/ai-observability/google.tsx +++ b/docs/onboarding/ai-observability/google.tsx @@ -1,6 +1,7 @@ import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper' import { StepDefinition } from '../steps' +import { GO_OTEL_TAB } from './_snippets/go-otel-tab' export const getGoogleSteps = (ctx: OnboardingComponentsContext): StepDefinition[] => { const { CodeBlock, CalloutBox, Markdown, Blockquote, dedent, snippets } = ctx @@ -39,6 +40,7 @@ export const getGoogleSteps = (ctx: OnboardingComponentsContext): StepDefinition npm install @posthog/ai posthog-node @google/genai `, }, + GO_OTEL_TAB, ]} /> diff --git a/docs/onboarding/ai-observability/openai.tsx b/docs/onboarding/ai-observability/openai.tsx index f1c95a21b67c..c055a2d13de8 100644 --- a/docs/onboarding/ai-observability/openai.tsx +++ b/docs/onboarding/ai-observability/openai.tsx @@ -1,6 +1,7 @@ import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper' import { StepDefinition } from '../steps' +import { GO_OTEL_TAB } from './_snippets/go-otel-tab' export const getOpenAISteps = (ctx: OnboardingComponentsContext): StepDefinition[] => { const { CodeBlock, CalloutBox, Markdown, Blockquote, dedent, snippets } = ctx @@ -40,6 +41,7 @@ export const getOpenAISteps = (ctx: OnboardingComponentsContext): StepDefinition npm install @posthog/ai posthog-node openai `, }, + GO_OTEL_TAB, ]} /> diff --git a/docs/onboarding/ai-observability/opentelemetry.tsx b/docs/onboarding/ai-observability/opentelemetry.tsx index 97f230d1fa84..ae9da6ea77f8 100644 --- a/docs/onboarding/ai-observability/opentelemetry.tsx +++ b/docs/onboarding/ai-observability/opentelemetry.tsx @@ -16,11 +16,11 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef <> - The [Node.js](https://github.com/PostHog/posthog-js/tree/main/examples/example-ai-openai) - and - [Python](https://github.com/PostHog/posthog-python/tree/master/examples/example-ai-openai) - OpenAI examples show a complete end-to-end OpenTelemetry setup. Swap the instrumentation for - any other `gen_ai.*`-emitting library to trace a different provider or framework. + The [Node.js](https://github.com/PostHog/posthog-js/tree/main/examples/example-ai-openai), + [Python](https://github.com/PostHog/posthog-python/tree/master/examples/example-ai-openai), + and [Go](https://github.com/PostHog/posthog-go/tree/main/otel/example) examples show a + complete end-to-end OpenTelemetry setup. Swap the instrumentation for any other + `gen_ai.*`-emitting library to trace a different provider or framework. @@ -46,6 +46,13 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef npm install openai @posthog/ai @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/instrumentation-openai `, }, + { + language: 'bash', + file: 'Go', + code: dedent` + go get github.com/posthog/posthog-go/otel go.opentelemetry.io/otel go.opentelemetry.io/otel/sdk + `, + }, ]} /> @@ -57,10 +64,10 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef content: ( <> - Configure OpenTelemetry to export spans to PostHog via the `PostHogSpanProcessor`. The processor - only forwards AI-related spans — spans whose name or attribute keys start with `gen_ai.`, - `llm.`, `ai.`, or `traceloop.` — and drops everything else. PostHog converts `gen_ai.*` spans - into `$ai_generation` events automatically. + Configure OpenTelemetry to export spans to PostHog via the `PostHogSpanProcessor` + (`posthogotel.NewSpanProcessor` in Go). The processor only forwards AI-related spans — spans + whose name or attribute keys start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.` — and drops + everything else. PostHog converts `gen_ai.*` spans into `$ai_generation` events automatically. ", + posthogotel.WithHost(""), + ) + if err != nil { + return nil, err + } + + resource := sdkresource.NewWithAttributes("", + attribute.String("service.name", "my-app"), + attribute.String("posthog.distinct_id", "user_123"), // optional: identifies the user in PostHog + attribute.String("foo", "bar"), // custom properties are passed through + ) + provider := sdktrace.NewTracerProvider( + sdktrace.WithResource(resource), + sdktrace.WithSpanProcessor(processor), + ) + otel.SetTracerProvider(provider) + return provider, nil + } + `, + }, ]} /> + + + {dedent` + In Go, if your app already has a \`TracerProvider\`, attach the processor to it with + \`provider.RegisterSpanProcessor(processor)\` instead of creating a new provider. Call + \`provider.Shutdown\` (or \`provider.ForceFlush\`) before exit so buffered spans are sent. + `} + ), }, @@ -135,6 +186,16 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef `$ai_generation` event. + + {dedent` + Go has no instrumentation libraries for provider SDKs yet: start a span around each call + and set the \`gen_ai.*\` attributes yourself, as shown in the Go tab. Frameworks that + already emit \`gen_ai.*\` spans, like [ADK Go](https://google.golang.org/adk), are captured + automatically. ADK Go sends message content as log records rather than span attributes, + so its generations arrive without prompts and responses. + `} + + @@ -234,7 +335,7 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef ), }, - getOtelSessionIdStep(ctx, { languages: ['Python', 'Node'] }), + getOtelSessionIdStep(ctx, { languages: ['Python', 'Node', 'Go'] }), { title: 'Other instrumentations, direct OTLP, and troubleshooting', badge: 'optional', @@ -253,7 +354,7 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef {dedent` - **Direct OTLP export.** If you run an OpenTelemetry Collector, or want to export from a language that isn't Python or Node.js, point any OTLP/HTTP exporter directly at PostHog's AI ingestion endpoint. PostHog accepts OTLP over HTTP in both \`application/x-protobuf\` and \`application/json\`, authenticated with a \`Bearer\` token. The endpoint is signal-specific (traces only), so use the \`OTEL_EXPORTER_OTLP_TRACES_*\` variants rather than the general \`OTEL_EXPORTER_OTLP_*\` ones (the SDK appends \`/v1/traces\` to the latter and would 404). + **Direct OTLP export.** If you run an OpenTelemetry Collector, or want to export from a language other than Python, Node.js, or Go, point any OTLP/HTTP exporter directly at PostHog's AI ingestion endpoint. PostHog accepts OTLP over HTTP in both \`application/x-protobuf\` and \`application/json\`, authenticated with a \`Bearer\` token. The endpoint is signal-specific (traces only), so use the \`OTEL_EXPORTER_OTLP_TRACES_*\` variants rather than the general \`OTEL_EXPORTER_OTLP_*\` ones (the SDK appends \`/v1/traces\` to the latter and would 404). `} diff --git a/docs/onboarding/steps.ts b/docs/onboarding/steps.ts index 465ad145ce81..3faa8395eaf2 100644 --- a/docs/onboarding/steps.ts +++ b/docs/onboarding/steps.ts @@ -24,3 +24,6 @@ export interface StepProps { export interface StepsProps { children: ReactNode } + +/** CodeBlock tab language that renders markdown prose with clickable links instead of highlighted code. */ +export const PROSE_LANGUAGE = 'prose' diff --git a/frontend/src/scenes/onboarding/shared/OnboardingDocsContentWrapper.tsx b/frontend/src/scenes/onboarding/shared/OnboardingDocsContentWrapper.tsx index 9fb0e383bf97..5fd6953d0bee 100644 --- a/frontend/src/scenes/onboarding/shared/OnboardingDocsContentWrapper.tsx +++ b/frontend/src/scenes/onboarding/shared/OnboardingDocsContentWrapper.tsx @@ -3,6 +3,7 @@ import React, { Children, ReactNode, createContext, isValidElement, useContext, import { StepProps, StepsProps } from '@posthog/shared-onboarding/steps' import { StepDefinition, StepModifier } from '@posthog/shared-onboarding/steps' +import { PROSE_LANGUAGE } from '@posthog/shared-onboarding/steps' import { CodeSnippet, getLanguage } from 'lib/components/CodeSnippet' import { CopyToClipboardInline } from 'lib/components/CopyToClipboard' @@ -194,7 +195,11 @@ function CodeBlock({ const block = codeBlocks[0] return (
- {block.code} + {block.language === PROSE_LANGUAGE ? ( + {block.code} + ) : ( + {block.code} + )} {hostHint}
) @@ -219,7 +224,11 @@ function CodeBlock({ }))} /> )} - {selectedBlock.code} + {selectedBlock.language === PROSE_LANGUAGE ? ( + {selectedBlock.code} + ) : ( + {selectedBlock.code} + )} {hostHint} )