Skip to content
Merged
209 changes: 209 additions & 0 deletions docs/onboarding/llm-analytics/cloudflare-ai-gateway.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/OnboardingDocsContentWrapper'

import { StepDefinition } from '../steps'

export const getCloudflareAIGatewaySteps = (ctx: OnboardingComponentsContext): StepDefinition[] => {
const { CodeBlock, CalloutBox, Markdown, Blockquote, dedent, snippets } = ctx

const NotableGenerationProperties = snippets?.NotableGenerationProperties

return [
{
title: 'Install dependencies',
badge: 'required',
content: (
<>
<CalloutBox type="fyi" icon="IconInfo" title="Full working examples">
<Markdown>
See the complete
[Node.js](https://github.com/PostHog/posthog-js/tree/main/examples/example-ai-cloudflare-ai-gateway)
and
[Python](https://github.com/PostHog/posthog-python/tree/master/examples/example-ai-cloudflare-ai-gateway)
examples on GitHub.
</Markdown>
</CalloutBox>

<Markdown>Install the OpenTelemetry SDK, the OpenAI instrumentation, and the OpenAI SDK.</Markdown>

<CodeBlock
blocks={[
{
language: 'bash',
file: 'Python',
code: dedent`
pip install openai opentelemetry-sdk posthog[otel] opentelemetry-instrumentation-openai-v2
`,
},
{
language: 'bash',
file: 'Node',
code: dedent`
npm install openai @posthog/ai @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/instrumentation-openai
`,
},
]}
/>
</>
),
},
{
title: 'Set up OpenTelemetry tracing',
badge: 'required',
content: (
<>
<Markdown>
Configure OpenTelemetry to auto-instrument OpenAI SDK calls and export traces to PostHog.
PostHog converts `gen_ai.*` spans into `$ai_generation` events automatically.
</Markdown>

<CodeBlock
blocks={[
{
language: 'python',
file: 'Python',
code: dedent`
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from posthog.ai.otel import PostHogSpanProcessor
from opentelemetry.instrumentation.openai_v2 import OpenAIInstrumentor

resource = Resource(attributes={
SERVICE_NAME: "my-app",
"posthog.distinct_id": "user_123", # optional: identifies the user in PostHog
"foo": "bar", # custom properties are passed through
})

provider = TracerProvider(resource=resource)
provider.add_span_processor(
PostHogSpanProcessor(
api_key="<ph_project_token>",
host="<ph_client_api_host>",
)
)
trace.set_tracer_provider(provider)

OpenAIInstrumentor().instrument()
`,
},
{
language: 'typescript',
file: 'Node',
code: dedent`
import { NodeSDK } from '@opentelemetry/sdk-node'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { PostHogSpanProcessor } from '@posthog/ai/otel'
import { OpenAIInstrumentation } from '@opentelemetry/instrumentation-openai'

const sdk = new NodeSDK({
resource: resourceFromAttributes({
'service.name': 'my-app',
'posthog.distinct_id': 'user_123', // optional: identifies the user in PostHog
foo: 'bar', // custom properties are passed through
}),
spanProcessors: [
new PostHogSpanProcessor({
apiKey: '<ph_project_token>',
host: '<ph_client_api_host>',
}),
],
instrumentations: [new OpenAIInstrumentation()],
})
sdk.start()
`,
},
]}
/>
</>
),
},
{
title: 'Call Cloudflare AI Gateway',
badge: 'required',
content: (
<>
<Markdown>
{dedent`
Cloudflare AI Gateway exposes an OpenAI-compatible \`compat\` endpoint at
\`https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/compat\`. Point the OpenAI SDK at
this URL with your upstream provider key (e.g. your OpenAI key) and pass your AI Gateway token via
the \`cf-aig-authorization\` header. Specify models as \`provider/model-id\` (for example
\`openai/gpt-5-mini\` or \`anthropic/claude-sonnet-4-5\`).
`}
</Markdown>

<CodeBlock
blocks={[
{
language: 'python',
file: 'Python',
code: dedent`
import openai

client = openai.OpenAI(
api_key="<openai_api_key>",
default_headers={
"cf-aig-authorization": "Bearer <cf_aig_token>",
},
base_url="https://gateway.ai.cloudflare.com/v1/<account_id>/<gateway_id>/compat",
)

response = client.chat.completions.create(
model="openai/gpt-5-mini",
max_completion_tokens=1024,
messages=[
{"role": "user", "content": "Tell me a fun fact about hedgehogs"}
],
)

print(response.choices[0].message.content)
`,
},
{
language: 'typescript',
file: 'Node',
code: dedent`
import OpenAI from 'openai'

const client = new OpenAI({
apiKey: '<openai_api_key>',
defaultHeaders: {
'cf-aig-authorization': 'Bearer <cf_aig_token>',
},
baseURL: 'https://gateway.ai.cloudflare.com/v1/<account_id>/<gateway_id>/compat',
})

const response = await client.chat.completions.create({
model: 'openai/gpt-5-mini',
max_completion_tokens: 1024,
messages: [{ role: 'user', content: 'Tell me a fun fact about hedgehogs' }],
})

console.log(response.choices[0].message.content)
`,
},
]}
/>

<Blockquote>
<Markdown>
{dedent`
**Note:** If you want to capture LLM events anonymously, omit the \`posthog.distinct_id\` resource attribute. See our docs on [anonymous vs identified events](https://posthog.com/docs/data/anonymous-vs-identified-events) to learn more.
`}
</Markdown>
</Blockquote>

<Markdown>
{dedent`
You can expect captured \`$ai_generation\` events to have the following properties:
`}
</Markdown>

{NotableGenerationProperties && <NotableGenerationProperties />}
</>
),
},
]
}

export const CloudflareAIGatewayInstallation = createInstallation(getCloudflareAIGatewaySteps)
Loading
Loading