diff --git a/api/server/routes/messages.js b/api/server/routes/messages.js index 0133fe8541..17e740c515 100644 --- a/api/server/routes/messages.js +++ b/api/server/routes/messages.js @@ -401,6 +401,17 @@ router.put('/:conversationId/:messageId/feedback', validateMessageReq, async (re sendFeedbackScore({ traceId: traceIdForMessage(messageId), feedback: updatedMessage.feedback, + metadata: { + messageId: updatedMessage.messageId ?? messageId, + parentMessageId: updatedMessage.parentMessageId, + conversationId: updatedMessage.conversationId ?? conversationId, + sessionId: updatedMessage.conversationId ?? conversationId, + userId: req?.user?.id, + endpoint: updatedMessage.endpoint, + sender: updatedMessage.sender, + isCreatedByUser: updatedMessage.isCreatedByUser, + tokenCount: updatedMessage.tokenCount, + }, }).catch((err) => logger.error('[langfuse] feedback score failed:', err)); } diff --git a/packages/api/src/langfuse/feedback.spec.ts b/packages/api/src/langfuse/feedback.spec.ts index daf64cf09e..3de2f727a4 100644 --- a/packages/api/src/langfuse/feedback.spec.ts +++ b/packages/api/src/langfuse/feedback.spec.ts @@ -59,6 +59,16 @@ describe('Langfuse feedback scores', () => { await sendFeedbackScore({ traceId: 'trace-id', feedback: { rating: 'thumbsUp', tag: 'helpful', text: 'nice' }, + metadata: { + messageId: 'message-id', + conversationId: 'conversation-id', + sessionId: 'conversation-id', + userId: 'user-id', + endpoint: 'agents', + empty: '', + missing: undefined, + }, + observationId: 'observation-id', }); expect(getFetchMock()).toHaveBeenCalledWith( @@ -80,8 +90,19 @@ describe('Langfuse feedback scores', () => { value: 1, dataType: 'BOOLEAN', comment: 'helpful — nice', - metadata: { rating: 'thumbsUp', tag: 'helpful' }, + observationId: 'observation-id', + metadata: { + rating: 'thumbsUp', + tag: 'helpful', + messageId: 'message-id', + conversationId: 'conversation-id', + sessionId: 'conversation-id', + userId: 'user-id', + endpoint: 'agents', + }, }); + expect(JSON.parse(init?.body as string).metadata).not.toHaveProperty('empty'); + expect(JSON.parse(init?.body as string).metadata).not.toHaveProperty('missing'); }); it('posts feedback scores to the configured Langfuse host', async () => { diff --git a/packages/api/src/langfuse/feedback.ts b/packages/api/src/langfuse/feedback.ts index db6e2abdc0..14d80df048 100644 --- a/packages/api/src/langfuse/feedback.ts +++ b/packages/api/src/langfuse/feedback.ts @@ -6,9 +6,13 @@ export type LangfuseFeedback = { text?: string; }; +export type LangfuseFeedbackMetadata = Record; + export type SendFeedbackScoreParams = { traceId: string; feedback?: LangfuseFeedback | null; + metadata?: LangfuseFeedbackMetadata; + observationId?: string; }; const DEFAULT_BASE_URL = 'https://cloud.langfuse.com'; @@ -42,9 +46,26 @@ const AUTHORIZATION = ENABLED : undefined; const ENVIRONMENT = process.env.LANGFUSE_TRACING_ENVIRONMENT; +function cleanMetadata( + metadata: LangfuseFeedbackMetadata, +): Record { + return Object.entries(metadata).reduce>( + (result, [key, value]) => { + if (value == null || (typeof value === 'string' && value.trim() === '')) { + return result; + } + result[key] = value; + return result; + }, + {}, + ); +} + export async function sendFeedbackScore({ traceId, feedback, + metadata = {}, + observationId, }: SendFeedbackScoreParams): Promise { if (!ENABLED || !AUTHORIZATION || !traceId) { return; @@ -70,7 +91,8 @@ export async function sendFeedbackScore({ value: feedback.rating === 'thumbsUp' ? 1 : 0, dataType: 'BOOLEAN', comment: [feedback.tag, feedback.text].filter(Boolean).join(' — ') || undefined, - metadata: { rating: feedback.rating, tag: feedback.tag }, + metadata: cleanMetadata({ ...metadata, rating: feedback.rating, tag: feedback.tag }), + ...(observationId ? { observationId } : {}), ...(ENVIRONMENT ? { environment: ENVIRONMENT } : {}), };