Files
zeekay 159e825022 merge: take upstream master fa1a9a60 onto the converged tree
The five days of upstream evolution since the 2026-08-01 import land through
bin/debrand: new query schema, billing counters, ducklake compaction, the
desktop sandbox default, capture evolution. Ours' invariants hold everywhere
they met it: IAM-native auth only, no ee, no upsell surfaces, one sessions
model, org-is-the-tenant, v1-first serving, fail-closed capture. Crates and
workflows the fork retired stay retired; the Lemon prefix finishes dissolving
into elements.
2026-08-06 05:33:45 -07:00

217 lines
9.8 KiB
TypeScript

import { OnboardingComponentsContext, createInstallation } from 'scenes/onboarding/shared/OnboardingDocsContentWrapper'
import { StepDefinition } from '../steps'
export const getConvexSteps = (ctx: OnboardingComponentsContext): StepDefinition[] => {
const { CodeBlock, CalloutBox, Markdown, dedent, snippets } = ctx
const NotableGenerationProperties = snippets?.NotableGenerationProperties
return [
{
title: 'Install dependencies',
badge: 'required',
content: (
<>
<Markdown>Install the Insights AI package, the Vercel AI SDK, and the OpenTelemetry SDK.</Markdown>
<CodeBlock
language="bash"
code={dedent`
npm install @hanzo/ai @ai-sdk/openai ai @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/api
`}
/>
</>
),
},
{
title: 'Set environment variables',
badge: 'required',
content: (
<>
<Markdown>
Set your Insights project API key and host as Convex environment variables. You can find these in
your [project settings](https://app.hanzo.ai/settings/project).
</Markdown>
<CodeBlock
language="bash"
code={dedent`
npx convex env set INSIGHTS_PROJECT_TOKEN "<ph_project_token>"
npx convex env set INSIGHTS_HOST "<ph_client_api_host>"
`}
/>
<Markdown>You also need your AI provider's API key (e.g. `OPENAI_API_KEY`):</Markdown>
<CodeBlock
language="bash"
code={dedent`
npx convex env set OPENAI_API_KEY "your_openai_api_key"
`}
/>
</>
),
},
{
title: 'Capture LLM events with OpenTelemetry',
badge: 'required',
content: (
<>
<Markdown>
Create a Convex action that initializes a `BasicTracerProvider` with Insights's span processor
and enables telemetry on your AI SDK calls. The provider is initialized at module scope so it
persists across warm V8 isolate invocations. Await `provider.forceFlush()` before returning
because Convex may finish the action while the processor's batch is still pending.
</Markdown>
<CodeBlock
language="typescript"
code={dedent`
import { trace } from '@opentelemetry/api'
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
import { InsightsSpanProcessor } from '@hanzo/ai/otel'
import { action } from './_generated/server'
import { v } from 'convex/values'
const provider = new BasicTracerProvider({
resource: resourceFromAttributes({
'service.name': 'my-convex-app',
}),
spanProcessors: [
new InsightsSpanProcessor({
projectToken: process.env.INSIGHTS_PROJECT_TOKEN!,
host: process.env.INSIGHTS_HOST,
}),
],
})
trace.setGlobalTracerProvider(provider)
export const generate = action({
args: {
prompt: v.string(),
distinctId: v.optional(v.string()),
},
handler: async (_ctx, args) => {
const distinctId = args.distinctId ?? 'anonymous'
const result = await generateText({
model: openai('gpt-5-mini'),
prompt: args.prompt,
experimental_telemetry: {
isEnabled: true,
functionId: 'my-convex-action',
metadata: {
insights_distinct_id: distinctId,
},
},
})
await provider.forceFlush()
return { text: result.text, usage: result.usage }
},
})
`}
/>
<CalloutBox type="fyi" icon="IconInfo" title="How this works">
<Markdown>
The `InsightsSpanProcessor` sends AI-related OpenTelemetry spans to Insights's OTLP ingestion
endpoint. `provider.forceFlush()` ensures its batch is sent before the Convex action
returns. Insights converts these into `$ai_generation` events automatically. The
`insights_distinct_id` metadata field links events to a specific user.
</Markdown>
</CalloutBox>
</>
),
},
{
title: 'Using Convex Agent',
badge: 'optional',
content: (
<>
<Markdown>
If you're using `@convex-dev/agent`, pass `experimental_telemetry` to the agent's `generateText`
call:
</Markdown>
<CodeBlock
language="typescript"
code={dedent`
import { trace } from '@opentelemetry/api'
import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { Agent } from '@convex-dev/agent'
import { openai } from '@ai-sdk/openai'
import { InsightsSpanProcessor } from '@hanzo/ai/otel'
import { components } from './_generated/api'
import { action } from './_generated/server'
import { v } from 'convex/values'
const provider = new BasicTracerProvider({
resource: resourceFromAttributes({
'service.name': 'my-convex-app',
}),
spanProcessors: [
new InsightsSpanProcessor({
projectToken: process.env.INSIGHTS_PROJECT_TOKEN!,
host: process.env.INSIGHTS_HOST,
}),
],
})
trace.setGlobalTracerProvider(provider)
export const generate = action({
args: {
prompt: v.string(),
distinctId: v.optional(v.string()),
},
handler: async (ctx, args) => {
const distinctId = args.distinctId ?? 'anonymous'
const supportAgent = new Agent(components.agent, {
name: 'support-agent',
languageModel: openai('gpt-5-mini'),
instructions: 'You are a helpful support agent.',
})
const { thread } = await supportAgent.createThread(ctx, {})
const result = await thread.generateText({
prompt: args.prompt,
experimental_telemetry: {
isEnabled: true,
functionId: 'convex-agent',
metadata: {
insights_distinct_id: distinctId,
},
},
})
await provider.forceFlush()
return { text: result.text, usage: result.totalUsage }
},
})
`}
/>
<Markdown>
{dedent`
You can expect captured \`$ai_generation\` events to have the following properties:
`}
</Markdown>
{NotableGenerationProperties && <NotableGenerationProperties />}
</>
),
},
]
}
export const ConvexInstallation = createInstallation(getConvexSteps)