fix: align langfuse fanout env parsing

This commit is contained in:
Ravi Kumar L 2026-06-22 10:57:34 +02:00
parent be4d874cef
commit fcf2a89189
5 changed files with 72 additions and 14 deletions

View file

@ -1383,6 +1383,38 @@ describe('Langfuse run config', () => {
});
});
it('only disables tenant fanout export when the emergency toggle is true', async () => {
process.env.LANGFUSE_PUBLIC_KEY = 'pk-central';
process.env.LANGFUSE_SECRET_KEY = 'sk-central';
process.env.LANGFUSE_FANOUT_ENABLED = 'true';
process.env.LANGFUSE_FANOUT_COLLECTOR_URL = 'http://collector-from-env:4318';
process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED = '1';
const callArgs = await callAndCaptureRunConfig({
tenantId: 'tenant-1',
appConfig: {
langfuse: {
publicKey: 'pk-tenant-1',
secretKey: 'sk-tenant-1',
baseUrl: 'https://cloud.langfuse.com',
},
} as AppConfig,
});
expect(callArgs.langfuse).toEqual({
deterministicTraceId: true,
baseUrl: 'http://collector-from-env:4318',
metadata: { 'librechat.tenant.id': 'tenant-1' },
publicKey: 'pk-tenant-1',
secretKey: 'sk-tenant-1',
tags: ['tenant:tenant-1'],
librechatTraceAttributes: {
'librechat.langfuse.tenant_export.enabled': 'true',
'librechat.langfuse.destination': 'eu',
},
});
});
it('lets tenant fanout.enabled=false override deployment fanout env', async () => {
process.env.LANGFUSE_FANOUT_ENABLED = 'true';
process.env.LANGFUSE_FANOUT_COLLECTOR_URL = 'http://collector-from-env:4318';

View file

@ -2,7 +2,7 @@ import type { AppConfig } from '@librechat/data-schemas';
import type { RunConfig } from '@librechat/agents';
import { resolveLangfuseTenantDestination } from './tenantDestinations';
import { normalizeString } from '~/utils/text';
import { isTrueEnv } from './utils';
import { isEnabled } from '~/utils';
type LangfuseRunConfig = NonNullable<RunConfig['langfuse']>;
type LangfuseRunConfigWithTraceAttributes = LangfuseRunConfig & {
@ -12,7 +12,7 @@ const TENANT_EXPORT_ATTRIBUTE = 'librechat.langfuse.tenant_export.enabled';
const TENANT_DESTINATION_ATTRIBUTE = 'librechat.langfuse.destination';
function isTenantExportEnabled(): boolean {
return !isTrueEnv(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED);
return !isEnabled(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED);
}
function mergeTraceMetadata(
@ -70,7 +70,7 @@ export function buildLangfuseConfig({
const fanout = config?.fanout;
const fanoutEnabled =
fanout?.enabled !== false &&
(fanout?.enabled === true || process.env.LANGFUSE_FANOUT_ENABLED === 'true');
(fanout?.enabled === true || isEnabled(process.env.LANGFUSE_FANOUT_ENABLED));
const fanoutCollectorUrl =
normalizeString(fanout?.collectorUrl) ??
normalizeString(process.env.LANGFUSE_FANOUT_COLLECTOR_URL);

View file

@ -1,7 +1,8 @@
import type { AppConfig } from '@librechat/data-schemas';
import { resolveLangfuseTenantDestination } from './tenantDestinations';
import { isFalseEnv, isTrueEnv, toBasicAuthorization } from './utils';
import { toBasicAuthorization } from './utils';
import { normalizeString } from '~/utils/text';
import { isEnabled } from '~/utils';
const DEFAULT_BASE_URL = 'https://cloud.langfuse.com';
@ -21,7 +22,7 @@ function isSampleRateEnabled(value?: string): boolean {
function isTracingEnabled(): boolean {
return (
!isFalseEnv(process.env.LANGFUSE_TRACING_ENABLED) &&
process.env.LANGFUSE_TRACING_ENABLED?.trim().toLowerCase() !== 'false' &&
isSampleRateEnabled(process.env.LANGFUSE_SAMPLE_RATE)
);
}
@ -66,7 +67,7 @@ function getTenantScoreDestination(appConfig?: AppConfig): LangfuseScoreDestinat
if (!isTracingEnabled()) {
return undefined;
}
if (isTrueEnv(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED)) {
if (isEnabled(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED)) {
return undefined;
}

View file

@ -396,6 +396,39 @@ describe('Langfuse feedback scores', () => {
);
});
it('only disables tenant scores when the emergency toggle is true', async () => {
process.env.LANGFUSE_FANOUT_CENTRAL_AUTH_HEADER = 'Basic central-auth';
process.env.LANGFUSE_FANOUT_CENTRAL_BASE_URL = 'http://central-langfuse:3000';
process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED = '1';
const { sendFeedbackScore } = await loadFeedback();
await sendFeedbackScore({
traceId: 'trace-id',
feedback: { rating: 'thumbsUp' },
appConfig: appConfigWithLangfuse({
publicKey: 'tenant-public-key',
secretKey: 'tenant-secret-key',
baseUrl: 'https://cloud.langfuse.com',
}),
});
expect(getFetchMock()).toHaveBeenCalledTimes(2);
expect(getFetchMock()).toHaveBeenCalledWith(
'http://central-langfuse:3000/api/public/scores',
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({ Authorization: 'Basic central-auth' }),
}),
);
expect(getFetchMock()).toHaveBeenCalledWith(
'https://cloud.langfuse.com/api/public/scores',
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({ Authorization: getTenantAuthorization() }),
}),
);
});
it('deduplicates matching central and tenant score destinations', async () => {
process.env.LANGFUSE_FANOUT_CENTRAL_AUTH_HEADER = getTenantAuthorization();
process.env.LANGFUSE_FANOUT_CENTRAL_BASE_URL = 'http://shared-langfuse:3000';

View file

@ -1,11 +1,3 @@
export function isFalseEnv(value?: string): boolean {
return value != null && ['0', 'false', 'no', 'off'].includes(value.trim().toLowerCase());
}
export function isTrueEnv(value?: string): boolean {
return value != null && ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());
}
export function toBasicAuthorization(publicKey: string, secretKey: string): string {
return `Basic ${Buffer.from(`${publicKey}:${secretKey}`).toString('base64')}`;
}