mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-10 08:13:46 +00:00
fix: treat blank tenant fanout toggle as disabled
This commit is contained in:
parent
6f36df4d12
commit
b17579ae40
10 changed files with 81 additions and 13 deletions
|
|
@ -132,7 +132,7 @@ NODE_MAX_OLD_SPACE_SIZE=6144
|
|||
# See otel/langfuse-fanout/README.md.
|
||||
# LANGFUSE_FANOUT_ENABLED=false
|
||||
# LANGFUSE_FANOUT_COLLECTOR_URL=http://langfuse-fanout-collector:4318
|
||||
# Emergency switch: set false to keep central collector export but skip tenant trace/score export.
|
||||
# Emergency switch: unset defaults enabled; set false or blank to keep central collector export but skip tenant trace/score export.
|
||||
# LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=true
|
||||
# Langfuse Cloud base URL options: https://cloud.langfuse.com (EU),
|
||||
# https://us.cloud.langfuse.com (US), https://jp.cloud.langfuse.com (JP),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ services:
|
|||
environment:
|
||||
- LANGFUSE_FANOUT_ENABLED=true
|
||||
- LANGFUSE_FANOUT_COLLECTOR_URL=http://langfuse-fanout-collector:4318
|
||||
- LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=${LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED:-true}
|
||||
- LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=${LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED-true}
|
||||
- LANGFUSE_FANOUT_TENANT_DESTINATIONS=${LANGFUSE_FANOUT_TENANT_DESTINATIONS:-eu=https://cloud.langfuse.com,us=https://us.cloud.langfuse.com,jp=https://jp.cloud.langfuse.com,hipaa=https://hipaa.cloud.langfuse.com}
|
||||
networks:
|
||||
- default
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ services:
|
|||
environment:
|
||||
- LANGFUSE_FANOUT_ENABLED=true
|
||||
- LANGFUSE_FANOUT_COLLECTOR_URL=http://langfuse-fanout-collector:4318
|
||||
- LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=${LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED:-true}
|
||||
- LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=${LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED-true}
|
||||
- LANGFUSE_FANOUT_TENANT_DESTINATIONS=${LANGFUSE_FANOUT_TENANT_DESTINATIONS:-eu=https://cloud.langfuse.com,us=https://us.cloud.langfuse.com,jp=https://jp.cloud.langfuse.com,hipaa=https://hipaa.cloud.langfuse.com}
|
||||
networks:
|
||||
- default
|
||||
|
|
|
|||
|
|
@ -64,9 +64,10 @@ When enabled, the chart also sets `LANGFUSE_FANOUT_ENABLED` and
|
|||
`LANGFUSE_FANOUT_COLLECTOR_URL` for the LibreChat app unless those values are
|
||||
already provided in `librechat.configEnv`.
|
||||
|
||||
Set `librechat.configEnv.LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=false` to keep
|
||||
central trace export flowing through the collector while disabling tenant trace
|
||||
and score export.
|
||||
Set `librechat.configEnv.LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=false` or a
|
||||
blank value to keep central trace export flowing through the collector while
|
||||
disabling tenant trace and score export. When omitted, tenant export defaults to
|
||||
enabled if tenant keys and a known destination are configured.
|
||||
|
||||
Langfuse tenant base URLs are selected from the startup-configured destination
|
||||
map rendered into LibreChat and the collector. Tenant API keys can still be added
|
||||
|
|
|
|||
|
|
@ -40,9 +40,10 @@ defined in this collector config.
|
|||
- Tenant app configuration must set a Langfuse base URL matching one of the
|
||||
startup destinations before tenant trace/score export is enabled; keys alone
|
||||
are treated as central-only.
|
||||
- `LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=false` can be set on LibreChat as an
|
||||
emergency switch to stop tenant trace and score export while keeping central
|
||||
collector export active.
|
||||
- `LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED=false` or a blank value can be set on
|
||||
LibreChat as an emergency switch to stop tenant trace and score export while
|
||||
keeping central collector export active. When omitted, tenant export defaults
|
||||
to enabled if tenant keys and a known destination are configured.
|
||||
- This supports Langfuse Cloud and self-hosted Langfuse as long as each allowed
|
||||
tenant base URL is configured at LibreChat/collector startup. Runtime tenant
|
||||
config selects from those known destinations; it does not inject arbitrary
|
||||
|
|
|
|||
|
|
@ -1351,6 +1351,32 @@ describe('Langfuse run config', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('treats blank tenant fanout export env as disabled', 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_ENABLED = ' ';
|
||||
|
||||
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' },
|
||||
tags: ['tenant:tenant-1'],
|
||||
});
|
||||
});
|
||||
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { AppConfig } from '@librechat/data-schemas';
|
||||
import type { RunConfig } from '@librechat/agents';
|
||||
import { resolveLangfuseTenantDestination } from './tenantDestinations';
|
||||
import { isFalseEnv, normalizeString } from './utils';
|
||||
import { isEnabledUnlessBlankOrFalse, normalizeString } from './utils';
|
||||
|
||||
type LangfuseRunConfig = NonNullable<RunConfig['langfuse']>;
|
||||
type LangfuseRunConfigWithTraceAttributes = LangfuseRunConfig & {
|
||||
|
|
@ -11,7 +11,7 @@ const TENANT_EXPORT_ATTRIBUTE = 'librechat.langfuse.tenant_export.enabled';
|
|||
const TENANT_DESTINATION_ATTRIBUTE = 'librechat.langfuse.destination';
|
||||
|
||||
function isTenantExportEnabled(): boolean {
|
||||
return !isFalseEnv(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED);
|
||||
return isEnabledUnlessBlankOrFalse(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED);
|
||||
}
|
||||
|
||||
function mergeTraceMetadata(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import type { AppConfig } from '@librechat/data-schemas';
|
||||
import { isFalseEnv, normalizeString, toBasicAuthorization } from './utils';
|
||||
import {
|
||||
isEnabledUnlessBlankOrFalse,
|
||||
isFalseEnv,
|
||||
normalizeString,
|
||||
toBasicAuthorization,
|
||||
} from './utils';
|
||||
import { resolveLangfuseTenantDestination } from './tenantDestinations';
|
||||
|
||||
const DEFAULT_BASE_URL = 'https://cloud.langfuse.com';
|
||||
|
|
@ -65,7 +70,7 @@ function getTenantScoreDestination(appConfig?: AppConfig): LangfuseScoreDestinat
|
|||
if (!isTracingEnabled()) {
|
||||
return undefined;
|
||||
}
|
||||
if (isFalseEnv(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED)) {
|
||||
if (!isEnabledUnlessBlankOrFalse(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_ENABLED)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -363,6 +363,32 @@ describe('Langfuse feedback scores', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('treats blank tenant fanout export env as disabled for scores', 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_ENABLED = ' ';
|
||||
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(1);
|
||||
expect(getFetchMock()).toHaveBeenCalledWith(
|
||||
'http://central-langfuse:3000/api/public/scores',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: expect.objectContaining({ Authorization: 'Basic central-auth' }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -6,6 +6,15 @@ export function isFalseEnv(value?: string): boolean {
|
|||
return value != null && ['0', 'false', 'no', 'off'].includes(value.trim().toLowerCase());
|
||||
}
|
||||
|
||||
export function isEnabledUnlessBlankOrFalse(value?: string): boolean {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const normalized = value.trim().toLowerCase();
|
||||
return normalized !== '' && !['0', 'false', 'no', 'off'].includes(normalized);
|
||||
}
|
||||
|
||||
export function toBasicAuthorization(publicKey: string, secretKey: string): string {
|
||||
return `Basic ${Buffer.from(`${publicKey}:${secretKey}`).toString('base64')}`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue