mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 11:00:55 +03:00
Feature/agentflow v2 (#4298)
* agent flow v2 * chat message background * conditon agent flow * add sticky note * update human input dynamic prompt * add HTTP node * add default tool icon * fix export duplicate agentflow v2 * add agentflow v2 marketplaces * refractor memoization, add iteration nodes * add agentflow v2 templates * add agentflow generator * add migration scripts for mysql, mariadb, posrgres and fix date filters for executions * update agentflow chat history config * fix get all flows error after deletion and rename * add previous nodes from parent node * update generator prompt * update run time state when using iteration nodes * prevent looping connection, prevent duplication of start node, add executeflow node, add nodes agentflow, chat history variable * update embed * convert form input to string * bump openai version * add react rewards * add prompt generator to prediction queue * add array schema to overrideconfig * UI touchup * update embedded chat version * fix node info dialog * update start node and loop default iteration * update UI fixes for agentflow v2 * fix async drop down * add export import to agentflowsv2, executions, fix UI bugs * add default empty object to flowlisttable * add ability to share trace link publicly, allow MCP tool use for Agent and Assistant * add runtime message length to variable, display conditions on UI * fix array validation * add ability to add knowledge from vector store and embeddings for agent * add agent tool require human input * add ephemeral memory to start node * update agent flow node to show vs and embeddings icons * feat: add import chat data functionality for AgentFlowV2 * feat: set chatMessage.executionId to null if not found in import JSON file or database * fix: MariaDB execution migration script to utf8mb4_unicode_520_ci --------- Co-authored-by: Ong Chung Yau <33013947+chungyau97@users.noreply.github.com> Co-authored-by: chungyau97 <chungyau97@gmail.com>
This commit is contained in:
+177
-115
@@ -29,7 +29,7 @@ import { ICommonObject, IDatabaseEntity, INodeData, IServerSideEventStreamer } f
|
||||
import { LangWatch, LangWatchSpan, LangWatchTrace, autoconvertTypedValues } from 'langwatch'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { ChatGenerationChunk } from '@langchain/core/outputs'
|
||||
import { AIMessageChunk } from '@langchain/core/messages'
|
||||
import { AIMessageChunk, BaseMessageLike } from '@langchain/core/messages'
|
||||
import { Serialized } from '@langchain/core/load/serializable'
|
||||
|
||||
interface AgentRun extends Run {
|
||||
@@ -635,137 +635,184 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
|
||||
}
|
||||
|
||||
export class AnalyticHandler {
|
||||
nodeData: INodeData
|
||||
options: ICommonObject = {}
|
||||
handlers: ICommonObject = {}
|
||||
private static instances: Map<string, AnalyticHandler> = new Map()
|
||||
private nodeData: INodeData
|
||||
private options: ICommonObject
|
||||
private handlers: ICommonObject = {}
|
||||
private initialized: boolean = false
|
||||
private analyticsConfig: string | undefined
|
||||
private chatId: string
|
||||
private createdAt: number
|
||||
|
||||
constructor(nodeData: INodeData, options: ICommonObject) {
|
||||
this.options = options
|
||||
private constructor(nodeData: INodeData, options: ICommonObject) {
|
||||
this.nodeData = nodeData
|
||||
this.init()
|
||||
this.options = options
|
||||
this.analyticsConfig = options.analytic
|
||||
this.chatId = options.chatId
|
||||
this.createdAt = Date.now()
|
||||
}
|
||||
|
||||
static getInstance(nodeData: INodeData, options: ICommonObject): AnalyticHandler {
|
||||
const chatId = options.chatId
|
||||
if (!chatId) throw new Error('ChatId is required for analytics')
|
||||
|
||||
// Reset instance if analytics config changed for this chat
|
||||
const instance = AnalyticHandler.instances.get(chatId)
|
||||
if (instance?.analyticsConfig !== options.analytic) {
|
||||
AnalyticHandler.resetInstance(chatId)
|
||||
}
|
||||
|
||||
if (!AnalyticHandler.instances.get(chatId)) {
|
||||
AnalyticHandler.instances.set(chatId, new AnalyticHandler(nodeData, options))
|
||||
}
|
||||
return AnalyticHandler.instances.get(chatId)!
|
||||
}
|
||||
|
||||
static resetInstance(chatId: string): void {
|
||||
AnalyticHandler.instances.delete(chatId)
|
||||
}
|
||||
|
||||
// Keep this as backup for orphaned instances
|
||||
static cleanup(maxAge: number = 3600000): void {
|
||||
const now = Date.now()
|
||||
for (const [chatId, instance] of AnalyticHandler.instances) {
|
||||
if (now - instance.createdAt > maxAge) {
|
||||
AnalyticHandler.resetInstance(chatId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.initialized) return
|
||||
|
||||
try {
|
||||
if (!this.options.analytic) return
|
||||
|
||||
const analytic = JSON.parse(this.options.analytic)
|
||||
|
||||
for (const provider in analytic) {
|
||||
const providerStatus = analytic[provider].status as boolean
|
||||
|
||||
if (providerStatus) {
|
||||
const credentialId = analytic[provider].credentialId as string
|
||||
const credentialData = await getCredentialData(credentialId ?? '', this.options)
|
||||
if (provider === 'langSmith') {
|
||||
const langSmithProject = analytic[provider].projectName as string
|
||||
const langSmithApiKey = getCredentialParam('langSmithApiKey', credentialData, this.nodeData)
|
||||
const langSmithEndpoint = getCredentialParam('langSmithEndpoint', credentialData, this.nodeData)
|
||||
|
||||
const client = new LangsmithClient({
|
||||
apiUrl: langSmithEndpoint ?? 'https://api.smith.langchain.com',
|
||||
apiKey: langSmithApiKey
|
||||
})
|
||||
|
||||
this.handlers['langSmith'] = { client, langSmithProject }
|
||||
} else if (provider === 'langFuse') {
|
||||
const release = analytic[provider].release as string
|
||||
const langFuseSecretKey = getCredentialParam('langFuseSecretKey', credentialData, this.nodeData)
|
||||
const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, this.nodeData)
|
||||
const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, this.nodeData)
|
||||
|
||||
const langfuse = new Langfuse({
|
||||
secretKey: langFuseSecretKey,
|
||||
publicKey: langFusePublicKey,
|
||||
baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com',
|
||||
sdkIntegration: 'Flowise',
|
||||
release
|
||||
})
|
||||
this.handlers['langFuse'] = { client: langfuse }
|
||||
} else if (provider === 'lunary') {
|
||||
const lunaryPublicKey = getCredentialParam('lunaryAppId', credentialData, this.nodeData)
|
||||
const lunaryEndpoint = getCredentialParam('lunaryEndpoint', credentialData, this.nodeData)
|
||||
|
||||
lunary.init({
|
||||
publicKey: lunaryPublicKey,
|
||||
apiUrl: lunaryEndpoint,
|
||||
runtime: 'flowise'
|
||||
})
|
||||
|
||||
this.handlers['lunary'] = { client: lunary }
|
||||
} else if (provider === 'langWatch') {
|
||||
const langWatchApiKey = getCredentialParam('langWatchApiKey', credentialData, this.nodeData)
|
||||
const langWatchEndpoint = getCredentialParam('langWatchEndpoint', credentialData, this.nodeData)
|
||||
|
||||
const langwatch = new LangWatch({
|
||||
apiKey: langWatchApiKey,
|
||||
endpoint: langWatchEndpoint
|
||||
})
|
||||
|
||||
this.handlers['langWatch'] = { client: langwatch }
|
||||
} else if (provider === 'arize') {
|
||||
const arizeApiKey = getCredentialParam('arizeApiKey', credentialData, this.nodeData)
|
||||
const arizeSpaceId = getCredentialParam('arizeSpaceId', credentialData, this.nodeData)
|
||||
const arizeEndpoint = getCredentialParam('arizeEndpoint', credentialData, this.nodeData)
|
||||
const arizeProject = analytic[provider].projectName as string
|
||||
|
||||
let arizeOptions: ArizeTracerOptions = {
|
||||
apiKey: arizeApiKey,
|
||||
spaceId: arizeSpaceId,
|
||||
baseUrl: arizeEndpoint ?? 'https://otlp.arize.com',
|
||||
projectName: arizeProject ?? 'default',
|
||||
sdkIntegration: 'Flowise',
|
||||
enableCallback: false
|
||||
}
|
||||
|
||||
const arize: Tracer | undefined = getArizeTracer(arizeOptions)
|
||||
const rootSpan: Span | undefined = undefined
|
||||
|
||||
this.handlers['arize'] = { client: arize, arizeProject, rootSpan }
|
||||
} else if (provider === 'phoenix') {
|
||||
const phoenixApiKey = getCredentialParam('phoenixApiKey', credentialData, this.nodeData)
|
||||
const phoenixEndpoint = getCredentialParam('phoenixEndpoint', credentialData, this.nodeData)
|
||||
const phoenixProject = analytic[provider].projectName as string
|
||||
|
||||
let phoenixOptions: PhoenixTracerOptions = {
|
||||
apiKey: phoenixApiKey,
|
||||
baseUrl: phoenixEndpoint ?? 'https://app.phoenix.arize.com',
|
||||
projectName: phoenixProject ?? 'default',
|
||||
sdkIntegration: 'Flowise',
|
||||
enableCallback: false
|
||||
}
|
||||
|
||||
const phoenix: Tracer | undefined = getPhoenixTracer(phoenixOptions)
|
||||
const rootSpan: Span | undefined = undefined
|
||||
|
||||
this.handlers['phoenix'] = { client: phoenix, phoenixProject, rootSpan }
|
||||
} else if (provider === 'opik') {
|
||||
const opikApiKey = getCredentialParam('opikApiKey', credentialData, this.nodeData)
|
||||
const opikEndpoint = getCredentialParam('opikUrl', credentialData, this.nodeData)
|
||||
const opikWorkspace = getCredentialParam('opikWorkspace', credentialData, this.nodeData)
|
||||
const opikProject = analytic[provider].opikProjectName as string
|
||||
|
||||
let opikOptions: OpikTracerOptions = {
|
||||
apiKey: opikApiKey,
|
||||
baseUrl: opikEndpoint ?? 'https://www.comet.com/opik/api',
|
||||
projectName: opikProject ?? 'default',
|
||||
workspace: opikWorkspace ?? 'default',
|
||||
sdkIntegration: 'Flowise',
|
||||
enableCallback: false
|
||||
}
|
||||
|
||||
const opik: Tracer | undefined = getOpikTracer(opikOptions)
|
||||
const rootSpan: Span | undefined = undefined
|
||||
|
||||
this.handlers['opik'] = { client: opik, opikProject, rootSpan }
|
||||
}
|
||||
await this.initializeProvider(provider, analytic[provider], credentialData)
|
||||
}
|
||||
}
|
||||
this.initialized = true
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Add getter for handlers (useful for debugging)
|
||||
getHandlers(): ICommonObject {
|
||||
return this.handlers
|
||||
}
|
||||
|
||||
async initializeProvider(provider: string, providerConfig: any, credentialData: any) {
|
||||
if (provider === 'langSmith') {
|
||||
const langSmithProject = providerConfig.projectName as string
|
||||
const langSmithApiKey = getCredentialParam('langSmithApiKey', credentialData, this.nodeData)
|
||||
const langSmithEndpoint = getCredentialParam('langSmithEndpoint', credentialData, this.nodeData)
|
||||
|
||||
const client = new LangsmithClient({
|
||||
apiUrl: langSmithEndpoint ?? 'https://api.smith.langchain.com',
|
||||
apiKey: langSmithApiKey
|
||||
})
|
||||
|
||||
this.handlers['langSmith'] = { client, langSmithProject }
|
||||
} else if (provider === 'langFuse') {
|
||||
const release = providerConfig.release as string
|
||||
const langFuseSecretKey = getCredentialParam('langFuseSecretKey', credentialData, this.nodeData)
|
||||
const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, this.nodeData)
|
||||
const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, this.nodeData)
|
||||
|
||||
const langfuse = new Langfuse({
|
||||
secretKey: langFuseSecretKey,
|
||||
publicKey: langFusePublicKey,
|
||||
baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com',
|
||||
sdkIntegration: 'Flowise',
|
||||
release
|
||||
})
|
||||
this.handlers['langFuse'] = { client: langfuse }
|
||||
} else if (provider === 'lunary') {
|
||||
const lunaryPublicKey = getCredentialParam('lunaryAppId', credentialData, this.nodeData)
|
||||
const lunaryEndpoint = getCredentialParam('lunaryEndpoint', credentialData, this.nodeData)
|
||||
|
||||
lunary.init({
|
||||
publicKey: lunaryPublicKey,
|
||||
apiUrl: lunaryEndpoint,
|
||||
runtime: 'flowise'
|
||||
})
|
||||
|
||||
this.handlers['lunary'] = { client: lunary }
|
||||
} else if (provider === 'langWatch') {
|
||||
const langWatchApiKey = getCredentialParam('langWatchApiKey', credentialData, this.nodeData)
|
||||
const langWatchEndpoint = getCredentialParam('langWatchEndpoint', credentialData, this.nodeData)
|
||||
|
||||
const langwatch = new LangWatch({
|
||||
apiKey: langWatchApiKey,
|
||||
endpoint: langWatchEndpoint
|
||||
})
|
||||
|
||||
this.handlers['langWatch'] = { client: langwatch }
|
||||
} else if (provider === 'arize') {
|
||||
const arizeApiKey = getCredentialParam('arizeApiKey', credentialData, this.nodeData)
|
||||
const arizeSpaceId = getCredentialParam('arizeSpaceId', credentialData, this.nodeData)
|
||||
const arizeEndpoint = getCredentialParam('arizeEndpoint', credentialData, this.nodeData)
|
||||
const arizeProject = providerConfig.projectName as string
|
||||
|
||||
let arizeOptions: ArizeTracerOptions = {
|
||||
apiKey: arizeApiKey,
|
||||
spaceId: arizeSpaceId,
|
||||
baseUrl: arizeEndpoint ?? 'https://otlp.arize.com',
|
||||
projectName: arizeProject ?? 'default',
|
||||
sdkIntegration: 'Flowise',
|
||||
enableCallback: false
|
||||
}
|
||||
|
||||
const arize: Tracer | undefined = getArizeTracer(arizeOptions)
|
||||
const rootSpan: Span | undefined = undefined
|
||||
|
||||
this.handlers['arize'] = { client: arize, arizeProject, rootSpan }
|
||||
} else if (provider === 'phoenix') {
|
||||
const phoenixApiKey = getCredentialParam('phoenixApiKey', credentialData, this.nodeData)
|
||||
const phoenixEndpoint = getCredentialParam('phoenixEndpoint', credentialData, this.nodeData)
|
||||
const phoenixProject = providerConfig.projectName as string
|
||||
|
||||
let phoenixOptions: PhoenixTracerOptions = {
|
||||
apiKey: phoenixApiKey,
|
||||
baseUrl: phoenixEndpoint ?? 'https://app.phoenix.arize.com',
|
||||
projectName: phoenixProject ?? 'default',
|
||||
sdkIntegration: 'Flowise',
|
||||
enableCallback: false
|
||||
}
|
||||
|
||||
const phoenix: Tracer | undefined = getPhoenixTracer(phoenixOptions)
|
||||
const rootSpan: Span | undefined = undefined
|
||||
|
||||
this.handlers['phoenix'] = { client: phoenix, phoenixProject, rootSpan }
|
||||
} else if (provider === 'opik') {
|
||||
const opikApiKey = getCredentialParam('opikApiKey', credentialData, this.nodeData)
|
||||
const opikEndpoint = getCredentialParam('opikUrl', credentialData, this.nodeData)
|
||||
const opikWorkspace = getCredentialParam('opikWorkspace', credentialData, this.nodeData)
|
||||
const opikProject = providerConfig.opikProjectName as string
|
||||
|
||||
let opikOptions: OpikTracerOptions = {
|
||||
apiKey: opikApiKey,
|
||||
baseUrl: opikEndpoint ?? 'https://www.comet.com/opik/api',
|
||||
projectName: opikProject ?? 'default',
|
||||
workspace: opikWorkspace ?? 'default',
|
||||
sdkIntegration: 'Flowise',
|
||||
enableCallback: false
|
||||
}
|
||||
|
||||
const opik: Tracer | undefined = getOpikTracer(opikOptions)
|
||||
const rootSpan: Span | undefined = undefined
|
||||
|
||||
this.handlers['opik'] = { client: opik, opikProject, rootSpan }
|
||||
}
|
||||
}
|
||||
|
||||
async onChainStart(name: string, input: string, parentIds?: ICommonObject) {
|
||||
const returnIds: ICommonObject = {
|
||||
langSmith: {},
|
||||
@@ -1077,6 +1124,11 @@ export class AnalyticHandler {
|
||||
chainSpan.end()
|
||||
}
|
||||
}
|
||||
|
||||
if (shutdown) {
|
||||
// Cleanup this instance when chain ends
|
||||
AnalyticHandler.resetInstance(this.chatId)
|
||||
}
|
||||
}
|
||||
|
||||
async onChainError(returnIds: ICommonObject, error: string | object, shutdown = false) {
|
||||
@@ -1155,9 +1207,14 @@ export class AnalyticHandler {
|
||||
chainSpan.end()
|
||||
}
|
||||
}
|
||||
|
||||
if (shutdown) {
|
||||
// Cleanup this instance when chain ends
|
||||
AnalyticHandler.resetInstance(this.chatId)
|
||||
}
|
||||
}
|
||||
|
||||
async onLLMStart(name: string, input: string, parentIds: ICommonObject) {
|
||||
async onLLMStart(name: string, input: string | BaseMessageLike[], parentIds: ICommonObject) {
|
||||
const returnIds: ICommonObject = {
|
||||
langSmith: {},
|
||||
langFuse: {},
|
||||
@@ -1169,13 +1226,18 @@ export class AnalyticHandler {
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun]
|
||||
|
||||
if (parentRun) {
|
||||
const inputs: any = {}
|
||||
if (Array.isArray(input)) {
|
||||
inputs.messages = input
|
||||
} else {
|
||||
inputs.prompts = [input]
|
||||
}
|
||||
const childLLMRun = await parentRun.createChild({
|
||||
name,
|
||||
run_type: 'llm',
|
||||
inputs: {
|
||||
prompts: [input]
|
||||
}
|
||||
inputs
|
||||
})
|
||||
await childLLMRun.postRun()
|
||||
this.handlers['langSmith'].llmRun = { [childLLMRun.id]: childLLMRun }
|
||||
|
||||
Reference in New Issue
Block a user