Feature/sse (#3125)

* Base changes for ServerSide Events (instead of socket.io)

* lint fixes

* adding of interface and separate methods for streaming events

* lint

* first draft, handles both internal and external prediction end points.

* lint fixes

* additional internal end point for streaming and associated changes

* return streamresponse as true to build agent flow

* 1) JSON formatting for internal events
2) other fixes

* 1) convert internal event to metadata to maintain consistency with external response

* fix action and metadata streaming

* fix for error when agent flow is aborted

* prevent subflows from streaming and other code cleanup

* prevent streaming from enclosed tools

* add fix for preventing chaintool streaming

* update lock file

* add open when hidden to sse

* Streaming errors

* Streaming errors

* add fix for showing error message

---------

Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
Vinod Kiran
2024-09-17 12:31:25 +05:30
committed by GitHub
parent 7a4c7efcab
commit 26444ac3ae
47 changed files with 1021 additions and 327 deletions
+29 -27
View File
@@ -9,9 +9,9 @@ import {
ISeqAgentsState,
ISeqAgentNode,
IUsedTool,
IDocument
IDocument,
IServerSideEventStreamer
} from 'flowise-components'
import { Server } from 'socket.io'
import { omit, cloneDeep, flatten, uniq } from 'lodash'
import { StateGraph, END, START } from '@langchain/langgraph'
import { Document } from '@langchain/core/documents'
@@ -53,7 +53,6 @@ import logger from './logger'
* @param {ICommonObject} incomingInput
* @param {boolean} isInternal
* @param {string} baseURL
* @param {Server} socketIO
*/
export const buildAgentGraph = async (
chatflow: IChatFlow,
@@ -62,7 +61,8 @@ export const buildAgentGraph = async (
incomingInput: IncomingInput,
isInternal: boolean,
baseURL?: string,
socketIO?: Server
sseStreamer?: IServerSideEventStreamer,
shouldStreamResponse?: boolean
): Promise<any> => {
try {
const appServer = getRunningExpressApp()
@@ -287,28 +287,31 @@ export const buildAgentGraph = async (
? output[agentName].messages[output[agentName].messages.length - 1].content
: lastWorkerResult
if (socketIO && incomingInput.socketIOClientId) {
if (shouldStreamResponse) {
if (!isStreamingStarted) {
isStreamingStarted = true
socketIO.to(incomingInput.socketIOClientId).emit('start', agentReasoning)
if (sseStreamer) {
sseStreamer.streamStartEvent(chatId, agentReasoning)
}
}
socketIO.to(incomingInput.socketIOClientId).emit('agentReasoning', agentReasoning)
if (sseStreamer) {
sseStreamer.streamAgentReasoningEvent(chatId, agentReasoning)
}
// Send loading next agent indicator
if (reasoning.next && reasoning.next !== 'FINISH' && reasoning.next !== 'END') {
socketIO
.to(incomingInput.socketIOClientId)
.emit('nextAgent', mapNameToLabel[reasoning.next].label || reasoning.next)
if (sseStreamer) {
sseStreamer.streamNextAgentEvent(chatId, mapNameToLabel[reasoning.next].label || reasoning.next)
}
}
}
}
} else {
finalResult = output.__end__.messages.length ? output.__end__.messages.pop()?.content : ''
if (Array.isArray(finalResult)) finalResult = output.__end__.instructions
if (socketIO && incomingInput.socketIOClientId) {
socketIO.to(incomingInput.socketIOClientId).emit('token', finalResult)
if (shouldStreamResponse && sseStreamer) {
sseStreamer.streamTokenEvent(chatId, finalResult)
}
}
}
@@ -321,9 +324,8 @@ export const buildAgentGraph = async (
if (!isSequential && !finalResult) {
if (lastWorkerResult) finalResult = lastWorkerResult
else if (finalSummarization) finalResult = finalSummarization
if (socketIO && incomingInput.socketIOClientId) {
socketIO.to(incomingInput.socketIOClientId).emit('token', finalResult)
if (shouldStreamResponse && sseStreamer) {
sseStreamer.streamTokenEvent(chatId, finalResult)
}
}
@@ -377,16 +379,16 @@ export const buildAgentGraph = async (
{ type: 'reject-button', label: rejectButtonText }
]
}
if (socketIO && incomingInput.socketIOClientId) {
socketIO.to(incomingInput.socketIOClientId).emit('token', finalResult)
socketIO.to(incomingInput.socketIOClientId).emit('action', finalAction)
if (shouldStreamResponse && sseStreamer) {
sseStreamer.streamTokenEvent(chatId, finalResult)
sseStreamer.streamActionEvent(chatId, finalAction)
}
}
totalUsedTools.push(...mappedToolCalls)
} else if (lastAgentReasoningMessage) {
finalResult = lastAgentReasoningMessage
if (socketIO && incomingInput.socketIOClientId) {
socketIO.to(incomingInput.socketIOClientId).emit('token', finalResult)
if (shouldStreamResponse && sseStreamer) {
sseStreamer.streamTokenEvent(chatId, finalResult)
}
}
}
@@ -394,10 +396,10 @@ export const buildAgentGraph = async (
totalSourceDocuments = uniq(flatten(totalSourceDocuments))
totalUsedTools = uniq(flatten(totalUsedTools))
if (socketIO && incomingInput.socketIOClientId) {
socketIO.to(incomingInput.socketIOClientId).emit('usedTools', totalUsedTools)
socketIO.to(incomingInput.socketIOClientId).emit('sourceDocuments', totalSourceDocuments)
socketIO.to(incomingInput.socketIOClientId).emit('end')
if (shouldStreamResponse && sseStreamer) {
sseStreamer.streamUsedToolsEvent(chatId, totalUsedTools)
sseStreamer.streamSourceDocumentsEvent(chatId, totalSourceDocuments)
sseStreamer.streamEndEvent(chatId)
}
return {
@@ -412,8 +414,8 @@ export const buildAgentGraph = async (
// clear agent memory because checkpoints were saved during runtime
await clearSessionMemory(nodes, appServer.nodesPool.componentNodes, chatId, appServer.AppDataSource, sessionId)
if (getErrorMessage(e).includes('Aborted')) {
if (socketIO && incomingInput.socketIOClientId) {
socketIO.to(incomingInput.socketIOClientId).emit('abort')
if (shouldStreamResponse && sseStreamer) {
sseStreamer.streamAbortEvent(chatId)
}
return { finalResult, agentReasoning }
}