mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🪢 feat: Langfuse Fanout Connection Setting (#14108)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
* feat: encrypt tenant Langfuse secret in admin config Add generic per-field secret encryption to the admin config layer: registered secret paths (langfuse.secretKey) are encrypted with encryptV3 on write and a non-secret fingerprint companion is stored. Admin config reads (base + per principal) redact registered secrets so they are never returned; the fingerprint is kept so the UI can show which key is configured. The Langfuse fanout read path decrypts the tenant secret before export. Adds secretKeyFingerprint to langfuseConfigSchema and tests for the encrypt/redact policy. * fix(api): secure admin config secret handling * fix(api): preserve encrypted langfuse config secrets * fix(api): couple config secret fingerprint deletion * fix(api): read langfuse fanout collector url from env * fix(api): display langfuse secret key hint * fix(api): remove langfuse secret fingerprint breadcrumbs * fix(api): use langfuse destination keys for tenant config * fix(api): remove langfuse config compatibility fallbacks * refactor(api): simplify langfuse secret helpers * refactor(api): simplify langfuse config secret handling * feat: in-app Langfuse connection settings panel Add a discoverable, admin-gated Langfuse connection panel inside LibreChat Settings (Dify-style): enable toggle, host, public key, masked write-only secret, configured-key fingerprint, and a test-connection action. Backed by a dedicated /api/admin/langfuse/connection endpoint that encrypts the secret at rest, returns metadata plus fingerprint on read, and validates credentials. Builds on the per-field encryption and fanout decrypt from the langfuse-config-encryption branch. * refactor: align Langfuse secret field to CustomUserVars pattern Use the established SecretInput plus Set/Unset state pill (com_ui_set/com_ui_unset) from the MCP CustomUserVars UI for the saved-secret state, instead of a bespoke masked input. * fix: drop em dash from saved-secret placeholder * feat: show loading state on Langfuse test connection button * feat: gate in-app Langfuse settings on fanout config and admin role * test: align Langfuse connection spec with SecretInput refactor * feat(langfuse): refine tenant connection controls * fix(admin): refine Langfuse connection verification * fix(langfuse): refine tenant connection settings * fix(langfuse): simplify export enablement controls * fix(langfuse): validate tenant export configuration * fix(langfuse): align startup fanout gate * fix(admin): time out Langfuse verification * fix(ui): rename Langfuse connection setting * fix(admin): enforce Langfuse config capability * feat(langfuse): require explicit tenant export activation * feat(langfuse): support single-tenant connection settings * fix(i18n): remove obsolete integrations label * fix(langfuse): authenticate ingestion verification * fix(langfuse): validate public key independently * fix(langfuse): localize connection errors * perf(config): skip Langfuse checks for non-admins * fix(langfuse): preserve trace sampling for feedback * test(langfuse): fix feedback sampling fixture * fix(langfuse): align secret preview field * fix(langfuse): harden connection settings state * fix(langfuse): preserve trace destination state * fix(langfuse): enforce tenant-wide routing invariants * fix(langfuse): preserve verified connection invariants * fix(langfuse): preserve stable project identity * fix(langfuse): warm project identity asynchronously --------- Co-authored-by: Ravi Kumar L <ravi.lazar@clickhouse.com> Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
91adcf3f2c
commit
af795be0c2
51 changed files with 4356 additions and 527 deletions
|
|
@ -268,6 +268,7 @@ const startServer = async () => {
|
|||
app.use('/api/auth', preAuthTenantMiddleware, routes.auth);
|
||||
app.use('/api/admin', routes.adminAuth);
|
||||
app.use('/api/admin/config', routes.adminConfig);
|
||||
app.use('/api/admin/langfuse', routes.adminLangfuse);
|
||||
app.use('/api/admin/grants', routes.adminGrants);
|
||||
app.use('/api/admin/groups', routes.adminGroups);
|
||||
app.use('/api/admin/roles', routes.adminRoles);
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ jest.mock('~/server/services/Config/ldap', () => ({
|
|||
}));
|
||||
|
||||
const mockHasCapability = jest.fn();
|
||||
const mockHasConfigCapability = jest.fn();
|
||||
jest.mock('~/server/middleware/roles/capabilities', () => ({
|
||||
hasCapability: (...args) => mockHasCapability(...args),
|
||||
hasConfigCapability: (...args) => mockHasConfigCapability(...args),
|
||||
}));
|
||||
|
||||
const mockGetTenantId = jest.fn(() => undefined);
|
||||
|
|
@ -104,6 +106,14 @@ afterEach(() => {
|
|||
delete process.env.ANALYTICS_GTM_ID;
|
||||
delete process.env.CUSTOM_FOOTER;
|
||||
delete process.env.HELP_AND_FAQ_URL;
|
||||
delete process.env.LANGFUSE_FANOUT_ENABLED;
|
||||
delete process.env.LANGFUSE_FANOUT_COLLECTOR_URL;
|
||||
delete process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED;
|
||||
delete process.env.LANGFUSE_PUBLIC_KEY;
|
||||
delete process.env.LANGFUSE_SECRET_KEY;
|
||||
delete process.env.LANGFUSE_TRACING_ENABLED;
|
||||
delete process.env.LANGFUSE_SAMPLE_RATE;
|
||||
delete process.env.TENANT_ISOLATION_STRICT;
|
||||
});
|
||||
|
||||
describe('GET /api/config', () => {
|
||||
|
|
@ -385,6 +395,119 @@ describe('GET /api/config', () => {
|
|||
expect(response.body.conversationImportMaxFileSize).toBe(5000000);
|
||||
});
|
||||
|
||||
it('should advertise Langfuse fanout only when the toggle and collector URL are configured', async () => {
|
||||
mockGetAppConfig.mockResolvedValue(baseAppConfig);
|
||||
mockHasCapability.mockResolvedValue(true);
|
||||
mockHasConfigCapability.mockResolvedValue(true);
|
||||
process.env.LANGFUSE_FANOUT_ENABLED = 'true';
|
||||
const app = createApp(mockUser);
|
||||
|
||||
let response = await request(app).get('/api/config');
|
||||
expect(response.body.langfuseFanoutEnabled).toBe(false);
|
||||
expect(response.body.langfuseConnectionAccess).toBe(true);
|
||||
|
||||
process.env.LANGFUSE_FANOUT_COLLECTOR_URL = ' ';
|
||||
response = await request(app).get('/api/config');
|
||||
expect(response.body.langfuseFanoutEnabled).toBe(false);
|
||||
expect(response.body.langfuseConnectionAccess).toBe(true);
|
||||
|
||||
process.env.LANGFUSE_FANOUT_COLLECTOR_URL = 'http://langfuse-fanout:4318';
|
||||
response = await request(app).get('/api/config');
|
||||
expect(response.body.langfuseFanoutEnabled).toBe(true);
|
||||
expect(response.body.langfuseConnectionAccess).toBe(true);
|
||||
});
|
||||
|
||||
it('hides Langfuse connection access when tenant export is emergency-disabled', async () => {
|
||||
mockGetAppConfig.mockResolvedValue(baseAppConfig);
|
||||
mockHasCapability.mockResolvedValue(true);
|
||||
mockHasConfigCapability.mockResolvedValue(true);
|
||||
process.env.LANGFUSE_FANOUT_ENABLED = 'true';
|
||||
process.env.LANGFUSE_FANOUT_COLLECTOR_URL = 'http://langfuse-fanout:4318';
|
||||
process.env.LANGFUSE_FANOUT_TENANT_EXPORT_DISABLED = 'true';
|
||||
const app = createApp(mockUser);
|
||||
|
||||
const response = await request(app).get('/api/config');
|
||||
|
||||
expect(response.body.langfuseFanoutEnabled).toBe(true);
|
||||
expect(response.body.langfuseConnectionAccess).toBe(false);
|
||||
expect(mockHasCapability).not.toHaveBeenCalled();
|
||||
expect(mockHasConfigCapability).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('advertises Langfuse connection access from capabilities rather than the user role', async () => {
|
||||
mockGetAppConfig.mockResolvedValue(baseAppConfig);
|
||||
process.env.TENANT_ISOLATION_STRICT = 'true';
|
||||
process.env.LANGFUSE_FANOUT_ENABLED = 'true';
|
||||
process.env.LANGFUSE_FANOUT_COLLECTOR_URL = 'http://langfuse-fanout:4318';
|
||||
const app = createApp({ ...mockUser, role: 'DELEGATED_ADMIN' });
|
||||
|
||||
mockHasCapability.mockImplementation(
|
||||
async (_user, capability) => capability === 'access:admin',
|
||||
);
|
||||
mockHasConfigCapability.mockResolvedValue(true);
|
||||
let response = await request(app).get('/api/config');
|
||||
expect(response.body.langfuseConnectionAccess).toBe(true);
|
||||
|
||||
mockHasConfigCapability.mockResolvedValue(false);
|
||||
response = await request(app).get('/api/config');
|
||||
expect(response.body.langfuseFanoutEnabled).toBe(true);
|
||||
expect(response.body.langfuseConnectionAccess).toBe(false);
|
||||
});
|
||||
|
||||
it('skips the Langfuse management capability check without admin access', async () => {
|
||||
mockGetAppConfig.mockResolvedValue(baseAppConfig);
|
||||
mockHasCapability.mockResolvedValue(false);
|
||||
const app = createApp(mockUser);
|
||||
|
||||
const response = await request(app).get('/api/config');
|
||||
|
||||
expect(response.body.langfuseConnectionAccess).toBe(false);
|
||||
expect(mockHasConfigCapability).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('advertises Langfuse connection access by default in single-tenant mode', async () => {
|
||||
mockGetAppConfig.mockResolvedValue(baseAppConfig);
|
||||
mockHasCapability.mockResolvedValue(true);
|
||||
mockHasConfigCapability.mockResolvedValue(true);
|
||||
const app = createApp(mockUser);
|
||||
|
||||
const response = await request(app).get('/api/config');
|
||||
|
||||
expect(response.body.langfuseFanoutEnabled).toBe(false);
|
||||
expect(response.body.langfuseConnectionAccess).toBe(true);
|
||||
});
|
||||
|
||||
it('hides single-tenant connection settings when environment credentials are configured', async () => {
|
||||
mockGetAppConfig.mockResolvedValue(baseAppConfig);
|
||||
mockHasCapability.mockResolvedValue(true);
|
||||
mockHasConfigCapability.mockResolvedValue(true);
|
||||
process.env.LANGFUSE_PUBLIC_KEY = 'pk-env';
|
||||
process.env.LANGFUSE_SECRET_KEY = 'sk-env';
|
||||
const app = createApp(mockUser);
|
||||
|
||||
const response = await request(app).get('/api/config');
|
||||
|
||||
expect(response.body.langfuseConnectionAccess).toBe(false);
|
||||
expect(mockHasCapability).not.toHaveBeenCalled();
|
||||
expect(mockHasConfigCapability).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['LANGFUSE_TRACING_ENABLED', 'false'],
|
||||
['LANGFUSE_SAMPLE_RATE', '0'],
|
||||
])('hides Langfuse connection settings when %s=%s', async (key, value) => {
|
||||
mockGetAppConfig.mockResolvedValue(baseAppConfig);
|
||||
mockHasCapability.mockResolvedValue(true);
|
||||
mockHasConfigCapability.mockResolvedValue(true);
|
||||
process.env[key] = value;
|
||||
const app = createApp(mockUser);
|
||||
|
||||
const response = await request(app).get('/api/config');
|
||||
|
||||
expect(response.body.langfuseConnectionAccess).toBe(false);
|
||||
expect(mockHasCapability).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should include post-login informational fields', async () => {
|
||||
process.env.ANALYTICS_GTM_ID = 'GTM-XYZ';
|
||||
process.env.CUSTOM_FOOTER = 'authenticated footer text';
|
||||
|
|
@ -495,6 +618,7 @@ describe('GET /api/config', () => {
|
|||
|
||||
it('should not call hasCapability when allowAccountDeletion is already true', async () => {
|
||||
mockGetAppConfig.mockResolvedValue(baseAppConfig);
|
||||
process.env.LANGFUSE_TRACING_ENABLED = 'false';
|
||||
const app = createApp(mockUser);
|
||||
|
||||
const response = await request(app).get('/api/config');
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ describe('PUT /:conversationId/:messageId/feedback', () => {
|
|||
messageId,
|
||||
conversationId: 'conversation-1',
|
||||
endpoint: 'openAI',
|
||||
langfuseSampled: true,
|
||||
langfuseDestinationIds: ['destination-1'],
|
||||
feedback,
|
||||
}),
|
||||
);
|
||||
|
|
@ -115,6 +117,8 @@ describe('PUT /:conversationId/:messageId/feedback', () => {
|
|||
);
|
||||
expect(sendFeedbackScore).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sampled: true,
|
||||
destinationIds: ['destination-1'],
|
||||
feedback: {
|
||||
rating: 'thumbsDown',
|
||||
tag: 'inaccurate',
|
||||
|
|
|
|||
50
api/server/routes/admin/langfuse.js
Normal file
50
api/server/routes/admin/langfuse.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
const express = require('express');
|
||||
const { createAdminLangfuseHandlers } = require('@librechat/api');
|
||||
const { SystemCapabilities } = require('@librechat/data-schemas');
|
||||
const {
|
||||
hasConfigCapability,
|
||||
requireCapability,
|
||||
} = require('~/server/middleware/roles/capabilities');
|
||||
const { invalidateConfigCaches } = require('~/server/services/Config');
|
||||
const { requireJwtAuth } = require('~/server/middleware');
|
||||
const db = require('~/models');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN);
|
||||
|
||||
async function requireLangfuseManage(req, res, next) {
|
||||
try {
|
||||
const id = req.user?.id ?? req.user?._id?.toString();
|
||||
if (!id) {
|
||||
return res.status(401).json({ message: 'Authentication required' });
|
||||
}
|
||||
const user = {
|
||||
id,
|
||||
role: req.user.role ?? '',
|
||||
tenantId: req.user.tenantId,
|
||||
idOnTheSource: req.user.idOnTheSource ?? null,
|
||||
};
|
||||
if (await hasConfigCapability(user, 'langfuse')) {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).json({ message: 'Forbidden' });
|
||||
} catch (_err) {
|
||||
return res.status(500).json({ message: 'Internal Server Error' });
|
||||
}
|
||||
}
|
||||
|
||||
const handlers = createAdminLangfuseHandlers({
|
||||
findConfigByPrincipal: db.findConfigByPrincipal,
|
||||
patchConfigFields: db.patchConfigFields,
|
||||
toggleConfigActive: db.toggleConfigActive,
|
||||
invalidateConfigCaches,
|
||||
});
|
||||
|
||||
router.use(requireJwtAuth, requireAdminAccess, requireLangfuseManage);
|
||||
|
||||
router.get('/connection', handlers.getConnection);
|
||||
router.put('/connection', handlers.updateConnection);
|
||||
router.post('/connection/test', handlers.testConnection);
|
||||
|
||||
module.exports = router;
|
||||
108
api/server/routes/admin/langfuse.test.js
Normal file
108
api/server/routes/admin/langfuse.test.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
const express = require('express');
|
||||
const request = require('supertest');
|
||||
|
||||
let deniedCapability;
|
||||
let canManageLangfuse;
|
||||
const middlewareCalls = [];
|
||||
const mockHasConfigCapability = jest.fn(() => Promise.resolve(canManageLangfuse));
|
||||
const mockRequireJwtAuth = jest.fn((req, _res, next) => {
|
||||
req.user = { id: 'user-1', role: 'DELEGATED_ADMIN', tenantId: 'tenant-a' };
|
||||
middlewareCalls.push('jwt');
|
||||
next();
|
||||
});
|
||||
const mockRequireCapability = jest.fn((capability) => (req, res, next) => {
|
||||
middlewareCalls.push(capability);
|
||||
if (deniedCapability === capability) {
|
||||
return res.status(403).json({ message: 'Forbidden' });
|
||||
}
|
||||
next();
|
||||
});
|
||||
const mockHandlers = {
|
||||
getConnection: jest.fn((_req, res) => res.status(200).json({ handler: 'get' })),
|
||||
updateConnection: jest.fn((_req, res) => res.status(200).json({ handler: 'update' })),
|
||||
testConnection: jest.fn((_req, res) => res.status(200).json({ handler: 'test' })),
|
||||
};
|
||||
|
||||
jest.mock('@librechat/data-schemas', () => ({
|
||||
SystemCapabilities: { ACCESS_ADMIN: 'access:admin' },
|
||||
}));
|
||||
|
||||
jest.mock('@librechat/api', () => ({
|
||||
createAdminLangfuseHandlers: jest.fn(() => mockHandlers),
|
||||
}));
|
||||
|
||||
jest.mock('~/server/middleware/roles/capabilities', () => ({
|
||||
requireCapability: mockRequireCapability,
|
||||
hasConfigCapability: mockHasConfigCapability,
|
||||
}));
|
||||
|
||||
jest.mock('~/server/middleware', () => ({
|
||||
requireJwtAuth: mockRequireJwtAuth,
|
||||
}));
|
||||
|
||||
jest.mock('~/server/services/Config', () => ({
|
||||
invalidateConfigCaches: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('~/models', () => ({
|
||||
findConfigByPrincipal: jest.fn(),
|
||||
patchConfigFields: jest.fn(),
|
||||
toggleConfigActive: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('admin Langfuse routes', () => {
|
||||
function createApp() {
|
||||
delete require.cache[require.resolve('./langfuse')];
|
||||
const router = require('./langfuse');
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use('/api/admin/langfuse', router);
|
||||
return app;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
deniedCapability = undefined;
|
||||
canManageLangfuse = true;
|
||||
middlewareCalls.length = 0;
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('requires admin access and Langfuse manage access for connection reads', async () => {
|
||||
const response = await request(createApp()).get('/api/admin/langfuse/connection').expect(200);
|
||||
|
||||
expect(response.body).toEqual({ handler: 'get' });
|
||||
expect(middlewareCalls).toEqual(['jwt', 'access:admin']);
|
||||
expect(mockHasConfigCapability).toHaveBeenCalledWith(
|
||||
{
|
||||
id: 'user-1',
|
||||
role: 'DELEGATED_ADMIN',
|
||||
tenantId: 'tenant-a',
|
||||
idOnTheSource: null,
|
||||
},
|
||||
'langfuse',
|
||||
);
|
||||
expect(mockHandlers.getConnection).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['PUT', '/api/admin/langfuse/connection', 'updateConnection'],
|
||||
['POST', '/api/admin/langfuse/connection/test', 'testConnection'],
|
||||
])('requires Langfuse manage access for %s %s', async (method, path, handlerName) => {
|
||||
const app = createApp();
|
||||
const response = await request(app)[method.toLowerCase()](path).send({}).expect(200);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
handler: handlerName === 'updateConnection' ? 'update' : 'test',
|
||||
});
|
||||
expect(middlewareCalls).toEqual(['jwt', 'access:admin']);
|
||||
expect(mockHandlers[handlerName]).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('blocks updates when the user lacks Langfuse manage access', async () => {
|
||||
canManageLangfuse = false;
|
||||
|
||||
await request(createApp()).put('/api/admin/langfuse/connection').send({}).expect(403);
|
||||
|
||||
expect(mockHandlers.updateConnection).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
const express = require('express');
|
||||
const {
|
||||
isEnabled,
|
||||
isLangfuseConnectionAvailable,
|
||||
isLangfuseFanoutEnabled,
|
||||
getBalanceConfig,
|
||||
getCloudFrontConfig,
|
||||
getAppConfigOptionsFromUser,
|
||||
|
|
@ -12,7 +14,7 @@ const {
|
|||
} = require('@librechat/api');
|
||||
const { EModelEndpoint, defaultSocialLogins } = require('librechat-data-provider');
|
||||
const { logger, getTenantId, SystemCapabilities } = require('@librechat/data-schemas');
|
||||
const { hasCapability } = require('~/server/middleware/roles/capabilities');
|
||||
const { hasCapability, hasConfigCapability } = require('~/server/middleware/roles/capabilities');
|
||||
const { getLdapConfig } = require('~/server/services/Config/ldap');
|
||||
const { getRumConfig } = require('~/server/services/Config/rum');
|
||||
const { getAppConfig } = require('~/server/services/Config/app');
|
||||
|
|
@ -249,6 +251,32 @@ router.get('/', async function (req, res) {
|
|||
|
||||
const balanceConfig = getBalanceConfig(appConfig);
|
||||
const cloudFront = buildCloudFrontStartupConfig();
|
||||
const langfuseFanoutEnabled = isLangfuseFanoutEnabled();
|
||||
const langfuseConnectionAvailable = isLangfuseConnectionAvailable();
|
||||
let langfuseConnectionAccess = false;
|
||||
|
||||
if (langfuseConnectionAvailable) {
|
||||
try {
|
||||
const userId = req.user.id ?? req.user._id?.toString();
|
||||
if (userId) {
|
||||
const capabilityUser = {
|
||||
id: userId,
|
||||
role: req.user.role ?? '',
|
||||
tenantId: req.user.tenantId,
|
||||
idOnTheSource: req.user.idOnTheSource ?? null,
|
||||
};
|
||||
const hasAdminAccess = await hasCapability(
|
||||
capabilityUser,
|
||||
SystemCapabilities.ACCESS_ADMIN,
|
||||
);
|
||||
if (hasAdminAccess) {
|
||||
langfuseConnectionAccess = await hasConfigCapability(capabilityUser, 'langfuse');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn(`[config] Langfuse capability check failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {TStartupConfig} */
|
||||
const payload = {
|
||||
|
|
@ -274,6 +302,8 @@ router.get('/', async function (req, res) {
|
|||
conversationImportMaxFileSize: process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES
|
||||
? parseInt(process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES, 10)
|
||||
: 0,
|
||||
langfuseFanoutEnabled,
|
||||
langfuseConnectionAccess,
|
||||
...(cloudFront ? { cloudFront } : {}),
|
||||
...(rum ? { rum } : {}),
|
||||
fileUploadSseEnabled: isEnabled(process.env.FILE_UPLOAD_SSE_ENABLED),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ const assistants = require('./assistants');
|
|||
const categories = require('./categories');
|
||||
const adminAuth = require('./admin/auth');
|
||||
const adminConfig = require('./admin/config');
|
||||
const adminLangfuse = require('./admin/langfuse');
|
||||
const adminGrants = require('./admin/grants');
|
||||
const adminGroups = require('./admin/groups');
|
||||
const adminRoles = require('./admin/roles');
|
||||
|
|
@ -43,6 +44,7 @@ module.exports = {
|
|||
auth,
|
||||
adminAuth,
|
||||
adminConfig,
|
||||
adminLangfuse,
|
||||
adminGrants,
|
||||
adminGroups,
|
||||
adminRoles,
|
||||
|
|
|
|||
|
|
@ -450,6 +450,8 @@ router.put(
|
|||
if (!isAssistantsEndpoint(updatedMessage.endpoint)) {
|
||||
sendFeedbackScore({
|
||||
traceId: traceIdForMessage(messageId),
|
||||
sampled: updatedMessage.langfuseSampled,
|
||||
destinationIds: updatedMessage.langfuseDestinationIds,
|
||||
feedback: updatedMessage.feedback,
|
||||
appConfig: req.config,
|
||||
metadata: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue