UNPKG

@posthog/ai

Version:
503 lines (490 loc) 23.5 kB
import OpenAIOrignal, { OpenAI, APIPromise, ClientOptions as ClientOptions$1, AzureOpenAI } from 'openai'; import { PostHog } from 'posthog-node'; import { Stream } from 'openai/streaming'; import { ParsedResponse } from 'openai/resources/responses/responses'; import { ResponseCreateParamsWithTools, ExtractParsedContentFromParams } from 'openai/lib/ResponsesParser'; import { LanguageModelV2, LanguageModelV3 } from '@ai-sdk/provider'; import AnthropicOriginal, { APIPromise as APIPromise$1 } from '@anthropic-ai/sdk'; import { Stream as Stream$1 } from '@anthropic-ai/sdk/streaming'; import { GoogleGenAI, GenerateContentParameters, GenerateContentResponse, EmbedContentParameters, EmbedContentResponse, GoogleGenAIOptions, Tool } from '@google/genai'; import { BaseCallbackHandler } from '@langchain/core/callbacks/base'; import { Serialized } from '@langchain/core/load/serializable'; import { ChainValues } from '@langchain/core/utils/types'; import { LLMResult } from '@langchain/core/outputs'; import { AgentAction, AgentFinish } from '@langchain/core/agents'; import { DocumentInterface } from '@langchain/core/documents'; import { BaseMessage } from '@langchain/core/messages'; import { ChatCompletionTool } from 'openai/resources/chat/completions'; /** * 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; /** * When true, returns a `PromptResult` object with metadata (source, name, version) * instead of a plain string. * * Omitting this option or setting it to false is deprecated and will be removed * in a future major version. */ withMetadata?: boolean; } /** * 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()` when `withMetadata: true`. * * 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 MonitoringEventPropertiesWithDefaults { distinctId?: string; traceId: string; properties?: Record<string, any>; privacyMode: boolean; groups?: Record<string, any>; modelOverride?: string; providerOverride?: string; costOverride?: CostOverride; captureImmediate?: boolean; } type MonitoringEventProperties = Partial<MonitoringEventPropertiesWithDefaults>; type MonitoringParams = { [K in keyof MonitoringEventProperties as `posthog${Capitalize<string & K>}`]: MonitoringEventProperties[K]; }; interface CostOverride { inputCost: number; outputCost: number; } declare enum AIEvent { Generation = "$ai_generation", Embedding = "$ai_embedding" } declare const Chat: typeof OpenAI.Chat; declare const Completions: typeof OpenAI.Chat.Completions; declare const Responses: typeof OpenAI.Responses; declare const Embeddings: typeof OpenAI.Embeddings; declare const Audio: typeof OpenAI.Audio; declare const Transcriptions: typeof OpenAI.Audio.Transcriptions; type ChatCompletion$1 = OpenAI.ChatCompletion; type ChatCompletionChunk$1 = OpenAI.ChatCompletionChunk; type ChatCompletionCreateParamsBase$1 = OpenAI.Chat.Completions.ChatCompletionCreateParams; type ChatCompletionCreateParamsNonStreaming$1 = OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming; type ChatCompletionCreateParamsStreaming$1 = OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming; type ResponsesCreateParamsBase = OpenAI.Responses.ResponseCreateParams; type ResponsesCreateParamsNonStreaming = OpenAI.Responses.ResponseCreateParamsNonStreaming; type ResponsesCreateParamsStreaming = OpenAI.Responses.ResponseCreateParamsStreaming; type CreateEmbeddingResponse$1 = OpenAI.CreateEmbeddingResponse; type EmbeddingCreateParams$1 = OpenAI.EmbeddingCreateParams; interface MonitoringOpenAIConfig$1 extends ClientOptions$1 { apiKey: string; posthog: PostHog; baseURL?: string; } type RequestOptions$2 = Record<string, unknown>; declare class PostHogOpenAI extends OpenAI { private readonly phClient; chat: WrappedChat$1; responses: WrappedResponses; embeddings: WrappedEmbeddings$1; audio: WrappedAudio; constructor(config: MonitoringOpenAIConfig$1); } declare class WrappedChat$1 extends Chat { constructor(parentClient: PostHogOpenAI, phClient: PostHog); completions: WrappedCompletions$1; } declare class WrappedCompletions$1 extends Completions { private readonly phClient; private readonly baseURL; constructor(client: OpenAI, phClient: PostHog); create(body: ChatCompletionCreateParamsNonStreaming$1 & MonitoringParams, options?: RequestOptions$2): APIPromise<ChatCompletion$1>; create(body: ChatCompletionCreateParamsStreaming$1 & MonitoringParams, options?: RequestOptions$2): APIPromise<Stream<ChatCompletionChunk$1>>; create(body: ChatCompletionCreateParamsBase$1 & MonitoringParams, options?: RequestOptions$2): APIPromise<ChatCompletion$1 | Stream<ChatCompletionChunk$1>>; } declare class WrappedResponses extends Responses { private readonly phClient; private readonly baseURL; constructor(client: OpenAI, phClient: PostHog); create(body: ResponsesCreateParamsNonStreaming & MonitoringParams, options?: RequestOptions$2): APIPromise<OpenAI.Responses.Response>; create(body: ResponsesCreateParamsStreaming & MonitoringParams, options?: RequestOptions$2): APIPromise<Stream<OpenAI.Responses.ResponseStreamEvent>>; create(body: ResponsesCreateParamsBase & MonitoringParams, options?: RequestOptions$2): APIPromise<OpenAI.Responses.Response | Stream<OpenAI.Responses.ResponseStreamEvent>>; parse<Params extends ResponseCreateParamsWithTools, ParsedT = ExtractParsedContentFromParams<Params>>(body: Params & MonitoringParams, options?: RequestOptions$2): APIPromise<ParsedResponse<ParsedT>>; } declare class WrappedEmbeddings$1 extends Embeddings { private readonly phClient; private readonly baseURL; constructor(client: OpenAI, phClient: PostHog); create(body: EmbeddingCreateParams$1 & MonitoringParams, options?: RequestOptions$2): APIPromise<CreateEmbeddingResponse$1>; } declare class WrappedAudio extends Audio { constructor(parentClient: PostHogOpenAI, phClient: PostHog); transcriptions: WrappedTranscriptions; } declare class WrappedTranscriptions extends Transcriptions { private readonly phClient; private readonly baseURL; constructor(client: OpenAI, phClient: PostHog); create(body: OpenAI.Audio.Transcriptions.TranscriptionCreateParamsNonStreaming<'json' | undefined> & MonitoringParams, options?: RequestOptions$2): APIPromise<OpenAI.Audio.Transcriptions.Transcription>; create(body: OpenAI.Audio.Transcriptions.TranscriptionCreateParamsNonStreaming<'verbose_json'> & MonitoringParams, options?: RequestOptions$2): APIPromise<OpenAI.Audio.Transcriptions.TranscriptionVerbose>; create(body: OpenAI.Audio.Transcriptions.TranscriptionCreateParamsNonStreaming<'srt' | 'vtt' | 'text'> & MonitoringParams, options?: RequestOptions$2): APIPromise<string>; create(body: OpenAI.Audio.Transcriptions.TranscriptionCreateParamsNonStreaming, options?: RequestOptions$2): APIPromise<OpenAI.Audio.Transcriptions.Transcription>; create(body: OpenAI.Audio.Transcriptions.TranscriptionCreateParamsStreaming & MonitoringParams, options?: RequestOptions$2): APIPromise<Stream<OpenAI.Audio.Transcriptions.TranscriptionStreamEvent>>; create(body: OpenAI.Audio.Transcriptions.TranscriptionCreateParamsStreaming & MonitoringParams, options?: RequestOptions$2): APIPromise<OpenAI.Audio.Transcriptions.TranscriptionCreateResponse | string | Stream<OpenAI.Audio.Transcriptions.TranscriptionStreamEvent>>; create(body: OpenAI.Audio.Transcriptions.TranscriptionCreateParams & MonitoringParams, options?: RequestOptions$2): APIPromise<OpenAI.Audio.Transcriptions.TranscriptionCreateResponse | string | Stream<OpenAI.Audio.Transcriptions.TranscriptionStreamEvent>>; } type ChatCompletion = OpenAIOrignal.ChatCompletion; type ChatCompletionChunk = OpenAIOrignal.ChatCompletionChunk; type ChatCompletionCreateParamsBase = OpenAIOrignal.Chat.Completions.ChatCompletionCreateParams; type ChatCompletionCreateParamsNonStreaming = OpenAIOrignal.Chat.Completions.ChatCompletionCreateParamsNonStreaming; type ChatCompletionCreateParamsStreaming = OpenAIOrignal.Chat.Completions.ChatCompletionCreateParamsStreaming; type CreateEmbeddingResponse = OpenAIOrignal.CreateEmbeddingResponse; type EmbeddingCreateParams = OpenAIOrignal.EmbeddingCreateParams; interface MonitoringOpenAIConfig { apiKey: string; posthog: PostHog; baseURL?: string; } type RequestOptions$1 = Record<string, any>; declare class PostHogAzureOpenAI extends AzureOpenAI { private readonly phClient; chat: WrappedChat; embeddings: WrappedEmbeddings; constructor(config: MonitoringOpenAIConfig); } declare class WrappedChat extends AzureOpenAI.Chat { constructor(parentClient: PostHogAzureOpenAI, phClient: PostHog); completions: WrappedCompletions; } declare class WrappedCompletions extends AzureOpenAI.Chat.Completions { private readonly phClient; private readonly baseURL; constructor(client: AzureOpenAI, phClient: PostHog); create(body: ChatCompletionCreateParamsNonStreaming & MonitoringParams, options?: RequestOptions$1): APIPromise<ChatCompletion>; create(body: ChatCompletionCreateParamsStreaming & MonitoringParams, options?: RequestOptions$1): APIPromise<Stream<ChatCompletionChunk>>; create(body: ChatCompletionCreateParamsBase & MonitoringParams, options?: RequestOptions$1): APIPromise<ChatCompletion | Stream<ChatCompletionChunk>>; } declare class WrappedEmbeddings extends AzureOpenAI.Embeddings { private readonly phClient; private readonly baseURL; constructor(client: AzureOpenAI, phClient: PostHog); create(body: EmbeddingCreateParams & MonitoringParams, options?: RequestOptions$1): APIPromise<CreateEmbeddingResponse>; } 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; type MessageCreateParamsNonStreaming = AnthropicOriginal.Messages.MessageCreateParamsNonStreaming; type MessageCreateParamsStreaming = AnthropicOriginal.Messages.MessageCreateParamsStreaming; type Message = AnthropicOriginal.Messages.Message; type RawMessageStreamEvent = AnthropicOriginal.Messages.RawMessageStreamEvent; type MessageCreateParamsBase = AnthropicOriginal.Messages.MessageCreateParams; type RequestOptions = AnthropicOriginal.RequestOptions; interface MonitoringAnthropicConfig { apiKey: string; posthog: PostHog; baseURL?: string; } declare class PostHogAnthropic extends AnthropicOriginal { private readonly phClient; messages: WrappedMessages; constructor(config: MonitoringAnthropicConfig); } declare class WrappedMessages extends AnthropicOriginal.Messages { private readonly phClient; private readonly baseURL; constructor(parentClient: PostHogAnthropic, phClient: PostHog); create(body: MessageCreateParamsNonStreaming, options?: RequestOptions): APIPromise$1<Message>; create(body: MessageCreateParamsStreaming & MonitoringParams, options?: RequestOptions): APIPromise$1<Stream$1<RawMessageStreamEvent>>; create(body: MessageCreateParamsBase & MonitoringParams, options?: RequestOptions): APIPromise$1<Stream$1<RawMessageStreamEvent> | Message>; } interface MonitoringGeminiConfig extends GoogleGenAIOptions { posthog: PostHog; } declare class PostHogGoogleGenAI { private readonly phClient; private readonly client; models: WrappedModels; constructor(config: MonitoringGeminiConfig); } declare class WrappedModels { private readonly phClient; private readonly client; constructor(client: GoogleGenAI, phClient: PostHog); generateContent(params: GenerateContentParameters & MonitoringParams): Promise<GenerateContentResponse>; generateContentStream(params: GenerateContentParameters & MonitoringParams): AsyncGenerator<GenerateContentResponse, void, unknown>; embedContent(params: EmbedContentParameters & MonitoringParams): Promise<EmbedContentResponse>; private formatPartsAsContentBlocks; private formatInput; private extractSystemInstruction; private formatInputForPostHog; } declare class LangChainCallbackHandler extends BaseCallbackHandler { name: string; private client; private distinctId?; private traceId?; private properties; private privacyMode; private groups; private debug; private runs; private parentTree; constructor(options: { client: PostHog; distinctId?: string | number; traceId?: string | number; properties?: Record<string, any>; privacyMode?: boolean; groups?: Record<string, any>; debug?: boolean; }); handleChainStart(chain: Serialized, inputs: ChainValues, runId: string, parentRunId?: string, tags?: string[], metadata?: Record<string, unknown>, _runType?: string, runName?: string): void; handleChainEnd(outputs: ChainValues, runId: string, parentRunId?: string, tags?: string[], _kwargs?: { inputs?: Record<string, unknown>; }): void; handleChainError(error: Error, runId: string, parentRunId?: string, tags?: string[], _kwargs?: { inputs?: Record<string, unknown>; }): void; handleChatModelStart(serialized: Serialized, messages: BaseMessage[][], runId: string, parentRunId?: string, extraParams?: Record<string, unknown>, tags?: string[], metadata?: Record<string, unknown>, runName?: string): void; handleLLMStart(serialized: Serialized, prompts: string[], runId: string, parentRunId?: string, extraParams?: Record<string, unknown>, tags?: string[], metadata?: Record<string, unknown>, runName?: string): void; handleLLMEnd(output: LLMResult, runId: string, parentRunId?: string, tags?: string[], _extraParams?: Record<string, unknown>): void; handleLLMError(err: Error, runId: string, parentRunId?: string, tags?: string[], _extraParams?: Record<string, unknown>): void; handleToolStart(tool: Serialized, input: string, runId: string, parentRunId?: string, tags?: string[], metadata?: Record<string, unknown>, runName?: string): void; handleToolEnd(output: any, runId: string, parentRunId?: string, tags?: string[]): void; handleToolError(err: Error, runId: string, parentRunId?: string, tags?: string[]): void; handleRetrieverStart(retriever: Serialized, query: string, runId: string, parentRunId?: string, tags?: string[], metadata?: Record<string, unknown>, name?: string): void; handleRetrieverEnd(documents: DocumentInterface[], runId: string, parentRunId?: string, tags?: string[]): void; handleRetrieverError(err: Error, runId: string, parentRunId?: string, tags?: string[]): void; handleAgentAction(action: AgentAction, runId: string, parentRunId?: string, tags?: string[]): void; handleAgentEnd(action: AgentFinish, runId: string, parentRunId?: string, tags?: string[]): void; private _setParentOfRun; private _popParentOfRun; private _findRootRun; private _setTraceOrSpanMetadata; private _setLLMMetadata; private _popRunMetadata; private _getTraceId; private _getParentRunId; private _popRunAndCaptureTraceOrSpan; private _captureTraceOrSpan; private _popRunAndCaptureGeneration; private _captureGeneration; private _logDebugEvent; private _getLangchainRunName; private _convertLcToolCallsToOai; private _extractRawResponse; private _convertMessageToDict; private _extractStopReason; private _parseUsageModel; private parseUsage; } 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 template = await prompts.get('support-system-prompt', { * cacheTtlSeconds: 300, * fallback: 'You are a helpful assistant.', * }) * * // Or fetch an exact published version * const v3Template = await prompts.get('support-system-prompt', { * version: 3, * }) * * // Compile with variables * const systemPrompt = prompts.compile(template, { * company: 'Acme Corp', * tier: 'premium', * }) * ``` */ declare class Prompts { private personalApiKey; private projectApiKey; private host; private defaultCacheTtlSeconds; private cache; private hasWarnedDeprecation; constructor(options: PromptsOptions); private getPromptCache; private getOrCreatePromptCache; private getPromptLabel; /** * Fetch a prompt by name from the PostHog API. * * When `withMetadata` is `true`, returns a `PromptResult` object with `source`, * `name`, and `version` metadata. When omitted or `false`, returns a plain string * (deprecated — will be removed in a future major version). */ get(name: string, options: GetPromptOptions & { withMetadata: true; }): Promise<PromptResult>; /** @deprecated Omitting `withMetadata` is deprecated. Pass `{ withMetadata: true }` to receive a `PromptResult`. */ get(name: string, options?: GetPromptOptions): Promise<string>; /** * 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; } type AnthropicTool = AnthropicOriginal.Tool; /** * 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?: ChatCompletionTool[] | AnthropicTool[] | Tool[] | null; stopReason?: string; /** 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, PostHogAnthropic as Anthropic, PostHogAzureOpenAI as AzureOpenAI, type CaptureAiGenerationOptions, PostHogGoogleGenAI as GoogleGenAI, LangChainCallbackHandler, PostHogOpenAI as OpenAI, type PromptCodeFallbackResult, type PromptRemoteResult, type PromptResult, Prompts, captureAiGeneration, wrapVercelLanguageModel as withTracing };