diff --git a/src/models/conversation.model.ts b/src/models/conversation.model.ts index 6ffbc8b8..9f2e6361 100644 --- a/src/models/conversation.model.ts +++ b/src/models/conversation.model.ts @@ -147,6 +147,10 @@ const conversationSchema = new mongoose.Schema resources: { type: [resourceSchema], default: [] + }, + summary: { + type: String, + default: '' } }, { diff --git a/src/services/conversation.service/lifecycle.ts b/src/services/conversation.service/lifecycle.ts index 454a8fa0..050cd528 100644 --- a/src/services/conversation.service/lifecycle.ts +++ b/src/services/conversation.service/lifecycle.ts @@ -6,8 +6,24 @@ import schedule from '../../jobs/schedule.js' import defineJob from '../../jobs/define.js' import logger from '../../config/logger.js' import adapterService from '../adapter.service.js' +import { getChatPromptResponse } from '../../agents/helpers/llmChain.js' +import { coreLLMModel, coreLLMPlatform, getModelChat } from '../../agents/helpers/getModelChat.js' +import { Conversation, User } from '../../models/index.js' +import { formatTranscript, formatMultiUserConversationHistory } from '../../agents/helpers/llmInputFormatters.js' +import getConversationHistory from '../../agents/helpers/getConversationHistory.js' const transcriptBatchInterval = 30 +const SUMMARIZATION_PROMPT = ` + Please summarize what happened during this conversation. Where possible, also draw conclusions about outcomes of the discussion. + When available, use as reference the listed speaker(s), moderator(s) and their bios, and event description. + + - **IMPORTANT**: you are summarizing for the event attendees. You are not worried about things like engagement or metrics. You want to provide a clear and concise summary of the key points and outcomes in a digestible format. + - **LENGTH**: write no more than three paragraphs. Be selective — prioritize the most significant points and outcomes over completeness. + - The tone is friendly and conversational. + - The event content will be made up of a transcript as well as participant messages. + - The transcript is drawing from what was said by the speakers in the event, or in some cases might be a video presentation of some kind. Be aware that speakers are generally allowed to use whatever media they would like during the conversation. + - The participant messages are from attendees in a group chat either on Zoom or within a custom-built front-end app. + - Participants might want to know what other participants were saying relative to the event wrap-up.` export const updateTranscriptStatus = async ( conversation, @@ -68,8 +84,51 @@ export async function doStopConversation(conversation) { await adapterService.stop(adapter) } doc.active = false + if (doc.transcript) { await updateTranscriptStatus(doc, 'stopped') + const owner = await User.findById(conversation.owner) + + if (owner) { + const conversationDoc = await Conversation.findOne({ _id: conversation._id }).populate('channels').populate({ path: 'messages', match: { channels: { $in: ['transcript', 'chat'] } } }) + + if (conversationDoc) { + const llm = await getModelChat(coreLLMPlatform, coreLLMModel, {maxTokens: 2000}) + const sortedMessages = conversationDoc.messages.sort( + (a, b) => (a.createdAt?.getTime() ?? 0) - (b.createdAt?.getTime() ?? 0) + ) + const transcriptMessages = sortedMessages.filter((m) => m.channels?.includes('transcript')) + const transcript = formatTranscript(transcriptMessages, 'UTC') + + const chatHistory = getConversationHistory(sortedMessages, { channels: ['chat'] }) + const sharedChat = + formatMultiUserConversationHistory(chatHistory) + .map((m) => (m.role === 'assistant' ? `Assistant: ${m.content}` : m.content)) + .join('\n') || 'No shared chat messages yet.' + + // Get speaker and moderator information if available + const speakers = `${conversationDoc.presenters?.map((p) => `${p.name}: ${p.bio}`).join(', ')}` || 'Not provided' + const moderators = `${conversationDoc.moderators?.map((m) => `${m.name}: ${m.bio}`).join(', ')}` || 'Not provided' + const eventDescription = conversationDoc.description || 'Not provided' + + const structuredSummary = await getChatPromptResponse( + llm, + SUMMARIZATION_PROMPT, + ` + Event Transcript: {transcript}, + Shared Chat: {sharedChat}, + Speaker(s): {speakers}, + Moderator(s): {moderators}, + Event Description: {eventDescription} + `, + { transcript, sharedChat, speakers, moderators, eventDescription } + ) + + logger.debug(`Conversation summary generated for conversation ${doc._id}`) + + doc.summary = structuredSummary + } else logger.warn(`No conversation document found for conversation ${doc._id}`) + } else logger.warn(`No owner found for conversation ${doc._id}`) } await doc.save() return doc diff --git a/src/types/index.types.ts b/src/types/index.types.ts index 942d91e5..b6dabc56 100644 --- a/src/types/index.types.ts +++ b/src/types/index.types.ts @@ -347,6 +347,7 @@ export interface IConversation { createdAt?: Date updatedAt?: Date messageCount(): number + summary?: string } export interface IPoll {