| | const { nanoid } = require('nanoid'); |
| | const { checkAccess } = require('@librechat/api'); |
| | const { logger } = require('@librechat/data-schemas'); |
| | const { |
| | Tools, |
| | Permissions, |
| | FileSources, |
| | EModelEndpoint, |
| | PermissionTypes, |
| | } = require('librechat-data-provider'); |
| | const { getRoleByName } = require('~/models/Role'); |
| | const { Files } = require('~/models'); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | async function processFileCitations({ user, appConfig, toolArtifact, toolCallId, metadata }) { |
| | try { |
| | if (!toolArtifact?.[Tools.file_search]?.sources) { |
| | return null; |
| | } |
| |
|
| | if (user) { |
| | try { |
| | const hasFileCitationsAccess = |
| | toolArtifact?.[Tools.file_search]?.fileCitations ?? |
| | (await checkAccess({ |
| | user, |
| | permissionType: PermissionTypes.FILE_CITATIONS, |
| | permissions: [Permissions.USE], |
| | getRoleByName, |
| | })); |
| |
|
| | if (!hasFileCitationsAccess) { |
| | logger.debug( |
| | `[processFileCitations] User ${user.id} does not have FILE_CITATIONS permission`, |
| | ); |
| | return null; |
| | } |
| | } catch (error) { |
| | logger.error( |
| | `[processFileCitations] Permission check failed for FILE_CITATIONS: ${error.message}`, |
| | ); |
| | logger.debug(`[processFileCitations] Proceeding with citations due to permission error`); |
| | } |
| | } |
| |
|
| | const maxCitations = appConfig.endpoints?.[EModelEndpoint.agents]?.maxCitations ?? 30; |
| | const maxCitationsPerFile = |
| | appConfig.endpoints?.[EModelEndpoint.agents]?.maxCitationsPerFile ?? 5; |
| | const minRelevanceScore = |
| | appConfig.endpoints?.[EModelEndpoint.agents]?.minRelevanceScore ?? 0.45; |
| |
|
| | const sources = toolArtifact[Tools.file_search].sources || []; |
| | const filteredSources = sources.filter((source) => source.relevance >= minRelevanceScore); |
| | if (filteredSources.length === 0) { |
| | logger.debug( |
| | `[processFileCitations] No sources above relevance threshold of ${minRelevanceScore}`, |
| | ); |
| | return null; |
| | } |
| |
|
| | const selectedSources = applyCitationLimits(filteredSources, maxCitations, maxCitationsPerFile); |
| | const enhancedSources = await enhanceSourcesWithMetadata(selectedSources, appConfig); |
| |
|
| | if (enhancedSources.length > 0) { |
| | const fileSearchAttachment = { |
| | type: Tools.file_search, |
| | [Tools.file_search]: { sources: enhancedSources }, |
| | toolCallId: toolCallId, |
| | messageId: metadata.run_id, |
| | conversationId: metadata.thread_id, |
| | name: `${Tools.file_search}_file_search_results_${nanoid()}`, |
| | }; |
| |
|
| | return fileSearchAttachment; |
| | } |
| |
|
| | return null; |
| | } catch (error) { |
| | logger.error('[processFileCitations] Error processing file citations:', error); |
| | return null; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | function applyCitationLimits(sources, maxCitations, maxCitationsPerFile) { |
| | const byFile = {}; |
| | sources.forEach((source) => { |
| | if (!byFile[source.fileId]) { |
| | byFile[source.fileId] = []; |
| | } |
| | byFile[source.fileId].push(source); |
| | }); |
| |
|
| | const representatives = []; |
| | for (const fileId in byFile) { |
| | const fileSources = byFile[fileId].sort((a, b) => b.relevance - a.relevance); |
| | const selectedFromFile = fileSources.slice(0, maxCitationsPerFile); |
| | representatives.push(...selectedFromFile); |
| | } |
| |
|
| | return representatives.sort((a, b) => b.relevance - a.relevance).slice(0, maxCitations); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | async function enhanceSourcesWithMetadata(sources, appConfig) { |
| | const fileIds = [...new Set(sources.map((source) => source.fileId))]; |
| |
|
| | let fileMetadataMap = {}; |
| | try { |
| | const files = await Files.find({ file_id: { $in: fileIds } }); |
| | fileMetadataMap = files.reduce((map, file) => { |
| | map[file.file_id] = file; |
| | return map; |
| | }, {}); |
| | } catch (error) { |
| | logger.error('[enhanceSourcesWithMetadata] Error looking up file metadata:', error); |
| | } |
| |
|
| | return sources.map((source) => { |
| | const fileRecord = fileMetadataMap[source.fileId] || {}; |
| | const configuredStorageType = fileRecord.source || appConfig?.fileStrategy || FileSources.local; |
| |
|
| | return { |
| | ...source, |
| | fileName: fileRecord.filename || source.fileName || 'Unknown File', |
| | metadata: { |
| | ...source.metadata, |
| | storageType: configuredStorageType, |
| | }, |
| | }; |
| | }); |
| | } |
| |
|
| | module.exports = { |
| | applyCitationLimits, |
| | processFileCitations, |
| | enhanceSourcesWithMetadata, |
| | }; |
| |
|