@posthog/ai
Version:
PostHog Node.js AI integrations
1,497 lines (1,484 loc) • 62.8 kB
JavaScript
import OpenAIOrignal, { AzureOpenAI } from 'openai';
import * as uuid from 'uuid';
import { v4 } from 'uuid';
import { experimental_wrapLanguageModel } from 'ai';
import AnthropicOriginal from '@anthropic-ai/sdk';
const getModelParams = params => {
if (!params) {
return {};
}
const modelParams = {};
const paramKeys = ['temperature', 'max_tokens', 'max_completion_tokens', 'top_p', 'frequency_penalty', 'presence_penalty', 'n', 'stop', 'stream', 'streaming'];
for (const key of paramKeys) {
if (key in params && params[key] !== undefined) {
modelParams[key] = params[key];
}
}
return modelParams;
};
const formatResponseAnthropic = response => {
// Example approach if "response.content" holds array of text segments, etc.
const output = [];
for (const choice of response.content ?? []) {
if (choice?.text) {
output.push({
role: 'assistant',
content: choice.text
});
}
}
return output;
};
const formatResponseOpenAI = response => {
const output = [];
for (const choice of response.choices ?? []) {
if (choice.message?.content) {
output.push({
role: choice.message.role,
content: choice.message.content
});
}
}
return output;
};
const mergeSystemPrompt = (params, provider) => {
if (provider == 'anthropic') {
const messages = params.messages || [];
if (!params.system) {
return messages;
}
const systemMessage = params.system;
return [{
role: 'system',
content: systemMessage
}, ...messages];
}
return params.messages;
};
const withPrivacyMode = (client, privacyMode, input) => {
return client.privacy_mode || privacyMode ? null : input;
};
const sendEventToPosthog = ({
client,
distinctId,
traceId,
model,
provider,
input,
output,
latency,
baseURL,
params,
httpStatus = 200,
usage = {},
isError = false,
error,
tools
}) => {
if (client.capture) {
let errorData = {};
if (isError) {
errorData = {
$ai_is_error: true,
$ai_error: error
};
}
let costOverrideData = {};
if (params.posthogCostOverride) {
const inputCostUSD = (params.posthogCostOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
const outputCostUSD = (params.posthogCostOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
costOverrideData = {
$ai_input_cost_usd: inputCostUSD,
$ai_output_cost_usd: outputCostUSD,
$ai_total_cost_usd: inputCostUSD + outputCostUSD
};
}
const additionalTokenValues = {
...(usage.reasoningTokens ? {
$ai_reasoning_tokens: usage.reasoningTokens
} : {}),
...(usage.cacheReadInputTokens ? {
$ai_cache_read_input_tokens: usage.cacheReadInputTokens
} : {}),
...(usage.cacheCreationInputTokens ? {
$ai_cache_creation_input_tokens: usage.cacheCreationInputTokens
} : {})
};
client.capture({
distinctId: distinctId ?? traceId,
event: '$ai_generation',
properties: {
$ai_provider: params.posthogProviderOverride ?? provider,
$ai_model: params.posthogModelOverride ?? model,
$ai_model_parameters: getModelParams(params),
$ai_input: withPrivacyMode(client, params.posthogPrivacyMode ?? false, input),
$ai_output_choices: withPrivacyMode(client, params.posthogPrivacyMode ?? false, output),
$ai_http_status: httpStatus,
$ai_input_tokens: usage.inputTokens ?? 0,
$ai_output_tokens: usage.outputTokens ?? 0,
...additionalTokenValues,
$ai_latency: latency,
$ai_trace_id: traceId,
$ai_base_url: baseURL,
...params.posthogProperties,
...(distinctId ? {} : {
$process_person_profile: false
}),
...(tools ? {
$ai_tools: tools
} : {}),
...errorData,
...costOverrideData
},
groups: params.posthogGroups
});
}
};
class PostHogOpenAI extends OpenAIOrignal {
constructor(config) {
const {
posthog,
...openAIConfig
} = config;
super(openAIConfig);
this.phClient = posthog;
this.chat = new WrappedChat$1(this, this.phClient);
}
}
class WrappedChat$1 extends OpenAIOrignal.Chat {
constructor(parentClient, phClient) {
super(parentClient);
this.completions = new WrappedCompletions$1(parentClient, phClient);
}
}
class WrappedCompletions$1 extends OpenAIOrignal.Chat.Completions {
constructor(client, phClient) {
super(client);
this.phClient = phClient;
}
// --- Implementation Signature
create(body, options) {
const {
posthogDistinctId,
posthogTraceId,
posthogProperties,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
posthogPrivacyMode = false,
posthogGroups,
...openAIParams
} = body;
const traceId = posthogTraceId ?? v4();
const startTime = Date.now();
const parentPromise = super.create(openAIParams, options);
if (openAIParams.stream) {
return parentPromise.then(value => {
if ('tee' in value) {
const [stream1, stream2] = value.tee();
(async () => {
try {
let accumulatedContent = '';
let usage = {
inputTokens: 0,
outputTokens: 0
};
for await (const chunk of stream1) {
const delta = chunk?.choices?.[0]?.delta?.content ?? '';
accumulatedContent += delta;
if (chunk.usage) {
usage = {
inputTokens: chunk.usage.prompt_tokens ?? 0,
outputTokens: chunk.usage.completion_tokens ?? 0,
reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
};
}
}
const latency = (Date.now() - startTime) / 1000;
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: openAIParams.model,
provider: 'openai',
input: openAIParams.messages,
output: [{
content: accumulatedContent,
role: 'assistant'
}],
latency,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: 200,
usage
});
} catch (error) {
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: openAIParams.model,
provider: 'openai',
input: openAIParams.messages,
output: [],
latency: 0,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: error?.status ? error.status : 500,
usage: {
inputTokens: 0,
outputTokens: 0
},
isError: true,
error: JSON.stringify(error)
});
}
})();
// Return the other stream to the user
return stream2;
}
return value;
});
} else {
const wrappedPromise = parentPromise.then(result => {
if ('choices' in result) {
const latency = (Date.now() - startTime) / 1000;
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: openAIParams.model,
provider: 'openai',
input: openAIParams.messages,
output: formatResponseOpenAI(result),
latency,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: 200,
usage: {
inputTokens: result.usage?.prompt_tokens ?? 0,
outputTokens: result.usage?.completion_tokens ?? 0,
reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0
}
});
}
return result;
}, error => {
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: openAIParams.model,
provider: 'openai',
input: openAIParams.messages,
output: [],
latency: 0,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: error?.status ? error.status : 500,
usage: {
inputTokens: 0,
outputTokens: 0
},
isError: true,
error: JSON.stringify(error)
});
throw error;
});
return wrappedPromise;
}
}
}
class PostHogAzureOpenAI extends AzureOpenAI {
constructor(config) {
const {
posthog,
...openAIConfig
} = config;
super(openAIConfig);
this.phClient = posthog;
this.chat = new WrappedChat(this, this.phClient);
}
}
class WrappedChat extends AzureOpenAI.Chat {
constructor(parentClient, phClient) {
super(parentClient);
this.completions = new WrappedCompletions(parentClient, phClient);
}
}
class WrappedCompletions extends AzureOpenAI.Chat.Completions {
constructor(client, phClient) {
super(client);
this.phClient = phClient;
}
// --- Implementation Signature
create(body, options) {
const {
posthogDistinctId,
posthogTraceId,
posthogProperties,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
posthogPrivacyMode = false,
posthogGroups,
...openAIParams
} = body;
const traceId = posthogTraceId ?? v4();
const startTime = Date.now();
const parentPromise = super.create(openAIParams, options);
if (openAIParams.stream) {
return parentPromise.then(value => {
let accumulatedContent = '';
let usage = {
inputTokens: 0,
outputTokens: 0
};
let model = openAIParams.model;
if ('tee' in value) {
const [stream1, stream2] = value.tee();
(async () => {
try {
for await (const chunk of stream1) {
const delta = chunk?.choices?.[0]?.delta?.content ?? '';
accumulatedContent += delta;
if (chunk.usage) {
if (chunk.model != model) {
model = chunk.model;
}
usage = {
inputTokens: chunk.usage.prompt_tokens ?? 0,
outputTokens: chunk.usage.completion_tokens ?? 0,
reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
};
}
}
const latency = (Date.now() - startTime) / 1000;
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model,
provider: 'azure',
input: openAIParams.messages,
output: [{
content: accumulatedContent,
role: 'assistant'
}],
latency,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: 200,
usage
});
} catch (error) {
// error handling
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model,
provider: 'azure',
input: openAIParams.messages,
output: JSON.stringify(error),
latency: 0,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: error?.status ? error.status : 500,
usage: {
inputTokens: 0,
outputTokens: 0
},
isError: true,
error: JSON.stringify(error)
});
}
})();
// Return the other stream to the user
return stream2;
}
return value;
});
} else {
const wrappedPromise = parentPromise.then(result => {
if ('choices' in result) {
const latency = (Date.now() - startTime) / 1000;
let model = openAIParams.model;
if (result.model != model) {
model = result.model;
}
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model,
provider: 'azure',
input: openAIParams.messages,
output: formatResponseOpenAI(result),
latency,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: 200,
usage: {
inputTokens: result.usage?.prompt_tokens ?? 0,
outputTokens: result.usage?.completion_tokens ?? 0,
reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0
}
});
}
return result;
}, error => {
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: openAIParams.model,
provider: 'azure',
input: openAIParams.messages,
output: [],
latency: 0,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: error?.status ? error.status : 500,
usage: {
inputTokens: 0,
outputTokens: 0
},
isError: true,
error: JSON.stringify(error)
});
throw error;
});
return wrappedPromise;
}
}
}
const mapVercelParams = params => {
return {
temperature: params.temperature,
max_tokens: params.maxTokens,
top_p: params.topP,
frequency_penalty: params.frequencyPenalty,
presence_penalty: params.presencePenalty,
stop: params.stopSequences,
stream: params.stream
};
};
const mapVercelPrompt = prompt => {
return prompt.map(p => {
let content = {};
if (Array.isArray(p.content)) {
content = p.content.map(c => {
if (c.type === 'text') {
return {
type: 'text',
content: c.text
};
} else if (c.type === 'image') {
return {
type: 'image',
content: {
// if image is a url use it, or use "none supported"
image: c.image instanceof URL ? c.image.toString() : 'raw images not supported',
mimeType: c.mimeType
}
};
} else if (c.type === 'file') {
return {
type: 'file',
content: {
file: c.data instanceof URL ? c.data.toString() : 'raw files not supported',
mimeType: c.mimeType
}
};
} else if (c.type === 'tool-call') {
return {
type: 'tool-call',
content: {
toolCallId: c.toolCallId,
toolName: c.toolName,
args: c.args
}
};
} else if (c.type === 'tool-result') {
return {
type: 'tool-result',
content: {
toolCallId: c.toolCallId,
toolName: c.toolName,
result: c.result,
isError: c.isError
}
};
}
return {
content: ''
};
});
} else {
content = {
type: 'text',
text: p.content
};
}
return {
role: p.role,
content
};
});
};
const mapVercelOutput = result => {
const output = {
...(result.text ? {
text: result.text
} : {}),
...(result.object ? {
object: result.object
} : {}),
...(result.reasoning ? {
reasoning: result.reasoning
} : {}),
...(result.response ? {
response: result.response
} : {}),
...(result.finishReason ? {
finishReason: result.finishReason
} : {}),
...(result.usage ? {
usage: result.usage
} : {}),
...(result.warnings ? {
warnings: result.warnings
} : {}),
...(result.providerMetadata ? {
toolCalls: result.providerMetadata
} : {})
};
// if text and no object or reasoning, return text
if (output.text && !output.object && !output.reasoning) {
return [{
content: output.text,
role: 'assistant'
}];
}
return [{
content: JSON.stringify(output),
role: 'assistant'
}];
};
const extractProvider = model => {
// vercel provider is in the format of provider.endpoint
const provider = model.provider.toLowerCase();
const providerName = provider.split('.')[0];
return providerName;
};
const createInstrumentationMiddleware = (phClient, model, options) => {
const middleware = {
wrapGenerate: async ({
doGenerate,
params
}) => {
const startTime = Date.now();
const mergedParams = {
...options,
...mapVercelParams(params)
};
try {
const result = await doGenerate();
const latency = (Date.now() - startTime) / 1000;
const modelId = options.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
const provider = options.posthogProviderOverride ?? extractProvider(model);
const baseURL = ''; // cannot currently get baseURL from vercel
const content = mapVercelOutput(result);
// let tools = result.toolCalls
const providerMetadata = result.providerMetadata;
const additionalTokenValues = {
...(providerMetadata?.openai?.reasoningTokens ? {
reasoningTokens: providerMetadata.openai.reasoningTokens
} : {}),
...(providerMetadata?.openai?.cachedPromptTokens ? {
cacheReadInputTokens: providerMetadata.openai.cachedPromptTokens
} : {}),
...(providerMetadata?.anthropic ? {
cacheReadInputTokens: providerMetadata.anthropic.cacheReadInputTokens,
cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
} : {})
};
sendEventToPosthog({
client: phClient,
distinctId: options.posthogDistinctId,
traceId: options.posthogTraceId,
model: modelId,
provider: provider,
input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
output: [{
content,
role: 'assistant'
}],
latency,
baseURL,
params: mergedParams,
httpStatus: 200,
usage: {
inputTokens: result.usage.promptTokens,
outputTokens: result.usage.completionTokens,
...additionalTokenValues
}
});
return result;
} catch (error) {
const modelId = model.modelId;
sendEventToPosthog({
client: phClient,
distinctId: options.posthogDistinctId,
traceId: options.posthogTraceId,
model: modelId,
provider: model.provider,
input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
output: [],
latency: 0,
baseURL: '',
params: mergedParams,
httpStatus: error?.status ? error.status : 500,
usage: {
inputTokens: 0,
outputTokens: 0
},
isError: true,
error: JSON.stringify(error)
});
throw error;
}
},
wrapStream: async ({
doStream,
params
}) => {
const startTime = Date.now();
let generatedText = '';
let usage = {};
const mergedParams = {
...options,
...mapVercelParams(params)
};
const modelId = options.posthogModelOverride ?? model.modelId;
const provider = options.posthogProviderOverride ?? extractProvider(model);
const baseURL = ''; // cannot currently get baseURL from vercel
try {
const {
stream,
...rest
} = await doStream();
const transformStream = new TransformStream({
transform(chunk, controller) {
if (chunk.type === 'text-delta') {
generatedText += chunk.textDelta;
}
if (chunk.type === 'finish') {
usage = {
inputTokens: chunk.usage?.promptTokens,
outputTokens: chunk.usage?.completionTokens
};
if (chunk.providerMetadata?.openai?.reasoningTokens) {
usage.reasoningTokens = chunk.providerMetadata.openai.reasoningTokens;
}
if (chunk.providerMetadata?.openai?.cachedPromptTokens) {
usage.cacheReadInputTokens = chunk.providerMetadata.openai.cachedPromptTokens;
}
if (chunk.providerMetadata?.anthropic?.cacheReadInputTokens) {
usage.cacheReadInputTokens = chunk.providerMetadata.anthropic.cacheReadInputTokens;
}
if (chunk.providerMetadata?.anthropic?.cacheCreationInputTokens) {
usage.cacheCreationInputTokens = chunk.providerMetadata.anthropic.cacheCreationInputTokens;
}
}
controller.enqueue(chunk);
},
flush() {
const latency = (Date.now() - startTime) / 1000;
sendEventToPosthog({
client: phClient,
distinctId: options.posthogDistinctId,
traceId: options.posthogTraceId,
model: modelId,
provider: provider,
input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
output: [{
content: generatedText,
role: 'assistant'
}],
latency,
baseURL,
params: mergedParams,
httpStatus: 200,
usage
});
}
});
return {
stream: stream.pipeThrough(transformStream),
...rest
};
} catch (error) {
sendEventToPosthog({
client: phClient,
distinctId: options.posthogDistinctId,
traceId: options.posthogTraceId,
model: modelId,
provider: provider,
input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
output: [],
latency: 0,
baseURL: '',
params: mergedParams,
httpStatus: error?.status ? error.status : 500,
usage: {
inputTokens: 0,
outputTokens: 0
},
isError: true,
error: JSON.stringify(error)
});
throw error;
}
}
};
return middleware;
};
const wrapVercelLanguageModel = (model, phClient, options) => {
const traceId = options.posthogTraceId ?? v4();
const middleware = createInstrumentationMiddleware(phClient, model, {
...options,
posthogTraceId: traceId,
posthogDistinctId: options.posthogDistinctId ?? traceId
});
const wrappedModel = experimental_wrapLanguageModel({
model,
middleware
});
return wrappedModel;
};
class PostHogAnthropic extends AnthropicOriginal {
constructor(config) {
const {
posthog,
...anthropicConfig
} = config;
super(anthropicConfig);
this.phClient = posthog;
this.messages = new WrappedMessages(this, this.phClient);
}
}
class WrappedMessages extends AnthropicOriginal.Messages {
constructor(parentClient, phClient) {
super(parentClient);
this.phClient = phClient;
}
create(body, options) {
const {
posthogDistinctId,
posthogTraceId,
posthogProperties,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
posthogPrivacyMode = false,
posthogGroups,
...anthropicParams
} = body;
const traceId = posthogTraceId ?? v4();
const startTime = Date.now();
const parentPromise = super.create(anthropicParams, options);
if (anthropicParams.stream) {
return parentPromise.then(value => {
let accumulatedContent = '';
const usage = {
inputTokens: 0,
outputTokens: 0,
cacheCreationInputTokens: 0,
cacheReadInputTokens: 0
};
if ('tee' in value) {
const [stream1, stream2] = value.tee();
(async () => {
try {
for await (const chunk of stream1) {
if ('delta' in chunk) {
if ('text' in chunk.delta) {
const delta = chunk?.delta?.text ?? '';
accumulatedContent += delta;
}
}
if (chunk.type == 'message_start') {
usage.inputTokens = chunk.message.usage.input_tokens ?? 0;
usage.cacheCreationInputTokens = chunk.message.usage.cache_creation_input_tokens ?? 0;
usage.cacheReadInputTokens = chunk.message.usage.cache_read_input_tokens ?? 0;
}
if ('usage' in chunk) {
usage.outputTokens = chunk.usage.output_tokens ?? 0;
}
}
const latency = (Date.now() - startTime) / 1000;
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: anthropicParams.model,
provider: 'anthropic',
input: mergeSystemPrompt(anthropicParams, 'anthropic'),
output: [{
content: accumulatedContent,
role: 'assistant'
}],
latency,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: 200,
usage
});
} catch (error) {
// error handling
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: anthropicParams.model,
provider: 'anthropic',
input: mergeSystemPrompt(anthropicParams, 'anthropic'),
output: [],
latency: 0,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: error?.status ? error.status : 500,
usage: {
inputTokens: 0,
outputTokens: 0
},
isError: true,
error: JSON.stringify(error)
});
}
})();
// Return the other stream to the user
return stream2;
}
return value;
});
} else {
const wrappedPromise = parentPromise.then(result => {
if ('content' in result) {
const latency = (Date.now() - startTime) / 1000;
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: anthropicParams.model,
provider: 'anthropic',
input: mergeSystemPrompt(anthropicParams, 'anthropic'),
output: formatResponseAnthropic(result),
latency,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: 200,
usage: {
inputTokens: result.usage.input_tokens ?? 0,
outputTokens: result.usage.output_tokens ?? 0,
cacheCreationInputTokens: result.usage.cache_creation_input_tokens ?? 0,
cacheReadInputTokens: result.usage.cache_read_input_tokens ?? 0
}
});
}
return result;
}, error => {
sendEventToPosthog({
client: this.phClient,
distinctId: posthogDistinctId ?? traceId,
traceId,
model: anthropicParams.model,
provider: 'anthropic',
input: mergeSystemPrompt(anthropicParams, 'anthropic'),
output: [],
latency: 0,
baseURL: this.baseURL ?? '',
params: body,
httpStatus: error?.status ? error.status : 500,
usage: {
inputTokens: 0,
outputTokens: 0
},
isError: true,
error: JSON.stringify(error)
});
throw error;
});
return wrappedPromise;
}
}
}
var decamelize = function (str, sep) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
sep = typeof sep === 'undefined' ? '_' : sep;
return str
.replace(/([a-z\d])([A-Z])/g, '$1' + sep + '$2')
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + sep + '$2')
.toLowerCase();
};
var camelcase = {exports: {}};
const UPPERCASE = /[\p{Lu}]/u;
const LOWERCASE = /[\p{Ll}]/u;
const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu;
const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
const SEPARATORS = /[_.\- ]+/;
const LEADING_SEPARATORS = new RegExp('^' + SEPARATORS.source);
const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, 'gu');
const NUMBERS_AND_IDENTIFIER = new RegExp('\\d+' + IDENTIFIER.source, 'gu');
const preserveCamelCase = (string, toLowerCase, toUpperCase) => {
let isLastCharLower = false;
let isLastCharUpper = false;
let isLastLastCharUpper = false;
for (let i = 0; i < string.length; i++) {
const character = string[i];
if (isLastCharLower && UPPERCASE.test(character)) {
string = string.slice(0, i) + '-' + string.slice(i);
isLastCharLower = false;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = true;
i++;
}
else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character)) {
string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = false;
isLastCharLower = true;
}
else {
isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
}
}
return string;
};
const preserveConsecutiveUppercase = (input, toLowerCase) => {
LEADING_CAPITAL.lastIndex = 0;
return input.replace(LEADING_CAPITAL, m1 => toLowerCase(m1));
};
const postProcess = (input, toUpperCase) => {
SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
NUMBERS_AND_IDENTIFIER.lastIndex = 0;
return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier))
.replace(NUMBERS_AND_IDENTIFIER, m => toUpperCase(m));
};
const camelCase = (input, options) => {
if (!(typeof input === 'string' || Array.isArray(input))) {
throw new TypeError('Expected the input to be `string | string[]`');
}
options = {
pascalCase: false,
preserveConsecutiveUppercase: false,
...options
};
if (Array.isArray(input)) {
input = input.map(x => x.trim())
.filter(x => x.length)
.join('-');
}
else {
input = input.trim();
}
if (input.length === 0) {
return '';
}
const toLowerCase = options.locale === false ?
string => string.toLowerCase() :
string => string.toLocaleLowerCase(options.locale);
const toUpperCase = options.locale === false ?
string => string.toUpperCase() :
string => string.toLocaleUpperCase(options.locale);
if (input.length === 1) {
return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
}
const hasUpperCase = input !== toLowerCase(input);
if (hasUpperCase) {
input = preserveCamelCase(input, toLowerCase, toUpperCase);
}
input = input.replace(LEADING_SEPARATORS, '');
if (options.preserveConsecutiveUppercase) {
input = preserveConsecutiveUppercase(input, toLowerCase);
}
else {
input = toLowerCase(input);
}
if (options.pascalCase) {
input = toUpperCase(input.charAt(0)) + input.slice(1);
}
return postProcess(input, toUpperCase);
};
camelcase.exports = camelCase;
// TODO: Remove this for the next major release
camelcase.exports.default = camelCase;
function keyToJson(key, map) {
return map?.[key] || decamelize(key);
}
function mapKeys(fields, mapper, map) {
const mapped = {};
for (const key in fields) {
if (Object.hasOwn(fields, key)) {
mapped[mapper(key, map)] = fields[key];
}
}
return mapped;
}
function shallowCopy(obj) {
return Array.isArray(obj) ? [...obj] : { ...obj };
}
function replaceSecrets(root, secretsMap) {
const result = shallowCopy(root);
for (const [path, secretId] of Object.entries(secretsMap)) {
const [last, ...partsReverse] = path.split(".").reverse();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let current = result;
for (const part of partsReverse.reverse()) {
if (current[part] === undefined) {
break;
}
current[part] = shallowCopy(current[part]);
current = current[part];
}
if (current[last] !== undefined) {
current[last] = {
lc: 1,
type: "secret",
id: [secretId],
};
}
}
return result;
}
/**
* Get a unique name for the module, rather than parent class implementations.
* Should not be subclassed, subclass lc_name above instead.
*/
function get_lc_unique_name(
// eslint-disable-next-line @typescript-eslint/no-use-before-define
serializableClass) {
// "super" here would refer to the parent class of Serializable,
// when we want the parent class of the module actually calling this method.
const parentClass = Object.getPrototypeOf(serializableClass);
const lcNameIsSubclassed = typeof serializableClass.lc_name === "function" &&
(typeof parentClass.lc_name !== "function" ||
serializableClass.lc_name() !== parentClass.lc_name());
if (lcNameIsSubclassed) {
return serializableClass.lc_name();
}
else {
return serializableClass.name;
}
}
class Serializable {
/**
* The name of the serializable. Override to provide an alias or
* to preserve the serialized module name in minified environments.
*
* Implemented as a static method to support loading logic.
*/
static lc_name() {
return this.name;
}
/**
* The final serialized identifier for the module.
*/
get lc_id() {
return [
...this.lc_namespace,
get_lc_unique_name(this.constructor),
];
}
/**
* A map of secrets, which will be omitted from serialization.
* Keys are paths to the secret in constructor args, e.g. "foo.bar.baz".
* Values are the secret ids, which will be used when deserializing.
*/
get lc_secrets() {
return undefined;
}
/**
* A map of additional attributes to merge with constructor args.
* Keys are the attribute names, e.g. "foo".
* Values are the attribute values, which will be serialized.
* These attributes need to be accepted by the constructor as arguments.
*/
get lc_attributes() {
return undefined;
}
/**
* A map of aliases for constructor args.
* Keys are the attribute names, e.g. "foo".
* Values are the alias that will replace the key in serialization.
* This is used to eg. make argument names match Python.
*/
get lc_aliases() {
return undefined;
}
constructor(kwargs, ..._args) {
Object.defineProperty(this, "lc_serializable", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "lc_kwargs", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.lc_kwargs = kwargs || {};
}
toJSON() {
if (!this.lc_serializable) {
return this.toJSONNotImplemented();
}
if (
// eslint-disable-next-line no-instanceof/no-instanceof
this.lc_kwargs instanceof Serializable ||
typeof this.lc_kwargs !== "object" ||
Array.isArray(this.lc_kwargs)) {
// We do not support serialization of classes with arg not a POJO
// I'm aware the check above isn't as strict as it could be
return this.toJSONNotImplemented();
}
const aliases = {};
const secrets = {};
const kwargs = Object.keys(this.lc_kwargs).reduce((acc, key) => {
acc[key] = key in this ? this[key] : this.lc_kwargs[key];
return acc;
}, {});
// get secrets, attributes and aliases from all superclasses
for (
// eslint-disable-next-line @typescript-eslint/no-this-alias
let current = Object.getPrototypeOf(this); current; current = Object.getPrototypeOf(current)) {
Object.assign(aliases, Reflect.get(current, "lc_aliases", this));
Object.assign(secrets, Reflect.get(current, "lc_secrets", this));
Object.assign(kwargs, Reflect.get(current, "lc_attributes", this));
}
// include all secrets used, even if not in kwargs,
// will be replaced with sentinel value in replaceSecrets
Object.keys(secrets).forEach((keyPath) => {
// eslint-disable-next-line @typescript-eslint/no-this-alias, @typescript-eslint/no-explicit-any
let read = this;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let write = kwargs;
const [last, ...partsReverse] = keyPath.split(".").reverse();
for (const key of partsReverse.reverse()) {
if (!(key in read) || read[key] === undefined)
return;
if (!(key in write) || write[key] === undefined) {
if (typeof read[key] === "object" && read[key] != null) {
write[key] = {};
}
else if (Array.isArray(read[key])) {
write[key] = [];
}
}
read = read[key];
write = write[key];
}
if (last in read && read[last] !== undefined) {
write[last] = write[last] || read[last];
}
});
return {
lc: 1,
type: "constructor",
id: this.lc_id,
kwargs: mapKeys(Object.keys(secrets).length ? replaceSecrets(kwargs, secrets) : kwargs, keyToJson, aliases),
};
}
toJSONNotImplemented() {
return {
lc: 1,
type: "not_implemented",
id: this.lc_id,
};
}
}
// Supabase Edge Function provides a `Deno` global object
// without `version` property
const isDeno = () => typeof Deno !== "undefined";
function getEnvironmentVariable(name) {
// Certain Deno setups will throw an error if you try to access environment variables
// https://github.com/langchain-ai/langchainjs/issues/1412
try {
if (typeof process !== "undefined") {
// eslint-disable-next-line no-process-env
return process.env?.[name];
}
else if (isDeno()) {
return Deno?.env.get(name);
}
else {
return undefined;
}
}
catch (e) {
return undefined;
}
}
/**
* Abstract class that provides a set of optional methods that can be
* overridden in derived classes to handle various events during the
* execution of a LangChain application.
*/
class BaseCallbackHandlerMethodsClass {
}
/**
* Abstract base class for creating callback handlers in the LangChain
* framework. It provides a set of optional methods that can be overridden
* in derived classes to handle various events during the execution of a
* LangChain application.
*/
class BaseCallbackHandler extends BaseCallbackHandlerMethodsClass {
get lc_namespace() {
return ["langchain_core", "callbacks", this.name];
}
get lc_secrets() {
return undefined;
}
get lc_attributes() {
return undefined;
}
get lc_aliases() {
return undefined;
}
/**
* The name of the serializable. Override to provide an alias or
* to preserve the serialized module name in minified environments.
*
* Implemented as a static method to support loading logic.
*/
static lc_name() {
return this.name;
}
/**
* The final serialized identifier for the module.
*/
get lc_id() {
return [
...this.lc_namespace,
get_lc_unique_name(this.constructor),
];
}
constructor(input) {
super();
Object.defineProperty(this, "lc_serializable", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "lc_kwargs", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "ignoreLLM", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "ignoreChain", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "ignoreAgent", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "ignoreRetriever", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "ignoreCustomEvent", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "raiseError", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "awaitHandlers", {
enumerable: true,
configurable: true,
writable: true,
value: getEnvironmentVariable("LANGCHAIN_CALLBACKS_BACKGROUND") === "false"
});
this.lc_kwargs = input || {};
if (input) {
this.ignoreLLM = input.ignoreLLM ?? this.ignoreLLM;
this.ignoreChain = input.ignoreChain ?? this.ignoreChain;
this.ignoreAgent = input.ignoreAgent ?? this.ignoreAgent;
this.ignoreRetriever = input.ignoreRetriever ?? this.ignoreRetriever;
this.ignoreCustomEvent =
input.ignoreCustomEvent ?? this.ignoreCustomEvent;
this.raiseError = input.raiseError ?? this.raiseError;
this.awaitHandlers =
this.raiseError || (input._awaitHandler ?? this.awaitHandlers);
}
}
copy() {
return new this.constructor(this);
}
toJSON() {
return Serializable.prototype.toJSON.call(this);
}
toJSONNotImplemented() {
return Serializable.prototype.toJSONNotImplemented.call(this);
}
static fromMethods(methods) {
class Handler extends BaseCallbackHandler {
constructor() {
super();
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: uuid.v4()
});
Object.assign(this, methods);
}
}
return new Handler();
}
}
class LangChainCallbackHandler extends BaseCallbackHandler {
constructor(options) {
if (!options.client) {
throw new Error('PostHog client is required');
}
super();
this.name = 'PosthogCallbackHandler';
this.runs = {};
this.parentTree = {};
this.client = options.client;
this.distinctId = options.distinctId;
this.traceId = options.traceId;
this.properties = options.properties || {};
this.privacyMode = options.privacyMode || false;
this.groups = options.groups || {};
this.debug = options.debug || false;
}
// ===== CALLBACK METHODS =====
handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, runName) {
this._logDebugEvent('on_chain_start', runId, parentRunId, {
inputs,
tags
});
this._setParentOfRun(runId, parentRunId);
this._setTraceOrSpanMetadata(chain, inputs, runId, parentRunId, metadata, tags, runName);
}
handleChainEnd(outputs, runId, parentRunId, tags,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
kwargs) {
this._logDebugEvent('on_chain_end', runId, parentRunId, {
outputs,
tags
});
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs);
}
handleChainError(error, runId, parentRunId, tags,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
kwargs) {
this._logDebugEvent('on_chain_error', runId, parentRunId, {
error,
tags
});
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, error);
}
handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata, runName) {
this._logDebugEvent('on_chat_model_start', runId, parentRunId, {
messages,
tags
});
this._setParentOfRun(runId, parentRunId);
// Flatten the two-dimensional messages and convert each message to a plain object
const input = messages.flat().map(m => this._convertMessageToDict(m));
this._setLLMMetadata(serialized, runId, input, metadata, extraParams, runName);
}
handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata, runName) {
this._logDebugEvent('on_llm_start', runId, parentRunId, {
prompts,
tags
});
this._setParentOfRun(runId, parentRunId);
this._setLLMMetadata(serialized, runId, prompts, metadata, extraParams, runName);
}
handleLLMEnd(output, runId, parentRunId, tags,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
extraParams) {
this._logDebugEvent('on_llm_end', runId, parentRunId, {
output,
tags
});
this._popRunAndCaptureGeneration(runId, parentRunId, output);
}
handleLLMError(err, runId, parentRunId, tags,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
extraParams) {
this._logDebugEvent('on_llm_error', runId, parentRunId, {
err,
tags
});
this._popRunAndCaptureGeneration(runId, parentRunId, err);
}
handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
this._logDebugEvent('on_tool_start', runId, parentRunId, {
input,
tags
});
this._setParentOfRun(runId, parentRunId);
this._setTraceOrSpanMetadata(tool, input, runId, parentRunId, metadata, tags, runName);
}
handleToolEnd(output, runId, parentRunId, tags) {
this._logDebugEvent('on_tool_end', runId, parentRunId, {
output,
tags
});
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, output);
}
handleToolError(err, runId, parentRunId, tags) {
this._logDebugEvent('on_tool_error', runId, parentRunId, {
err,
tags
});
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, err);
}
handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
this._logDebugEvent('on_retriever_start', runId, parentRunId, {
query,
tags
});
this._setParentOfRun(runId, parentRunId);
this._setTraceOrSpanMetadata(retriever, query, runId, parentRunId, metadata, tags, name);
}
handleRetrieverEnd(documents, runId, parentRunId, tags) {
this._logDebugEvent('on_retriever_end', runId, parentRunId, {
documents,
tags
});
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, documents);
}
handleRetrieverError(err, runId, parentRunId, tags) {
this._logDebug