mirror of
https://github.com/hanzoai/insights-js.git
synced 2026-11-02 14:31:14 +00:00
* 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.
84 lines
2.9 KiB
TypeScript
84 lines
2.9 KiB
TypeScript
import { Agent } from '@convex-dev/agent'
|
|
import { openai } from '@ai-sdk/openai'
|
|
import { components } from '../_generated/api'
|
|
import { action } from '../_generated/server'
|
|
import { v } from 'convex/values'
|
|
import { posthog } from '../posthog.js'
|
|
|
|
const supportAgent = new Agent(components.agent, {
|
|
name: 'support-agent',
|
|
languageModel: openai('gpt-5-mini'),
|
|
instructions: 'You are a helpful support agent. Answer questions concisely.',
|
|
})
|
|
|
|
export const generate = action({
|
|
args: {
|
|
prompt: v.string(),
|
|
distinctId: v.optional(v.string()),
|
|
},
|
|
handler: async (ctx, args) => {
|
|
const { thread } = await supportAgent.createThread(ctx, {})
|
|
|
|
const traceId = crypto.randomUUID()
|
|
const startTime = Date.now()
|
|
|
|
// Collect usage metadata from the usageHandler callback, then combine it
|
|
// with the full result to send a comprehensive $ai_generation event.
|
|
const usageData: {
|
|
model?: string
|
|
provider?: string
|
|
agentName?: string
|
|
} = {}
|
|
|
|
const result = await thread.generateText(
|
|
{ prompt: args.prompt },
|
|
{
|
|
usageHandler: async (_usageCtx, { model, provider, agentName }) => {
|
|
usageData.model = model
|
|
usageData.provider = provider
|
|
usageData.agentName = agentName
|
|
},
|
|
}
|
|
)
|
|
|
|
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: usageData.provider,
|
|
$ai_model: usageData.model,
|
|
$ai_span_name: usageData.agentName,
|
|
|
|
// Token usage (from totalUsage to account for multi-step tool calls)
|
|
$ai_input_tokens: result.totalUsage.inputTokens,
|
|
$ai_output_tokens: result.totalUsage.outputTokens,
|
|
|
|
// Cache tokens (if the provider reports them)
|
|
$ai_cache_read_input_tokens: result.totalUsage.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.totalUsage,
|
|
}
|
|
},
|
|
})
|