Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/onboarding/ai-observability/_snippets/go-otel-tab.tsx
Original file line number Diff line number Diff line change
@@ -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.",
}
Original file line number Diff line number Diff line change
@@ -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' */
Expand Down Expand Up @@ -58,6 +59,7 @@ export const getOpenAICompatibleSteps = (
npm install @posthog/ai posthog-node openai
`,
},
GO_OTEL_TAB,
]}
/>
</>
Expand Down
37 changes: 36 additions & 1 deletion docs/onboarding/ai-observability/_snippets/otel-session-id.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
Expand Down Expand Up @@ -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 => {
Expand Down
2 changes: 2 additions & 0 deletions docs/onboarding/ai-observability/anthropic.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -40,6 +41,7 @@ export const getAnthropicSteps = (ctx: OnboardingComponentsContext): StepDefinit
npm install @posthog/ai posthog-node @anthropic-ai/sdk
`,
},
GO_OTEL_TAB,
]}
/>
</>
Expand Down
7 changes: 6 additions & 1 deletion docs/onboarding/ai-observability/aws-bedrock.tsx
Original file line number Diff line number Diff line change
@@ -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[] => {
Expand Down Expand Up @@ -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).",
},
]}
/>
</>
Expand Down
2 changes: 2 additions & 0 deletions docs/onboarding/ai-observability/azure-openai.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -41,6 +42,7 @@ export const getAzureOpenAISteps = (ctx: OnboardingComponentsContext): StepDefin
npm install @posthog/ai posthog-node openai
`,
},
GO_OTEL_TAB,
]}
/>
</>
Expand Down
2 changes: 2 additions & 0 deletions docs/onboarding/ai-observability/google.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -39,6 +40,7 @@ export const getGoogleSteps = (ctx: OnboardingComponentsContext): StepDefinition
npm install @posthog/ai posthog-node @google/genai
`,
},
GO_OTEL_TAB,
]}
/>
</>
Expand Down
2 changes: 2 additions & 0 deletions docs/onboarding/ai-observability/openai.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -40,6 +41,7 @@ export const getOpenAISteps = (ctx: OnboardingComponentsContext): StepDefinition
npm install @posthog/ai posthog-node openai
`,
},
GO_OTEL_TAB,
]}
/>
</>
Expand Down
123 changes: 112 additions & 11 deletions docs/onboarding/ai-observability/opentelemetry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef
<>
<CalloutBox type="fyi" icon="IconInfo" title="Full working examples">
<Markdown>
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.
</Markdown>
</CalloutBox>

Expand All @@ -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
`,
},
]}
/>
</>
Expand All @@ -57,10 +64,10 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef
content: (
<>
<Markdown>
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.
</Markdown>

<CodeBlock
Expand Down Expand Up @@ -119,8 +126,52 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef
sdk.start()
`,
},
{
language: 'go',
file: 'Go',
code: dedent`
import (
"context"

posthogotel "github.com/posthog/posthog-go/otel"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
sdkresource "go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func setupTracing(ctx context.Context) (*sdktrace.TracerProvider, error) {
processor, err := posthogotel.NewSpanProcessor(ctx, "<ph_project_token>",
posthogotel.WithHost("<ph_client_api_host>"),
)
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
}
`,
},
]}
/>

<Markdown>
{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.
`}
</Markdown>
</>
),
},
Expand All @@ -135,6 +186,16 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef
`$ai_generation` event.
</Markdown>

<Markdown>
{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
Comment thread
marco-g-pm marked this conversation as resolved.
automatically. ADK Go sends message content as log records rather than span attributes,
so its generations arrive without prompts and responses.
`}
</Markdown>

<CodeBlock
blocks={[
{
Expand Down Expand Up @@ -171,6 +232,46 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef
console.log(response.choices[0].message.content)
`,
},
{
language: 'go',
file: 'Go',
code: dedent`
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)

tracer := otel.Tracer("my-app")
_, span := tracer.Start(ctx, "chat gpt-5-mini")
// Set the request attributes before the call so a failed call still
// carries the gen_ai.* keys the PostHog span filter looks for
span.SetAttributes(
attribute.String("gen_ai.operation.name", "chat"),
attribute.String("gen_ai.provider.name", "openai"),
attribute.String("gen_ai.request.model", "gpt-5-mini"),
// JSON-serialized chat messages
attribute.String("gen_ai.input.messages", \`[{"role":"user","content":"Tell me a fun fact about hedgehogs"}]\`),
attribute.String("server.address", "api.openai.com"),
)

resp, err := client.Chat.Completions.New(ctx, params) // your existing LLM call
if err != nil {
span.RecordError(err)
Comment thread
marco-g-pm marked this conversation as resolved.
// Status Error is what marks the event as a failed generation in PostHog
span.SetStatus(codes.Error, err.Error())
span.End()
return err
}
Comment thread
marco-g-pm marked this conversation as resolved.

span.SetAttributes(
attribute.String("gen_ai.output.messages", \`[{"role":"assistant","content":"Hedgehogs have around 5,000 spines."}]\`),
attribute.Int("gen_ai.usage.input_tokens", int(resp.Usage.PromptTokens)),
attribute.Int("gen_ai.usage.output_tokens", int(resp.Usage.CompletionTokens)),
)
span.End()
`,
},
]}
/>

Expand Down Expand Up @@ -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',
Expand All @@ -253,7 +354,7 @@ export const getOpenTelemetrySteps = (ctx: OnboardingComponentsContext): StepDef

<Markdown>
{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).
`}
</Markdown>

Expand Down
3 changes: 3 additions & 0 deletions docs/onboarding/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Loading
Loading