diff --git a/.env.example b/.env.example index d895a940cb..2f2eb94067 100644 --- a/.env.example +++ b/.env.example @@ -164,6 +164,9 @@ 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 +# App-side switch: set true to tell the Langfuse SDK not to create media uploads +# for central/fallback collector traces. Tenant-routed media uploads are unchanged. +# LANGFUSE_FANOUT_CENTRAL_MEDIA_UPLOAD_DISABLED=false # Emergency switch: unset/false defaults enabled; set true to keep central fanout export but skip tenant trace/score export. # LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED=false # Langfuse Cloud base URL options: https://cloud.langfuse.com (EU), diff --git a/api/package.json b/api/package.json index 1e19efac3b..00b164646b 100644 --- a/api/package.json +++ b/api/package.json @@ -46,7 +46,7 @@ "@azure/storage-blob": "^12.30.0", "@google/genai": "^2.8.0", "@keyv/redis": "^4.3.3", - "@librechat/agents": "^3.3.12", + "@librechat/agents": "^3.3.13", "@librechat/api": "*", "@librechat/data-schemas": "*", "@microsoft/microsoft-graph-client": "^3.0.7", diff --git a/package-lock.json b/package-lock.json index f133e3ae52..110f552ca6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "@azure/storage-blob": "^12.30.0", "@google/genai": "^2.8.0", "@keyv/redis": "^4.3.3", - "@librechat/agents": "^3.3.12", + "@librechat/agents": "^3.3.13", "@librechat/api": "*", "@librechat/data-schemas": "*", "@microsoft/microsoft-graph-client": "^3.0.7", @@ -10616,9 +10616,9 @@ } }, "node_modules/@librechat/agents": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.3.12.tgz", - "integrity": "sha512-kTB50KMlEXR3jMLmGMZzzSkne019k57Wy3Fv0dGX7I4G/oxJMgqZsw70KqiAMYokNOQnc1DoWGmIcZLgJYcewA==", + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.3.13.tgz", + "integrity": "sha512-NOaUCvMaq82ftzZr/Z/3KL1LzfKO0YRHibhFigcx22KxrmeioyJ32w4RnrBsYGhLx0gDrI4nWlW6Sk0ZAH/YBQ==", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.115.0", @@ -42707,7 +42707,7 @@ "@azure/storage-blob": "^12.30.0", "@google/genai": "^2.8.0", "@keyv/redis": "^4.3.3", - "@librechat/agents": "^3.3.12", + "@librechat/agents": "^3.3.13", "@librechat/data-schemas": "*", "@modelcontextprotocol/sdk": "^1.29.0", "@opentelemetry/api": "^1.9.0", diff --git a/packages/api/package.json b/packages/api/package.json index 0a86914278..32af7f7a35 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -107,7 +107,7 @@ "@azure/storage-blob": "^12.30.0", "@google/genai": "^2.8.0", "@keyv/redis": "^4.3.3", - "@librechat/agents": "^3.3.12", + "@librechat/agents": "^3.3.13", "@librechat/data-schemas": "*", "@modelcontextprotocol/sdk": "^1.29.0", "@opentelemetry/api": "^1.9.0", diff --git a/packages/api/src/agents/__tests__/run-summarization.test.ts b/packages/api/src/agents/__tests__/run-summarization.test.ts index f85e2d1797..e59cee3a94 100644 --- a/packages/api/src/agents/__tests__/run-summarization.test.ts +++ b/packages/api/src/agents/__tests__/run-summarization.test.ts @@ -222,6 +222,7 @@ beforeEach(() => { delete process.env.LANGFUSE_HOST; delete process.env.LANGFUSE_FANOUT_ENABLED; delete process.env.LANGFUSE_FANOUT_COLLECTOR_URL; + delete process.env.LANGFUSE_FANOUT_CENTRAL_MEDIA_UPLOAD_DISABLED; delete process.env.LANGFUSE_FANOUT_TENANT_DESTINATIONS; delete process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED; delete process.env.LANGFUSE_TRACING_ENABLED; diff --git a/packages/api/src/langfuse/config.spec.ts b/packages/api/src/langfuse/config.spec.ts index 44d4a5f097..6f10d6ee8b 100644 --- a/packages/api/src/langfuse/config.spec.ts +++ b/packages/api/src/langfuse/config.spec.ts @@ -12,6 +12,7 @@ const envKeys = [ 'LANGFUSE_BASEURL', 'LANGFUSE_FANOUT_ENABLED', 'LANGFUSE_FANOUT_COLLECTOR_URL', + 'LANGFUSE_FANOUT_CENTRAL_MEDIA_UPLOAD_DISABLED', 'LANGFUSE_FANOUT_TENANT_DESTINATIONS', 'LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED', 'LANGFUSE_TRACING_ENABLED', @@ -184,6 +185,7 @@ describe('buildLangfuseConfig', () => { 'librechat.langfuse.destination': 'eu', }, }); + expect(config).not.toHaveProperty('mediaUploadEnabled'); }); it('fails closed to central-only export when tenant secret decryption fails', async () => { @@ -438,4 +440,39 @@ describe('buildLangfuseConfig', () => { tags: ['tenant:tenant-1'], }); }); + + it.each(['true', '1', 'yes', 'on'])( + 'disables central collector media uploads when LANGFUSE_FANOUT_CENTRAL_MEDIA_UPLOAD_DISABLED is %s', + async (value) => { + process.env.LANGFUSE_FANOUT_ENABLED = 'true'; + process.env.LANGFUSE_FANOUT_COLLECTOR_URL = 'http://collector-from-env:4318'; + process.env.LANGFUSE_FANOUT_CENTRAL_MEDIA_UPLOAD_DISABLED = value; + const { buildLangfuseConfig } = await import('./config'); + + expect(buildLangfuseConfig({ tenantId: 'tenant-1' })).toEqual({ + deterministicTraceId: true, + baseUrl: 'http://collector-from-env:4318', + mediaUploadEnabled: false, + metadata: { 'librechat.tenant.id': 'tenant-1' }, + tags: ['tenant:tenant-1'], + }); + }, + ); + + it.each(['false', '0', 'no', 'off', ''])( + 'keeps central collector media uploads at the SDK default when LANGFUSE_FANOUT_CENTRAL_MEDIA_UPLOAD_DISABLED is %s', + async (value) => { + process.env.LANGFUSE_FANOUT_ENABLED = 'true'; + process.env.LANGFUSE_FANOUT_COLLECTOR_URL = 'http://collector-from-env:4318'; + process.env.LANGFUSE_FANOUT_CENTRAL_MEDIA_UPLOAD_DISABLED = value; + const { buildLangfuseConfig } = await import('./config'); + + expect(buildLangfuseConfig({ tenantId: 'tenant-1' })).toEqual({ + deterministicTraceId: true, + baseUrl: 'http://collector-from-env:4318', + metadata: { 'librechat.tenant.id': 'tenant-1' }, + tags: ['tenant:tenant-1'], + }); + }, + ); }); diff --git a/packages/api/src/langfuse/config.ts b/packages/api/src/langfuse/config.ts index e29ef4a4a1..cdea9160a7 100644 --- a/packages/api/src/langfuse/config.ts +++ b/packages/api/src/langfuse/config.ts @@ -2,6 +2,7 @@ import type { AppConfig } from '@librechat/data-schemas'; import type { RunConfig } from '@librechat/agents'; import { hasLangfuseEnvCredentials, + isLangfuseCentralMediaUploadDisabled, isLangfuseFanoutEnabled, isLangfuseTenantExportEnabled, isLangfuseTraceSampled, @@ -16,6 +17,7 @@ import { traceIdForMessage } from './trace'; type LangfuseRunConfig = NonNullable; type LangfuseRunConfigWithTraceAttributes = LangfuseRunConfig & { librechatTraceAttributes?: Record; + mediaUploadEnabled?: boolean; }; type LangfuseTenantDestination = NonNullable>; type LangfuseExportPlan = @@ -226,6 +228,9 @@ export function buildLangfuseConfig({ break; case 'fanoutCollector': langfuse.baseUrl = exportPlan.collectorUrl; + if (isLangfuseCentralMediaUploadDisabled()) { + langfuse.mediaUploadEnabled = false; + } break; case 'disabled': langfuse.enabled = false; diff --git a/packages/api/src/langfuse/policy.ts b/packages/api/src/langfuse/policy.ts index c1d3c79a19..c8ac5368fd 100644 --- a/packages/api/src/langfuse/policy.ts +++ b/packages/api/src/langfuse/policy.ts @@ -8,6 +8,10 @@ export function isLangfuseTenantExportEnabled(): boolean { return !isTrueEnv(process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED); } +export function isLangfuseCentralMediaUploadDisabled(): boolean { + return isTrueEnv(process.env.LANGFUSE_FANOUT_CENTRAL_MEDIA_UPLOAD_DISABLED); +} + export function isLangfuseFanoutEnabled(): boolean { return ( isTrueEnv(process.env.LANGFUSE_FANOUT_ENABLED) &&