@posthog/ai
Version:
PostHog Node.js AI integrations
250 lines (243 loc) • 8.69 kB
TypeScript
import { LanguageModelV2, LanguageModelV3 } from '@ai-sdk/provider';
import { PostHog } from 'posthog-node';
/**
* Token usage information for AI model responses
*/
interface TokenUsage {
inputTokens?: number;
outputTokens?: number;
reasoningTokens?: unknown;
cacheReadInputTokens?: unknown;
cacheCreationInputTokens?: unknown;
webSearchCount?: number;
rawUsage?: unknown;
}
/**
* Options for fetching a prompt
*/
interface GetPromptOptions {
cacheTtlSeconds?: number;
fallback?: string;
version?: number;
}
/**
* Result from the Prompts API or local cache — carries real metadata.
*/
interface PromptRemoteResult {
source: 'api' | 'cache' | 'stale_cache';
prompt: string;
name: string;
version: number;
}
/**
* Result when the fetch failed and no cache was available — fell back to the
* hardcoded fallback string. name and version are undefined so they remain
* accessible on the PromptResult union without a type guard.
*/
interface PromptCodeFallbackResult {
source: 'code_fallback';
prompt: string;
name: undefined;
version: undefined;
}
/**
* Discriminated union returned by `Prompts.get()`.
*
* Narrow on `source` to guarantee metadata, or access `result.name` /
* `result.version` directly as `string | undefined` / `number | undefined`.
*/
type PromptResult = PromptRemoteResult | PromptCodeFallbackResult;
/**
* Variables for prompt compilation
*/
type PromptVariables = Record<string, string | number | boolean>;
/**
* Direct options for initializing Prompts without a PostHog client
*/
interface PromptsDirectOptions {
personalApiKey: string;
projectApiKey: string;
host?: string;
defaultCacheTtlSeconds?: number;
}
interface CostOverride {
inputCost: number;
outputCost: number;
}
declare enum AIEvent {
Generation = "$ai_generation",
Embedding = "$ai_embedding"
}
type LanguageModel = LanguageModelV2 | LanguageModelV3;
interface ClientOptions {
posthogDistinctId?: string;
posthogTraceId?: string;
posthogProperties?: Record<string, any>;
posthogPrivacyMode?: boolean;
posthogGroups?: Record<string, any>;
posthogModelOverride?: string;
posthogProviderOverride?: string;
posthogCostOverride?: CostOverride;
posthogCaptureImmediate?: boolean;
}
/**
* Wraps a Vercel AI SDK language model (V2 or V3) with PostHog tracing.
* Automatically detects the model version and applies appropriate instrumentation.
*/
declare const wrapVercelLanguageModel: <T extends LanguageModel>(model: T, phClient: PostHog, options: ClientOptions) => T;
interface PromptsWithPostHogOptions {
posthog: PostHog;
defaultCacheTtlSeconds?: number;
}
type PromptsOptions = PromptsWithPostHogOptions | PromptsDirectOptions;
/**
* Prompts class for fetching and compiling LLM prompts from PostHog
*
* @example
* ```ts
* // With PostHog client
* const prompts = new Prompts({ posthog })
*
* // Or with direct options (no PostHog client needed)
* const prompts = new Prompts({
* personalApiKey: 'phx_xxx',
* projectApiKey: 'phc_xxx',
* host: 'https://us.posthog.com',
* })
*
* // Fetch with caching and fallback
* const result = await prompts.get('support-system-prompt', {
* cacheTtlSeconds: 300,
* fallback: 'You are a helpful assistant.',
* })
*
* // Or fetch an exact published version
* const v3 = await prompts.get('support-system-prompt', {
* version: 3,
* })
*
* // Compile with variables
* const systemPrompt = prompts.compile(result.prompt, {
* company: 'Acme Corp',
* tier: 'premium',
* })
* ```
*/
declare class Prompts {
private personalApiKey;
private projectApiKey;
private host;
private defaultCacheTtlSeconds;
private cache;
constructor(options: PromptsOptions);
private getPromptCache;
private getOrCreatePromptCache;
private getPromptLabel;
/**
* Fetch a prompt by name from the PostHog API.
*
* Returns a `PromptResult` object carrying the prompt text alongside `source`,
* `name`, and `version` metadata. Read `result.prompt` for the template string.
*/
get(name: string, options?: GetPromptOptions): Promise<PromptResult>;
/**
* Internal method that handles cache + fetch logic, returning full metadata.
* Does NOT handle the string `fallback` option — callers handle that.
*/
private getInternal;
/**
* Compile a prompt template with variable substitution
*
* Variables in the format `{{variableName}}` will be replaced with values from the variables object.
* Unmatched variables are left unchanged.
*
* @param prompt - The prompt template string
* @param variables - Object containing variable values
* @returns The compiled prompt string
*/
compile(prompt: string, variables: PromptVariables): string;
/**
* Clear the cache for a specific prompt or all prompts
*
* @param name - Optional prompt name to clear. If provided, clears all cached versions for that prompt unless a version is also provided.
* @param version - Optional prompt version to clear. Requires a prompt name.
*/
clearCache(name?: string, version?: number): void;
private fetchPromptFromApi;
}
/**
* Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
* directly so that any caller — first-party SDK wrappers and external code
* alike — produces an identical event.
*/
interface CaptureAiGenerationOptions {
distinctId?: string;
/** Auto-generated when omitted. */
traceId?: string;
/** Defaults to `$ai_generation`. */
eventType?: AIEvent;
/** Required for the event to be useful, but accepted as optional so SDK wrappers can pass through whatever they detect. */
model?: string;
provider: string;
input: unknown;
output: unknown;
/** Maps to `$ai_model_parameters` (temperature, max_tokens, top_p, …). */
modelParameters?: Record<string, unknown>;
baseURL?: string;
httpStatus?: number;
/** Wall-clock latency in seconds. */
latency?: number;
/** Time from request start to the first streamed token, in seconds. */
timeToFirstToken?: number;
usage?: TokenUsage;
/** Extra event properties merged into the captured event. */
properties?: Record<string, unknown>;
/** Mapping of group type to group id, matching `EventMessage.groups`. */
groups?: Record<string, string | number>;
privacyMode?: boolean;
/**
* For SDK wrappers: overrides the auto-detected model. External callers
* should pass `model` directly instead.
*/
modelOverride?: string;
/**
* For SDK wrappers: overrides the auto-detected provider. External callers
* should pass `provider` directly instead.
*/
providerOverride?: string;
costOverride?: CostOverride;
tools?: unknown[] | null;
stopReason?: string;
/**
* Provider-assigned ID for the generation (e.g. OpenAI's `chatcmpl-…` /
* `resp_…`). Maps to `$ai_completion_id`. Response IDs generalize across
* providers, so this lives in the shared schema rather than under
* `providerMetadata`.
*/
completionId?: string;
/**
* Provider-specific response metadata that has no place in the shared,
* provider-agnostic `$ai_*` schema (e.g. OpenAI's `system_fingerprint` and
* `request_id`). Maps to `$ai_provider_metadata`; omitted when empty.
*/
providerMetadata?: Record<string, unknown>;
/** When set, the event is captured as an error. */
error?: unknown;
/** Awaits delivery instead of batching. Useful in serverless environments. */
captureImmediate?: boolean;
}
/**
* Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
*
* This is the canonical primitive that every `@posthog/ai` wrapper
* (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
* external code can use it directly to instrument LLM calls made through
* arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
* same events the SDK wrappers produce.
*
* When `error` is set, the event is captured as an error. If the error is an
* object, it is mutated in place to set `__posthog_previously_captured_error`
* so callers can re-throw the original error reference safely.
*/
declare const captureAiGeneration: (client: PostHog, options: CaptureAiGenerationOptions) => Promise<void>;
export { AIEvent, type CaptureAiGenerationOptions, type PromptCodeFallbackResult, type PromptRemoteResult, type PromptResult, Prompts, captureAiGeneration, wrapVercelLanguageModel as withTracing };