Files
Richard SolomouandGitHub e38d995dec fix(convex): remove "use node" directive from Convex examples (#3321)
* style(convex): apply prettier formatting to example-convex

* refactor(convex): replace @opentelemetry/sdk-node with runtime-agnostic sdk-trace-base

Remove "use node" directive from all Convex example files by switching
from @opentelemetry/sdk-node (Node.js-only) to @opentelemetry/sdk-trace-base
with BasicTracerProvider + BatchSpanProcessor. Add a performance polyfill
for Convex's V8 isolate which lacks the performance global that
@opentelemetry/core expects.

* fix(convex): use class-based performance polyfill with relative timing

* fix(convex): move tracer provider to module scope to avoid silent no-op

trace.setGlobalTracerProvider() only applies on the first call — subsequent
calls are silently ignored. Since Convex V8 isolates are warm-reused, the
provider must be initialized at module scope. Per-request distinctId is
already passed via experimental_telemetry metadata.

* refactor(convex): extract OTEL provider setup into shared otelSetup.ts

Move provider creation and global registration out of action files into
a shared setup file. PostHogTraceExporter is now clearly framed as a
standard OTEL span processor that sits alongside other exporters in the
user's OTEL config, rather than owning the entire provider.

* refactor(convex): inline OTEL setup back into individual example files

Keep the provider setup co-located with the action so each example is
self-contained and easy to follow without jumping between files.

* fix(convex): remove competitor names from comments

* style(convex): format example-convex files

* fix(convex): use SimpleSpanProcessor instead of BatchSpanProcessor

Convex actions terminate immediately after returning, so BatchSpanProcessor
never gets a chance to flush. SimpleSpanProcessor sends spans synchronously,
ensuring they're exported before the action completes.
2026-04-02 19:25:41 +03:00

62 lines
2.0 KiB
TypeScript

import { generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
import { action } from '../_generated/server'
import { v } from 'convex/values'
import { posthog } from '../posthog.js'
// Demonstrates using the Vercel AI SDK (without @convex-dev/agent)
// to call an LLM and capture $ai_generation events to PostHog.
export const generate = action({
args: {
prompt: v.string(),
distinctId: v.optional(v.string()),
},
handler: async (ctx, args) => {
const traceId = crypto.randomUUID()
const startTime = Date.now()
const result = await generateText({
model: openai('gpt-5-mini'),
prompt: args.prompt,
})
const latency = (Date.now() - startTime) / 1000
await posthog.capture(ctx, {
distinctId: args.distinctId ?? 'anonymous',
event: '$ai_generation',
properties: {
// Trace ID groups multiple generations into a single trace
$ai_trace_id: traceId,
// Core identification
$ai_provider: 'openai',
$ai_model: 'gpt-5-mini',
// Token usage
$ai_input_tokens: result.usage.inputTokens,
$ai_output_tokens: result.usage.outputTokens,
// Cache tokens (if the provider reports them)
$ai_cache_read_input_tokens: result.usage.cachedInputTokens,
// Performance
$ai_latency: latency,
// Input/output content
$ai_input: [{ role: 'user', content: args.prompt }],
$ai_output_choices: [{ role: 'assistant', content: result.text }],
// Generation metadata — the AI SDK doesn't expose HTTP status directly,
// so we infer success/failure from the finish reason.
$ai_is_error: result.finishReason === 'error',
},
})
return {
text: result.text,
usage: result.usage,
}
},
})